diff --git a/previews/PR3536/.documenter-siteinfo.json b/previews/PR3536/.documenter-siteinfo.json new file mode 100644 index 00000000000..5da4074597f --- /dev/null +++ b/previews/PR3536/.documenter-siteinfo.json @@ -0,0 +1 @@ +{"documenter":{"julia_version":"1.9.3","generation_timestamp":"2023-10-06T12:05:20","documenter_version":"1.1.0"}} \ No newline at end of file diff --git a/previews/PR3536/JuMP.pdf b/previews/PR3536/JuMP.pdf new file mode 100644 index 00000000000..df5f5237164 Binary files /dev/null and b/previews/PR3536/JuMP.pdf differ diff --git a/previews/PR3536/api/JuMP.Containers/index.html b/previews/PR3536/api/JuMP.Containers/index.html new file mode 100644 index 00000000000..92764c67286 --- /dev/null +++ b/previews/PR3536/api/JuMP.Containers/index.html @@ -0,0 +1,192 @@ + +JuMP.Containers · JuMP

JuMP.Containers

This page lists the public API of JuMP.Containers.

Info

This page is an unstructured list of the JuMP.Containers API. For a more structured overview, read the Manual or Tutorial parts of this documentation.

Load all of the public the API into the current scope with:

using JuMP.Containers

Alternatively, load only the module with:

import JuMP.Containers

and then prefix all calls with JuMP.Containers. to create JuMP.Containers.<NAME>.

DenseAxisArray

JuMP.Containers.DenseAxisArrayType
DenseAxisArray(data::Array{T, N}, axes...) where {T, N}

Construct a JuMP array with the underlying data specified by the data array and the given axes. Exactly N axes must be provided, and their lengths must match size(data) in the corresponding dimensions.

Example

julia> array = Containers.DenseAxisArray([1 2; 3 4], [:a, :b], 2:3)
+2-dimensional DenseAxisArray{Int64,2,...} with index sets:
+    Dimension 1, [:a, :b]
+    Dimension 2, 2:3
+And data, a 2×2 Matrix{Int64}:
+ 1  2
+ 3  4
+
+julia> array[:b, 3]
+4
source
DenseAxisArray{T}(undef, axes...) where T

Construct an uninitialized DenseAxisArray with element-type T indexed over the given axes.

Example

julia> array = Containers.DenseAxisArray{Float64}(undef, [:a, :b], 1:2);
+
+julia> fill!(array, 1.0)
+2-dimensional DenseAxisArray{Float64,2,...} with index sets:
+    Dimension 1, [:a, :b]
+    Dimension 2, 1:2
+And data, a 2×2 Matrix{Float64}:
+ 1.0  1.0
+ 1.0  1.0
+
+julia> array[:a, 2] = 5.0
+5.0
+
+julia> array[:a, 2]
+5.0
+
+julia> array
+2-dimensional DenseAxisArray{Float64,2,...} with index sets:
+    Dimension 1, [:a, :b]
+    Dimension 2, 1:2
+And data, a 2×2 Matrix{Float64}:
+ 1.0  5.0
+ 1.0  1.0
source

SparseAxisArray

JuMP.Containers.SparseAxisArrayType
struct SparseAxisArray{T,N,K<:NTuple{N, Any}} <: AbstractArray{T,N}
+    data::Dict{K,T}
+end

N-dimensional array with elements of type T where only a subset of the entries are defined. The entries with indices idx = (i1, i2, ..., iN) in keys(data) has value data[idx]. Note that as opposed to SparseArrays.AbstractSparseArray, the missing entries are not assumed to be zero(T), they are simply not part of the array. This means that the result of map(f, sa::SparseAxisArray) or f.(sa::SparseAxisArray) has the same sparsity structure than sa even if f(zero(T)) is not zero.

Example

julia> dict = Dict((:a, 2) => 1.0, (:a, 3) => 2.0, (:b, 3) => 3.0)
+Dict{Tuple{Symbol, Int64}, Float64} with 3 entries:
+  (:a, 3) => 2.0
+  (:b, 3) => 3.0
+  (:a, 2) => 1.0
+
+julia> array = Containers.SparseAxisArray(dict)
+SparseAxisArray{Float64, 2, Tuple{Symbol, Int64}} with 3 entries:
+  [a, 2]  =  1.0
+  [a, 3]  =  2.0
+  [b, 3]  =  3.0
+
+julia> array[:b, 3]
+3.0
source

Containers.@container

JuMP.Containers.@containerMacro
@container([i=..., j=..., ...], expr[, container = :Auto])

Create a container with indices i, j, ... and values given by expr that may depend on the value of the indices.

@container(ref[i=..., j=..., ...], expr[, container = :Auto])

Same as above but the container is assigned to the variable of name ref.

The type of container can be controlled by the container keyword.

Note

When the index set is explicitly given as 1:n for any expression n, it is transformed to Base.OneTo(n) before being given to container.

Example

julia> Containers.@container([i = 1:3, j = 1:3], i + j)
+3×3 Matrix{Int64}:
+ 2  3  4
+ 3  4  5
+ 4  5  6
+
+julia> I = 1:3
+1:3
+
+julia> Containers.@container(x[i = I, j = I], i + j);
+
+julia> x
+2-dimensional DenseAxisArray{Int64,2,...} with index sets:
+    Dimension 1, 1:3
+    Dimension 2, 1:3
+And data, a 3×3 Matrix{Int64}:
+ 2  3  4
+ 3  4  5
+ 4  5  6
+
+julia> Containers.@container([i = 2:3, j = 1:3], i + j)
+2-dimensional DenseAxisArray{Int64,2,...} with index sets:
+    Dimension 1, 2:3
+    Dimension 2, Base.OneTo(3)
+And data, a 2×3 Matrix{Int64}:
+ 3  4  5
+ 4  5  6
+
+julia> Containers.@container([i = 1:3, j = 1:3; i <= j], i + j)
+SparseAxisArray{Int64, 2, Tuple{Int64, Int64}} with 6 entries:
+  [1, 1]  =  2
+  [1, 2]  =  3
+  [1, 3]  =  4
+  [2, 2]  =  4
+  [2, 3]  =  5
+  [3, 3]  =  6
source

Containers.container

JuMP.Containers.containerFunction
container(f::Function, indices[[, ::Type{C} = AutoContainerType], names])

Create a container of type C with index names names, indices indices and values at given indices given by f.

If the method with names is not specialized on Type{C}, it falls back to calling container(f, indices, c) for backwards compatibility with containers not supporting index names.

Example

julia> Containers.container((i, j) -> i + j, Containers.vectorized_product(Base.OneTo(3), Base.OneTo(3)))
+3×3 Matrix{Int64}:
+ 2  3  4
+ 3  4  5
+ 4  5  6
+
+julia> Containers.container((i, j) -> i + j, Containers.vectorized_product(1:3, 1:3))
+2-dimensional DenseAxisArray{Int64,2,...} with index sets:
+    Dimension 1, 1:3
+    Dimension 2, 1:3
+And data, a 3×3 Matrix{Int64}:
+ 2  3  4
+ 3  4  5
+ 4  5  6
+
+julia> Containers.container((i, j) -> i + j, Containers.vectorized_product(2:3, Base.OneTo(3)))
+2-dimensional DenseAxisArray{Int64,2,...} with index sets:
+    Dimension 1, 2:3
+    Dimension 2, Base.OneTo(3)
+And data, a 2×3 Matrix{Int64}:
+ 3  4  5
+ 4  5  6
+
+julia> Containers.container((i, j) -> i + j, Containers.nested(() -> 1:3, i -> i:3, condition = (i, j) -> isodd(i) || isodd(j)))
+SparseAxisArray{Int64, 2, Tuple{Int64, Int64}} with 5 entries:
+  [1, 1]  =  2
+  [1, 2]  =  3
+  [1, 3]  =  4
+  [2, 3]  =  5
+  [3, 3]  =  6
source

Containers.rowtable

JuMP.Containers.rowtableFunction
rowtable([f::Function=identity,] x; [header::Vector{Symbol} = Symbol[]])

Applies the function f to all elements of the variable container x, returning the result as a Vector of NamedTuples, where header is a vector containing the corresponding axis names.

If x is an N-dimensional array, there must be N+1 names, so that the last name corresponds to the result of f(x[i]).

If header is left empty, then the default header is [:x1, :x2, ..., :xN, :y].

Info

A Vector of NamedTuples implements the Tables.jl interface, and so the result can be used as input for any function that consumes a 'Tables.jl' compatible source.

Example

julia> model = Model();
+
+julia> @variable(model, x[i=1:2, j=i:2] >= 0, start = i+j);
+
+julia> Containers.rowtable(start_value, x; header = [:i, :j, :start])
+3-element Vector{NamedTuple{(:i, :j, :start), Tuple{Int64, Int64, Float64}}}:
+ (i = 1, j = 2, start = 3.0)
+ (i = 1, j = 1, start = 2.0)
+ (i = 2, j = 2, start = 4.0)
+
+julia> Containers.rowtable(x)
+3-element Vector{NamedTuple{(:x1, :x2, :y), Tuple{Int64, Int64, VariableRef}}}:
+ (x1 = 1, x2 = 2, y = x[1,2])
+ (x1 = 1, x2 = 1, y = x[1,1])
+ (x1 = 2, x2 = 2, y = x[2,2])
source

Containers.default_container

Containers.nested

JuMP.Containers.nestedFunction
nested(iterators...; condition = (args...) -> true)

Create a NestedIterator.

Example

julia> iterator = Containers.nested(
+           () -> 1:2,
+           (i,) -> ["A", "B"];
+           condition = (i, j) -> isodd(i) || j == "B",
+       );
+
+julia> collect(iterator)
+3-element Vector{Tuple{Int64, String}}:
+ (1, "A")
+ (1, "B")
+ (2, "B")
source

Containers.vectorized_product

Containers.build_ref_sets

JuMP.Containers.build_ref_setsFunction
build_ref_sets(_error::Function, expr)

Helper function for macros to construct container objects.

Warning

This function is for advanced users implementing JuMP extensions. See container_code for more details.

Arguments

  • _error: a function that takes a String and throws an error, potentially annotating the input string with extra information such as from which macro it was thrown from. Use error if you do not want a modified error message.
  • expr: an Expr that specifies the container, e.g., :(x[i = 1:3, [:red, :blue], k = S; i + k <= 6])

Returns

  1. index_vars: a Vector{Any} of names for the index variables, e.g., [:i, gensym(), :k]. These may also be expressions, like :((i, j)) from a call like :(x[(i, j) in S]).
  2. indices: an iterator over the indices, for example, Containers.NestedIterator

Example

See container_code for a worked example.

source

Containers.container_code

JuMP.Containers.container_codeFunction
container_code(
+    index_vars::Vector{Any},
+    indices::Expr,
+    code,
+    requested_container::Union{Symbol,Expr},
+)

Used in macros to construct a call to container. This should be used in conjunction with build_ref_sets.

Arguments

  • index_vars::Vector{Any}: a vector of names for the indices of the container. These may also be expressions, like :((i, j)) from a call like :(x[(i, j) in S]).
  • indices::Expr: an expression that evaluates to an iterator of the indices.
  • code: an expression or literal constant for the value to be stored in the container as a function of the named index_vars.
  • requested_container: passed to the third argument of container. For built-in JuMP types, choose one of :Array, :DenseAxisArray, :SparseAxisArray, or :Auto. For a user-defined container, this expression must evaluate to the correct type.
Warning

In most cases, you should esc(code) before passing it to container_code.

Example

julia> macro foo(ref_sets, code)
+           index_vars, indices = Containers.build_ref_sets(error, ref_sets)
+           return Containers.container_code(
+               index_vars,
+               indices,
+               esc(code),
+               :Auto,
+            )
+       end
+@foo (macro with 1 method)
+
+julia> @foo(x[i=1:2, j=["A", "B"]], j^i)
+2-dimensional DenseAxisArray{String,2,...} with index sets:
+    Dimension 1, Base.OneTo(2)
+    Dimension 2, ["A", "B"]
+And data, a 2×2 Matrix{String}:
+ "A"   "B"
+ "AA"  "BB"
source

Containers.AutoContainerType

Containers.NestedIterator

JuMP.Containers.NestedIteratorType
struct NestedIterator{T}
+    iterators::T # Tuple of functions
+    condition::Function
+end

Iterators over the tuples that are produced by a nested for loop.

Construct a NestedIterator using nested.

Example

julia> iterators = (() -> 1:2, (i,) -> ["A", "B"]);
+
+julia> condition = (i, j) -> isodd(i) || j == "B";
+
+julia> x = Containers.NestedIterator(iterators, condition);
+
+julia> for (i, j) in x
+           println((i, j))
+       end
+(1, "A")
+(1, "B")
+(2, "B")

is the same as

julia> for i in iterators[1]()
+           for j in iterators[2](i)
+               if condition(i, j)
+                   println((i, j))
+               end
+           end
+       end
+(1, "A")
+(1, "B")
+(2, "B")
source

Containers.VectorizedProductIterator

diff --git a/previews/PR3536/api/JuMP/index.html b/previews/PR3536/api/JuMP/index.html new file mode 100644 index 00000000000..7ee0e09bb74 --- /dev/null +++ b/previews/PR3536/api/JuMP/index.html @@ -0,0 +1,2074 @@ + +JuMP · JuMP

JuMP

This page lists the public API of JuMP.

Info

This page is an unstructured list of the JuMP API. For a more structured overview, read the Manual or Tutorial parts of this documentation.

Load all of the public the API into the current scope with:

using JuMP

Alternatively, load only the module with:

import JuMP

and then prefix all calls with JuMP. to create JuMP.<NAME>.

@NLconstraint

JuMP.@NLconstraintMacro
@NLconstraint(model::GenericModel, expr)

Add a constraint described by the nonlinear expression expr. See also @constraint. For example:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @NLconstraint(model, sin(x) <= 1)
+sin(x) - 1.0 ≤ 0
+
+julia> @NLconstraint(model, [i = 1:3], sin(i * x) <= 1 / i)
+3-element Vector{NonlinearConstraintRef{ScalarShape}}:
+ (sin(1.0 * x) - 1.0 / 1.0) - 0.0 ≤ 0
+ (sin(2.0 * x) - 1.0 / 2.0) - 0.0 ≤ 0
+ (sin(3.0 * x) - 1.0 / 3.0) - 0.0 ≤ 0
source

@NLconstraints

JuMP.@NLconstraintsMacro
@NLconstraints(model, args...)

Adds multiple nonlinear constraints to model at once, in the same fashion as the @NLconstraint macro.

The model must be the first argument, and multiple constraints can be added on multiple lines wrapped in a begin ... end block.

The macro returns a tuple containing the constraints that were defined.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @variable(model, y);
+
+julia> @variable(model, t);
+
+julia> @variable(model, z[1:2]);
+
+julia> a = [4, 5];
+
+julia> @NLconstraints(model, begin
+           t >= sqrt(x^2 + y^2)
+           [i = 1:2], z[i] <= log(a[i])
+       end)
+((t - sqrt(x ^ 2.0 + y ^ 2.0)) - 0.0 ≥ 0, NonlinearConstraintRef{ScalarShape}[(z[1] - log(4.0)) - 0.0 ≤ 0, (z[2] - log(5.0)) - 0.0 ≤ 0])
source

@NLexpression

JuMP.@NLexpressionMacro
@NLexpression(args...)

Efficiently build a nonlinear expression which can then be inserted in other nonlinear constraints and the objective. See also [@expression]. For example:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> @NLexpression(model, my_expr, sin(x)^2 + cos(x^2))
+subexpression[1]: sin(x) ^ 2.0 + cos(x ^ 2.0)
+
+julia> @NLconstraint(model, my_expr + y >= 5)
+(subexpression[1] + y) - 5.0 ≥ 0
+
+julia> @NLobjective(model, Min, my_expr)

Indexing over sets and anonymous expressions are also supported:

julia> @NLexpression(model, my_expr_1[i=1:3], sin(i * x))
+3-element Vector{NonlinearExpression}:
+ subexpression[2]: sin(1.0 * x)
+ subexpression[3]: sin(2.0 * x)
+ subexpression[4]: sin(3.0 * x)
+
+julia> my_expr_2 = @NLexpression(model, log(1 + sum(exp(my_expr_1[i]) for i in 1:2)))
+subexpression[5]: log(1.0 + (exp(subexpression[2]) + exp(subexpression[3])))
source

@NLexpressions

JuMP.@NLexpressionsMacro
@NLexpressions(model, args...)

Adds multiple nonlinear expressions to model at once, in the same fashion as the @NLexpression macro.

The model must be the first argument, and multiple expressions can be added on multiple lines wrapped in a begin ... end block.

The macro returns a tuple containing the expressions that were defined.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @variable(model, y);
+
+julia> @variable(model, z[1:2]);
+
+julia> a = [4, 5];
+
+julia> @NLexpressions(model, begin
+           my_expr, sqrt(x^2 + y^2)
+           my_expr_1[i = 1:2], log(a[i]) - z[i]
+       end)
+(subexpression[1]: sqrt(x ^ 2.0 + y ^ 2.0), NonlinearExpression[subexpression[2]: log(4.0) - z[1], subexpression[3]: log(5.0) - z[2]])
source

@NLobjective

JuMP.@NLobjectiveMacro
@NLobjective(model, sense, expression)

Add a nonlinear objective to model with optimization sense sense. sense must be Max or Min.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @NLobjective(model, Max, 2x + 1 + sin(x))
+
+julia> print(model)
+Max 2.0 * x + 1.0 + sin(x)
+Subject to
source

@NLparameter

JuMP.@NLparameterMacro
@NLparameter(model, param == value)

Create and return a nonlinear parameter param attached to the model model with initial value set to value. Nonlinear parameters may be used only in nonlinear expressions.

Example

julia> model = Model();
+
+julia> @NLparameter(model, x == 10)
+x == 10.0
+
+julia> value(x)
+10.0
@NLparameter(model, value = param_value)

Create and return an anonymous nonlinear parameter param attached to the model model with initial value set to param_value. Nonlinear parameters may be used only in nonlinear expressions.

Example

julia> model = Model();
+
+julia> x = @NLparameter(model, value = 10)
+parameter[1] == 10.0
+
+julia> value(x)
+10.0
@NLparameter(model, param_collection[...] == value_expr)

Create and return a collection of nonlinear parameters param_collection attached to the model model with initial value set to value_expr (may depend on index sets). Uses the same syntax for specifying index sets as @variable.

Example

julia> model = Model();
+
+julia> @NLparameter(model, y[i = 1:3] == 2 * i)
+3-element Vector{NonlinearParameter}:
+ parameter[1] == 2.0
+ parameter[2] == 4.0
+ parameter[3] == 6.0
+
+julia> value(y[2])
+4.0
@NLparameter(model, [...] == value_expr)

Create and return an anonymous collection of nonlinear parameters attached to the model model with initial value set to value_expr (may depend on index sets). Uses the same syntax for specifying index sets as @variable.

Example

julia> model = Model();
+
+julia> y = @NLparameter(model, [i = 1:3] == 2 * i)
+3-element Vector{NonlinearParameter}:
+ parameter[1] == 2.0
+ parameter[2] == 4.0
+ parameter[3] == 6.0
+
+julia> value(y[2])
+4.0
source

@NLparameters

JuMP.@NLparametersMacro
 @NLparameters(model, args...)

Create and return multiple nonlinear parameters attached to model model, in the same fashion as @NLparameter macro.

The model must be the first argument, and multiple parameters can be added on multiple lines wrapped in a begin ... end block. Distinct parameters need to be placed on separate lines as in the following example.

The macro returns a tuple containing the parameters that were defined.

Example

julia> model = Model();
+
+julia> @NLparameters(model, begin
+           x == 10
+           b == 156
+       end);
+
+julia> value(x)
+10.0
source

@build_constraint

JuMP.@build_constraintMacro
@build_constraint(constraint_expr)

Constructs a ScalarConstraint or VectorConstraint using the same machinery as @constraint but without adding the constraint to a model.

Constraints using broadcast operators like x .<= 1 are also supported and will create arrays of ScalarConstraint or VectorConstraint.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @build_constraint(2x >= 1)
+ScalarConstraint{AffExpr, MathOptInterface.GreaterThan{Float64}}(2 x, MathOptInterface.GreaterThan{Float64}(1.0))
source

@constraint

JuMP.@constraintMacro
@constraint(m::GenericModel, expr, kw_args...)

Add a constraint described by the expression expr.

@constraint(m::GenericModel, ref[i=..., j=..., ...], expr, kw_args...)

Add a group of constraints described by the expression expr parametrized by i, j, ...

The expression expr can either be

  • of the form func in set constraining the function func to belong to the set set which is either a MOI.AbstractSet or one of the JuMP shortcuts SecondOrderCone, RotatedSecondOrderCone and PSDCone, e.g. @constraint(model, [1, x-1, y-2] in SecondOrderCone()) constrains the norm of [x-1, y-2] be less than 1;
  • of the form a sign b, where sign is one of ==, , >=, and <= building the single constraint enforcing the comparison to hold for the expression a and b, e.g. @constraint(m, x^2 + y^2 == 1) constrains x and y to lie on the unit circle;
  • of the form a ≤ b ≤ c or a ≥ b ≥ c (where and <= (resp. and >=) can be used interchangeably) constraining the paired the expression b to lie between a and c;
  • of the forms @constraint(m, a .sign b) or @constraint(m, a .sign b .sign c) which broadcast the constraint creation to each element of the vectors.

The recognized keyword arguments in kw_args are the following:

  • base_name: Sets the name prefix used to generate constraint names. It corresponds to the constraint name for scalar constraints, otherwise, the constraint names are set to base_name[...] for each index ... of the axes axes.
  • container: Specify the container type.
  • set_string_name::Bool = true: control whether to set the MOI.ConstraintName attribute. Passing set_string_name = false can improve performance.

Note for extending the constraint macro

Each constraint will be created using add_constraint(m, build_constraint(_error, func, set)) where

  • _error is an error function showing the constraint call in addition to the error message given as argument,
  • func is the expression that is constrained
  • and set is the set in which it is constrained to belong.

For expr of the first type (i.e. @constraint(m, func in set)), func and set are passed unchanged to build_constraint but for the other types, they are determined from the expressions and signs. For instance, @constraint(m, x^2 + y^2 == 1) is transformed into add_constraint(m, build_constraint(_error, x^2 + y^2, MOI.EqualTo(1.0))).

To extend JuMP to accept new constraints of this form, it is necessary to add the corresponding methods to build_constraint. Note that this will likely mean that either func or set will be some custom type, rather than e.g. a Symbol, since we will likely want to dispatch on the type of the function or set appearing in the constraint.

For extensions that need to create constraints with more information than just func and set, an additional positional argument can be specified to @constraint that will then be passed on build_constraint. Hence, we can enable this syntax by defining extensions of build_constraint(_error, func, set, my_arg; kw_args...). This produces the user syntax: @constraint(model, ref[...], expr, my_arg, kw_args...).

source

@constraints

JuMP.@constraintsMacro
@constraints(model, args...)

Adds groups of constraints at once, in the same fashion as the @constraint macro.

The model must be the first argument, and multiple constraints can be added on multiple lines wrapped in a begin ... end block.

The macro returns a tuple containing the constraints that were defined.

Example

julia> model = Model();
+
+julia> @variable(model, w);
+
+julia> @variable(model, x);
+
+julia> @variable(model, y);
+
+julia> @variable(model, z[1:3]);
+
+julia> @constraints(model, begin
+           x >= 1
+           y - w <= 2
+           sum_to_one[i=1:3], z[i] + y == 1
+       end);
+
+julia> print(model)
+Feasibility
+Subject to
+ sum_to_one[1] : y + z[1] = 1
+ sum_to_one[2] : y + z[2] = 1
+ sum_to_one[3] : y + z[3] = 1
+ x ≥ 1
+ -w + y ≤ 2
source

@expression

JuMP.@expressionMacro
@expression(args...)

Efficiently builds a linear or quadratic expression but does not add to model immediately. Instead, returns the expression which can then be inserted in other constraints.

Example

julia> model = Model();
+
+julia> @variable(model, x[1:5]);
+
+julia> @variable(model, y);
+
+julia> @variable(model, z);
+
+julia> @expression(model, shared, sum(i * x[i] for i in 1:5))
+x[1] + 2 x[2] + 3 x[3] + 4 x[4] + 5 x[5]
+
+julia> @constraint(model, shared + y >= 5)
+x[1] + 2 x[2] + 3 x[3] + 4 x[4] + 5 x[5] + y ≥ 5
+
+julia> @constraint(model, shared + z <= 10)
+x[1] + 2 x[2] + 3 x[3] + 4 x[4] + 5 x[5] + z ≤ 10

The ref accepts index sets in the same way as @variable, and those indices can be used in the construction of the expressions:

julia> @expression(model, expr[i = 1:3], i * sum(x[j] for j in 1:3))
+3-element Vector{AffExpr}:
+ x[1] + x[2] + x[3]
+ 2 x[1] + 2 x[2] + 2 x[3]
+ 3 x[1] + 3 x[2] + 3 x[3]

Anonymous syntax is also supported:

julia> expr = @expression(model, [i in 1:3], i * sum(x[j] for j in 1:3))
+3-element Vector{AffExpr}:
+ x[1] + x[2] + x[3]
+ 2 x[1] + 2 x[2] + 2 x[3]
+ 3 x[1] + 3 x[2] + 3 x[3]
source

@expressions

JuMP.@expressionsMacro
@expressions(model, args...)

Adds multiple expressions to model at once, in the same fashion as the @expression macro.

The model must be the first argument, and multiple expressions can be added on multiple lines wrapped in a begin ... end block.

The macro returns a tuple containing the expressions that were defined.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @variable(model, y);
+
+julia> @variable(model, z[1:2]);
+
+julia> a = [4, 5];
+
+julia> @expressions(model, begin
+           my_expr, x^2 + y^2
+           my_expr_1[i = 1:2], a[i] - z[i]
+       end)
+(x² + y², AffExpr[-z[1] + 4, -z[2] + 5])
source

@objective

JuMP.@objectiveMacro
@objective(model::GenericModel, sense, func)

Set the objective sense to sense and objective function to func. The objective sense can be either Min, Max, MOI.MIN_SENSE, MOI.MAX_SENSE or MOI.FEASIBILITY_SENSE; see MOI.ObjectiveSense.

In order to set the sense programmatically, i.e., when sense is a Julia variable whose value is the sense, one of the three MOI.ObjectiveSense values should be used.

Example

To minimize the value of the variable x, do as follows:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @objective(model, Min, x)
+x

To maximize the value of the affine expression 2x - 1, do as follows:

julia> @objective(model, Max, 2x - 1)
+2 x - 1

To set a quadratic objective and set the objective sense programmatically, do as follows:

julia> sense = MIN_SENSE
+MIN_SENSE::OptimizationSense = 0
+
+julia> @objective(model, sense, x^2 - 2x + 1)
+x² - 2 x + 1
source

@operator

JuMP.@operatorMacro
@operator(model, operator, dim, f[, ∇f[, ∇²f]])

Add the nonlinear operator operator in model with dim arguments, and create a new NonlinearOperator object called operator in the current scope.

The function f evaluates the operator and must return a scalar.

The optional function ∇f evaluates the first derivative, and the optional function ∇²f evaluates the second derivative.

∇²f may be provided only if ∇f is also provided.

Univariate syntax

If dim == 1, then the method signatures of each function must be:

  • f(::T)::T where {T<:Real}
  • ∇f(::T)::T where {T<:Real}
  • ∇²f(::T)::T where {T<:Real}

Multivariate syntax

If dim > 1, then the method signatures of each function must be:

  • f(x::T...)::T where {T<:Real}
  • ∇f(g::AbstractVector{T}, x::T...)::Nothing where {T<:Real}
  • ∇²f(H::AbstractMatrix{T}, x::T...)::Nothing where {T<:Real}

Where the gradient vector g and Hessian matrix H are filled in-place. For the Hessian, you must fill in the non-zero lower-triangular entries only. Setting an off-diagonal upper-triangular element may error.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> f(x::Float64) = x^2
+f (generic function with 1 method)
+
+julia> ∇f(x::Float64) = 2 * x
+∇f (generic function with 1 method)
+
+julia> ∇²f(x::Float64) = 2.0
+∇²f (generic function with 1 method)
+
+julia> @operator(model, op_f, 1, f, ∇f, ∇²f)
+NonlinearOperator(f, :op_f)
+
+julia> @objective(model, Min, op_f(x))
+op_f(x)
+
+julia> op_f(2.0)
+4.0
+
+julia> model[:op_f]
+NonlinearOperator(f, :op_f)
+
+julia> model[:op_f](x)
+op_f(x)

Non-macro version

This macro is provided as helpful syntax that matches the style of the rest of the JuMP macros. However, you may also add operators outside the macro using add_nonlinear_operator. For example:

julia> model = Model();
+
+julia> f(x) = x^2
+f (generic function with 1 method)
+
+julia> @operator(model, op_f, 1, f)
+NonlinearOperator(f, :op_f)

is equivalent to

julia> model = Model();
+
+julia> f(x) = x^2
+f (generic function with 1 method)
+
+julia> op_f = model[:op_f] = add_nonlinear_operator(model, 1, f; name = :op_f)
+NonlinearOperator(f, :op_f)
source

@variable

JuMP.@variableMacro
@variable(model, expr, args..., kw_args...)

Add a variable to the model model described by the expression expr, the positional arguments args and the keyword arguments kw_args.

Anonymous and named variables

expr must be one of the forms:

  • Omitted, like @variable(model), which creates an anonymous variable
  • A single symbol like @variable(model, x)
  • A container expression like @variable(model, x[i=1:3])
  • An anoymous container expression like @variable(model, [i=1:3])

Bounds

In addition, the expression can have bounds, such as:

  • @variable(model, x >= 0)
  • @variable(model, x <= 0)
  • @variable(model, x == 0)
  • @variable(model, 0 <= x <= 1)

and bounds can depend on the indices of the container expressions:

  • @variable(model, -i <= x[i=1:3] <= i)

Sets

You can explicitly specify the set to which the variable belongs:

  • @variable(model, x in MOI.Interval(0.0, 1.0))

For more information on this syntax, read Variables constrained on creation.

Positional arguments

The recognized positional arguments in args are the following:

  • Bin: restricts the variable to the MOI.ZeroOne set, that is, {0, 1}. For example, @variable(model, x, Bin). Note: you cannot use @variable(model, Bin), use the binary keyword instead.
  • Int: restricts the variable to the set of integers, that is, ..., -2, -1, 0, 1, 2, ... For example, @variable(model, x, Int). Note: you cannot use @variable(model, Int), use the integer keyword instead.
  • Symmetric: Only available when creating a square matrix of variables, i.e., when expr is of the form varname[1:n,1:n] or varname[i=1:n,j=1:n], it creates a symmetric matrix of variables.
  • PSD: A restrictive extension to Symmetric which constraints a square matrix of variables to Symmetric and constrains to be positive semidefinite.

Keyword arguments

Four keyword arguments are useful in all cases:

  • base_name: Sets the name prefix used to generate variable names. It corresponds to the variable name for scalar variable, otherwise, the variable names are set to base_name[...] for each index ... of the axes axes.
  • start::Float64: specify the value passed to set_start_value for each variable
  • container: specify the container type. See Forcing the container type for more information.
  • set_string_name::Bool = true: control whether to set the MOI.VariableName attribute. Passing set_string_name = false can improve performance.

Other keyword arguments are needed to disambiguate sitations with anonymous variables:

  • lower_bound::Float64: an alternative to x >= lb, sets the value of the variable lower bound.
  • upper_bound::Float64: an alternative to x <= ub, sets the value of the variable upper bound.
  • binary::Bool: an alternative to passing Bin, sets whether the variable is binary or not.
  • integer::Bool: an alternative to passing Int, sets whether the variable is integer or not.
  • set::MOI.AbstractSet: an alternative to using x in set
  • variable_type: used by JuMP extensions. See Extend @variable for more information.

Example

The following are equivalent ways of creating a variable x of name x with lower bound 0:

julia> model = Model();
+
+julia> @variable(model, x >= 0)
+x
julia> model = Model();
+
+julia> @variable(model, x, lower_bound = 0)
+x
julia> model = Model();
+
+julia> x = @variable(model, base_name = "x", lower_bound = 0)
+x

Other examples:

julia> model = Model();
+
+julia> @variable(model, x[i=1:3] <= i, Int, start = sqrt(i), lower_bound = -i)
+3-element Vector{VariableRef}:
+ x[1]
+ x[2]
+ x[3]
+
+julia> @variable(model, y[i=1:3], container = DenseAxisArray, set = MOI.ZeroOne())
+1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
+    Dimension 1, Base.OneTo(3)
+And data, a 3-element Vector{VariableRef}:
+ y[1]
+ y[2]
+ y[3]
+
+julia> @variable(model, z[i=1:3], set_string_name = false)
+3-element Vector{VariableRef}:
+ _[7]
+ _[8]
+ _[9]
source

@variables

JuMP.@variablesMacro
@variables(model, args...)

Adds multiple variables to model at once, in the same fashion as the @variable macro.

The model must be the first argument, and multiple variables can be added on multiple lines wrapped in a begin ... end block.

The macro returns a tuple containing the variables that were defined.

Example

julia> model = Model();
+
+julia> @variables(model, begin
+           x
+           y[i = 1:2] >= 0, (start = i)
+           z, Bin, (start = 0, base_name = "Z")
+       end)
+(x, VariableRef[y[1], y[2]], Z)
Note

Keyword arguments must be contained within parentheses (refer to the example above).

source

add_bridge

JuMP.add_bridgeFunction
add_bridge(
+    model::GenericModel{T},
+    BT::Type{<:MOI.Bridges.AbstractBridge};
+    coefficient_type::Type{S} = T,
+) where {T,S}

Add BT{T} to the list of bridges that can be used to transform unsupported constraints into an equivalent formulation using only constraints supported by the optimizer.

See also: remove_bridge.

Example

julia> model = Model();
+
+julia> add_bridge(model, MOI.Bridges.Constraint.SOCtoNonConvexQuadBridge)
+
+julia> add_bridge(
+           model,
+           MOI.Bridges.Constraint.NumberConversionBridge;
+           coefficient_type = Complex{Float64}
+       )
source

add_constraint

JuMP.add_constraintFunction
add_constraint(model::GenericModel, con::AbstractConstraint, name::String="")

Add a constraint con to Model model and sets its name.

source

add_nonlinear_constraint

JuMP.add_nonlinear_constraintFunction
add_nonlinear_constraint(model::Model, expr::Expr)

Add a nonlinear constraint described by the Julia expression ex to model.

This function is most useful if the expression ex is generated programmatically, and you cannot use @NLconstraint.

Notes

  • You must interpolate the variables directly into the expression expr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> add_nonlinear_constraint(model, :($(x) + $(x)^2 <= 1))
+(x + x ^ 2.0) - 1.0 ≤ 0
source

add_nonlinear_expression

JuMP.add_nonlinear_expressionFunction
add_nonlinear_expression(model::Model, expr::Expr)

Add a nonlinear expression expr to model.

This function is most useful if the expression expr is generated programmatically, and you cannot use @NLexpression.

Notes

  • You must interpolate the variables directly into the expression expr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> add_nonlinear_expression(model, :($(x) + $(x)^2))
+subexpression[1]: x + x ^ 2.0
source

add_nonlinear_operator

JuMP.add_nonlinear_operatorFunction
add_nonlinear_operator(
+    model::Model,
+    dim::Int,
+    f::Function,
+    [∇f::Function,]
+    [∇²f::Function];
+    [name::Symbol = Symbol(f),]
+)

Add a new nonlinear operator with dim input arguments to model and associate it with the name name.

The function f evaluates the operator and must return a scalar.

The optional function ∇f evaluates the first derivative, and the optional function ∇²f evaluates the second derivative.

∇²f may be provided only if ∇f is also provided.

Univariate syntax

If dim == 1, then the method signatures of each function must be:

  • f(::T)::T where {T<:Real}
  • ∇f(::T)::T where {T<:Real}
  • ∇²f(::T)::T where {T<:Real}

Multivariate syntax

If dim > 1, then the method signatures of each function must be:

  • f(x::T...)::T where {T<:Real}
  • ∇f(g::AbstractVector{T}, x::T...)::Nothing where {T<:Real}
  • ∇²f(H::AbstractMatrix{T}, x::T...)::Nothing where {T<:Real}

Where the gradient vector g and Hessian matrix H are filled in-place. For the Hessian, you must fill in the non-zero lower-triangular entries only. Setting an off-diagonal upper-triangular element may error.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> f(x::Float64) = x^2
+f (generic function with 1 method)
+
+julia> ∇f(x::Float64) = 2 * x
+∇f (generic function with 1 method)
+
+julia> ∇²f(x::Float64) = 2.0
+∇²f (generic function with 1 method)
+
+julia> op_f = add_nonlinear_operator(model, 1, f, ∇f, ∇²f)
+NonlinearOperator(f, :f)
+
+julia> @objective(model, Min, op_f(x))
+f(x)
+
+julia> op_f(2.0)
+4.0
source

add_nonlinear_parameter

add_to_expression!

JuMP.add_to_expression!Function
add_to_expression!(expression, terms...)

Updates expression in place to expression + (*)(terms...). This is typically much more efficient than expression += (*)(terms...). For example, add_to_expression!(expression, a, b) produces the same result as expression += a*b, and add_to_expression!(expression, a) produces the same result as expression += a.

Only a few methods are defined, mostly for internal use, and only for the cases when (1) they can be implemented efficiently and (2) expression is capable of storing the result. For example, add_to_expression!(::AffExpr, ::GenericVariableRef, ::GenericVariableRef) is not defined because a GenericAffExpr cannot store the product of two variables.

source

add_to_function_constant

JuMP.add_to_function_constantFunction
add_to_function_constant(constraint::ConstraintRef, value)

Add value to the function constant term.

Note that for scalar constraints, JuMP will aggregate all constant terms onto the right-hand side of the constraint so instead of modifying the function, the set will be translated by -value. For example, given a constraint 2x <= 3, add_to_function_constant(c, 4) will modify it to 2x <= -1.

Example

For scalar constraints, the set is translated by -value:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con, 0 <= 2x - 1 <= 2)
+con : 2 x ∈ [1, 3]
+
+julia> add_to_function_constant(con, 4)
+
+julia> con
+con : 2 x ∈ [-3, -1]

For vector constraints, the constant is added to the function:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @variable(model, y);
+
+julia> @constraint(model, con, [x + y, x, y] in SecondOrderCone())
+con : [x + y, x, y] ∈ MathOptInterface.SecondOrderCone(3)
+
+julia> add_to_function_constant(con, [1, 2, 2])
+
+julia> con
+con : [x + y + 1, x + 2, y + 2] ∈ MathOptInterface.SecondOrderCone(3)
source

add_variable

JuMP.add_variableFunction
add_variable(m::GenericModel, v::AbstractVariable, name::String="")

Add a variable v to Model m and sets its name.

source

all_constraints

JuMP.all_constraintsFunction
all_constraints(model::GenericModel, function_type, set_type)::Vector{<:ConstraintRef}

Return a list of all constraints currently in the model where the function has type function_type and the set has type set_type. The constraints are ordered by creation time.

See also list_of_constraint_types and num_constraints.

Example

julia> model = Model();
+
+julia> @variable(model, x >= 0, Bin);
+
+julia> @constraint(model, 2x <= 1);
+
+julia> all_constraints(model, VariableRef, MOI.GreaterThan{Float64})
+1-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.GreaterThan{Float64}}, ScalarShape}}:
+ x ≥ 0
+
+julia> all_constraints(model, VariableRef, MOI.ZeroOne)
+1-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}, ScalarShape}}:
+ x binary
+
+julia> all_constraints(model, AffExpr, MOI.LessThan{Float64})
+1-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ 2 x ≤ 1
source
all_constraints(
+    model::GenericModel;
+    include_variable_in_set_constraints::Bool,
+)::Vector{ConstraintRef}

Return a list of all constraints in model.

If include_variable_in_set_constraints == true, then VariableRef constraints such as VariableRef-in-Integer are included. To return only the structural constraints (e.g., the rows in the constraint matrix of a linear program), pass include_variable_in_set_constraints = false.

Example

julia> model = Model();
+
+julia> @variable(model, x >= 0, Int);
+
+julia> @constraint(model, 2x <= 1);
+
+julia> @NLconstraint(model, x^2 <= 1);
+
+julia> all_constraints(model; include_variable_in_set_constraints = true)
+4-element Vector{ConstraintRef}:
+ 2 x ≤ 1
+ x ≥ 0
+ x integer
+ x ^ 2.0 - 1.0 ≤ 0
+
+julia> all_constraints(model; include_variable_in_set_constraints = false)
+2-element Vector{ConstraintRef}:
+ 2 x ≤ 1
+ x ^ 2.0 - 1.0 ≤ 0

Performance considerations

Note that this function is type-unstable because it returns an abstractly typed vector. If performance is a problem, consider using list_of_constraint_types and a function barrier. See the Performance tips for extensions section of the documentation for more details.

source

all_nonlinear_constraints

JuMP.all_nonlinear_constraintsFunction
all_nonlinear_constraints(model::GenericModel)

Return a vector of all nonlinear constraint references in the model in the order they were added to the model.

source

all_variables

JuMP.all_variablesFunction
all_variables(model::GenericModel{T})::Vector{GenericVariableRef{T}} where {T}

Returns a list of all variables currently in the model. The variables are ordered by creation time.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @variable(model, y);
+
+julia> all_variables(model)
+2-element Vector{VariableRef}:
+ x
+ y
source

anonymous_name

JuMP.anonymous_nameFunction
anonymous_name(::MIME, x::AbstractVariableRef)

The name to use for an anonymous variable x when printing.

source

backend

JuMP.backendFunction
backend(model::GenericModel)

Return the lower-level MathOptInterface model that sits underneath JuMP. This model depends on which operating mode JuMP is in (see mode).

  • If JuMP is in DIRECT mode (i.e., the model was created using direct_model), the backend will be the optimizer passed to direct_model.
  • If JuMP is in MANUAL or AUTOMATIC mode, the backend is a MOI.Utilities.CachingOptimizer.

This function should only be used by advanced users looking to access low-level MathOptInterface or solver-specific functionality.

Notes

If JuMP is not in DIRECT mode, the type returned by backend may change between any JuMP releases. Therefore, only use the public API exposed by MathOptInterface, and do not access internal fields. If you require access to the innermost optimizer, see unsafe_backend. Alternatively, use direct_model to create a JuMP model in DIRECT mode.

See also: unsafe_backend.

source

barrier_iterations

JuMP.barrier_iterationsFunction
barrier_iterations(model::GenericModel)

Gets the cumulative number of barrier iterations during the most recent optimization.

Solvers must implement MOI.BarrierIterations() to use this function.

source

bridge_constraints

JuMP.bridge_constraintsFunction
bridge_constraints(model::GenericModel)

When in direct mode, return false.

When in manual or automatic mode, return a Bool indicating whether the optimizer is set and unsupported constraints are automatically bridged to equivalent supported constraints when an appropriate transformation is available.

source

build_constraint

JuMP.build_constraintFunction
build_constraint(
+    _error::Function,
+    Q::LinearAlgebra.Symmetric{V, M},
+    ::PSDCone,
+) where {V<:AbstractJuMPScalar,M<:AbstractMatrix{V}}

Return a VectorConstraint of shape SymmetricMatrixShape constraining the matrix Q to be positive semidefinite.

This function is used by the @constraint macros as follows:

julia> import LinearAlgebra
+
+julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2]);
+
+julia> @constraint(model, LinearAlgebra.Symmetric(Q) in PSDCone())
+[Q[1,1]  Q[1,2];
+ Q[1,2]  Q[2,2]] ∈ PSDCone()

The form above is usually used when the entries of Q are affine or quadratic expressions, but it can also be used when the entries are variables to get the reference of the semidefinite constraint, e.g.,

julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2], Symmetric)
+2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
+ Q[1,1]  Q[1,2]
+ Q[1,2]  Q[2,2]
+
+julia> @constraint(model, Q in PSDCone())
+[Q[1,1]  Q[1,2];
+ Q[1,2]  Q[2,2]] ∈ PSDCone()
source
build_constraint(
+    _error::Function,
+    Q::AbstractMatrix{<:AbstractJuMPScalar},
+    ::PSDCone,
+)

Return a VectorConstraint of shape SquareMatrixShape constraining the matrix Q to be symmetric and positive semidefinite.

This function is used by the @constraint macro as follows:

julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2]);
+
+julia> @constraint(model, Q in PSDCone())
+[Q[1,1]  Q[1,2];
+ Q[2,1]  Q[2,2]] ∈ PSDCone()
source
build_constraint(
+    _error::Function,
+    Q::LinearAlgebra.Hermitian{V,M},
+    ::HermitianPSDCone,
+) where {V<:AbstractJuMPScalar,M<:AbstractMatrix{V}}

Return a VectorConstraint of shape HermitianMatrixShape constraining the matrix Q to be Hermitian positive semidefinite.

This function is used by the @constraint macros as follows:

julia> import LinearAlgebra
+
+julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2]);
+
+julia> @constraint(model, LinearAlgebra.Hermitian(Q) in HermitianPSDCone())
+[Q[1,1]  Q[1,2];
+ Q[1,2]  Q[2,2]] ∈ HermitianPSDCone()
source
build_constraint(
+    _error::Function,
+    f::AbstractVector{<:AbstractJuMPScalar},
+    ::Nonnegatives,
+    extra::Union{MOI.AbstractVectorSet,AbstractVectorSet},
+)

A helper method that re-writes

@constraint(model, X >= Y, extra)

into

@constraint(model, X - Y in extra)
source
build_constraint(
+    _error::Function,
+    f::AbstractVector{<:AbstractJuMPScalar},
+    ::Nonpositives,
+    extra::Union{MOI.AbstractVectorSet,AbstractVectorSet},
+)

A helper method that re-writes

@constraint(model, Y <= X, extra)

into

@constraint(model, X - Y in extra)
source

build_variable

JuMP.build_variableFunction
build_variable(
+    _error::Function,
+    info::VariableInfo,
+    args...;
+    kwargs...,
+)

Return a new AbstractVariable object.

This method should only be implemented by developers creating JuMP extensions. It should never be called by users of JuMP.

Arguments

  • _error: a function to call instead of error. _error annotates the error message with additional information for the user.
  • info: an instance of VariableInfo. This has a variety of fields relating to the variable such as info.lower_bound and info.binary.
  • args: optional additional positional arguments for extending the @variable macro.
  • kwargs: optional keyword arguments for extending the @variable macro.

See also: @variable

Warning

Extensions should define a method with ONE positional argument to dispatch the call to a different method. Creating an extension that relies on multiple positional arguments leads to MethodErrors if the user passes the arguments in the wrong order.

Example

@variable(model, x, Foo)

will call

build_variable(_error::Function, info::VariableInfo, ::Type{Foo})

Passing special-case positional arguments such as Bin, Int, and PSD is okay, along with keyword arguments:

@variable(model, x, Int, Foo(), mykwarg = true)
+# or
+@variable(model, x, Foo(), Int, mykwarg = true)

will call

build_variable(_error::Function, info::VariableInfo, ::Foo; mykwarg)

and info.integer will be true.

Note that the order of the positional arguments does not matter.

source
build_variable(_error::Function, variables, ::SymmetricMatrixSpace)

Return a VariablesConstrainedOnCreation of shape SymmetricMatrixShape creating variables in MOI.Reals, i.e. "free" variables unless they are constrained after their creation.

This function is used by the @variable macro as follows:

julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2], Symmetric)
+2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
+ Q[1,1]  Q[1,2]
+ Q[1,2]  Q[2,2]
source
build_variable(_error::Function, variables, ::SkewSymmetricMatrixSpace)

Return a VariablesConstrainedOnCreation of shape SkewSymmetricMatrixShape creating variables in MOI.Reals, i.e. "free" variables unless they are constrained after their creation.

This function is used by the @variable macro as follows:

julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2] in SkewSymmetricMatrixSpace())
+2×2 Matrix{AffExpr}:
+ 0        Q[1,2]
+ -Q[1,2]  0
source
build_variable(_error::Function, variables, ::HermitianMatrixSpace)

Return a VariablesConstrainedOnCreation of shape HermitianMatrixShape creating variables in MOI.Reals, i.e. "free" variables unless they are constrained after their creation.

This function is used by the @variable macro as follows:

julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2] in HermitianMatrixSpace())
+2×2 LinearAlgebra.Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
+ real(Q[1,1])                    real(Q[1,2]) + imag(Q[1,2]) im
+ real(Q[1,2]) - imag(Q[1,2]) im  real(Q[2,2])
source
build_variable(_error::Function, variables, ::PSDCone)

Return a VariablesConstrainedOnCreation of shape SymmetricMatrixShape constraining the variables to be positive semidefinite.

This function is used by the @variable macro as follows:

julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2], PSD)
+2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
+ Q[1,1]  Q[1,2]
+ Q[1,2]  Q[2,2]
source

callback_node_status

callback_value

JuMP.callback_valueFunction
callback_value(cb_data, x::GenericVariableRef)

Return the primal solution of a variable inside a callback.

cb_data is the argument to the callback function, and the type is dependent on the solver.

source
callback_value(cb_data, expr::Union{GenericAffExpr, GenericQuadExpr})

Return the primal solution of an affine or quadratic expression inside a callback by getting the value for each variable appearing in the expression.

cb_data is the argument to the callback function, and the type is dependent on the solver.

source

check_belongs_to_model

coefficient

JuMP.coefficientFunction
coefficient(v1::GenericVariableRef{T}, v2::GenericVariableRef{T}) where {T}

Return one(T) if v1 == v2, and zero(T) otherwise.

This is a fallback for other coefficient methods to simplify code in which the expression may be a single variable.

source
coefficient(a::GenericAffExpr{C,V}, v::V) where {C,V}

Return the coefficient associated with variable v in the affine expression a.

source
coefficient(a::GenericAffExpr{C,V}, v1::V, v2::V) where {C,V}

Return the coefficient associated with the term v1 * v2 in the quadratic expression a.

Note that coefficient(a, v1, v2) is the same as coefficient(a, v2, v1).

source
coefficient(a::GenericQuadExpr{C,V}, v::V) where {C,V}

Return the coefficient associated with variable v in the affine component of a.

source

compute_conflict!

JuMP.compute_conflict!Function
compute_conflict!(model::GenericModel)

Compute a conflict if the model is infeasible. If an optimizer has not been set yet (see set_optimizer), a NoOptimizer error is thrown.

The status of the conflict can be checked with the MOI.ConflictStatus model attribute. Then, the status for each constraint can be queried with the MOI.ConstraintConflictStatus attribute.

source

constant

JuMP.constantFunction
constant(aff::GenericAffExpr{C, V})::C

Return the constant of the affine expression.

source
constant(aff::GenericQuadExpr{C, V})::C

Return the constant of the quadratic expression.

source

constraint_by_name

JuMP.constraint_by_nameFunction
constraint_by_name(model::AbstractModel,
+                   name::String)::Union{ConstraintRef, Nothing}

Return the reference of the constraint with name attribute name or Nothing if no constraint has this name attribute. Throws an error if several constraints have name as their name attribute.

constraint_by_name(model::AbstractModel,
+                   name::String,
+                   F::Type{<:Union{AbstractJuMPScalar,
+                                   Vector{<:AbstractJuMPScalar},
+                                   MOI.AbstactFunction}},
+                   S::Type{<:MOI.AbstractSet})::Union{ConstraintRef, Nothing}

Similar to the method above, except that it throws an error if the constraint is not an F-in-S contraint where F is either the JuMP or MOI type of the function, and S is the MOI type of the set. This method is recommended if you know the type of the function and set since its returned type can be inferred while for the method above (i.e. without F and S), the exact return type of the constraint index cannot be inferred.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, con, x^2 == 1)
+con : x² = 1
+
+julia> constraint_by_name(model, "kon")
+
+julia> constraint_by_name(model, "con")
+con : x² = 1
+
+julia> constraint_by_name(model, "con", AffExpr, MOI.EqualTo{Float64})
+
+julia> constraint_by_name(model, "con", QuadExpr, MOI.EqualTo{Float64})
+con : x² = 1
source

constraint_object

JuMP.constraint_objectFunction
constraint_object(con_ref::ConstraintRef)

Return the underlying constraint data for the constraint referenced by ref.

source

constraint_ref_with_index

constraint_string

JuMP.constraint_stringFunction
constraint_string(
+    mode::MIME,
+    ref::ConstraintRef;
+    in_math_mode::Bool = false)

Return a string representation of the constraint ref, given the mode.

source

constraints_string

JuMP.constraints_stringFunction
constraints_string(mode, model::AbstractModel)::Vector{String}

Return a list of Strings describing each constraint of the model.

source

copy_conflict

JuMP.copy_conflictFunction
copy_conflict(model::GenericModel)

Return a copy of the current conflict for the model model and a GenericReferenceMap that can be used to obtain the variable and constraint reference of the new model corresponding to a given model's reference.

This is a convenience function that provides a filtering function for copy_model.

Note

Model copy is not supported in DIRECT mode, i.e. when a model is constructed using the direct_model constructor instead of the Model constructor. Moreover, independently on whether an optimizer was provided at model construction, the new model will have no optimizer, i.e., an optimizer will have to be provided to the new model in the optimize! call.

Example

In the following example, a model model is constructed with a variable x and two constraints c1 and c2. This model has no solution, as the two constraints are mutually exclusive. The solver is asked to compute a conflict with compute_conflict!. The parts of model participating in the conflict are then copied into a model iis_model.

julia> using JuMP
+
+julia> import Gurobi
+
+julia> model = Model(Gurobi.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x >= 0)
+x
+
+julia> @constraint(model, c1, x >= 2)
+c1 : x ≥ 2
+
+julia> @constraint(model, c2, x <= 1)
+c2 : x ≤ 1
+
+julia> optimize!(model)
+
+julia> compute_conflict!(model)
+
+julia> if get_attribute(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND
+           iis_model, reference_map = copy_conflict(model)
+           print(iis_model)
+       end
+Feasibility
+Subject to
+ c1 : x ≥ 2
+ c2 : x ≤ 1
source

copy_extension_data

JuMP.copy_extension_dataFunction
copy_extension_data(data, new_model::AbstractModel, model::AbstractModel)

Return a copy of the extension data data of the model model to the extension data of the new model new_model.

A method should be added for any JuMP extension storing data in the ext field.

Warning

Do not engage in type piracy by implementing this method for types of data that you did not define! JuMP extensions should store types that they define in model.ext, rather than regular Julia types.

source

copy_model

JuMP.copy_modelFunction
copy_model(model::GenericModel; filter_constraints::Union{Nothing, Function}=nothing)

Return a copy of the model model and a GenericReferenceMap that can be used to obtain the variable and constraint reference of the new model corresponding to a given model's reference. A Base.copy(::AbstractModel) method has also been implemented, it is similar to copy_model but does not return the reference map.

If the filter_constraints argument is given, only the constraints for which this function returns true will be copied. This function is given a constraint reference as argument.

Note

Model copy is not supported in DIRECT mode, i.e. when a model is constructed using the direct_model constructor instead of the Model constructor. Moreover, independently on whether an optimizer was provided at model construction, the new model will have no optimizer, i.e., an optimizer will have to be provided to the new model in the optimize! call.

Example

In the following example, a model model is constructed with a variable x and a constraint cref. It is then copied into a model new_model with the new references assigned to x_new and cref_new.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, cref, x == 2)
+cref : x = 2
+
+julia> new_model, reference_map = copy_model(model);
+
+julia> x_new = reference_map[x]
+x
+
+julia> cref_new = reference_map[cref]
+cref : x = 2
source

delete

JuMP.deleteFunction
delete(model::GenericModel, con_ref::ConstraintRef)

Delete the constraint associated with constraint_ref from the model model.

Note that delete does not unregister the name from the model, so adding a new constraint of the same name will throw an error. Use unregister to unregister the name after deletion.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, c, 2x <= 1)
+c : 2 x ≤ 1
+
+julia> delete(model, c)
+
+julia> unregister(model, :c)
+
+julia> print(model)
+Feasibility
+Subject to
+
+julia> model[:c]
+ERROR: KeyError: key :c not found
+Stacktrace:
+[...]
source
delete(model::GenericModel, con_refs::Vector{<:ConstraintRef})

Delete the constraints associated with con_refs from the model model. Solvers may implement specialized methods for deleting multiple constraints of the same concrete type, i.e., when isconcretetype(eltype(con_refs)). These may be more efficient than repeatedly calling the single constraint delete method.

See also: unregister

source
delete(model::GenericModel, variable_ref::GenericVariableRef)

Delete the variable associated with variable_ref from the model model.

Note that delete does not unregister the name from the model, so adding a new variable of the same name will throw an error. Use unregister to unregister the name after deletion.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> delete(model, x)
+
+julia> unregister(model, :x)
+
+julia> print(model)
+Feasibility
+Subject to
+
+julia> model[:x]
+ERROR: KeyError: key :x not found
+Stacktrace:
+[...]
source
delete(model::GenericModel, variable_refs::Vector{<:GenericVariableRef})

Delete the variables associated with variable_refs from the model model. Solvers may implement methods for deleting multiple variables that are more efficient than repeatedly calling the single variable delete method.

See also: unregister

source
delete(model::Model, c::NonlinearConstraintRef)

Delete the nonlinear constraint c from model.

source

delete_lower_bound

delete_upper_bound

direct_generic_model

JuMP.direct_generic_modelFunction
direct_generic_model(
+    value_type::Type{T},
+    backend::MOI.ModelLike;
+) where {T<:Real}

Return a new JuMP model using backend to store the model and solve it.

As opposed to the Model constructor, no cache of the model is stored outside of backend and no bridges are automatically applied to backend.

Notes

The absence of a cache reduces the memory footprint but, it is important to bear in mind the following implications of creating models using this direct mode:

  • When backend does not support an operation, such as modifying constraints or adding variables/constraints after solving, an error is thrown. For models created using the Model constructor, such situations can be dealt with by storing the modifications in a cache and loading them into the optimizer when optimize! is called.
  • No constraint bridging is supported by default.
  • The optimizer used cannot be changed the model is constructed.
  • The model created cannot be copied.
source
direct_generic_model(::Type{T}, factory::MOI.OptimizerWithAttributes)

Create a direct_generic_model using factory, a MOI.OptimizerWithAttributes object created by optimizer_with_attributes.

Example

julia> import HiGHS
+
+julia> optimizer = optimizer_with_attributes(
+           HiGHS.Optimizer,
+           "presolve" => "off",
+           MOI.Silent() => true,
+       );
+
+julia> model = direct_generic_model(Float64, optimizer)
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: DIRECT
+Solver name: HiGHS

is equivalent to:

julia> import HiGHS
+
+julia> model = direct_generic_model(Float64, HiGHS.Optimizer())
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: DIRECT
+Solver name: HiGHS
+
+julia> set_attribute(model, "presolve", "off")
+
+julia> set_attribute(model, MOI.Silent(), true)
source

direct_model

JuMP.direct_modelFunction
direct_model(backend::MOI.ModelLike)

Return a new JuMP model using backend to store the model and solve it.

As opposed to the Model constructor, no cache of the model is stored outside of backend and no bridges are automatically applied to backend.

Notes

The absence of a cache reduces the memory footprint but, it is important to bear in mind the following implications of creating models using this direct mode:

  • When backend does not support an operation, such as modifying constraints or adding variables/constraints after solving, an error is thrown. For models created using the Model constructor, such situations can be dealt with by storing the modifications in a cache and loading them into the optimizer when optimize! is called.
  • No constraint bridging is supported by default.
  • The optimizer used cannot be changed the model is constructed.
  • The model created cannot be copied.
source
direct_model(factory::MOI.OptimizerWithAttributes)

Create a direct_model using factory, a MOI.OptimizerWithAttributes object created by optimizer_with_attributes.

Example

julia> import HiGHS
+
+julia> optimizer = optimizer_with_attributes(
+           HiGHS.Optimizer,
+           "presolve" => "off",
+           MOI.Silent() => true,
+       );
+
+julia> model = direct_model(optimizer)
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: DIRECT
+Solver name: HiGHS

is equivalent to:

julia> import HiGHS
+
+julia> model = direct_model(HiGHS.Optimizer())
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: DIRECT
+Solver name: HiGHS
+
+julia> set_attribute(model, "presolve", "off")
+
+julia> set_attribute(model, MOI.Silent(), true)
source

drop_zeros!

JuMP.drop_zeros!Function
drop_zeros!(expr::GenericAffExpr)

Remove terms in the affine expression with 0 coefficients.

source
drop_zeros!(expr::GenericQuadExpr)

Remove terms in the quadratic expression with 0 coefficients.

source

dual

JuMP.dualFunction
dual(con_ref::ConstraintRef; result::Int = 1)

Return the dual value of constraint con_ref associated with result index result of the most-recent solution returned by the solver.

Use has_dual to check if a result exists before asking for values.

See also: result_count, shadow_price.

source
dual(c::NonlinearConstraintRef)

Return the dual of the nonlinear constraint c.

source

dual_objective_value

JuMP.dual_objective_valueFunction
dual_objective_value(model::GenericModel; result::Int = 1)

Return the value of the objective of the dual problem associated with result index result of the most-recent solution returned by the solver.

Throws MOI.UnsupportedAttribute{MOI.DualObjectiveValue} if the solver does not support this attribute.

See also: result_count.

source

dual_shape

JuMP.dual_shapeFunction
dual_shape(shape::AbstractShape)::AbstractShape

Returns the shape of the dual space of the space of objects of shape shape. By default, the dual_shape of a shape is itself. See the examples section below for an example for which this is not the case.

Example

Consider polynomial constraints for which the dual is moment constraints and moment constraints for which the dual is polynomial constraints. Shapes for polynomials can be defined as follows:

struct Polynomial
+    coefficients::Vector{Float64}
+    monomials::Vector{Monomial}
+end
+struct PolynomialShape <: AbstractShape
+    monomials::Vector{Monomial}
+end
+JuMP.reshape_vector(x::Vector, shape::PolynomialShape) = Polynomial(x, shape.monomials)

and a shape for moments can be defined as follows:

struct Moments
+    coefficients::Vector{Float64}
+    monomials::Vector{Monomial}
+end
+struct MomentsShape <: AbstractShape
+    monomials::Vector{Monomial}
+end
+JuMP.reshape_vector(x::Vector, shape::MomentsShape) = Moments(x, shape.monomials)

Then dual_shape allows the definition of the shape of the dual of polynomial and moment constraints:

dual_shape(shape::PolynomialShape) = MomentsShape(shape.monomials)
+dual_shape(shape::MomentsShape) = PolynomialShape(shape.monomials)
source

dual_start_value

JuMP.dual_start_valueFunction
dual_start_value(con_ref::ConstraintRef)

Return the dual start value (MOI attribute ConstraintDualStart) of the constraint con_ref.

Note: If no dual start value has been set, dual_start_value will return nothing.

See also set_dual_start_value.

source

dual_status

error_if_direct_mode

JuMP.error_if_direct_modeFunction
error_if_direct_mode(model::GenericModel, func::Symbol)

Errors if model is in direct mode during a call from the function named func.

Used internally within JuMP, or by JuMP extensions who do not want to support models in direct mode.

source

fix

JuMP.fixFunction
fix(v::GenericVariableRef, value::Number; force::Bool = false)

Fix a variable to a value. Update the fixing constraint if one exists, otherwise create a new one.

If the variable already has variable bounds and force=false, calling fix will throw an error. If force=true, existing variable bounds will be deleted, and the fixing constraint will be added. Note a variable will have no bounds after a call to unfix.

See also FixRef, is_fixed, fix_value, unfix.

Examples

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> is_fixed(x)
+false
+
+julia> fix(x, 1.0)
+
+julia> is_fixed(x)
+true
julia> model = Model();
+
+julia> @variable(model, 0 <= x <= 1);
+
+julia> is_fixed(x)
+false
+
+julia> fix(x, 1.0; force = true)
+
+julia> is_fixed(x)
+true
source

fix_discrete_variables

JuMP.fix_discrete_variablesFunction
fix_discrete_variables([var_value::Function = value,] model::GenericModel)

Modifies model to convert all binary and integer variables to continuous variables with fixed bounds of var_value(x).

Return

Returns a function that can be called without any arguments to restore the original model. The behavior of this function is undefined if additional changes are made to the affected variables in the meantime.

Notes

  • An error is thrown if semi-continuous or semi-integer constraints are present (support may be added for these in the future).
  • All other constraints are ignored (left in place). This includes discrete constraints like SOS and indicator constraints.

Example

julia> model = Model();
+
+julia> @variable(model, x, Bin, start = 1);
+
+julia> @variable(model, 1 <= y <= 10, Int, start = 2);
+
+julia> @objective(model, Min, x + y);
+
+julia> undo_relax = fix_discrete_variables(start_value, model);
+
+julia> print(model)
+Min x + y
+Subject to
+ x = 1
+ y = 2
+
+julia> undo_relax()
+
+julia> print(model)
+Min x + y
+Subject to
+ y ≥ 1
+ y ≤ 10
+ y integer
+ x binary
source

fix_value

JuMP.fix_valueFunction
fix_value(v::GenericVariableRef)

Return the value to which a variable is fixed.

Error if one does not exist.

See also FixRef, is_fixed, fix, unfix.

Examples

julia> model = Model();
+
+julia> @variable(model, x == 1);
+
+julia> fix_value(x)
+1.0
source

flatten!

JuMP.flatten!Function
flatten!(expr::GenericNonlinearExpr)

Flatten a nonlinear expression in-place by lifting nested + and * nodes into a single n-ary operation.

Motivation

Nonlinear expressions created using operator overloading can be deeply nested and unbalanced. For example, prod(x for i in 1:4) creates *(x, *(x, *(x, x))) instead of the more preferable *(x, x, x, x).

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> y = prod(x for i in 1:4)
+((x²) * x) * x
+
+julia> flatten!(y)
+(x²) * x * x
+
+julia> flatten!(sin(prod(x for i in 1:4)))
+sin((x²) * x * x)
source

function_string

JuMP.function_stringFunction
function_string(
+    mode::MIME,
+    func::Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}},
+)

Return a String representing the function func using print mode mode.

source

get_attribute

JuMP.get_attributeFunction
get_attribute(model::GenericModel, attr::MOI.AbstractModelAttribute)
+get_attribute(x::GenericVariableRef, attr::MOI.AbstractVariableAttribute)
+get_attribute(cr::ConstraintRef, attr::MOI.AbstractConstraintAttribute)

Get the value of a solver-specifc attribute attr.

This is equivalent to calling MOI.get with the associated MOI model and, for variables and constraints, with the associated MOI.VariableIndex or MOI.ConstraintIndex.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, c, 2 * x <= 1)
+c : 2 x ≤ 1
+
+julia> get_attribute(model, MOI.Name())
+""
+
+julia> get_attribute(x, MOI.VariableName())
+"x"
+
+julia> get_attribute(c, MOI.ConstraintName())
+"c"
source
get_attribute(
+    model::Union{GenericModel,MOI.OptimizerWithAttributes},
+    attr::Union{AbstractString,MOI.AbstractOptimizerAttribute},
+)

Get the value of a solver-specifc attribute attr.

This is equivalent to calling MOI.get with the associated MOI model.

If attr is an AbstractString, it is converted to MOI.RawOptimizerAttribute.

Example

julia> import HiGHS
+
+julia> opt = optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => true);
+
+julia> model = Model(opt);
+
+julia> get_attribute(model, "output_flag")
+true
+
+julia> get_attribute(model, MOI.RawOptimizerAttribute("output_flag"))
+true
+
+julia> get_attribute(opt, "output_flag")
+true
+
+julia> get_attribute(opt, MOI.RawOptimizerAttribute("output_flag"))
+true
source

get_optimizer_attribute

JuMP.get_optimizer_attributeFunction
get_optimizer_attribute(
+    model::Union{GenericModel,MOI.OptimizerWithAttributes},
+    attr::Union{AbstractString,MOI.AbstractOptimizerAttribute},
+)

Return the value associated with the solver-specific attribute attr.

If attr is an AbstractString, this is equivalent to get_optimizer_attribute(model, MOI.RawOptimizerAttribute(name)).

Compat

This method will remain in all v1.X releases of JuMP, but it may be removed in a future v2.0 release. We recommend using get_attribute instead.

See also: set_optimizer_attribute, set_optimizer_attributes.

Example

julia> import Ipopt
+
+julia> model = Model(Ipopt.Optimizer);
+
+julia> get_optimizer_attribute(model, MOI.Silent())
+false
source

has_duals

has_lower_bound

has_start_value

has_upper_bound

has_values

JuMP.has_valuesFunction
has_values(model::GenericModel; result::Int = 1)

Return true if the solver has a primal solution in result index result available to query, otherwise return false.

See also value and result_count.

source

in_set_string

JuMP.in_set_stringFunction
in_set_string(mode::MIME, set)

Return a String representing the membership to the set set using print mode mode.

source

index

JuMP.indexFunction
index(cr::ConstraintRef)::MOI.ConstraintIndex

Return the index of the constraint that corresponds to cr in the MOI backend.

source
index(v::GenericVariableRef)::MOI.VariableIndex

Return the index of the variable that corresponds to v in the MOI backend.

source
index(p::NonlinearParameter)::MOI.Nonlinear.ParameterIndex

Return the index of the nonlinear parameter associated with p.

source
index(ex::NonlinearExpression)::MOI.Nonlinear.ExpressionIndex

Return the index of the nonlinear expression associated with ex.

source

is_binary

is_fixed

JuMP.is_fixedFunction
is_fixed(v::GenericVariableRef)

Return true if v is a fixed variable. If true, the fixed value can be queried with fix_value.

See also FixRef, fix_value, fix, unfix.

Examples

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> is_fixed(x)
+false
+
+julia> fix(x, 1.0)
+
+julia> is_fixed(x)
+true
source

is_integer

JuMP.is_integerFunction
is_integer(v::GenericVariableRef)

Return true if v is constrained to be integer.

See also IntegerRef, set_integer, unset_integer.

Examples

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> is_integer(x)
+false
+
+julia> set_integer(x)
+
+julia> is_integer(x)
+true
source

is_parameter

JuMP.is_parameterFunction
is_parameter(x::GenericVariableRef)::Bool

Return true if x is constrained to be a parameter.

See also ParameterRef, set_parameter_value, parameter_value.

Examples

julia> model = Model();
+
+julia> @variable(model, p in Parameter(2))
+p
+
+julia> is_parameter(p)
+true
+
+julia> @variable(model, x)
+x
+
+julia> is_parameter(x)
+false
source

is_valid

JuMP.is_validFunction
is_valid(model::GenericModel, con_ref::ConstraintRef{<:AbstractModel})

Return true if constraint_ref refers to a valid constraint in model.

source
is_valid(model::GenericModel, variable_ref::GenericVariableRef)

Return true if variable refers to a valid variable in model.

source
is_valid(model::Model, c::NonlinearConstraintRef)

Return true if c refers to a valid nonlinear constraint in model.

source

isequal_canonical

JuMP.isequal_canonicalFunction
isequal_canonical(
+    aff::GenericAffExpr{C,V},
+    other::GenericAffExpr{C,V}
+) where {C,V}

Return true if aff is equal to other after dropping zeros and disregarding the order. Mainly useful for testing.

source

jump_function

jump_function_type

latex_formulation

JuMP.latex_formulationFunction
latex_formulation(model::AbstractModel)

Wrap model in a type so that it can be pretty-printed as text/latex in a notebook like IJulia, or in Documenter.

To render the model, end the cell with latex_formulation(model), or call display(latex_formulation(model)) in to force the display of the model from inside a function.

source

linear_terms

JuMP.linear_termsFunction
linear_terms(aff::GenericAffExpr{C, V})

Provides an iterator over coefficient-variable tuples (a_i::C, x_i::V) in the linear part of the affine expression.

source
linear_terms(quad::GenericQuadExpr{C, V})

Provides an iterator over tuples (coefficient::C, variable::V) in the linear part of the quadratic expression.

source

list_of_constraint_types

JuMP.list_of_constraint_typesFunction
list_of_constraint_types(model::GenericModel)::Vector{Tuple{Type,Type}}

Return a list of tuples of the form (F, S) where F is a JuMP function type and S is an MOI set type such that all_constraints(model, F, S) returns a nonempty list.

Example

julia> model = Model();
+
+julia> @variable(model, x >= 0, Bin);
+
+julia> @constraint(model, 2x <= 1);
+
+julia> list_of_constraint_types(model)
+3-element Vector{Tuple{Type, Type}}:
+ (AffExpr, MathOptInterface.LessThan{Float64})
+ (VariableRef, MathOptInterface.GreaterThan{Float64})
+ (VariableRef, MathOptInterface.ZeroOne)

Performance considerations

Iterating over the list of function and set types is a type-unstable operation. Consider using a function barrier. See the Performance tips for extensions section of the documentation for more details.

source

lower_bound

lp_sensitivity_report

JuMP.lp_sensitivity_reportFunction
lp_sensitivity_report(model::GenericModel{T}; atol::T = Base.rtoldefault(T))::SensitivityReport{T} where {T}

Given a linear program model with a current optimal basis, return a SensitivityReport object, which maps:

  • Every variable reference to a tuple (d_lo, d_hi)::Tuple{T,T}, explaining how much the objective coefficient of the corresponding variable can change by, such that the original basis remains optimal.
  • Every constraint reference to a tuple (d_lo, d_hi)::Tuple{T,T}, explaining how much the right-hand side of the corresponding constraint can change by, such that the basis remains optimal.

Both tuples are relative, rather than absolute. So given a objective coefficient of 1.0 and a tuple (-0.5, 0.5), the objective coefficient can range between 1.0 - 0.5 an 1.0 + 0.5.

atol is the primal/dual optimality tolerance, and should match the tolerance of the solver used to compute the basis.

Note: interval constraints are NOT supported.

Example

julia> import HiGHS
+
+julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, -1 <= x <= 2)
+x
+
+julia> @objective(model, Min, x)
+x
+
+julia> optimize!(model)
+
+julia> report = lp_sensitivity_report(model; atol = 1e-7);
+
+julia> dx_lo, dx_hi = report[x]
+(-1.0, Inf)
+
+julia> println(
+           "The objective coefficient of `x` can decrease by $dx_lo or " *
+           "increase by $dx_hi."
+       )
+The objective coefficient of `x` can decrease by -1.0 or increase by Inf.
+
+julia> dRHS_lo, dRHS_hi = report[LowerBoundRef(x)]
+(-Inf, 3.0)
+
+julia> println(
+           "The lower bound of `x` can decrease by $dRHS_lo or increase " *
+           "by $dRHS_hi."
+       )
+The lower bound of `x` can decrease by -Inf or increase by 3.0.
source

map_coefficients

JuMP.map_coefficientsFunction
map_coefficients(f::Function, a::GenericAffExpr)

Apply f to the coefficients and constant term of an GenericAffExpr a and return a new expression.

See also: map_coefficients_inplace!

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> a = GenericAffExpr(1.0, x => 1.0)
+x + 1
+
+julia> map_coefficients(c -> 2 * c, a)
+2 x + 2
+
+julia> a
+x + 1
source
map_coefficients(f::Function, a::GenericQuadExpr)

Apply f to the coefficients and constant term of an GenericQuadExpr a and return a new expression.

See also: map_coefficients_inplace!

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> a = @expression(model, x^2 + x + 1)
+x² + x + 1
+
+julia> map_coefficients(c -> 2 * c, a)
+2 x² + 2 x + 2
+
+julia> a
+x² + x + 1
source

map_coefficients_inplace!

JuMP.map_coefficients_inplace!Function
map_coefficients_inplace!(f::Function, a::GenericAffExpr)

Apply f to the coefficients and constant term of an GenericAffExpr a and update them in-place.

See also: map_coefficients

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> a = GenericAffExpr(1.0, x => 1.0)
+x + 1
+
+julia> map_coefficients_inplace!(c -> 2 * c, a)
+2 x + 2
+
+julia> a
+2 x + 2
source
map_coefficients_inplace!(f::Function, a::GenericQuadExpr)

Apply f to the coefficients and constant term of an GenericQuadExpr a and update them in-place.

See also: map_coefficients

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> a = @expression(model, x^2 + x + 1)
+x² + x + 1
+
+julia> map_coefficients_inplace!(c -> 2 * c, a)
+2 x² + 2 x + 2
+
+julia> a
+2 x² + 2 x + 2
source

mode

model_convert

JuMP.model_convertFunction
model_convert(
+    model::AbstractModel,
+    rhs::Union{
+        AbstractConstraint,
+        Number,
+        AbstractJuMPScalar,
+        MOI.AbstractSet,
+    },
+)

Convert the coefficients and constants of functions and sets in the rhs to the coefficient type value_type(typeof(model)).

Purpose

Creating and adding a constraint is a two-step process. The first step calls build_constraint, and the result of that is passed to add_constraint.

However, because build_constraint does not take the model as an argument, the coefficients and constants of the function or set might be different than value_type(typeof(model)).

Therefore, the result of build_constraint is converted in a call to model_convert before the result is passed to add_constraint.

source

model_string

JuMP.model_stringFunction
model_string(mode::MIME, model::AbstractModel)

Return a String representation of model given the mode.

source

moi_function

moi_function_type

moi_set

JuMP.moi_setFunction
moi_set(constraint::AbstractConstraint)

Return the set of the constraint constraint in the function-in-set form as a MathOptInterface.AbstractSet.

moi_set(s::AbstractVectorSet, dim::Int)

Returns the MOI set of dimension dim corresponding to the JuMP set s.

moi_set(s::AbstractScalarSet)

Returns the MOI set corresponding to the JuMP set s.

source

name

JuMP.nameFunction
name(con_ref::ConstraintRef)

Get a constraint's name attribute.

source
name(v::GenericVariableRef)::String

Get a variable's name attribute.

source
name(model::AbstractModel)

Return the MOI.Name attribute of model's backend, or a default if empty.

source

node_count

JuMP.node_countFunction
node_count(model::GenericModel)

Gets the total number of branch-and-bound nodes explored during the most recent optimization in a Mixed Integer Program.

Solvers must implement MOI.NodeCount() to use this function.

source

nonlinear_constraint_string

JuMP.nonlinear_constraint_stringFunction
nonlinear_constraint_string(
+    model::GenericModel,
+    mode::MIME,
+    c::_NonlinearConstraint,
+)

Return a string representation of the nonlinear constraint c belonging to model, given the mode.

source

nonlinear_dual_start_value

nonlinear_expr_string

JuMP.nonlinear_expr_stringFunction
nonlinear_expr_string(
+    model::GenericModel,
+    mode::MIME,
+    c::MOI.Nonlinear.Expression,
+)

Return a string representation of the nonlinear expression c belonging to model, given the mode.

source

nonlinear_model

JuMP.nonlinear_modelFunction
nonlinear_model(
+    model::GenericModel;
+    force::Bool = false,
+)::Union{MOI.Nonlinear.Model,Nothing}

If model has nonlinear components, return a MOI.Nonlinear.Model, otherwise return nothing.

If force, always return a MOI.Nonlinear.Model, and if one does not exist for the model, create an empty one.

source

normalized_coefficient

normalized_rhs

num_constraints

JuMP.num_constraintsFunction
num_constraints(model::GenericModel, function_type, set_type)::Int64

Return the number of constraints currently in the model where the function has type function_type and the set has type set_type.

See also list_of_constraint_types and all_constraints.

Example

julia> model = Model();
+
+julia> @variable(model, x >= 0, Bin);
+
+julia> @variable(model, y);
+
+julia> @constraint(model, y in MOI.GreaterThan(1.0));
+
+julia> @constraint(model, y <= 1.0);
+
+julia> @constraint(model, 2x <= 1);
+
+julia> num_constraints(model, VariableRef, MOI.GreaterThan{Float64})
+2
+
+julia> num_constraints(model, VariableRef, MOI.ZeroOne)
+1
+
+julia> num_constraints(model, AffExpr, MOI.LessThan{Float64})
+2
source
num_constraints(model::GenericModel; count_variable_in_set_constraints::Bool)

Return the number of constraints in model.

If count_variable_in_set_constraints == true, then VariableRef constraints such as VariableRef-in-Integer are included. To count only the number of structural constraints (e.g., the rows in the constraint matrix of a linear program), pass count_variable_in_set_constraints = false.

Example

julia> model = Model();
+
+julia> @variable(model, x >= 0, Int);
+
+julia> @constraint(model, 2x <= 1);
+
+julia> num_constraints(model; count_variable_in_set_constraints = true)
+3
+
+julia> num_constraints(model; count_variable_in_set_constraints = false)
+1
source

num_nonlinear_constraints

num_variables

object_dictionary

JuMP.object_dictionaryFunction
object_dictionary(model::GenericModel)

Return the dictionary that maps the symbol name of a variable, constraint, or expression to the corresponding object.

Objects are registered to a specific symbol in the macros. For example, @variable(model, x[1:2, 1:2]) registers the array of variables x to the symbol :x.

This method should be defined for any subtype of AbstractModel.

source

objective_bound

JuMP.objective_boundFunction
objective_bound(model::GenericModel)

Return the best known bound on the optimal objective value after a call to optimize!(model).

For scalar-valued objectives, this function returns a Float64. For vector-valued objectives, it returns a Vector{Float64}.

In the case of a vector-valued objective, this returns the ideal point, that is, the point obtained if each objective was optimized independently.

source

objective_function

JuMP.objective_functionFunction
objective_function(
+    model::GenericModel,
+    T::Type = objective_function_type(model),
+)

Return an object of type T representing the objective function.

Error if the objective is not convertible to type T.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @objective(model, Min, 2x + 1)
+2 x + 1
+
+julia> objective_function(model, AffExpr)
+2 x + 1
+
+julia> objective_function(model, QuadExpr)
+2 x + 1
+
+julia> typeof(objective_function(model, QuadExpr))
+QuadExpr (alias for GenericQuadExpr{Float64, GenericVariableRef{Float64}})

We see with the last two commands that even if the objective function is affine, as it is convertible to a quadratic function, it can be queried as a quadratic function and the result is quadratic.

However, it is not convertible to a variable.

julia> objective_function(model, VariableRef)
+ERROR: InexactError: convert(MathOptInterface.VariableIndex, 1.0 + 2.0 MOI.VariableIndex(1))
+[...]
source

objective_function_string

objective_function_type

objective_sense

objective_value

JuMP.objective_valueFunction
objective_value(model::GenericModel; result::Int = 1)

Return the objective value associated with result index result of the most-recent solution returned by the solver.

For scalar-valued objectives, this function returns a Float64. For vector-valued objectives, it returns a Vector{Float64}.

See also: result_count.

source

op_ifelse

JuMP.op_ifelseFunction
op_ifelse(a, x, y)

A function that falls back to ifelse(a, x, y), but when called with a JuMP variables or expression in the first argument, returns a GenericNonlinearExpr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> op_ifelse(true, 1.0, 2.0)
+1.0
+
+julia> op_ifelse(x, 1.0, 2.0)
+ifelse(x, 1.0, 2.0)
+
+julia> op_ifelse(true, x, 2.0)
+x
source

operator_to_set

JuMP.operator_to_setFunction
operator_to_set(_error::Function, ::Val{sense_symbol})

Converts a sense symbol to a set set such that @constraint(model, func sense_symbol 0) is equivalent to @constraint(model, func in set) for any func::AbstractJuMPScalar.

Example

Once a custom set is defined you can directly create a JuMP constraint with it:

julia> struct CustomSet{T} <: MOI.AbstractScalarSet
+           value::T
+       end
+
+julia> Base.copy(x::CustomSet) = CustomSet(x.value)
+
+julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> cref = @constraint(model, x in CustomSet(1.0))
+x ∈ CustomSet{Float64}(1.0)

However, there might be an appropriate sign that could be used in order to provide a more convenient syntax:

julia> JuMP.operator_to_set(::Function, ::Val{:⊰}) = CustomSet(0.0)
+
+julia> MOIU.supports_shift_constant(::Type{<:CustomSet}) = true
+
+julia> MOIU.shift_constant(set::CustomSet, value) = CustomSet(set.value + value)
+
+julia> cref = @constraint(model, x ⊰ 1)
+x ∈ CustomSet{Float64}(1.0)

Note that the whole function is first moved to the right-hand side, then the sign is transformed into a set with zero constant and finally the constant is moved to the set with MOIU.shift_constant.

source

operator_warn

JuMP.operator_warnFunction
operator_warn(model::AbstractModel)
+operator_warn(model::GenericModel)

This function is called on the model whenever two affine expressions are added together without using destructive_add!, and at least one of the two expressions has more than 50 terms.

For the case of Model, if this function is called more than 20,000 times then a warning is generated once.

source

optimize!

JuMP.optimize!Function
optimize!(
+    model::GenericModel;
+    ignore_optimize_hook = (model.optimize_hook === nothing),
+    _differentiation_backend::MOI.Nonlinear.AbstractAutomaticDifferentiation =
+        MOI.Nonlinear.SparseReverseMode(),
+    kwargs...,
+)

Optimize the model.

If an optimizer has not been set yet (see set_optimizer), a NoOptimizer error is thrown.

If ignore_optimize_hook == true, the optimize hook is ignored and the model is solved as if the hook was not set. Keyword arguments kwargs are passed to the optimize_hook. An error is thrown if optimize_hook is nothing and keyword arguments are provided.

Experimental features

These features may change or be removed in any future version of JuMP.

Pass _differentiation_backend to set the MOI.Nonlinear.AbstractAutomaticDifferentiation backend used to compute derivatives of nonlinear programs.

If you require only :ExprGraph, it is more efficient to pass _differentiation_backend = MOI.Nonlinear.ExprGraphOnly().

source

optimizer_index

JuMP.optimizer_indexFunction
optimizer_index(x::GenericVariableRef)::MOI.VariableIndex
+optimizer_index(x::ConstraintRef{<:GenericModel})::MOI.ConstraintIndex

Return the index that corresponds to x in the optimizer model.

Throws NoOptimizer if no optimizer is set, and throws an ErrorException if the optimizer is set but is not attached.

source

optimizer_with_attributes

JuMP.optimizer_with_attributesFunction
optimizer_with_attributes(optimizer_constructor, attrs::Pair...)

Groups an optimizer constructor with the list of attributes attrs. Note that it is equivalent to MOI.OptimizerWithAttributes.

When provided to the Model constructor or to set_optimizer, it creates an optimizer by calling optimizer_constructor(), and then sets the attributes using set_attribute.

See also: set_attribute, get_attribute.

Note

The string names of the attributes are specific to each solver. One should consult the solver's documentation to find the attributes of interest.

Example

julia> import HiGHS
+
+julia> optimizer = optimizer_with_attributes(
+           HiGHS.Optimizer, "presolve" => "off", MOI.Silent() => true,
+       );
+
+julia> model = Model(optimizer);

is equivalent to:

julia> import HiGHS
+
+julia> model = Model(HiGHS.Optimizer);
+
+julia> set_attribute(model, "presolve", "off")
+
+julia> set_attribute(model, MOI.Silent(), true)
source

owner_model

parameter_value

JuMP.parameter_valueFunction
parameter_value(x::GenericVariableRef)

Return the value of the parameter x.

Errors if x is not a parameter.

See also ParameterRef, is_parameter, set_parameter_value.

Examples

julia> model = Model();
+
+julia> @variable(model, p in Parameter(2))
+p
+
+julia> parameter_value(p)
+2.0
+
+julia> set_parameter_value(p, 2.5)
+
+julia> parameter_value(p)
+2.5
source

parse_constraint

JuMP.parse_constraintFunction
parse_constraint(_error::Function, expr::Expr)

The entry-point for all constraint-related parsing.

Arguments

  • The _error function is passed everywhere to provide better error messages
  • expr comes from the @constraint macro. There are two possibilities:
    • @constraint(model, expr)
    • @constraint(model, name[args], expr)
    In both cases, expr is the main component of the constraint.

Supported syntax

JuMP currently supports the following expr objects:

  • lhs <= rhs
  • lhs == rhs
  • lhs >= rhs
  • l <= body <= u
  • u >= body >= l
  • lhs ⟂ rhs
  • lhs in rhs
  • lhs ∈ rhs
  • z => {constraint}
  • !z => {constraint}

as well as all broadcasted variants.

Extensions

The infrastructure behind parse_constraint is extendable. See parse_constraint_head and parse_constraint_call for details.

source

parse_constraint_call

JuMP.parse_constraint_callFunction
parse_constraint_call(
+    _error::Function,
+    is_vectorized::Bool,
+    ::Val{op},
+    args...,
+)

Implement this method to intercept the parsing of a :call expression with operator op.

Warning

Extending the constraint macro at parse time is an advanced operation and has the potential to interfere with existing JuMP syntax. Please discuss with the developer chatroom before publishing any code that implements these methods.

Arguments

  • _error: a function that accepts a String and throws the string as an error, along with some descriptive information of the macro from which it was thrown.
  • is_vectorized: a boolean to indicate if op should be broadcast or not
  • op: the first element of the .args field of the Expr to intercept
  • args...: the .args field of the Expr.

Returns

This function must return:

  • parse_code::Expr: an expression containing any setup or rewriting code that needs to be called before build_constraint
  • build_code::Expr: an expression that calls build_constraint( or build_constraint.( depending on is_vectorized.

See also: parse_constraint_head, build_constraint

source
parse_constraint_call(
+    _error::Function,
+    vectorized::Bool,
+    ::Val{op},
+    lhs,
+    rhs,
+) where {op}

Fallback handler for binary operators. These might be infix operators like @constraint(model, lhs op rhs), or normal operators like @constraint(model, op(lhs, rhs)).

In both cases, we rewrite as lhs - rhs in operator_to_set(_error, op).

See operator_to_set for details.

source

parse_constraint_head

JuMP.parse_constraint_headFunction
parse_constraint_head(_error::Function, ::Val{head}, args...)

Implement this method to intercept the parsing of an expression with head head.

Warning

Extending the constraint macro at parse time is an advanced operation and has the potential to interfere with existing JuMP syntax. Please discuss with the developer chatroom before publishing any code that implements these methods.

Arguments

  • _error: a function that accepts a String and throws the string as an error, along with some descriptive information of the macro from which it was thrown.
  • head: the .head field of the Expr to intercept
  • args...: the .args field of the Expr.

Returns

This function must return:

  • is_vectorized::Bool: whether the expression represents a broadcasted expression like x .<= 1
  • parse_code::Expr: an expression containing any setup or rewriting code that needs to be called before build_constraint
  • build_code::Expr: an expression that calls build_constraint( or build_constraint.( depending on is_vectorized.

Existing implementations

JuMP currently implements:

  • ::Val{:call}, which forwards calls to parse_constraint_call
  • ::Val{:comparison}, which handles the special case of l <= body <= u.

See also: parse_constraint_call, build_constraint

source

parse_one_operator_variable

JuMP.parse_one_operator_variableFunction
parse_one_operator_variable(_error::Function, infoexpr::_VariableInfoExpr, sense::Val{S}, value) where S

Update infoexr for a variable expression in the @variable macro of the form variable name S value.

source

parse_ternary_variable

JuMP.parse_ternary_variableFunction
parse_ternary_variable(_error, variable_info, lhs_sense, lhs, rhs_sense, rhs)

A hook for JuMP extensiosn to intercept the parsing of a :comparison expression, which has the form lhs lhs_sense variable rhs_sense rhs.

source

parse_variable

JuMP.parse_variableFunction
parse_variable(_error::Function, ::_VariableInfoExpr, args...)

A hook for extensions to intercept the parsing of inequality constraints in the @variable macro.

source

primal_feasibility_report

JuMP.primal_feasibility_reportFunction
primal_feasibility_report(
+    model::GenericModel{T},
+    point::AbstractDict{GenericVariableRef{T},T} = _last_primal_solution(model),
+    atol::T = zero(T),
+    skip_missing::Bool = false,
+)::Dict{Any,T}

Given a dictionary point, which maps variables to primal values, return a dictionary whose keys are the constraints with an infeasibility greater than the supplied tolerance atol. The value corresponding to each key is the respective infeasibility. Infeasibility is defined as the distance between the primal value of the constraint (see MOI.ConstraintPrimal) and the nearest point by Euclidean distance in the corresponding set.

Notes

  • If skip_missing = true, constraints containing variables that are not in point will be ignored.
  • If skip_missing = false and a partial primal solution is provided, an error will be thrown.
  • If no point is provided, the primal solution from the last time the model was solved is used.

Example

julia> model = Model();
+
+julia> @variable(model, 0.5 <= x <= 1);
+
+julia> primal_feasibility_report(model, Dict(x => 0.2))
+Dict{Any, Float64} with 1 entry:
+  x ≥ 0.5 => 0.3
source
primal_feasibility_report(
+    point::Function,
+    model::GenericModel{T};
+    atol::T = zero(T),
+    skip_missing::Bool = false,
+) where {T}

A form of primal_feasibility_report where a function is passed as the first argument instead of a dictionary as the second argument.

Example

julia> model = Model();
+
+julia> @variable(model, 0.5 <= x <= 1, start = 1.3);
+
+julia> primal_feasibility_report(model) do v
+           return start_value(v)
+       end
+Dict{Any, Float64} with 1 entry:
+  x ≤ 1 => 0.3
source

primal_status

JuMP.print_active_bridgesFunction
print_active_bridges([io::IO = stdout,] model::GenericModel)

Print a list of the variable, constraint, and objective bridges that are currently used in the model.

source
print_active_bridges([io::IO = stdout,] model::GenericModel, ::Type{F}) where {F}

Print a list of bridges required for an objective function of type F.

source
print_active_bridges(
+    [io::IO = stdout,]
+    model::GenericModel,
+    F::Type,
+    S::Type{<:MOI.AbstractSet},
+)

Print a list of bridges required for a constraint of type F-in-S.

source
print_active_bridges(
+    [io::IO = stdout,]
+    model::GenericModel,
+    S::Type{<:MOI.AbstractSet},
+)

Print a list of bridges required to add a variable constrained to the set S.

source
JuMP.print_bridge_graphFunction
 print_bridge_graph([io::IO,] model::GenericModel)

Print the hyper-graph containing all variable, constraint, and objective types that could be obtained by bridging the variables, constraints, and objectives that are present in the model.

Warning

This function is intended for advanced users. If you want to see only the bridges that are currently used, use print_active_bridges instead.

Explanation of output

Each node in the hyper-graph corresponds to a variable, constraint, or objective type.

  • Variable nodes are indicated by [ ]
  • Constraint nodes are indicated by ( )
  • Objective nodes are indicated by | |

The number inside each pair of brackets is an index of the node in the hyper-graph.

Note that this hyper-graph is the full list of possible transformations. When the bridged model is created, we select the shortest hyper-path(s) from this graph, so many nodes may be un-used.

For more information, see Legat, B., Dowson, O., Garcia, J., and Lubin, M. (2020). "MathOptInterface: a data structure for mathematical optimization problems." URL: https://arxiv.org/abs/2002.03447

source

quad_terms

JuMP.quad_termsFunction
quad_terms(quad::GenericQuadExpr{C, V})

Provides an iterator over tuples (coefficient::C, var_1::V, var_2::V) in the quadratic part of the quadratic expression.

source

raw_status

JuMP.raw_statusFunction
raw_status(model::GenericModel)

Return the reason why the solver stopped in its own words (i.e., the MathOptInterface model attribute RawStatusString).

source

read_from_file

JuMP.read_from_fileFunction
read_from_file(
+    filename::String;
+    format::MOI.FileFormats.FileFormat = MOI.FileFormats.FORMAT_AUTOMATIC,
+    kwargs...,
+)

Return a JuMP model read from filename in the format format.

If the filename ends in .gz, it will be uncompressed using Gzip. If the filename ends in .bz2, it will be uncompressed using BZip2.

Other kwargs are passed to the Model constructor of the chosen format.

source

reduced_cost

JuMP.reduced_costFunction
reduced_cost(x::GenericVariableRef{T})::T where {T}

Return the reduced cost associated with variable x.

Equivalent to querying the shadow price of the active variable bound (if one exists and is active).

See also: shadow_price.

source

register

JuMP.registerFunction
register(
+    model::Model,
+    op::Symbol,
+    dimension::Integer,
+    f::Function;
+    autodiff:Bool = false,
+)

Register the user-defined function f that takes dimension arguments in model as the symbol op.

The function f must support all subtypes of Real as arguments. Do not assume that the inputs are Float64.

Notes

  • For this method, you must explicitly set autodiff = true, because no user-provided gradient function ∇f is given.
  • Second-derivative information is only computed if dimension == 1.
  • op does not have to be the same symbol as f, but it is generally more readable if it is.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> f(x::T) where {T<:Real} = x^2
+f (generic function with 1 method)
+
+julia> register(model, :foo, 1, f; autodiff = true)
+
+julia> @NLobjective(model, Min, foo(x))
julia> model = Model();
+
+julia> @variable(model, x[1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> g(x::T, y::T) where {T<:Real} = x * y
+g (generic function with 1 method)
+
+julia> register(model, :g, 2, g; autodiff = true)
+
+julia> @NLobjective(model, Min, g(x[1], x[2]))
source
register(
+    model::Model,
+    s::Symbol,
+    dimension::Integer,
+    f::Function,
+    ∇f::Function;
+    autodiff:Bool = false,
+)

Register the user-defined function f that takes dimension arguments in model as the symbol s. In addition, provide a gradient function ∇f.

The functions fand ∇f must support all subtypes of Real as arguments. Do not assume that the inputs are Float64.

Notes

  • If the function f is univariate (i.e., dimension == 1), ∇f must return a number which represents the first-order derivative of the function f.
  • If the function f is multi-variate, ∇f must have a signature matching ∇f(g::AbstractVector{T}, args::T...) where {T<:Real}, where the first argument is a vector g that is modified in-place with the gradient.
  • If autodiff = true and dimension == 1, use automatic differentiation to compute the second-order derivative information. If autodiff = false, only first-order derivative information will be used.
  • s does not have to be the same symbol as f, but it is generally more readable if it is.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> f(x::T) where {T<:Real} = x^2
+f (generic function with 1 method)
+
+julia> ∇f(x::T) where {T<:Real} = 2 * x
+∇f (generic function with 1 method)
+
+julia> register(model, :foo, 1, f, ∇f; autodiff = true)
+
+julia> @NLobjective(model, Min, foo(x))
julia> model = Model();
+
+julia> @variable(model, x[1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> g(x::T, y::T) where {T<:Real} = x * y
+g (generic function with 1 method)
+
+julia> function ∇g(g::AbstractVector{T}, x::T, y::T) where {T<:Real}
+           g[1] = y
+           g[2] = x
+           return
+       end
+∇g (generic function with 1 method)
+
+julia> register(model, :g, 2, g, ∇g)
+
+julia> @NLobjective(model, Min, g(x[1], x[2]))
source
register(
+    model::Model,
+    s::Symbol,
+    dimension::Integer,
+    f::Function,
+    ∇f::Function,
+    ∇²f::Function,
+)

Register the user-defined function f that takes dimension arguments in model as the symbol s. In addition, provide a gradient function ∇f and a hessian function ∇²f.

∇f and ∇²f must return numbers corresponding to the first- and second-order derivatives of the function f respectively.

Notes

  • Because automatic differentiation is not used, you can assume the inputs are all Float64.
  • This method will throw an error if dimension > 1.
  • s does not have to be the same symbol as f, but it is generally more readable if it is.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> f(x::Float64) = x^2
+f (generic function with 1 method)
+
+julia> ∇f(x::Float64) = 2 * x
+∇f (generic function with 1 method)
+
+julia> ∇²f(x::Float64) = 2.0
+∇²f (generic function with 1 method)
+
+julia> register(model, :foo, 1, f, ∇f, ∇²f)
+
+julia> @NLobjective(model, Min, foo(x))
+
source

relative_gap

JuMP.relative_gapFunction
relative_gap(model::GenericModel)

Return the final relative optimality gap after a call to optimize!(model). Exact value depends upon implementation of MathOptInterface.RelativeGap() by the particular solver used for optimization.

source

relax_integrality

JuMP.relax_integralityFunction
relax_integrality(model::GenericModel)

Modifies model to "relax" all binary and integrality constraints on variables. Specifically,

  • Binary constraints are deleted, and variable bounds are tightened if necessary to ensure the variable is constrained to the interval $[0, 1]$.
  • Integrality constraints are deleted without modifying variable bounds.
  • An error is thrown if semi-continuous or semi-integer constraints are present (support may be added for these in the future).
  • All other constraints are ignored (left in place). This includes discrete constraints like SOS and indicator constraints.

Returns a function that can be called without any arguments to restore the original model. The behavior of this function is undefined if additional changes are made to the affected variables in the meantime.

Example

julia> model = Model();
+
+julia> @variable(model, x, Bin);
+
+julia> @variable(model, 1 <= y <= 10, Int);
+
+julia> @objective(model, Min, x + y);
+
+julia> undo_relax = relax_integrality(model);
+
+julia> print(model)
+Min x + y
+Subject to
+ x ≥ 0
+ y ≥ 1
+ x ≤ 1
+ y ≤ 10
+
+julia> undo_relax()
+
+julia> print(model)
+Min x + y
+Subject to
+ y ≥ 1
+ y ≤ 10
+ y integer
+ x binary
source

relax_with_penalty!

JuMP.relax_with_penalty!Function
relax_with_penalty!(
+    model::GenericModel{T},
+    [penalties::Dict{ConstraintRef,T}];
+    [default::Union{Nothing,Real} = nothing,]
+) where {T}

Destructively modify the model in-place to create a penalized relaxation of the constraints.

Warning

This is a destructive routine that modifies the model in-place. If you don't want to modify the original model, use copy_model to create a copy before calling relax_with_penalty!.

Reformulation

See MOI.Utilities.ScalarPenaltyRelaxation for details of the reformulation.

For each constraint ci, the penalty passed to MOI.Utilities.ScalarPenaltyRelaxation is get(penalties, ci, default). If the value is nothing, because ci does not exist in penalties and default = nothing, then the constraint is skipped.

Return value

This function returns a Dict{ConstraintRef,AffExpr} that maps each constraint index to the corresponding y + z as an AffExpr. In an optimal solution, query the value of these functions to compute the violation of each constraint.

Relax a subset of constraints

To relax a subset of constraints, pass a penalties dictionary and set default = nothing.

Example

julia> function new_model()
+           model = Model()
+           @variable(model, x)
+           @objective(model, Max, 2x + 1)
+           @constraint(model, c1, 2x - 1 <= -2)
+           @constraint(model, c2, 3x >= 0)
+           return model
+       end
+new_model (generic function with 1 method)
+
+julia> model_1 = new_model();
+
+julia> penalty_map = relax_with_penalty!(model_1; default = 2.0);
+
+julia> penalty_map[model_1[:c1]]
+_[3]
+
+julia> penalty_map[model_1[:c2]]
+_[2]
+
+julia> print(model_1)
+Max 2 x - 2 _[2] - 2 _[3] + 1
+Subject to
+ c2 : 3 x + _[2] ≥ 0
+ c1 : 2 x - _[3] ≤ -1
+ _[2] ≥ 0
+ _[3] ≥ 0
+
+julia> model_2 = new_model();
+
+julia> relax_with_penalty!(model_2, Dict(model_2[:c2] => 3.0))
+Dict{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.GreaterThan{Float64}}, ScalarShape}, AffExpr} with 1 entry:
+  c2 : 3 x + _[2] ≥ 0 => _[2]
+
+julia> print(model_2)
+Max 2 x - 3 _[2] + 1
+Subject to
+ c2 : 3 x + _[2] ≥ 0
+ c1 : 2 x ≤ -1
+ _[2] ≥ 0
source

remove_bridge

JuMP.remove_bridgeFunction
remove_bridge(
+    model::GenericModel{S},
+    BT::Type{<:MOI.Bridges.AbstractBridge};
+    coefficient_type::Type{T} = S,
+) where {S,T}

Remove BT{T} from the list of bridges that can be used to transform unsupported constraints into an equivalent formulation using only constraints supported by the optimizer.

See also: add_bridge.

Example

julia> model = Model();
+
+julia> add_bridge(model, MOI.Bridges.Constraint.SOCtoNonConvexQuadBridge)
+
+julia> remove_bridge(model, MOI.Bridges.Constraint.SOCtoNonConvexQuadBridge)
+
+julia> add_bridge(
+           model,
+           MOI.Bridges.Constraint.NumberConversionBridge;
+           coefficient_type = Complex{Float64},
+       )
+
+julia> remove_bridge(
+           model,
+           MOI.Bridges.Constraint.NumberConversionBridge;
+           coefficient_type = Complex{Float64},
+       )
source

reshape_set

JuMP.reshape_setFunction
reshape_set(vectorized_set::MOI.AbstractSet, shape::AbstractShape)

Return a set in its original shape shape given its vectorized form vectorized_form.

Example

Given a SymmetricMatrixShape of vectorized form [1, 2, 3] in MOI.PositiveSemidefinieConeTriangle(2), the following code returns the set of the original constraint Symmetric(Matrix[1 2; 2 3]) in PSDCone():

julia> reshape_set(MOI.PositiveSemidefiniteConeTriangle(2), SymmetricMatrixShape(2))
+PSDCone()
source

reshape_vector

JuMP.reshape_vectorFunction
reshape_vector(vectorized_form::Vector, shape::AbstractShape)

Return an object in its original shape shape given its vectorized form vectorized_form.

Example

Given a SymmetricMatrixShape of vectorized form [1, 2, 3], the following code returns the matrix Symmetric(Matrix[1 2; 2 3]):

julia> reshape_vector([1, 2, 3], SymmetricMatrixShape(2))
+2×2 LinearAlgebra.Symmetric{Int64, Matrix{Int64}}:
+ 1  2
+ 2  3
source

result_count

reverse_sense

JuMP.reverse_senseFunction
reverse_sense(::Val{T}) where {T}

Given an (in)equality symbol T, return a new Val object with the opposite (in)equality symbol.

source

set_attribute

JuMP.set_attributeFunction
set_attribute(model::GenericModel, attr::MOI.AbstractModelAttribute, value)
+set_attribute(x::GenericVariableRef, attr::MOI.AbstractVariableAttribute, value)
+set_attribute(cr::ConstraintRef, attr::MOI.AbstractConstraintAttribute, value)

Set the value of a solver-specifc attribute attr to value.

This is equivalent to calling MOI.set with the associated MOI model and, for variables and constraints, with the associated MOI.VariableIndex or MOI.ConstraintIndex.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, c, 2 * x <= 1)
+c : 2 x ≤ 1
+
+julia> set_attribute(model, MOI.Name(), "model_new")
+
+julia> set_attribute(x, MOI.VariableName(), "x_new")
+
+julia> set_attribute(c, MOI.ConstraintName(), "c_new")
source
set_attribute(
+    model::Union{GenericModel,MOI.OptimizerWithAttributes},
+    attr::Union{AbstractString,MOI.AbstractOptimizerAttribute},
+    value,
+)

Set the value of a solver-specifc attribute attr to value.

This is equivalent to calling MOI.set with the associated MOI model.

If attr is an AbstractString, it is converted to MOI.RawOptimizerAttribute.

Example

julia> import HiGHS
+
+julia> opt = optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false);
+
+julia> model = Model(opt);
+
+julia> set_attribute(model, "output_flag", false)
+
+julia> set_attribute(model, MOI.RawOptimizerAttribute("output_flag"), true)
+
+julia> set_attribute(opt, "output_flag", true)
+
+julia> set_attribute(opt, MOI.RawOptimizerAttribute("output_flag"), false)
source

set_attributes

JuMP.set_attributesFunction
set_attributes(
+    destination::Union{
+        GenericModel,
+        MOI.OptimizerWithAttributes,
+        GenericVariableRef,
+        ConstraintRef,
+    },
+    pairs::Pair...,
+)

Given a list of attribute => value pairs, calls set_attribute(destination, attribute, value) for each pair.

See also: set_attribute, get_attribute.

Example

julia> import Ipopt
+
+julia> model = Model(Ipopt.Optimizer);
+
+julia> set_attributes(model, "tol" => 1e-4, "max_iter" => 100)

is equivalent to:

julia> import Ipopt
+
+julia> model = Model(Ipopt.Optimizer);
+
+julia> set_attribute(model, "tol", 1e-4)
+
+julia> set_attribute(model, "max_iter", 100)
source

set_binary

JuMP.set_binaryFunction
set_binary(v::GenericVariableRef)

Add a constraint on the variable v that it must take values in the set $\{0,1\}$.

See also BinaryRef, is_binary, unset_binary.

Examples

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> is_binary(x)
+false
+
+julia> set_binary(x)
+
+julia> is_binary(x)
+true
source

set_dual_start_value

JuMP.set_dual_start_valueFunction
set_dual_start_value(con_ref::ConstraintRef, value)

Set the dual start value (MOI attribute ConstraintDualStart) of the constraint con_ref to value. To remove a dual start value set it to nothing.

See also dual_start_value.

source

set_integer

JuMP.set_integerFunction
set_integer(variable_ref::GenericVariableRef)

Add an integrality constraint on the variable variable_ref.

See also IntegerRef, is_integer, unset_integer.

Examples

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> is_integer(x)
+false
+
+julia> set_integer(x)
+
+julia> is_integer(x)
+true
source

set_lower_bound

JuMP.set_lower_boundFunction
set_lower_bound(v::GenericVariableRef, lower::Number)

Set the lower bound of a variable. If one does not exist, create a new lower bound constraint.

See also LowerBoundRef, has_lower_bound, lower_bound, delete_lower_bound.

Examples

julia> model = Model();
+
+julia> @variable(model, x >= 1.0);
+
+julia> lower_bound(x)
+1.0
+
+julia> set_lower_bound(x, 2.0)
+
+julia> lower_bound(x)
+2.0
source

set_name

JuMP.set_nameFunction
set_name(con_ref::ConstraintRef, s::AbstractString)

Set a constraint's name attribute.

source
set_name(v::GenericVariableRef, s::AbstractString)

Set a variable's name attribute.

source

set_nonlinear_dual_start_value

JuMP.set_nonlinear_dual_start_valueFunction
set_nonlinear_dual_start_value(
+    model::Model,
+    start::Union{Nothing,Vector{Float64}},
+)

Set the value of the MOI attribute MOI.NLPBlockDualStart.

The start vector corresponds to the Lagrangian duals of the nonlinear constraints, in the order given by all_nonlinear_constraints. That is, you must pass a single start vector corresponding to all of the nonlinear constraints in a single function call; you cannot set the dual start value of nonlinear constraints one-by-one. The example below demonstrates how to use all_nonlinear_constraints to create a mapping between the nonlinear constraint references and the start vector.

Pass nothing to unset a previous start.

Example

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> nl1 = @NLconstraint(model, x[1] <= sqrt(x[2]));
+
+julia> nl2 = @NLconstraint(model, x[1] >= exp(x[2]));
+
+julia> start = Dict(nl1 => -1.0, nl2 => 1.0);
+
+julia> start_vector = [start[con] for con in all_nonlinear_constraints(model)]
+2-element Vector{Float64}:
+ -1.0
+  1.0
+
+julia> set_nonlinear_dual_start_value(model, start_vector)
+
+julia> nonlinear_dual_start_value(model)
+2-element Vector{Float64}:
+ -1.0
+  1.0
source

set_nonlinear_objective

JuMP.set_nonlinear_objectiveFunction
set_nonlinear_objective(
+    model::Model,
+    sense::MOI.OptimizationSense,
+    expr::Expr,
+)

Set the nonlinear objective of model to the expression expr, with the optimization sense sense.

This function is most useful if the expression expr is generated programmatically, and you cannot use @NLobjective.

Notes

  • You must interpolate the variables directly into the expression expr.
  • You must use MIN_SENSE or MAX_SENSE instead of Min and Max.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> set_nonlinear_objective(model, MIN_SENSE, :($(x) + $(x)^2))
source

set_normalized_coefficient

JuMP.set_normalized_coefficientFunction
set_normalized_coefficient(con_ref::ConstraintRef, variable::GenericVariableRef, value)

Set the coefficient of variable in the constraint constraint to value.

Note that prior to this step, JuMP will aggregate multiple terms containing the same variable. For example, given a constraint 2x + 3x <= 2, set_normalized_coefficient(con, x, 4) will create the constraint 4x <= 2.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, con, 2x + 3x <= 2)
+con : 5 x ≤ 2
+
+julia> set_normalized_coefficient(con, x, 4)
+
+julia> con
+con : 4 x ≤ 2
source

set_normalized_coefficients

JuMP.set_normalized_coefficientsFunction
set_normalized_coefficients(
+    con_ref::ConstraintRef,
+    variable,
+    new_coefficients::Vector{Tuple{Int64,T}},
+)

Set the coefficients of variable in the constraint con_ref to new_coefficients, where each element in new_coefficients is a tuple which maps the row to a new coefficient.

Note that prior to this step, during constraint creation, JuMP will aggregate multiple terms containing the same variable.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, con, [2x + 3x, 4x] in MOI.Nonnegatives(2))
+con : [5 x, 4 x] ∈ MathOptInterface.Nonnegatives(2)
+
+julia> set_normalized_coefficients(con, x, [(1, 2.0), (2, 5.0)])
+
+julia> con
+con : [2 x, 5 x] ∈ MathOptInterface.Nonnegatives(2)
source

set_normalized_rhs

JuMP.set_normalized_rhsFunction
set_normalized_rhs(con_ref::ConstraintRef, value)

Set the right-hand side term of constraint to value.

Note that prior to this step, JuMP will aggregate all constant terms onto the right-hand side of the constraint. For example, given a constraint 2x + 1 <= 2, set_normalized_rhs(con, 4) will create the constraint 2x <= 4, not 2x + 1 <= 4.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con, 2x + 1 <= 2)
+con : 2 x ≤ 1
+
+julia> set_normalized_rhs(con, 4)
+
+julia> con
+con : 2 x ≤ 4
source

set_objective

JuMP.set_objectiveFunction
set_objective(model::AbstractModel, sense::MOI.OptimizationSense, func)

The functional equivalent of the @objective macro.

Sets the objective sense and objective function simultaneously, and is equivalent to calling set_objective_sense and set_objective_function separately.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> set_objective(model, MIN_SENSE, x)
source

set_objective_coefficient

JuMP.set_objective_coefficientFunction
set_objective_coefficient(model::GenericModel, variable::GenericVariableRef, coefficient::Real)

Set the linear objective coefficient associated with Variable to coefficient.

Note: this function will throw an error if a nonlinear objective is set.

source

set_objective_function

JuMP.set_objective_functionFunction
set_objective_function(model::GenericModel, func::MOI.AbstractFunction)
+set_objective_function(model::GenericModel, func::AbstractJuMPScalar)
+set_objective_function(model::GenericModel, func::Real)
+set_objective_function(model::GenericModel, func::Vector{<:AbstractJuMPScalar})

Sets the objective function of the model to the given function. See set_objective_sense to set the objective sense. These are low-level functions; the recommended way to set the objective is with the @objective macro.

source

set_objective_sense

JuMP.set_objective_senseFunction
set_objective_sense(model::GenericModel, sense::MOI.OptimizationSense)

Sets the objective sense of the model to the given sense. See set_objective_function to set the objective function. These are low-level functions; the recommended way to set the objective is with the @objective macro.

source

set_optimize_hook

JuMP.set_optimize_hookFunction
set_optimize_hook(model::GenericModel, f::Union{Function,Nothing})

Set the function f as the optimize hook for model.

f should have a signature f(model::GenericModel; kwargs...), where the kwargs are those passed to optimize!.

Notes

  • The optimize hook should generally modify the model, or some external state in some way, and then call optimize!(model; ignore_optimize_hook = true) to optimize the problem, bypassing the hook.
  • Use set_optimize_hook(model, nothing) to unset an optimize hook.

Example

julia> model = Model();
+
+julia> function my_hook(model::Model; kwargs...)
+           println(kwargs)
+           println("Calling with `ignore_optimize_hook = true`")
+           optimize!(model; ignore_optimize_hook = true)
+           return
+       end
+my_hook (generic function with 1 method)
+
+julia> set_optimize_hook(model, my_hook)
+my_hook (generic function with 1 method)
+
+julia> optimize!(model; test_arg = true)
+Base.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:test_arg,), Tuple{Bool}}}(:test_arg => 1)
+Calling with `ignore_optimize_hook = true`
+ERROR: NoOptimizer()
+[...]
source

set_optimizer

JuMP.set_optimizerFunction
set_optimizer(
+    model::GenericModel,
+    optimizer_factory;
+    add_bridges::Bool = true,
+)

Creates an empty MathOptInterface.AbstractOptimizer instance by calling optimizer_factory() and sets it as the optimizer of model. Specifically, optimizer_factory must be callable with zero arguments and return an empty MathOptInterface.AbstractOptimizer.

If add_bridges is true, constraints and objectives that are not supported by the optimizer are automatically bridged to equivalent supported formulation. Passing add_bridges = false can improve performance if the solver natively supports all of the elements in model.

See set_attribute for setting solver-specific parameters of the optimizer.

Example

julia> import HiGHS
+
+julia> model = Model();
+
+julia> set_optimizer(model, () -> HiGHS.Optimizer())
+
+julia> set_optimizer(model, HiGHS.Optimizer; add_bridges = false)
source

set_optimizer_attribute

JuMP.set_optimizer_attributeFunction
set_optimizer_attribute(
+    model::Union{GenericModel,MOI.OptimizerWithAttributes},
+    attr::Union{AbstractString,MOI.AbstractOptimizerAttribute},
+    value,
+)

Set the solver-specific attribute attr in model to value.

If attr is an AbstractString, this is equivalent to set_optimizer_attribute(model, MOI.RawOptimizerAttribute(name), value).

Compat

This method will remain in all v1.X releases of JuMP, but it may be removed in a future v2.0 release. We recommend using set_attribute instead.

See also: set_optimizer_attributes, get_optimizer_attribute.

Example

julia> model = Model();
+
+julia> set_optimizer_attribute(model, MOI.Silent(), true)
source

set_optimizer_attributes

JuMP.set_optimizer_attributesFunction
set_optimizer_attributes(
+    model::Union{GenericModel,MOI.OptimizerWithAttributes},
+    pairs::Pair...,
+)

Given a list of attribute => value pairs, calls set_optimizer_attribute(model, attribute, value) for each pair.

Compat

This method will remain in all v1.X releases of JuMP, but it may be removed in a future v2.0 release. We recommend using set_attributes instead.

See also: set_optimizer_attribute, get_optimizer_attribute.

Example

julia> import Ipopt
+
+julia> model = Model(Ipopt.Optimizer);
+
+julia> set_optimizer_attributes(model, "tol" => 1e-4, "max_iter" => 100)

is equivalent to:

julia> import Ipopt
+
+julia> model = Model(Ipopt.Optimizer);
+
+julia> set_optimizer_attribute(model, "tol", 1e-4)
+
+julia> set_optimizer_attribute(model, "max_iter", 100)
source

set_parameter_value

JuMP.set_parameter_valueFunction
set_parameter_value(x::GenericVariableRef, value)

Update the parameter constraint on the variable x to value.

Errors if x is not a parameter.

See also ParameterRef, is_parameter, parameter_value.

Examples

julia> model = Model();
+
+julia> @variable(model, p in Parameter(2))
+p
+
+julia> parameter_value(p)
+2.0
+
+julia> set_parameter_value(p, 2.5)
+
+julia> parameter_value(p)
+2.5
source

set_silent

JuMP.set_silentFunction
set_silent(model::GenericModel)

Takes precedence over any other attribute controlling verbosity and requires the solver to produce no output.

See also: unset_silent.

source

set_start_value

JuMP.set_start_valueFunction
set_start_value(con_ref::ConstraintRef, value)

Set the primal start value (MOI.ConstraintPrimalStart) of the constraint con_ref to value. To remove a primal start value set it to nothing.

See also start_value.

source
set_start_value(variable::GenericVariableRef, value::Union{Real,Nothing})

Set the start value (MOI attribute VariablePrimalStart) of the variable to value.

Pass nothing to unset the start value.

Note: VariablePrimalStarts are sometimes called "MIP-starts" or "warmstarts".

See also start_value.

source

set_start_values

JuMP.set_start_valuesFunction
set_start_values(
+    model::GenericModel;
+    variable_primal_start::Union{Nothing,Function} = value,
+    constraint_primal_start::Union{Nothing,Function} = value,
+    constraint_dual_start::Union{Nothing,Function} = dual,
+    nonlinear_dual_start::Union{Nothing,Function} = nonlinear_dual_start_value,
+)

Set the primal and dual starting values in model using the functions provided.

If any keyword argument is nothing, the corresponding start value is skipped.

If the optimizer does not support setting the starting value, the value will be skipped.

variable_primal_start

This function controls the primal starting solution for the variables. It is equivalent to calling set_start_value for each variable, or setting the MOI.VariablePrimalStart attribute.

If it is a function, it must have the form variable_primal_start(x::VariableRef) that maps each variable x to the starting primal value.

The default is value.

constraint_primal_start

This function controls the primal starting solution for the constraints. It is equivalent to calling set_start_value for each constraint, or setting the MOI.ConstraintPrimalStart attribute.

If it is a function, it must have the form constraint_primal_start(ci::ConstraintRef) that maps each constraint ci to the starting primal value.

The default is value.

constraint_dual_start

This function controls the dual starting solution for the constraints. It is equivalent to calling set_dual_start_value for each constraint, or setting the MOI.ConstraintDualStart attribute.

If it is a function, it must have the form constraint_dual_start(ci::ConstraintRef) that maps each constraint ci to the starting dual value.

The default is dual.

nonlinear_dual_start

This function controls the dual starting solution for the nonlinear constraints It is equivalent to calling set_nonlinear_dual_start_value.

If it is a function, it must have the form nonlinear_dual_start(model::GenericModel) that returns a vector corresponding to the dual start of the constraints.

The default is nonlinear_dual_start_value.

source

set_string_names_on_creation

JuMP.set_string_names_on_creationFunction
set_string_names_on_creation(model::GenericModel, value::Bool)

Set the default argument of the set_string_name keyword in the @variable and @constraint macros to value. This is used to determine whether to assign String names to all variables and constraints in model.

By default, value is true. However, for larger models calling set_string_names_on_creation(model, false) can improve performance at the cost of reducing the readability of printing and solver log messages.

source

set_time_limit_sec

set_upper_bound

JuMP.set_upper_boundFunction
set_upper_bound(v::GenericVariableRef, upper::Number)

Set the upper bound of a variable. If one does not exist, create an upper bound constraint.

See also UpperBoundRef, has_upper_bound, upper_bound, delete_upper_bound.

Examples

julia> model = Model();
+
+julia> @variable(model, x <= 1.0);
+
+julia> upper_bound(x)
+1.0
+
+julia> set_upper_bound(x, 2.0)
+
+julia> upper_bound(x)
+2.0
source

set_value

JuMP.set_valueFunction
set_value(p::NonlinearParameter, v::Number)

Store the value v in the nonlinear parameter p.

Example

julia> model = Model();
+
+julia> @NLparameter(model, p == 0)
+p == 0.0
+
+julia> set_value(p, 5)
+5
+
+julia> value(p)
+5.0
source

shadow_price

JuMP.shadow_priceFunction
shadow_price(con_ref::ConstraintRef)

Return the change in the objective from an infinitesimal relaxation of the constraint.

This value is computed from dual and can be queried only when has_duals is true and the objective sense is MIN_SENSE or MAX_SENSE (not FEASIBILITY_SENSE). For linear constraints, the shadow prices differ at most in sign from the dual value depending on the objective sense.

See also reduced_cost.

Notes

  • The function simply translates signs from dual and does not validate the conditions needed to guarantee the sensitivity interpretation of the shadow price. The caller is responsible, e.g., for checking whether the solver converged to an optimal primal-dual pair or a proof of infeasibility.
  • The computation is based on the current objective sense of the model. If this has changed since the last solve, the results will be incorrect.
  • Relaxation of equality constraints (and hence the shadow price) is defined based on which sense of the equality constraint is active.
source

shape

JuMP.shapeFunction
shape(c::AbstractConstraint)::AbstractShape

Return the shape of the constraint c.

source

show_backend_summary

JuMP.show_backend_summaryFunction
show_backend_summary(io::IO, model::GenericModel)

Print a summary of the optimizer backing model.

AbstractModels should implement this method.

source

show_constraints_summary

show_objective_function_summary

simplex_iterations

JuMP.simplex_iterationsFunction
simplex_iterations(model::GenericModel)

Gets the cumulative number of simplex iterations during the most-recent optimization.

Solvers must implement MOI.SimplexIterations() to use this function.

source

solution_summary

JuMP.solution_summaryFunction
solution_summary(model::GenericModel; result::Int = 1, verbose::Bool = false)

Return a struct that can be used print a summary of the solution in result result.

If verbose=true, write out the primal solution for every variable and the dual solution for every constraint, excluding those with empty names.

Example

When called at the REPL, the summary is automatically printed:

julia> model = Model();
+
+julia> solution_summary(model)
+* Solver : No optimizer attached.
+
+* Status
+  Result count       : 0
+  Termination status : OPTIMIZE_NOT_CALLED
+  Message from the solver:
+  "optimize not called"
+
+* Candidate solution (result #1)
+  Primal status      : NO_SOLUTION
+  Dual status        : NO_SOLUTION
+
+* Work counters

Use print to force the printing of the summary from inside a function:

julia> model = Model();
+
+julia> function foo(model)
+           print(solution_summary(model))
+           return
+       end
+foo (generic function with 1 method)
+
+julia> foo(model)
+* Solver : No optimizer attached.
+
+* Status
+  Result count       : 0
+  Termination status : OPTIMIZE_NOT_CALLED
+  Message from the solver:
+  "optimize not called"
+
+* Candidate solution (result #1)
+  Primal status      : NO_SOLUTION
+  Dual status        : NO_SOLUTION
+
+* Work counters
source

solve_time

JuMP.solve_timeFunction
solve_time(model::GenericModel)

If available, returns the solve time reported by the solver. Returns "ArgumentError: ModelLike of type Solver.Optimizer does not support accessing the attribute MathOptInterface.SolveTimeSec()" if the attribute is not implemented.

source

solver_name

JuMP.solver_nameFunction
solver_name(model::GenericModel)

If available, returns the SolverName property of the underlying optimizer.

Returns "No optimizer attached" in AUTOMATIC or MANUAL modes when no optimizer is attached.

Returns "SolverName() attribute not implemented by the optimizer." if the attribute is not implemented.

source

start_value

JuMP.start_valueFunction
start_value(con_ref::ConstraintRef)

Return the primal start value (MOI.ConstraintPrimalStart) of the constraint con_ref.

Note: If no primal start value has been set, start_value will return nothing.

See also set_start_value.

source
start_value(v::GenericVariableRef)

Return the start value (MOI attribute VariablePrimalStart) of the variable v.

Note: VariablePrimalStarts are sometimes called "MIP-starts" or "warmstarts".

See also set_start_value.

source

termination_status

time_limit_sec

triangle_vec

JuMP.triangle_vecFunction
triangle_vec(matrix::Matrix)

Return the upper triangle of a matrix concatenated into a vector in the order required by JuMP and MathOptInterface for Triangle sets.

Example

julia> model = Model();
+
+julia> @variable(model, X[1:3, 1:3], Symmetric);
+
+julia> @variable(model, t)
+t
+
+julia> @constraint(model, [t; triangle_vec(X)] in MOI.RootDetConeTriangle(3))
+[t, X[1,1], X[1,2], X[2,2], X[1,3], X[2,3], X[3,3]] ∈ MathOptInterface.RootDetConeTriangle(3)
source

unfix

JuMP.unfixFunction
unfix(v::GenericVariableRef)

Delete the fixing constraint of a variable.

Error if one does not exist.

See also FixRef, is_fixed, fix_value, fix.

Examples

julia> model = Model();
+
+julia> @variable(model, x == 1);
+
+julia> is_fixed(x)
+true
+
+julia> unfix(x)
+
+julia> is_fixed(x)
+false
source

unregister

JuMP.unregisterFunction
unregister(model::GenericModel, key::Symbol)

Unregister the name key from model so that a new variable, constraint, or expression can be created with the same key.

Note that this will not delete the object model[key]; it will just remove the reference at model[key]. To delete the object, use delete as well.

See also: delete, object_dictionary.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, x)
+ERROR: An object of name x is already attached to this model. If this
+    is intended, consider using the anonymous construction syntax, e.g.,
+    `x = @variable(model, [1:N], ...)` where the name of the object does
+    not appear inside the macro.
+
+    Alternatively, use `unregister(model, :x)` to first unregister
+    the existing name from the model. Note that this will not delete the
+    object; it will just remove the reference at `model[:x]`.
+
+Stacktrace:
+[...]
+
+julia> num_variables(model)
+1
+
+julia> unregister(model, :x)
+
+julia> @variable(model, x)
+x
+
+julia> num_variables(model)
+2
source

unsafe_backend

JuMP.unsafe_backendFunction
unsafe_backend(model::GenericModel)

Return the innermost optimizer associated with the JuMP model model.

This function should only be used by advanced users looking to access low-level solver-specific functionality. It has a high-risk of incorrect usage. We strongly suggest you use the alternative suggested below.

See also: backend.

Unsafe behavior

This function is unsafe for two main reasons.

First, the formulation and order of variables and constraints in the unsafe backend may be different to the variables and constraints in model. This can happen because of bridges, or because the solver requires the variables or constraints in a specific order. In addition, the variable or constraint index returned by index at the JuMP level may be different to the index of the corresponding variable or constraint in the unsafe_backend. There is no solution to this. Use the alternative suggested below instead.

Second, the unsafe_backend may be empty, or lack some modifications made to the JuMP model. Thus, before calling unsafe_backend you should first call MOI.Utilities.attach_optimizer to ensure that the backend is synchronized with the JuMP model.

julia> import HiGHS
+
+julia> model = Model(HiGHS.Optimizer)
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: EMPTY_OPTIMIZER
+Solver name: HiGHS
+
+julia> MOI.Utilities.attach_optimizer(model)
+
+julia> inner = unsafe_backend(model)
+A HiGHS model with 0 columns and 0 rows.

Moreover, if you modify the JuMP model, the reference you have to the backend (i.e., inner in the example above) may be out-dated, and you should call MOI.Utilities.attach_optimizer again.

This function is also unsafe in the reverse direction: if you modify the unsafe backend, e.g., by adding a new constraint to inner, the changes may be silently discarded by JuMP when the JuMP model is modified or solved.

Alternative

Instead of unsafe_backend, create a model using direct_model and call backend instead.

For example, instead of:

julia> import HiGHS
+
+julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x >= 0)
+x
+
+julia> MOI.Utilities.attach_optimizer(model)
+
+julia> highs = unsafe_backend(model)
+A HiGHS model with 1 columns and 0 rows.

Use:

julia> import HiGHS
+
+julia> model = direct_model(HiGHS.Optimizer());
+
+julia> set_silent(model)
+
+julia> @variable(model, x >= 0)
+x
+
+julia> highs = backend(model)  # No need to call `attach_optimizer`.
+A HiGHS model with 1 columns and 0 rows.
source

unset_binary

JuMP.unset_binaryFunction
unset_binary(variable_ref::GenericVariableRef)

Remove the binary constraint on the variable variable_ref.

See also BinaryRef, is_binary, set_binary.

Examples

julia> model = Model();
+
+julia> @variable(model, x, Bin);
+
+julia> is_binary(x)
+true
+
+julia> unset_binary(x)
+
+julia> is_binary(x)
+false
source

unset_integer

JuMP.unset_integerFunction
unset_integer(variable_ref::GenericVariableRef)

Remove the integrality constraint on the variable variable_ref.

Errors if one does not exist.

See also IntegerRef, is_integer, set_integer.

Examples

julia> model = Model();
+
+julia> @variable(model, x, Int);
+
+julia> is_integer(x)
+true
+
+julia> unset_integer(x)
+
+julia> is_integer(x)
+false
source

unset_silent

JuMP.unset_silentFunction
unset_silent(model::GenericModel)

Neutralize the effect of the set_silent function and let the solver attributes control the verbosity.

See also: set_silent.

source

unset_time_limit_sec

upper_bound

value

JuMP.valueFunction
value(con_ref::ConstraintRef; result::Int = 1)

Return the primal value of constraint con_ref associated with result index result of the most-recent solution returned by the solver.

That is, if con_ref is the reference of a constraint func-in-set, it returns the value of func evaluated at the value of the variables (given by value(::GenericVariableRef)).

Use has_values to check if a result exists before asking for values.

See also: result_count.

Note

For scalar constraints, the constant is moved to the set so it is not taken into account in the primal value of the constraint. For instance, the constraint @constraint(model, 2x + 3y + 1 == 5) is transformed into 2x + 3y-in-MOI.EqualTo(4) so the value returned by this function is the evaluation of 2x + 3y.

source
value(var_value::Function, con_ref::ConstraintRef)

Evaluate the primal value of the constraint con_ref using var_value(v) as the value for each variable v.

source
value(v::GenericVariableRef; result = 1)

Return the value of variable v associated with result index result of the most-recent returned by the solver.

Use has_values to check if a result exists before asking for values.

See also: result_count.

source
value(var_value::Function, v::GenericVariableRef)

Evaluate the value of the variable v as var_value(v).

source
value(var_value::Function, ex::GenericAffExpr)

Evaluate ex using var_value(v) as the value for each variable v.

source
value(v::GenericAffExpr; result::Int = 1)

Return the value of the GenericAffExpr v associated with result index result of the most-recent solution returned by the solver.

See also: result_count.

source
value(var_value::Function, ex::GenericQuadExpr)

Evaluate ex using var_value(v) as the value for each variable v.

source
value(v::GenericQuadExpr; result::Int = 1)

Return the value of the GenericQuadExpr v associated with result index result of the most-recent solution returned by the solver.

Replaces getvalue for most use cases.

See also: result_count.

source
value(p::NonlinearParameter)

Return the current value stored in the nonlinear parameter p.

Example

julia> model = Model();
+
+julia> @NLparameter(model, p == 10)
+p == 10.0
+
+julia> value(p)
+10.0
source
value(ex::NonlinearExpression; result::Int = 1)

Return the value of the NonlinearExpression ex associated with result index result of the most-recent solution returned by the solver.

Replaces getvalue for most use cases.

See also: result_count.

source
value(var_value::Function, ex::NonlinearExpression)

Evaluate ex using var_value(v) as the value for each variable v.

source
value(c::NonlinearConstraintRef; result::Int = 1)

Return the value of the NonlinearConstraintRef c associated with result index result of the most-recent solution returned by the solver.

See also: result_count.

source
value(var_value::Function, c::NonlinearConstraintRef)

Evaluate c using var_value(v) as the value for each variable v.

source

value_type

JuMP.value_typeFunction
value_type(::Type{<:Union{AbstractModel,AbstractVariableRef}})

Return the return type of value for variables of that model. It defaults to Float64 if it is not implemented.

source

variable_by_name

JuMP.variable_by_nameFunction
variable_by_name(
+    model::AbstractModel,
+    name::String,
+)::Union{AbstractVariableRef,Nothing}

Returns the reference of the variable with name attribute name or Nothing if no variable has this name attribute. Throws an error if several variables have name as their name attribute.

Examples

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> variable_by_name(model, "x")
+x
+
+julia> @variable(model, base_name="x")
+x
+
+julia> variable_by_name(model, "x")
+ERROR: Multiple variables have the name x.
+Stacktrace:
+ [1] error(::String) at ./error.jl:33
+ [2] get(::MOIU.Model{Float64}, ::Type{MathOptInterface.VariableIndex}, ::String) at /home/blegat/.julia/dev/MathOptInterface/src/Utilities/model.jl:222
+ [3] get at /home/blegat/.julia/dev/MathOptInterface/src/Utilities/universalfallback.jl:201 [inlined]
+ [4] get(::MathOptInterface.Utilities.CachingOptimizer{MathOptInterface.AbstractOptimizer,MathOptInterface.Utilities.UniversalFallback{MOIU.Model{Float64}}}, ::Type{MathOptInterface.VariableIndex}, ::String) at /home/blegat/.julia/dev/MathOptInterface/src/Utilities/cachingoptimizer.jl:490
+ [5] variable_by_name(::GenericModel, ::String) at /home/blegat/.julia/dev/JuMP/src/variables.jl:268
+ [6] top-level scope at none:0
+
+julia> var = @variable(model, base_name="y")
+y
+
+julia> variable_by_name(model, "y")
+y
+
+julia> set_name(var, "z")
+
+julia> variable_by_name(model, "y")
+
+julia> variable_by_name(model, "z")
+z
+
+julia> @variable(model, u[1:2])
+2-element Vector{VariableRef}:
+ u[1]
+ u[2]
+
+julia> variable_by_name(model, "u[2]")
+u[2]
source

variable_ref_type

JuMP.variable_ref_typeFunction
variable_ref_type(::Union{F,Type{F}}) where {F}

A helper function used internally by JuMP and some JuMP extensions. Returns the variable type associated with the model or expression type F.

source

vectorize

JuMP.vectorizeFunction
vectorize(matrix::AbstractMatrix, ::Shape)

Convert the matrix into a vector according to Shape.

source

write_to_file

JuMP.write_to_fileFunction
write_to_file(
+    model::GenericModel,
+    filename::String;
+    format::MOI.FileFormats.FileFormat = MOI.FileFormats.FORMAT_AUTOMATIC,
+    kwargs...,
+)

Write the JuMP model model to filename in the format format.

If the filename ends in .gz, it will be compressed using Gzip. If the filename ends in .bz2, it will be compressed using BZip2.

Other kwargs are passed to the Model constructor of the chosen format.

source

AbstractConstraint

JuMP.AbstractConstraintType
abstract type AbstractConstraint

An abstract base type for all constraint types. AbstractConstraints store the function and set directly, unlike ConstraintRefs that are merely references to constraints stored in a model. AbstractConstraints do not need to be attached to a model.

source

AbstractJuMPScalar

JuMP.AbstractJuMPScalarType
AbstractJuMPScalar <: MutableArithmetics.AbstractMutable

Abstract base type for all scalar types

The subtyping of AbstractMutable will allow calls of some Base functions to be redirected to a method in MA that handles type promotion more carefully (e.g. the promotion in sparse matrix products in SparseArrays usually does not work for JuMP types) and exploits the mutability of AffExpr and QuadExpr.

source

AbstractModel

JuMP.AbstractModelType
AbstractModel

An abstract type that should be subtyped for users creating JuMP extensions.

source

AbstractScalarSet

JuMP.AbstractScalarSetType
AbstractScalarSet

An abstract type for defining new scalar sets in JuMP.

Implement moi_set(::AbstractScalarSet) to convert the type into an MOI set.

See also: moi_set.

source

AbstractShape

AbstractVariable

AbstractVariableRef

JuMP.AbstractVariableRefType
AbstractVariableRef

Variable returned by add_variable. Affine (resp. quadratic) operations with variables of type V<:AbstractVariableRef and coefficients of type T create a GenericAffExpr{T,V} (resp. GenericQuadExpr{T,V}).

source

AbstractVectorSet

JuMP.AbstractVectorSetType
AbstractVectorSet

An abstract type for defining new sets in JuMP.

Implement moi_set(::AbstractVectorSet, dim::Int) to convert the type into an MOI set.

See also: moi_set.

source

AffExpr

BinaryRef

JuMP.BinaryRefFunction
BinaryRef(v::GenericVariableRef)

Return a constraint reference to the constraint constraining v to be binary. Errors if one does not exist.

See also is_binary, set_binary, unset_binary.

Examples

julia> model = Model();
+
+julia> @variable(model, x, Bin);
+
+julia> BinaryRef(x)
+x binary
source

BridgeableConstraint

JuMP.BridgeableConstraintType
BridgeableConstraint(
+    constraint::C,
+    bridge_type::B;
+    coefficient_type::Type{T} = Float64,
+) where {C<:AbstractConstraint,B<:Type{<:MOI.Bridges.AbstractBridge},T}

An AbstractConstraint representinng that constraint that can be bridged by the bridge of type bridge_type{coefficient_type}.

Adding a BridgeableConstraint to a model is equivalent to:

add_bridge(model, bridge_type; coefficient_type = coefficient_type)
+add_constraint(model, constraint)

Example

Given a new scalar set type CustomSet with a bridge CustomBridge that can bridge F-in-CustomSet constraints, when the user does:

model = Model()
+@variable(model, x)
+@constraint(model, x + 1 in CustomSet())
+optimize!(model)

with an optimizer that does not support F-in-CustomSet constraints, the constraint will not be bridged unless they first call add_bridge(model, CustomBridge).

In order to automatically add the CustomBridge to any model to which an F-in-CustomSet is added, add the following method:

function JuMP.build_constraint(
+    _error::Function,
+    func::AbstractJuMPScalar,
+    set::CustomSet,
+)
+    constraint = ScalarConstraint(func, set)
+    return BridgeableConstraint(constraint, CustomBridge)
+end

Note

JuMP extensions should extend JuMP.build_constraint only if they also defined CustomSet, for three reasons:

  1. It is problematic if multiple extensions overload the same JuMP method.
  2. A missing method will not inform the users that they forgot to load the extension module defining the build_constraint method.
  3. Defining a method where neither the function nor any of the argument types are defined in the package is called type piracy and is discouraged in the Julia style guide.
source

ComplexPlane

JuMP.ComplexPlaneType
ComplexPlane

Complex plane object that can be used to create a complex variable in the @variable macro.

Example

Consider the following example:

julia> model = Model();
+
+julia> @variable(model, x in ComplexPlane())
+real(x) + imag(x) im
+
+julia> all_variables(model)
+2-element Vector{VariableRef}:
+ real(x)
+ imag(x)

We see in the output of the last command that two real variables were created. The Julia variable x binds to an affine expression in terms of these two variables that parametrize the complex plane.

source

ComplexVariable

ConstraintNotOwned

JuMP.ConstraintNotOwnedType
struct ConstraintNotOwned{C <: ConstraintRef} <: Exception
+    constraint_ref::C
+end

The constraint constraint_ref was used in a model different to owner_model(constraint_ref).

source

ConstraintRef

FixRef

JuMP.FixRefFunction
FixRef(v::GenericVariableRef)

Return a constraint reference to the constraint fixing the value of v.

Errors if one does not exist.

See also is_fixed, fix_value, fix, unfix.

Examples

julia> model = Model();
+
+julia> @variable(model, x == 1);
+
+julia> FixRef(x)
+x = 1
source

GenericAffExpr

JuMP.GenericAffExprType
mutable struct GenericAffExpr{CoefType,VarType} <: AbstractJuMPScalar
+    constant::CoefType
+    terms::OrderedDict{VarType,CoefType}
+end

An expression type representing an affine expression of the form: $\sum a_i x_i + c$.

Fields

  • .constant: the constant c in the expression.
  • .terms: an OrderedDict, with keys of VarType and values of CoefType describing the sparse vector a.
source

GenericModel

JuMP.GenericModelType
GenericModel{T}(
+    [optimizer_factory;]
+    add_bridges::Bool = true,
+) where {T<:Real}

Create a new instance of a JuMP model.

If optimizer_factory is provided, the model is initialized with the optimizer returned by MOI.instantiate(optimizer_factory).

If optimizer_factory is not provided, use set_optimizer to set the optimizer before calling optimize!.

If add_bridges, JuMP adds a MOI.Bridges.LazyBridgeOptimizer to automatically reformulate the problem into a form supported by the optimizer.

Value type T

Passing a type other than Float64 as the value type T is an advanced operation. The value type must match that expected by the chosen optimizer. Consult the optimizers documentation for details.

If not documented, assume that the optimizer supports only Float64.

Choosing an unsupported value type will throw an MOI.UnsupportedConstraint or an MOI.UnsupportedAttribute error, the timing of which (during the model construction or during a call to optimize!) depends on how the solver is interfaced to JuMP.

Example

julia> model = GenericModel{BigFloat}();
+
+julia> typeof(model)
+GenericModel{BigFloat}
source

GenericNonlinearExpr

JuMP.GenericNonlinearExprType
GenericNonlinearExpr{V}(head::Symbol, args::Vector{Any})
+GenericNonlinearExpr{V}(head::Symbol, args::Any...)

The scalar-valued nonlinear function head(args...), represented as a symbolic expression tree, with the call operator head and ordered arguments in args.

V is the type of AbstractVariableRef present in the expression, and is used to help dispatch JuMP extensions.

head

The head::Symbol must be an operator supported by the model.

The default list of supported univariate operators is given by:

and the default list of supported multivariate operators is given by:

Additional operators can be add using @operator.

See the full list of operators supported by a MOI.ModelLike by querying the MOI.ListOfSupportedNonlinearOperators attribute.

args

The vector args contains the arguments to the nonlinear function. If the operator is univariate, it must contain one element. Otherwise, it may contain multiple elements.

Given a subtype of AbstractVariableRef, V, for GenericNonlinearExpr{V}, each element must be one of the following:

where T<:Real and T == value_type(V).

Unsupported operators

If the optimizer does not support head, an MOI.UnsupportedNonlinearOperator error will be thrown.

There is no guarantee about when this error will be thrown; it may be thrown when the function is first added to the model, or it may be thrown when optimize! is called.

Example

To represent the function $f(x) = sin(x)^2$, do:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> f = sin(x)^2
+sin(x) ^ 2.0
+
+julia> f = GenericNonlinearExpr{VariableRef}(
+           :^,
+           GenericNonlinearExpr{VariableRef}(:sin, x),
+           2.0,
+       )
+sin(x) ^ 2.0
source

GenericQuadExpr

JuMP.GenericQuadExprType
mutable struct GenericQuadExpr{CoefType,VarType} <: AbstractJuMPScalar
+    aff::GenericAffExpr{CoefType,VarType}
+    terms::OrderedDict{UnorderedPair{VarType}, CoefType}
+end

An expression type representing an quadratic expression of the form: $\sum q_{i,j} x_i x_j + \sum a_i x_i + c$.

Fields

  • .aff: an GenericAffExpr representing the affine portion of the expression.
  • .terms: an OrderedDict, with keys of UnorderedPair{VarType} and values of CoefType, describing the sparse list of terms q.
source

GenericReferenceMap

JuMP.GenericReferenceMapType
GenericReferenceMap{T}

Mapping between variable and constraint reference of a model and its copy. The reference of the copied model can be obtained by indexing the map with the reference of the corresponding reference of the original model.

source

GenericVariableRef

JuMP.GenericVariableRefType
GenericVariableRef{T} <: AbstractVariableRef

Holds a reference to the model and the corresponding MOI.VariableIndex.

source

HermitianMatrixShape

HermitianMatrixSpace

JuMP.HermitianMatrixSpaceType
HermitianMatrixSpace()

Use in the @variable macro to constrain a matrix of variables to be hermitian.

Example

julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2] in HermitianMatrixSpace())
+2×2 LinearAlgebra.Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
+ real(Q[1,1])                    real(Q[1,2]) + imag(Q[1,2]) im
+ real(Q[1,2]) - imag(Q[1,2]) im  real(Q[2,2])
source

HermitianPSDCone

JuMP.HermitianPSDConeType
HermitianPSDCone

Hermitian positive semidefinite cone object that can be used to create a Hermitian positive semidefinite square matrix in the @variable and @constraint macros.

Example

Consider the following example:

julia> model = Model();
+
+julia> @variable(model, H[1:3, 1:3] in HermitianPSDCone())
+3×3 LinearAlgebra.Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
+ real(H[1,1])                    …  real(H[1,3]) + imag(H[1,3]) im
+ real(H[1,2]) - imag(H[1,2]) im     real(H[2,3]) + imag(H[2,3]) im
+ real(H[1,3]) - imag(H[1,3]) im     real(H[3,3])
+
+julia> all_variables(model)
+9-element Vector{VariableRef}:
+ real(H[1,1])
+ real(H[1,2])
+ real(H[2,2])
+ real(H[1,3])
+ real(H[2,3])
+ real(H[3,3])
+ imag(H[1,2])
+ imag(H[1,3])
+ imag(H[2,3])
+
+julia> all_constraints(model, Vector{VariableRef}, MOI.HermitianPositiveSemidefiniteConeTriangle)
+1-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.HermitianPositiveSemidefiniteConeTriangle}}}:
+ [real(H[1,1]), real(H[1,2]), real(H[2,2]), real(H[1,3]), real(H[2,3]), real(H[3,3]), imag(H[1,2]), imag(H[1,3]), imag(H[2,3])] ∈ MathOptInterface.HermitianPositiveSemidefiniteConeTriangle(3)

We see in the output of the last commands that 9 real variables were created. The matrix H contrains affine expressions in terms of these 9 variables that parametrize a Hermitian matrix.

source

IntegerRef

JuMP.IntegerRefFunction
IntegerRef(v::GenericVariableRef)

Return a constraint reference to the constraint constraining v to be integer.

Errors if one does not exist.

See also is_integer, set_integer, unset_integer.

Examples

julia> model = Model();
+
+julia> @variable(model, x, Int);
+
+julia> IntegerRef(x)
+x integer
source

LinearTermIterator

JuMP.LinearTermIteratorType
LinearTermIterator{GAE<:GenericAffExpr}

A struct that implements the iterate protocol in order to iterate over tuples of (coefficient, variable) in the GenericAffExpr.

source

LowerBoundRef

Model

JuMP.ModelType
Model([optimizer_factory;] add_bridges::Bool = true)

Create a new instance of a JuMP model.

If optimizer_factory is provided, the model is initialized with thhe optimizer returned by MOI.instantiate(optimizer_factory).

If optimizer_factory is not provided, use set_optimizer to set the optimizer before calling optimize!.

If add_bridges, JuMP adds a MOI.Bridges.LazyBridgeOptimizer to automatically reformulate the problem into a form supported by the optimizer.

Example

julia> import Ipopt
+
+julia> model = Model(Ipopt.Optimizer);
+
+julia> solver_name(model)
+"Ipopt"
+
+julia> import HiGHS
+
+julia> import MultiObjectiveAlgorithms as MOA
+
+julia> model = Model(() -> MOA.Optimizer(HiGHS.Optimizer); add_bridges = false);
source

ModelMode

JuMP.ModelModeType
ModelMode

An enum to describe the state of the CachingOptimizer inside a JuMP model.

source

NLPEvaluator

JuMP.NLPEvaluatorFunction
NLPEvaluator(
+    model::Model,
+    _differentiation_backend::MOI.Nonlinear.AbstractAutomaticDifferentiation =
+        MOI.Nonlinear.SparseReverseMode(),
+)

Return an MOI.AbstractNLPEvaluator constructed from model

Warning

Before using, you must initialize the evaluator using MOI.initialize.

Experimental

These features may change or be removed in any future version of JuMP.

Pass _differentiation_backend to specify the differentiation backend used to compute derivatives.

source

NoOptimizer

NonlinearConstraintIndex

NonlinearConstraintRef

NonlinearExpr

NonlinearExpression

NonlinearOperator

JuMP.NonlinearOperatorType
NonlinearOperator(func::Function, head::Symbol)

A callable struct (functor) representing a function named head.

When called with AbstractJuMPScalars, the struct returns a GenericNonlinearExpr.

When called with non-JuMP types, the struct returns the evaluation of func(args...).

Unless head is special-cased by the optimizer, the operator must have already been added to the model using add_nonlinear_operator or @operator.

Example

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> f(x::Float64) = x^2
+f (generic function with 1 method)
+
+julia> ∇f(x::Float64) = 2 * x
+∇f (generic function with 1 method)
+
+julia> ∇²f(x::Float64) = 2.0
+∇²f (generic function with 1 method)
+
+julia> @operator(model, op_f, 1, f, ∇f, ∇²f)
+NonlinearOperator(f, :op_f)
+
+julia> bar = NonlinearOperator(f, :op_f)
+NonlinearOperator(f, :op_f)
+
+julia> @objective(model, Min, bar(x))
+op_f(x)
+
+julia> bar(2.0)
+4.0
source

NonlinearParameter

Nonnegatives

JuMP.NonnegativesType
Nonnegatives()

The JuMP equivalent of the MOI.Nonnegatives set, in which the dimension is inferred from the corresponding function.

Example

julia> model = Model();
+
+julia> @variable(model, x[1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> @constraint(model, x in Nonnegatives())
+[x[1], x[2]] ∈ MathOptInterface.Nonnegatives(2)
+
+julia> A = [1 2; 3 4];
+
+julia> b = [5, 6];
+
+julia> @constraint(model, A * x >= b)
+[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonnegatives(2)
source

Nonpositives

JuMP.NonpositivesType
Nonpositives()

The JuMP equivalent of the MOI.Nonpositives set, in which the dimension is inferred from the corresponding function.

Example

julia> model = Model();
+
+julia> @variable(model, x[1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> @constraint(model, x in Nonpositives())
+[x[1], x[2]] ∈ MathOptInterface.Nonpositives(2)
+
+julia> A = [1 2; 3 4];
+
+julia> b = [5, 6];
+
+julia> @constraint(model, A * x <= b)
+[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonpositives(2)
source

OptimizationSense

OptimizeNotCalled

PSDCone

JuMP.PSDConeType
PSDCone

Positive semidefinite cone object that can be used to constrain a square matrix to be positive semidefinite in the @constraint macro. If the matrix has type Symmetric then the columns vectorization (the vector obtained by concatenating the columns) of its upper triangular part is constrained to belong to the MOI.PositiveSemidefiniteConeTriangle set, otherwise its column vectorization is constrained to belong to the MOI.PositiveSemidefiniteConeSquare set.

Example

Consider the following example:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> a = [ x 2x
+            2x  x];
+
+julia> b = [1 2
+            2 4];
+
+julia> cref = @constraint(model, a >= b, PSDCone())
+[x - 1    2 x - 2;
+ 2 x - 2  x - 4] ∈ PSDCone()
+
+julia> jump_function(constraint_object(cref))
+4-element Vector{AffExpr}:
+ x - 1
+ 2 x - 2
+ 2 x - 2
+ x - 4
+
+julia> moi_set(constraint_object(cref))
+MathOptInterface.PositiveSemidefiniteConeSquare(2)

We see in the output of the last command that the vectorization of the matrix is constrained to belong to the PositiveSemidefiniteConeSquare.

julia> using LinearAlgebra # For Symmetric
+
+julia> cref = @constraint(model, Symmetric(a - b) in PSDCone())
+[x - 1    2 x - 2;
+ 2 x - 2  x - 4] ∈ PSDCone()
+
+julia> jump_function(constraint_object(cref))
+3-element Vector{AffExpr}:
+ x - 1
+ 2 x - 2
+ x - 4
+
+julia> moi_set(constraint_object(cref))
+MathOptInterface.PositiveSemidefiniteConeTriangle(2)

As we see in the output of the last command, the vectorization of only the upper triangular part of the matrix is constrained to belong to the PositiveSemidefiniteConeSquare.

source

Parameter

JuMP.ParameterType
Parameter(value)

A short-cut for the MOI.Parameter set.

Example

julia> model = Model();
+
+julia> @variable(model, x in Parameter(2))
+x
+
+julia> print(model)
+Feasibility
+Subject to
+ x ∈ MathOptInterface.Parameter{Float64}(2.0)
source

ParameterRef

JuMP.ParameterRefFunction
ParameterRef(x::GenericVariableRef)

Return a constraint reference to the constraint constraining x to be a parameter.

Errors if one does not exist.

See also is_parameter, set_parameter_value, parameter_value.

Examples

julia> model = Model();
+
+julia> @variable(model, p in Parameter(2))
+p
+
+julia> ParameterRef(p)
+p ∈ MathOptInterface.Parameter{Float64}(2.0)
+
+julia> @variable(model, x);
+
+julia> ParameterRef(x)
+ERROR: Variable x is not a parameter.
+Stacktrace:
+[...]
source

QuadExpr

QuadTermIterator

JuMP.QuadTermIteratorType
QuadTermIterator{GQE<:GenericQuadExpr}

A struct that implements the iterate protocol in order to iterate over tuples of (coefficient, variable, variable) in the GenericQuadExpr.

source

ReferenceMap

JuMP.ReferenceMapType
GenericReferenceMap{T}

Mapping between variable and constraint reference of a model and its copy. The reference of the copied model can be obtained by indexing the map with the reference of the corresponding reference of the original model.

source

ResultStatusCode

JuMP.ResultStatusCodeType
ResultStatusCode

An Enum of possible values for the PrimalStatus and DualStatus attributes.

The values indicate how to interpret the result vector.

Values

Possible values are:

  • NO_SOLUTION: the result vector is empty.
  • FEASIBLE_POINT: the result vector is a feasible point.
  • NEARLY_FEASIBLE_POINT: the result vector is feasible if some constraint tolerances are relaxed.
  • INFEASIBLE_POINT: the result vector is an infeasible point.
  • INFEASIBILITY_CERTIFICATE: the result vector is an infeasibility certificate. If the PrimalStatus is INFEASIBILITY_CERTIFICATE, then the primal result vector is a certificate of dual infeasibility. If the DualStatus is INFEASIBILITY_CERTIFICATE, then the dual result vector is a proof of primal infeasibility.
  • NEARLY_INFEASIBILITY_CERTIFICATE: the result satisfies a relaxed criterion for a certificate of infeasibility.
  • REDUCTION_CERTIFICATE: the result vector is an ill-posed certificate; see this article for details. If the PrimalStatus is REDUCTION_CERTIFICATE, then the primal result vector is a proof that the dual problem is ill-posed. If the DualStatus is REDUCTION_CERTIFICATE, then the dual result vector is a proof that the primal is ill-posed.
  • NEARLY_REDUCTION_CERTIFICATE: the result satisfies a relaxed criterion for an ill-posed certificate.
  • UNKNOWN_RESULT_STATUS: the result vector contains a solution with an unknown interpretation.
  • OTHER_RESULT_STATUS: the result vector contains a solution with an interpretation not covered by one of the statuses defined above
source

RotatedSecondOrderCone

JuMP.RotatedSecondOrderConeType
RotatedSecondOrderCone

Rotated second order cone object that can be used to constrain the square of the euclidean norm of a vector x to be less than or equal to $2tu$ where t and u are nonnegative scalars. This is a shortcut for the MOI.RotatedSecondOrderCone.

Example

The following constrains $\|(x-1, x-2)\|^2_2 \le 2tx$ and $t, x \ge 0$:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, t)
+t
+
+julia> @constraint(model, [t, x, x-1, x-2] in RotatedSecondOrderCone())
+[t, x, x - 1, x - 2] ∈ MathOptInterface.RotatedSecondOrderCone(4)
source

SOS1

JuMP.SOS1Type
SOS1

SOS1 (Special Ordered Sets type 1) object than can be used to constrain a vector x to a set where at most 1 variable can take a non-zero value, all others being at 0. The weights, when specified, induce an ordering of the variables; as such, they should be unique values. The kth element in the set corresponds to the kth weight in weights. See here for a description of SOS constraints and their potential uses. This is a shortcut for the MathOptInterface.SOS1 set.

source

SOS2

JuMP.SOS2Type
SOS2

SOS1 (Special Ordered Sets type 2) object than can be used to constrain a vector x to a set where at most 2 variables can take a non-zero value, all others being at 0. In addition, if two are non-zero these must be consecutive in their ordering. The weights induce an ordering of the variables; as such, they should be unique values. The kth element in the set corresponds to the kth weight in weights. See here for a description of SOS constraints and their potential uses. This is a shortcut for the MathOptInterface.SOS2 set.

source

ScalarConstraint

JuMP.ScalarConstraintType
struct ScalarConstraint

The data for a scalar constraint. The func field contains a JuMP object representing the function and the set field contains the MOI set. See also the documentation on JuMP's representation of constraints for more background.

source

ScalarShape

ScalarVariable

SecondOrderCone

JuMP.SecondOrderConeType
SecondOrderCone

Second order cone object that can be used to constrain the euclidean norm of a vector x to be less than or equal to a nonnegative scalar t. This is a shortcut for the MOI.SecondOrderCone.

Example

The following constrains $\|(x-1, x-2)\|_2 \le t$ and $t \ge 0$:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, t)
+t
+
+julia> @constraint(model, [t, x-1, x-2] in SecondOrderCone())
+[t, x - 1, x - 2] ∈ MathOptInterface.SecondOrderCone(3)
source

Semicontinuous

JuMP.SemicontinuousType
Semicontinuous(lower, upper)

A short-cut for the MOI.Semicontinuous set.

This short-cut is useful because it automatically promotes lower and upper to the same type, and converts them into the element type supported by the JuMP model.

Example

julia> model = Model();
+
+julia> @variable(model, x in Semicontinuous(1, 2))
+x
+
+julia> print(model)
+Feasibility
+Subject to
+ x ∈ MathOptInterface.Semicontinuous{Int64}(1, 2)
source

Semiinteger

JuMP.SemiintegerType
Semiinteger(lower, upper)

A short-cut for the MOI.Semiinteger set.

This short-cut is useful because it automatically promotes lower and upper to the same type, and converts them into the element type supported by the JuMP model.

Example

julia> model = Model();
+
+julia> @variable(model, x in Semiinteger(3, 5))
+x
+
+julia> print(model)
+Feasibility
+Subject to
+ x ∈ MathOptInterface.Semiinteger{Int64}(3, 5)
source

SensitivityReport

SkewSymmetricMatrixShape

JuMP.SkewSymmetricMatrixShapeType
SkewSymmetricMatrixShape

Shape object for a skew symmetric square matrix of side_dimension rows and columns. The vectorized form contains the entries of the upper-right triangular part of the matrix (without the diagonal) given column by column (or equivalently, the entries of the lower-left triangular part given row by row). The diagonal is zero.

source

SkewSymmetricMatrixSpace

JuMP.SkewSymmetricMatrixSpaceType
SkewSymmetricMatrixSpace()

Use in the @variable macro to constrain a matrix of variables to be skew-symmetric.

Example

julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2] in SkewSymmetricMatrixSpace())
+2×2 Matrix{AffExpr}:
+ 0        Q[1,2]
+ -Q[1,2]  0
source

SquareMatrixShape

JuMP.SquareMatrixShapeType
SquareMatrixShape

Shape object for a square matrix of side_dimension rows and columns. The vectorized form contains the entries of the the matrix given column by column (or equivalently, the entries of the lower-left triangular part given row by row).

source

SymmetricMatrixShape

JuMP.SymmetricMatrixShapeType
SymmetricMatrixShape

Shape object for a symmetric square matrix of side_dimension rows and columns. The vectorized form contains the entries of the upper-right triangular part of the matrix given column by column (or equivalently, the entries of the lower-left triangular part given row by row).

source

SymmetricMatrixSpace

JuMP.SymmetricMatrixSpaceType
SymmetricMatrixSpace()

Use in the @variable macro to constrain a matrix of variables to be symmetric.

Example

julia> model = Model();
+
+julia> @variable(model, Q[1:2, 1:2] in SymmetricMatrixSpace())
+2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
+ Q[1,1]  Q[1,2]
+ Q[1,2]  Q[2,2]
source

TerminationStatusCode

JuMP.TerminationStatusCodeType
TerminationStatusCode

An Enum of possible values for the TerminationStatus attribute. This attribute is meant to explain the reason why the optimizer stopped executing in the most recent call to optimize!.

Values

Possible values are:

  • OPTIMIZE_NOT_CALLED: The algorithm has not started.
  • OPTIMAL: The algorithm found a globally optimal solution.
  • INFEASIBLE: The algorithm concluded that no feasible solution exists.
  • DUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem. If, additionally, a feasible (primal) solution is known to exist, this status typically implies that the problem is unbounded, with some technical exceptions.
  • LOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, could not find directions for improvement, or otherwise completed its search without global guarantees.
  • LOCALLY_INFEASIBLE: The algorithm converged to an infeasible point or otherwise completed its search without finding a feasible solution, without guarantees that no feasible solution exists.
  • INFEASIBLE_OR_UNBOUNDED: The algorithm stopped because it decided that the problem is infeasible or unbounded; this occasionally happens during MIP presolve.
  • ALMOST_OPTIMAL: The algorithm found a globally optimal solution to relaxed tolerances.
  • ALMOST_INFEASIBLE: The algorithm concluded that no feasible solution exists within relaxed tolerances.
  • ALMOST_DUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem within relaxed tolerances.
  • ALMOST_LOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, or could not find directions for improvement within relaxed tolerances.
  • ITERATION_LIMIT: An iterative algorithm stopped after conducting the maximum number of iterations.
  • TIME_LIMIT: The algorithm stopped after a user-specified computation time.
  • NODE_LIMIT: A branch-and-bound algorithm stopped because it explored a maximum number of nodes in the branch-and-bound tree.
  • SOLUTION_LIMIT: The algorithm stopped because it found the required number of solutions. This is often used in MIPs to get the solver to return the first feasible solution it encounters.
  • MEMORY_LIMIT: The algorithm stopped because it ran out of memory.
  • OBJECTIVE_LIMIT: The algorithm stopped because it found a solution better than a minimum limit set by the user.
  • NORM_LIMIT: The algorithm stopped because the norm of an iterate became too large.
  • OTHER_LIMIT: The algorithm stopped due to a limit not covered by one of the _LIMIT_ statuses above.
  • SLOW_PROGRESS: The algorithm stopped because it was unable to continue making progress towards the solution.
  • NUMERICAL_ERROR: The algorithm stopped because it encountered unrecoverable numerical error.
  • INVALID_MODEL: The algorithm stopped because the model is invalid.
  • INVALID_OPTION: The algorithm stopped because it was provided an invalid option.
  • INTERRUPTED: The algorithm stopped because of an interrupt signal.
  • OTHER_ERROR: The algorithm stopped because of an error not covered by one of the statuses defined above.
source

UnorderedPair

UpperBoundRef

VariableConstrainedOnCreation

JuMP.VariableConstrainedOnCreationType
VariableConstrainedOnCreation <: AbstractVariable

Variable scalar_variables constrained to belong to set.

Adding this variable can be understood as doing:

function JuMP.add_variable(
+    model::GenericModel,
+    variable::VariableConstrainedOnCreation,
+    names,
+)
+    var_ref = add_variable(model, variable.scalar_variable, name)
+    add_constraint(model, VectorConstraint(var_ref, variable.set))
+    return var_ref
+end

but adds the variables with MOI.add_constrained_variable(model, variable.set) instead. See the MOI documentation for the difference between adding the variables with MOI.add_constrained_variable and adding them with MOI.add_variable and adding the constraint separately.

source

VariableInfo

JuMP.VariableInfoType
VariableInfo{S,T,U,V}

A struct by JuMP internally when creating variables. This may also be used by JuMP extensions to create new types of variables.

See also: ScalarVariable.

source

VariableNotOwned

JuMP.VariableNotOwnedType
struct VariableNotOwned{V<:AbstractVariableRef} <: Exception
+    variable::V
+end

The variable variable was used in a model different to owner_model(variable).

source

VariableRef

JuMP.VariableRefType
GenericVariableRef{T} <: AbstractVariableRef

Holds a reference to the model and the corresponding MOI.VariableIndex.

source

VariablesConstrainedOnCreation

JuMP.VariablesConstrainedOnCreationType
VariablesConstrainedOnCreation <: AbstractVariable

Vector of variables scalar_variables constrained to belong to set. Adding this variable can be thought as doing:

function JuMP.add_variable(
+    model::GenericModel,
+    variable::VariablesConstrainedOnCreation,
+    names,
+)
+    v_names = vectorize(names, variable.shape)
+    var_refs = add_variable.(model, variable.scalar_variables, v_names)
+    add_constraint(model, VectorConstraint(var_refs, variable.set))
+    return reshape_vector(var_refs, variable.shape)
+end

but adds the variables with MOI.add_constrained_variables(model, variable.set) instead. See the MOI documentation for the difference between adding the variables with MOI.add_constrained_variables and adding them with MOI.add_variables and adding the constraint separately.

source

VectorConstraint

JuMP.VectorConstraintType
struct VectorConstraint

The data for a vector constraint. The func field contains a JuMP object representing the function and the set field contains the MOI set. The shape field contains an AbstractShape matching the form in which the constraint was constructed (e.g., by using matrices or flat vectors). See also the documentation on JuMP's representation of constraints.

source

VectorShape

JuMP.VectorShapeType
VectorShape

Vector for which the vectorized form corresponds exactly to the vector given.

source

Zeros

JuMP.ZerosType
Zeros()

The JuMP equivalent of the MOI.Zeros set, in which the dimension is inferred from the corresponding function.

Example

julia> model = Model();
+
+julia> @variable(model, x[1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> @constraint(model, x in Zeros())
+[x[1], x[2]] ∈ MathOptInterface.Zeros(2)
+
+julia> A = [1 2; 3 4];
+
+julia> b = [5, 6];
+
+julia> @constraint(model, A * x == b)
+[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Zeros(2)
source

ALMOST_DUAL_INFEASIBLE

ALMOST_INFEASIBLE

ALMOST_LOCALLY_SOLVED

JuMP.ALMOST_LOCALLY_SOLVEDConstant
ALMOST_LOCALLY_SOLVED::TerminationStatusCode

An instance of the TerminationStatusCode enum.

ALMOST_LOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, or could not find directions for improvement within relaxed tolerances.

source

ALMOST_OPTIMAL

AUTOMATIC

DIRECT

JuMP.DIRECTConstant

moi_backend field holds an AbstractOptimizer. No extra copy of the model is stored. The moi_backend must support add_constraint etc.

source

DUAL_INFEASIBLE

JuMP.DUAL_INFEASIBLEConstant
DUAL_INFEASIBLE::TerminationStatusCode

An instance of the TerminationStatusCode enum.

DUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem. If, additionally, a feasible (primal) solution is known to exist, this status typically implies that the problem is unbounded, with some technical exceptions.

source

FEASIBILITY_SENSE

FEASIBLE_POINT

INFEASIBILITY_CERTIFICATE

JuMP.INFEASIBILITY_CERTIFICATEConstant
INFEASIBILITY_CERTIFICATE::ResultStatusCode

An instance of the ResultStatusCode enum.

INFEASIBILITY_CERTIFICATE: the result vector is an infeasibility certificate. If the PrimalStatus is INFEASIBILITY_CERTIFICATE, then the primal result vector is a certificate of dual infeasibility. If the DualStatus is INFEASIBILITY_CERTIFICATE, then the dual result vector is a proof of primal infeasibility.

source

INFEASIBLE

INFEASIBLE_OR_UNBOUNDED

JuMP.INFEASIBLE_OR_UNBOUNDEDConstant
INFEASIBLE_OR_UNBOUNDED::TerminationStatusCode

An instance of the TerminationStatusCode enum.

INFEASIBLE_OR_UNBOUNDED: The algorithm stopped because it decided that the problem is infeasible or unbounded; this occasionally happens during MIP presolve.

source

INFEASIBLE_POINT

INTERRUPTED

INVALID_MODEL

INVALID_OPTION

ITERATION_LIMIT

LOCALLY_INFEASIBLE

JuMP.LOCALLY_INFEASIBLEConstant
LOCALLY_INFEASIBLE::TerminationStatusCode

An instance of the TerminationStatusCode enum.

LOCALLY_INFEASIBLE: The algorithm converged to an infeasible point or otherwise completed its search without finding a feasible solution, without guarantees that no feasible solution exists.

source

LOCALLY_SOLVED

JuMP.LOCALLY_SOLVEDConstant
LOCALLY_SOLVED::TerminationStatusCode

An instance of the TerminationStatusCode enum.

LOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, could not find directions for improvement, or otherwise completed its search without global guarantees.

source

MANUAL

JuMP.MANUALConstant

moi_backend field holds a CachingOptimizer in MANUAL mode.

source

MAX_SENSE

MEMORY_LIMIT

MIN_SENSE

NEARLY_FEASIBLE_POINT

NEARLY_INFEASIBILITY_CERTIFICATE

NEARLY_REDUCTION_CERTIFICATE

NODE_LIMIT

JuMP.NODE_LIMITConstant
NODE_LIMIT::TerminationStatusCode

An instance of the TerminationStatusCode enum.

NODE_LIMIT: A branch-and-bound algorithm stopped because it explored a maximum number of nodes in the branch-and-bound tree.

source

NORM_LIMIT

NO_SOLUTION

NUMERICAL_ERROR

OBJECTIVE_LIMIT

OPTIMAL

OPTIMIZE_NOT_CALLED

OTHER_ERROR

JuMP.OTHER_ERRORConstant
OTHER_ERROR::TerminationStatusCode

An instance of the TerminationStatusCode enum.

OTHER_ERROR: The algorithm stopped because of an error not covered by one of the statuses defined above.

source

OTHER_LIMIT

OTHER_RESULT_STATUS

JuMP.OTHER_RESULT_STATUSConstant
OTHER_RESULT_STATUS::ResultStatusCode

An instance of the ResultStatusCode enum.

OTHER_RESULT_STATUS: the result vector contains a solution with an interpretation not covered by one of the statuses defined above

source

REDUCTION_CERTIFICATE

JuMP.REDUCTION_CERTIFICATEConstant
REDUCTION_CERTIFICATE::ResultStatusCode

An instance of the ResultStatusCode enum.

REDUCTION_CERTIFICATE: the result vector is an ill-posed certificate; see this article for details. If the PrimalStatus is REDUCTION_CERTIFICATE, then the primal result vector is a proof that the dual problem is ill-posed. If the DualStatus is REDUCTION_CERTIFICATE, then the dual result vector is a proof that the primal is ill-posed.

source

SLOW_PROGRESS

JuMP.SLOW_PROGRESSConstant
SLOW_PROGRESS::TerminationStatusCode

An instance of the TerminationStatusCode enum.

SLOW_PROGRESS: The algorithm stopped because it was unable to continue making progress towards the solution.

source

SOLUTION_LIMIT

JuMP.SOLUTION_LIMITConstant
SOLUTION_LIMIT::TerminationStatusCode

An instance of the TerminationStatusCode enum.

SOLUTION_LIMIT: The algorithm stopped because it found the required number of solutions. This is often used in MIPs to get the solver to return the first feasible solution it encounters.

source

TIME_LIMIT

UNKNOWN_RESULT_STATUS

op_and

JuMP.op_andConstant
op_and(x, y)

A function that falls back to x & y, but when called with JuMP variables or expressions, returns a GenericNonlinearExpr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> op_and(true, false)
+false
+
+julia> op_and(true, x)
+true && x
source

op_equal_to

JuMP.op_equal_toConstant
op_equal_to(x, y)

A function that falls back to x == y, but when called with JuMP variables or expressions, returns a GenericNonlinearExpr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> op_equal_to(2, 2)
+true
+
+julia> op_equal_to(x, 2)
+x == 2
source

op_greater_than_or_equal_to

JuMP.op_greater_than_or_equal_toConstant
op_greater_than_or_equal_to(x, y)

A function that falls back to x >= y, but when called with JuMP variables or expressions, returns a GenericNonlinearExpr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> op_greater_than_or_equal_to(2, 2)
+true
+
+julia> op_greater_than_or_equal_to(x, 2)
+x >= 2
source

op_less_than_or_equal_to

JuMP.op_less_than_or_equal_toConstant
op_less_than_or_equal_to(x, y)

A function that falls back to x <= y, but when called with JuMP variables or expressions, returns a GenericNonlinearExpr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> op_less_than_or_equal_to(2, 2)
+true
+
+julia> op_less_than_or_equal_to(x, 2)
+x <= 2
source

op_or

JuMP.op_orConstant
op_or(x, y)

A function that falls back to x | y, but when called with JuMP variables or expressions, returns a GenericNonlinearExpr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> op_or(true, false)
+true
+
+julia> op_or(true, x)
+true || x
source

op_strictly_greater_than

JuMP.op_strictly_greater_thanConstant
op_strictly_greater_than(x, y)

A function that falls back to x > y, but when called with JuMP variables or expressions, returns a GenericNonlinearExpr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> op_strictly_greater_than(1, 2)
+false
+
+julia> op_strictly_greater_than(x, 2)
+x > 2
source

op_strictly_less_than

JuMP.op_strictly_less_thanConstant
op_strictly_less_than(x, y)

A function that falls back to x < y, but when called with JuMP variables or expressions, returns a GenericNonlinearExpr.

Example

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> op_strictly_less_than(1, 2)
+true
+
+julia> op_strictly_less_than(x, 2)
+x < 2
source

Base.empty!(::GenericModel)

Base.empty!Method
empty!(model::GenericModel)::GenericModel

Empty the model, that is, remove all variables, constraints and model attributes but not optimizer attributes. Always return the argument.

Note: removes extensions data.

source

Base.isempty(::GenericModel)

Base.isemptyMethod
isempty(model::GenericModel)

Verifies whether the model is empty, that is, whether the MOI backend is empty and whether the model is in the same state as at its creation apart from optimizer attributes.

source

Base.copy(::AbstractModel)

Base.copyMethod
copy(model::AbstractModel)

Return a copy of the model model. It is similar to copy_model except that it does not return the mapping between the references of model and its copy.

Note

Model copy is not supported in DIRECT mode, i.e. when a model is constructed using the direct_model constructor instead of the Model constructor. Moreover, independently on whether an optimizer was provided at model construction, the new model will have no optimizer, i.e., an optimizer will have to be provided to the new model in the optimize! call.

Example

In the following example, a model model is constructed with a variable x and a constraint cref. It is then copied into a model new_model with the new references assigned to x_new and cref_new.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, cref, x == 2)
+cref : x = 2
+
+julia> new_model = copy(model);
+
+julia> x_new = model[:x]
+x
+
+julia> cref_new = model[:cref]
+cref : x = 2
source

Base.write(::IO, ::GenericModel; ::MOI.FileFormats.FileFormat)

Base.writeMethod
Base.write(
+    io::IO,
+    model::GenericModel;
+    format::MOI.FileFormats.FileFormat = MOI.FileFormats.FORMAT_MOF,
+    kwargs...,
+)

Write the JuMP model model to io in the format format.

Other kwargs are passed to the Model constructor of the chosen format.

source

MOI.Utilities.reset_optimizer(::GenericModel)

MOI.Utilities.drop_optimizer(::GenericModel)

MOI.Utilities.attach_optimizer(::GenericModel)

diff --git a/previews/PR3536/assets/case9mod.png b/previews/PR3536/assets/case9mod.png new file mode 100644 index 00000000000..a1ad127bb15 Binary files /dev/null and b/previews/PR3536/assets/case9mod.png differ diff --git a/previews/PR3536/assets/documenter.js b/previews/PR3536/assets/documenter.js new file mode 100644 index 00000000000..f93caf595e2 --- /dev/null +++ b/previews/PR3536/assets/documenter.js @@ -0,0 +1,883 @@ +// Generated by Documenter.jl +requirejs.config({ + paths: { + 'highlight-julia': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia.min', + 'headroom': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/headroom.min', + 'jqueryui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min', + 'minisearch': 'https://cdn.jsdelivr.net/npm/minisearch@6.1.0/dist/umd/index.min', + 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min', + 'mathjax': 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-AMS_HTML', + 'headroom-jquery': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/jQuery.headroom.min', + 'highlight': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min', + 'highlight-julia-repl': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia-repl.min', + }, + shim: { + "highlight-julia": { + "deps": [ + "highlight" + ] + }, + "mathjax": { + "exports": "MathJax" + }, + "headroom-jquery": { + "deps": [ + "jquery", + "headroom" + ] + }, + "highlight-julia-repl": { + "deps": [ + "highlight" + ] + } +} +}); +//////////////////////////////////////////////////////////////////////////////// +require(['mathjax'], function(MathJax) { +MathJax.Hub.Config({ + "jax": [ + "input/TeX", + "output/HTML-CSS", + "output/NativeMML" + ], + "TeX": { + "equationNumbers": { + "autoNumber": "AMS" + } + }, + "tex2jax": { + "inlineMath": [ + [ + "$", + "$" + ], + [ + "\\(", + "\\)" + ] + ], + "processEscapes": true + }, + "config": [ + "MMLorHTML.js" + ], + "extensions": [ + "MathMenu.js", + "MathZoom.js", + "TeX/AMSmath.js", + "TeX/AMSsymbols.js", + "TeX/autobold.js", + "TeX/autoload-all.js" + ] +} +); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'highlight', 'highlight-julia', 'highlight-julia-repl'], function($) { +$(document).ready(function() { + hljs.highlightAll(); +}) + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +var isExpanded = true; + +$(document).on("click", ".docstring header", function () { + let articleToggleTitle = "Expand docstring"; + + if ($(this).siblings("section").is(":visible")) { + $(this) + .find(".docstring-article-toggle-button") + .removeClass("fa-chevron-down") + .addClass("fa-chevron-right"); + } else { + $(this) + .find(".docstring-article-toggle-button") + .removeClass("fa-chevron-right") + .addClass("fa-chevron-down"); + + articleToggleTitle = "Collapse docstring"; + } + + $(this) + .find(".docstring-article-toggle-button") + .prop("title", articleToggleTitle); + $(this).siblings("section").slideToggle(); +}); + +$(document).on("click", ".docs-article-toggle-button", function () { + let articleToggleTitle = "Expand docstring"; + let navArticleToggleTitle = "Expand all docstrings"; + + if (isExpanded) { + $(this).removeClass("fa-chevron-up").addClass("fa-chevron-down"); + $(".docstring-article-toggle-button") + .removeClass("fa-chevron-down") + .addClass("fa-chevron-right"); + + isExpanded = false; + + $(".docstring section").slideUp(); + } else { + $(this).removeClass("fa-chevron-down").addClass("fa-chevron-up"); + $(".docstring-article-toggle-button") + .removeClass("fa-chevron-right") + .addClass("fa-chevron-down"); + + isExpanded = true; + articleToggleTitle = "Collapse docstring"; + navArticleToggleTitle = "Collapse all docstrings"; + + $(".docstring section").slideDown(); + } + + $(this).prop("title", navArticleToggleTitle); + $(".docstring-article-toggle-button").prop("title", articleToggleTitle); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require([], function() { +function addCopyButtonCallbacks() { + for (const el of document.getElementsByTagName("pre")) { + const button = document.createElement("button"); + button.classList.add("copy-button", "fa-solid", "fa-copy"); + button.setAttribute("aria-label", "Copy this code block"); + button.setAttribute("title", "Copy"); + + el.appendChild(button); + + const success = function () { + button.classList.add("success", "fa-check"); + button.classList.remove("fa-copy"); + }; + + const failure = function () { + button.classList.add("error", "fa-xmark"); + button.classList.remove("fa-copy"); + }; + + button.addEventListener("click", function () { + copyToClipboard(el.innerText).then(success, failure); + + setTimeout(function () { + button.classList.add("fa-copy"); + button.classList.remove("success", "fa-check", "fa-xmark"); + }, 5000); + }); + } +} + +function copyToClipboard(text) { + // clipboard API is only available in secure contexts + if (window.navigator && window.navigator.clipboard) { + return window.navigator.clipboard.writeText(text); + } else { + return new Promise(function (resolve, reject) { + try { + const el = document.createElement("textarea"); + el.textContent = text; + el.style.position = "fixed"; + el.style.opacity = 0; + document.body.appendChild(el); + el.select(); + document.execCommand("copy"); + + resolve(); + } catch (err) { + reject(err); + } finally { + document.body.removeChild(el); + } + }); + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", addCopyButtonCallbacks); +} else { + addCopyButtonCallbacks(); +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'headroom', 'headroom-jquery'], function($, Headroom) { + +// Manages the top navigation bar (hides it when the user starts scrolling down on the +// mobile). +window.Headroom = Headroom; // work around buggy module loading? +$(document).ready(function () { + $("#documenter .docs-navbar").headroom({ + tolerance: { up: 10, down: 10 }, + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'minisearch'], function($, minisearch) { + +// In general, most search related things will have "search" as a prefix. +// To get an in-depth about the thought process you can refer: https://hetarth02.hashnode.dev/series/gsoc + +let results = []; +let timer = undefined; + +let data = documenterSearchIndex["docs"].map((x, key) => { + x["id"] = key; // minisearch requires a unique for each object + return x; +}); + +// list below is the lunr 2.1.3 list minus the intersect with names(Base) +// (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with) +// ideally we'd just filter the original list but it's not available as a variable +const stopWords = new Set([ + "a", + "able", + "about", + "across", + "after", + "almost", + "also", + "am", + "among", + "an", + "and", + "are", + "as", + "at", + "be", + "because", + "been", + "but", + "by", + "can", + "cannot", + "could", + "dear", + "did", + "does", + "either", + "ever", + "every", + "from", + "got", + "had", + "has", + "have", + "he", + "her", + "hers", + "him", + "his", + "how", + "however", + "i", + "if", + "into", + "it", + "its", + "just", + "least", + "like", + "likely", + "may", + "me", + "might", + "most", + "must", + "my", + "neither", + "no", + "nor", + "not", + "of", + "off", + "often", + "on", + "or", + "other", + "our", + "own", + "rather", + "said", + "say", + "says", + "she", + "should", + "since", + "so", + "some", + "than", + "that", + "the", + "their", + "them", + "then", + "there", + "these", + "they", + "this", + "tis", + "to", + "too", + "twas", + "us", + "wants", + "was", + "we", + "were", + "what", + "when", + "who", + "whom", + "why", + "will", + "would", + "yet", + "you", + "your", +]); + +let index = new minisearch({ + fields: ["title", "text"], // fields to index for full-text search + storeFields: ["location", "title", "text", "category", "page"], // fields to return with search results + processTerm: (term) => { + let word = stopWords.has(term) ? null : term; + if (word) { + // custom trimmer that doesn't strip @ and !, which are used in julia macro and function names + word = word + .replace(/^[^a-zA-Z0-9@!]+/, "") + .replace(/[^a-zA-Z0-9@!]+$/, ""); + } + + return word ?? null; + }, + // add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not find anything if searching for "add!", only for the entire qualification + tokenize: (string) => string.split(/[\s\-\.]+/), + // options which will be applied during the search + searchOptions: { + boost: { title: 100 }, + fuzzy: 2, + processTerm: (term) => { + let word = stopWords.has(term) ? null : term; + if (word) { + word = word + .replace(/^[^a-zA-Z0-9@!]+/, "") + .replace(/[^a-zA-Z0-9@!]+$/, ""); + } + + return word ?? null; + }, + tokenize: (string) => string.split(/[\s\-\.]+/), + }, +}); + +index.addAll(data); + +let filters = [...new Set(data.map((x) => x.category))]; +var modal_filters = make_modal_body_filters(filters); +var filter_results = []; + +$(document).on("keyup", ".documenter-search-input", function (event) { + // Adding a debounce to prevent disruptions from super-speed typing! + debounce(() => update_search(filter_results), 300); +}); + +$(document).on("click", ".search-filter", function () { + if ($(this).hasClass("search-filter-selected")) { + $(this).removeClass("search-filter-selected"); + } else { + $(this).addClass("search-filter-selected"); + } + + // Adding a debounce to prevent disruptions from crazy clicking! + debounce(() => get_filters(), 300); +}); + +/** + * A debounce function, takes a function and an optional timeout in milliseconds + * + * @function callback + * @param {number} timeout + */ +function debounce(callback, timeout = 300) { + clearTimeout(timer); + timer = setTimeout(callback, timeout); +} + +/** + * Make/Update the search component + * + * @param {string[]} selected_filters + */ +function update_search(selected_filters = []) { + let initial_search_body = ` +
Type something to get started!
+ `; + + let querystring = $(".documenter-search-input").val(); + + if (querystring.trim()) { + results = index.search(querystring, { + filter: (result) => { + // Filtering results + if (selected_filters.length === 0) { + return result.score >= 1; + } else { + return ( + result.score >= 1 && selected_filters.includes(result.category) + ); + } + }, + }); + + let search_result_container = ``; + let search_divider = `
`; + + if (results.length) { + let links = []; + let count = 0; + let search_results = ""; + + results.forEach(function (result) { + if (result.location) { + // Checking for duplication of results for the same page + if (!links.includes(result.location)) { + search_results += make_search_result(result, querystring); + count++; + } + + links.push(result.location); + } + }); + + let result_count = `
${count} result(s)
`; + + search_result_container = ` +
+ ${modal_filters} + ${search_divider} + ${result_count} +
+ ${search_results} +
+
+ `; + } else { + search_result_container = ` +
+ ${modal_filters} + ${search_divider} +
0 result(s)
+
+
No result found!
+ `; + } + + if ($(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").removeClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(search_result_container); + } else { + filter_results = []; + modal_filters = make_modal_body_filters(filters, filter_results); + + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(initial_search_body); + } +} + +/** + * Make the modal filter html + * + * @param {string[]} filters + * @param {string[]} selected_filters + * @returns string + */ +function make_modal_body_filters(filters, selected_filters = []) { + let str = ``; + + filters.forEach((val) => { + if (selected_filters.includes(val)) { + str += `${val}`; + } else { + str += `${val}`; + } + }); + + let filter_html = ` +
+ Filters: + ${str} +
+ `; + + return filter_html; +} + +/** + * Make the result component given a minisearch result data object and the value of the search input as queryString. + * To view the result object structure, refer: https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchresult + * + * @param {object} result + * @param {string} querystring + * @returns string + */ +function make_search_result(result, querystring) { + let search_divider = `
`; + let display_link = + result.location.slice(Math.max(0), Math.min(50, result.location.length)) + + (result.location.length > 30 ? "..." : ""); // To cut-off the link because it messes with the overflow of the whole div + + if (result.page !== "") { + display_link += ` (${result.page})`; + } + + let textindex = new RegExp(`\\b${querystring}\\b`, "i").exec(result.text); + let text = + textindex !== null + ? result.text.slice( + Math.max(textindex.index - 100, 0), + Math.min( + textindex.index + querystring.length + 100, + result.text.length + ) + ) + : ""; // cut-off text before and after from the match + + let display_result = text.length + ? "..." + + text.replace( + new RegExp(`\\b${querystring}\\b`, "i"), // For first occurrence + '$&' + ) + + "..." + : ""; // highlights the match + + let in_code = false; + if (!["page", "section"].includes(result.category.toLowerCase())) { + in_code = true; + } + + // We encode the full url to escape some special characters which can lead to broken links + let result_div = ` + +
+
${result.title}
+
${result.category}
+
+

+ ${display_result} +

+
+ ${display_link} +
+
+ ${search_divider} + `; + + return result_div; +} + +/** + * Get selected filters, remake the filter html and lastly update the search modal + */ +function get_filters() { + let ele = $(".search-filters .search-filter-selected").get(); + filter_results = ele.map((x) => $(x).text().toLowerCase()); + modal_filters = make_modal_body_filters(filters, filter_results); + update_search(filter_results); +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Modal settings dialog +$(document).ready(function () { + var settings = $("#documenter-settings"); + $("#documenter-settings-button").click(function () { + settings.toggleClass("is-active"); + }); + // Close the dialog if X is clicked + $("#documenter-settings button.delete").click(function () { + settings.removeClass("is-active"); + }); + // Close dialog if ESC is pressed + $(document).keyup(function (e) { + if (e.keyCode == 27) settings.removeClass("is-active"); + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +let search_modal_header = ` + +`; + +let initial_search_body = ` +
Type something to get started!
+`; + +let search_modal_footer = ` + +`; + +$(document.body).append( + ` + + ` +); + +document.querySelector(".docs-search-query").addEventListener("click", () => { + openModal(); +}); + +document.querySelector(".close-search-modal").addEventListener("click", () => { + closeModal(); +}); + +$(document).on("click", ".search-result-link", function () { + closeModal(); +}); + +document.addEventListener("keydown", (event) => { + if ((event.ctrlKey || event.metaKey) && event.key === "/") { + openModal(); + } else if (event.key === "Escape") { + closeModal(); + } + + return false; +}); + +// Functions to open and close a modal +function openModal() { + let searchModal = document.querySelector("#search-modal"); + + searchModal.classList.add("is-active"); + document.querySelector(".documenter-search-input").focus(); +} + +function closeModal() { + let searchModal = document.querySelector("#search-modal"); + let initial_search_body = ` +
Type something to get started!
+ `; + + searchModal.classList.remove("is-active"); + document.querySelector(".documenter-search-input").blur(); + + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".documenter-search-input").val(""); + $(".search-modal-card-body").html(initial_search_body); +} + +document + .querySelector("#search-modal .modal-background") + .addEventListener("click", () => { + closeModal(); + }); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Manages the showing and hiding of the sidebar. +$(document).ready(function () { + var sidebar = $("#documenter > .docs-sidebar"); + var sidebar_button = $("#documenter-sidebar-button"); + sidebar_button.click(function (ev) { + ev.preventDefault(); + sidebar.toggleClass("visible"); + if (sidebar.hasClass("visible")) { + // Makes sure that the current menu item is visible in the sidebar. + $("#documenter .docs-menu a.is-active").focus(); + } + }); + $("#documenter > .docs-main").bind("click", function (ev) { + if ($(ev.target).is(sidebar_button)) { + return; + } + if (sidebar.hasClass("visible")) { + sidebar.removeClass("visible"); + } + }); +}); + +// Resizes the package name / sitename in the sidebar if it is too wide. +// Inspired by: https://github.com/davatron5000/FitText.js +$(document).ready(function () { + e = $("#documenter .docs-autofit"); + function resize() { + var L = parseInt(e.css("max-width"), 10); + var L0 = e.width(); + if (L0 > L) { + var h0 = parseInt(e.css("font-size"), 10); + e.css("font-size", (L * h0) / L0); + // TODO: make sure it survives resizes? + } + } + // call once and then register events + resize(); + $(window).resize(resize); + $(window).on("orientationchange", resize); +}); + +// Scroll the navigation bar to the currently selected menu item +$(document).ready(function () { + var sidebar = $("#documenter .docs-menu").get(0); + var active = $("#documenter .docs-menu .is-active").get(0); + if (typeof active !== "undefined") { + sidebar.scrollTop = active.offsetTop - sidebar.offsetTop - 15; + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Theme picker setup +$(document).ready(function () { + // onchange callback + $("#documenter-themepicker").change(function themepick_callback(ev) { + var themename = $("#documenter-themepicker option:selected").attr("value"); + if (themename === "auto") { + // set_theme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + window.localStorage.removeItem("documenter-theme"); + } else { + // set_theme(themename); + window.localStorage.setItem("documenter-theme", themename); + } + // We re-use the global function from themeswap.js to actually do the swapping. + set_theme_from_local_storage(); + }); + + // Make sure that the themepicker displays the correct theme when the theme is retrieved + // from localStorage + if (typeof window.localStorage !== "undefined") { + var theme = window.localStorage.getItem("documenter-theme"); + if (theme !== null) { + $("#documenter-themepicker option").each(function (i, e) { + e.selected = e.value === theme; + }); + } + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// update the version selector with info from the siteinfo.js and ../versions.js files +$(document).ready(function () { + // If the version selector is disabled with DOCUMENTER_VERSION_SELECTOR_DISABLED in the + // siteinfo.js file, we just return immediately and not display the version selector. + if ( + typeof DOCUMENTER_VERSION_SELECTOR_DISABLED === "boolean" && + DOCUMENTER_VERSION_SELECTOR_DISABLED + ) { + return; + } + + var version_selector = $("#documenter .docs-version-selector"); + var version_selector_select = $("#documenter .docs-version-selector select"); + + version_selector_select.change(function (x) { + target_href = version_selector_select + .children("option:selected") + .get(0).value; + window.location.href = target_href; + }); + + // add the current version to the selector based on siteinfo.js, but only if the selector is empty + if ( + typeof DOCUMENTER_CURRENT_VERSION !== "undefined" && + $("#version-selector > option").length == 0 + ) { + var option = $( + "" + ); + version_selector_select.append(option); + } + + if (typeof DOC_VERSIONS !== "undefined") { + var existing_versions = version_selector_select.children("option"); + var existing_versions_texts = existing_versions.map(function (i, x) { + return x.text; + }); + DOC_VERSIONS.forEach(function (each) { + var version_url = documenterBaseURL + "/../" + each + "/"; + var existing_id = $.inArray(each, existing_versions_texts); + // if not already in the version selector, add it as a new option, + // otherwise update the old option with the URL and enable it + if (existing_id == -1) { + var option = $( + "" + ); + version_selector_select.append(option); + } else { + var option = existing_versions[existing_id]; + option.value = version_url; + option.disabled = false; + } + }); + } + + // only show the version selector if the selector has been populated + if (version_selector_select.children("option").length > 0) { + version_selector.toggleClass("visible"); + } +}); + +}) diff --git a/previews/PR3536/assets/extra_styles.css b/previews/PR3536/assets/extra_styles.css new file mode 100644 index 00000000000..1f7df51cf0f --- /dev/null +++ b/previews/PR3536/assets/extra_styles.css @@ -0,0 +1,4 @@ +.display-light-only {display: block;} +.display-dark-only {display: none;} +.theme--documenter-dark .display-light-only {display: none;} +.theme--documenter-dark .display-dark-only {display: block;} diff --git a/previews/PR3536/assets/full_sudoku.png b/previews/PR3536/assets/full_sudoku.png new file mode 100644 index 00000000000..f3f84a9c006 Binary files /dev/null and b/previews/PR3536/assets/full_sudoku.png differ diff --git a/previews/PR3536/assets/logo-dark-with-text-background.svg b/previews/PR3536/assets/logo-dark-with-text-background.svg new file mode 100644 index 00000000000..eb4cf0b3249 --- /dev/null +++ b/previews/PR3536/assets/logo-dark-with-text-background.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + JuMP project + + + Logo for JuMP + + Logo for JuMP, an algebraic modeling language and a collection of supporting packages for mathematical optimization embedded in the Julia programming language + August 2018 + + + + + + + + + + + + diff --git a/previews/PR3536/assets/logo-dark-with-text.svg b/previews/PR3536/assets/logo-dark-with-text.svg new file mode 100644 index 00000000000..1031633f5d4 --- /dev/null +++ b/previews/PR3536/assets/logo-dark-with-text.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + JuMP project + + + Logo for JuMP + + Logo for JuMP, an algebraic modeling language and a collection of supporting packages for mathematical optimization embedded in the Julia programming language + August 2018 + + + + + + + + + + + + diff --git a/previews/PR3536/assets/logo-dark-without-text.svg b/previews/PR3536/assets/logo-dark-without-text.svg new file mode 100644 index 00000000000..a6221d61d43 --- /dev/null +++ b/previews/PR3536/assets/logo-dark-without-text.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + JuMP project + + + Logo for JuMP + + Logo for JuMP, an algebraic modeling language and a collection of supporting packages for mathematical optimization embedded in the Julia programming language + August 2018 + + + + + + + + + + + + diff --git a/previews/PR3536/assets/logo-dark.svg b/previews/PR3536/assets/logo-dark.svg new file mode 100644 index 00000000000..4014762ca62 --- /dev/null +++ b/previews/PR3536/assets/logo-dark.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + JuMP project + + + Logo for JuMP + + Logo for JuMP, an algebraic modeling language and a collection of supporting packages for mathematical optimization embedded in the Julia programming language + August 2018 + + + + + + + + + + + + diff --git a/previews/PR3536/assets/logo-with-text-background.svg b/previews/PR3536/assets/logo-with-text-background.svg new file mode 100644 index 00000000000..807e338e91c --- /dev/null +++ b/previews/PR3536/assets/logo-with-text-background.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + JuMP project + + + Logo for JuMP + + Logo for JuMP, an algebraic modeling language and a collection of supporting packages for mathematical optimization embedded in the Julia programming language + August 2018 + + + + + + + + + + + + diff --git a/previews/PR3536/assets/logo-with-text.pdf b/previews/PR3536/assets/logo-with-text.pdf new file mode 100644 index 00000000000..aacf7bac495 Binary files /dev/null and b/previews/PR3536/assets/logo-with-text.pdf differ diff --git a/previews/PR3536/assets/logo-with-text.svg b/previews/PR3536/assets/logo-with-text.svg new file mode 100644 index 00000000000..60ebbc89883 --- /dev/null +++ b/previews/PR3536/assets/logo-with-text.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + JuMP project + + + Logo for JuMP + + Logo for JuMP, an algebraic modeling language and a collection of supporting packages for mathematical optimization embedded in the Julia programming language + August 2018 + + + + + + + + + + + + diff --git a/previews/PR3536/assets/logo-without-text.svg b/previews/PR3536/assets/logo-without-text.svg new file mode 100644 index 00000000000..8bf39aa46ce --- /dev/null +++ b/previews/PR3536/assets/logo-without-text.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + JuMP project + + + Logo for JuMP + + Logo for JuMP, an algebraic modeling language and a collection of supporting packages for mathematical optimization embedded in the Julia programming language + August 2018 + + + + + + + + + + + + diff --git a/previews/PR3536/assets/logo.svg b/previews/PR3536/assets/logo.svg new file mode 100644 index 00000000000..31fa5edeeab --- /dev/null +++ b/previews/PR3536/assets/logo.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + JuMP project + + + Logo for JuMP + + Logo for JuMP, an algebraic modeling language and a collection of supporting packages for mathematical optimization embedded in the Julia programming language + August 2018 + + + + + + + + + + + + diff --git a/previews/PR3536/assets/logo_constructor.jl b/previews/PR3536/assets/logo_constructor.jl new file mode 100644 index 00000000000..50ec1a82fbf --- /dev/null +++ b/previews/PR3536/assets/logo_constructor.jl @@ -0,0 +1,395 @@ +# This script rebuilds the JuMP logos. Install Luxor using +# +# import Pkg; Pkg.pkg"add Luxor@3" + +using Luxor + +""" + _logo_no_text(; color::String) + +Creates the three Julia dots with the constraints above. +""" +function _logo_no_text(; color::String) + setcolor(color) + setline(8) + setlinecap("round") + line(Point(20, 68), Point(120, 28), :stroke) + line(Point(4, 120), Point(120, 4), :stroke) + pts = box(Point(86, 84), 40, 40; vertices = true) + @layer begin + setcolor(Luxor.julia_red) + circle(pts[1], 17, :fill) + setcolor(Luxor.julia_green) + circle(pts[3], 17, :fill) + setcolor(Luxor.julia_purple) + circle(pts[4], 17, :fill) + end + return +end + +""" + _jump_text_outlines(; color::String) + +Creates the text part of the JuMP logo with the top-aligned "u". +""" +function _jump_text_outlines(; color::String) + setcolor(color) + @layer begin + # "J" + newsubpath() + move(Point(34.018, 6.789)) + line(Point(46.768, 6.789)) + line(Point(46.768, 49.619)) + curve( + Point(46.768, 53.479), + Point(47.058, 57.329), + Point(46.118, 61.119), + ) + curve( + Point(45.022, 65.714), + Point(42.273, 69.748), + Point(38.398, 72.449), + ) + curve( + Point(35.706, 74.325), + Point(32.589, 75.501), + Point(29.328, 75.869), + ) + curve(Point(18.823, 77.054), Point(8.995, 70.07), Point(6.658, 59.759)) + line(Point(19.038, 56.599)) + curve( + Point(19.632, 59.365), + Point(21.576, 61.655), + Point(24.208, 62.689), + ) + curve( + Point(25.38, 63.124), + Point(26.637, 63.278), + Point(27.878, 63.139), + ) + curve( + Point(29.023, 63.018), + Point(30.122, 62.62), + Point(31.078, 61.979), + ) + curve( + Point(33.848, 60.049), + Point(34.018, 56.639), + Point(34.018, 53.639), + ) + closepath() + # "U" + move(Point(60.478, 6.789)) + line(Point(72.998, 6.789)) + line(Point(72.998, 30.599)) + curve( + Point(72.998, 35.209), + Point(73.308, 38.439), + Point(73.948, 40.229), + ) + curve( + Point(74.504, 41.912), + Point(75.575, 43.378), + Point(77.008, 44.419), + ) + curve( + Point(78.538, 45.459), + Point(80.359, 45.988), + Point(82.208, 45.929), + ) + curve( + Point(84.063, 45.982), + Point(85.891, 45.469), + Point(87.448, 44.459), + ) + curve( + Point(88.948, 43.389), + Point(90.07, 41.869), + Point(90.648, 40.119), + ) + curve( + Point(91.155, 38.699), + Point(91.408, 35.659), + Point(91.408, 30.999), + ) + line(Point(91.408, 6.819)) + line(Point(103.838, 6.819)) + line(Point(103.838, 27.739)) + curve( + Point(103.838, 36.349), + Point(103.148, 42.259), + Point(101.838, 45.429), + ) + curve( + Point(100.362, 49.077), + Point(97.799, 52.185), + Point(94.498, 54.329), + ) + curve( + Point(91.258, 56.403), + Point(87.145, 57.439), + Point(82.158, 57.439), + ) + curve( + Point(76.738, 57.439), + Point(72.362, 56.233), + Point(69.028, 53.819), + ) + curve( + Point(65.63, 51.311), + Point(63.149, 47.755), + Point(61.968, 43.699), + ) + curve( + Point(61.028, 40.699), + Point(60.518, 35.259), + Point(60.518, 27.379), + ) + closepath() + # "M" + move(Point(175.278, 73.459)) + line(Point(187.868, 73.459)) + line(Point(187.868, 6.779)) + line(Point(173.468, 6.779)) + line(Point(152.578, 35.449)) + line(Point(131.688, 6.779)) + line(Point(117.188, 6.779)) + line(Point(117.188, 73.459)) + line(Point(129.778, 73.459)) + line(Point(129.778, 24.969)) + line(Point(151.048, 54.029)) + line(Point(153.528, 54.029)) + line(Point(175.278, 25.059)) + closepath() + # "P" + move(Point(199.348, 6.789)) + line(Point(212.808, 6.789)) + curve( + Point(220.141, 6.789), + Point(225.395, 7.456), + Point(228.568, 8.789), + ) + curve( + Point(231.747, 10.093), + Point(234.423, 12.387), + Point(236.198, 15.329), + ) + curve( + Point(238.123, 18.596), + Point(239.09, 22.339), + Point(238.988, 26.129), + ) + curve( + Point(239.137, 30.302), + Point(237.848, 34.402), + Point(235.338, 37.739), + ) + curve( + Point(232.823, 40.92), + Point(229.346, 43.204), + Point(225.428, 44.249), + ) + curve( + Point(222.988, 44.916), + Point(218.531, 45.249), + Point(212.058, 45.249), + ) + line(Point(212.058, 73.509)) + line(Point(199.348, 73.509)) + closepath() + newsubpath() + move(Point(212.058, 32.869)) + line(Point(216.058, 32.869)) + curve( + Point(218.29, 32.974), + Point(220.524, 32.745), + Point(222.688, 32.189), + ) + curve( + Point(223.879, 31.776), + Point(224.904, 30.986), + Point(225.608, 29.939), + ) + curve( + Point(226.346, 28.816), + Point(226.72, 27.493), + Point(226.678, 26.149), + ) + curve( + Point(226.815, 23.864), + Point(225.653, 21.687), + Point(223.678, 20.529), + ) + curve( + Point(222.258, 19.659), + Point(219.568, 19.249), + Point(215.628, 19.249), + ) + line(Point(212.058, 19.249)) + closepath() + end + fillpath() + return +end + +""" + logo_with_text(; + filename::String, + color::String, + background::String = "", + verbose::Bool = false, + ) + +Create the JuMP logo with the text on the right-hand side. + +If `verbose`, print the margin lines. +""" +function logo_with_text(; + filename::String, + color::String, + background::String = "", + verbose::Bool = false, +) + width, height, margin = 415, 150, 10 + Drawing(width, height, filename) + if !isempty(background) + @layer begin + translate(O) + translate(width / 2, height / 2) + setcolor(background) + box(O, width - 1, height - 1, 10, :fill) + end + end + @layer begin + translate(O) + if verbose + line(Point(0, margin), Point(width, margin), :stroke) + line(Point(margin, 0), Point(margin, height), :stroke) + w = width - margin + line(Point(w, 0), Point(w, height), :stroke) + h = height - margin + line(Point(0, h), Point(width, h), :stroke) + end + translate(10, 13) + _logo_no_text(; color = color) + translate(130, 40) + scale(1.1) + _jump_text_outlines(; color = color) + end + finish() + add_license(filename) + return +end + +""" + logo_square(; filename::String, color::String, verbose::Bool = false) + +Create the JuMP logo with the text vertically beneath the pictogram. + +If `verbose`, print the margin lines. +""" +function logo_square(; filename::String, color::String, verbose::Bool = false) + N, margin, w = 200, 10, 110 + Drawing(N, N, filename) + @layer begin + translate(O) + if verbose + line(Point(0, margin), Point(N, margin), :stroke) + line(Point(margin, 0), Point(margin, N), :stroke) + line(Point(N - margin, 0), Point(N - margin, N), :stroke) + line(Point(0, N - margin), Point(N, N - margin), :stroke) + lhs = (N - w) / 2 + line(Point(lhs, 0), Point(lhs, N), :stroke) + line(Point(N - lhs, 0), Point(N - lhs, N), :stroke) + end + translate((N - w) / 2, margin) + scale(0.9) + _logo_no_text(; color = color) + translate(-50, 135) + scale(0.9) + _jump_text_outlines(; color = color) + end + finish() + add_license(filename) + return +end + +""" + logo_no_text(; filename::String, color::String) + +Create the JuMP logo without the "JuMP" text. +""" +function logo_no_text(; filename::String, color::String) + Drawing(150, 150, filename) + @layer begin + translate(O) + translate(10, 10) + _logo_no_text(; color = color) + end + finish() + add_license(filename) + return +end + +function add_license(filename) + header = """ + + + + + + JuMP project + + + Logo for JuMP + + Logo for JuMP, an algebraic modeling language and a collection of supporting packages for mathematical optimization embedded in the Julia programming language + August 2018 + + + + + + + + + + + + """ + file = read(filename, String) + file = replace(file, " header) + file = replace(file, "" => footer) + write(filename, file) + return +end + +color(prefix) = isempty(prefix) ? "#333" : "#ffe" +dark_color(prefix) = isempty(prefix) ? "#ffe" : "#333" + +for prefix in ("", "-dark") + path = joinpath(@__DIR__, "logo$(prefix)") + logo_with_text(; filename = "$(path)-with-text.svg", color = color(prefix)) + logo_with_text(; + filename = "$(path)-with-text-background.svg", + color = color(prefix), + background = dark_color(prefix), + ) + logo_square(; filename = "$(path).svg", color = color(prefix)) + logo_no_text(; filename = "$(path)-without-text.svg", color = color(prefix)) +end diff --git a/previews/PR3536/assets/n_queens4.png b/previews/PR3536/assets/n_queens4.png new file mode 100644 index 00000000000..f86590cf5ed Binary files /dev/null and b/previews/PR3536/assets/n_queens4.png differ diff --git a/previews/PR3536/assets/numfocus-logo.png b/previews/PR3536/assets/numfocus-logo.png new file mode 100644 index 00000000000..cbc35030695 Binary files /dev/null and b/previews/PR3536/assets/numfocus-logo.png differ diff --git a/previews/PR3536/assets/partial_sudoku.png b/previews/PR3536/assets/partial_sudoku.png new file mode 100644 index 00000000000..4866e46def6 Binary files /dev/null and b/previews/PR3536/assets/partial_sudoku.png differ diff --git a/previews/PR3536/assets/power_systems.png b/previews/PR3536/assets/power_systems.png new file mode 100644 index 00000000000..b84d9249552 Binary files /dev/null and b/previews/PR3536/assets/power_systems.png differ diff --git a/previews/PR3536/assets/space_shuttle.png b/previews/PR3536/assets/space_shuttle.png new file mode 100644 index 00000000000..dca29fb2ee8 Binary files /dev/null and b/previews/PR3536/assets/space_shuttle.png differ diff --git a/previews/PR3536/assets/themes/documenter-dark.css b/previews/PR3536/assets/themes/documenter-dark.css new file mode 100644 index 00000000000..691b83abda7 --- /dev/null +++ b/previews/PR3536/assets/themes/documenter-dark.css @@ -0,0 +1,7 @@ +html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus,html.theme--documenter-dark .pagination-ellipsis:focus,html.theme--documenter-dark .file-cta:focus,html.theme--documenter-dark .file-name:focus,html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .button:focus,html.theme--documenter-dark .is-focused.pagination-previous,html.theme--documenter-dark .is-focused.pagination-next,html.theme--documenter-dark .is-focused.pagination-link,html.theme--documenter-dark .is-focused.pagination-ellipsis,html.theme--documenter-dark .is-focused.file-cta,html.theme--documenter-dark .is-focused.file-name,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-focused.button,html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active,html.theme--documenter-dark .pagination-ellipsis:active,html.theme--documenter-dark .file-cta:active,html.theme--documenter-dark .file-name:active,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .button:active,html.theme--documenter-dark .is-active.pagination-previous,html.theme--documenter-dark .is-active.pagination-next,html.theme--documenter-dark .is-active.pagination-link,html.theme--documenter-dark .is-active.pagination-ellipsis,html.theme--documenter-dark .is-active.file-cta,html.theme--documenter-dark .is-active.file-name,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .is-active.button{outline:none}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-ellipsis[disabled],html.theme--documenter-dark .file-cta[disabled],html.theme--documenter-dark .file-name[disabled],html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--documenter-dark .pagination-next,html.theme--documenter-dark fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--documenter-dark .pagination-link,html.theme--documenter-dark fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--documenter-dark .file-cta,html.theme--documenter-dark fieldset[disabled] .file-cta,fieldset[disabled] html.theme--documenter-dark .file-name,html.theme--documenter-dark fieldset[disabled] .file-name,fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark fieldset[disabled] .select select,html.theme--documenter-dark .select fieldset[disabled] select,html.theme--documenter-dark fieldset[disabled] .textarea,html.theme--documenter-dark fieldset[disabled] .input,html.theme--documenter-dark fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--documenter-dark .button,html.theme--documenter-dark fieldset[disabled] .button{cursor:not-allowed}html.theme--documenter-dark .tabs,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .breadcrumb,html.theme--documenter-dark .file,html.theme--documenter-dark .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after,html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--documenter-dark .admonition:not(:last-child),html.theme--documenter-dark .tabs:not(:last-child),html.theme--documenter-dark .pagination:not(:last-child),html.theme--documenter-dark .message:not(:last-child),html.theme--documenter-dark .level:not(:last-child),html.theme--documenter-dark .breadcrumb:not(:last-child),html.theme--documenter-dark .block:not(:last-child),html.theme--documenter-dark .title:not(:last-child),html.theme--documenter-dark .subtitle:not(:last-child),html.theme--documenter-dark .table-container:not(:last-child),html.theme--documenter-dark .table:not(:last-child),html.theme--documenter-dark .progress:not(:last-child),html.theme--documenter-dark .notification:not(:last-child),html.theme--documenter-dark .content:not(:last-child),html.theme--documenter-dark .box:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .modal-close,html.theme--documenter-dark .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before,html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before{height:2px;width:50%}html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{height:50%;width:2px}html.theme--documenter-dark .modal-close:hover,html.theme--documenter-dark .delete:hover,html.theme--documenter-dark .modal-close:focus,html.theme--documenter-dark .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--documenter-dark .modal-close:active,html.theme--documenter-dark .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--documenter-dark .is-small.modal-close,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--documenter-dark .is-small.delete,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--documenter-dark .is-medium.modal-close,html.theme--documenter-dark .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--documenter-dark .is-large.modal-close,html.theme--documenter-dark .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--documenter-dark .control.is-loading::after,html.theme--documenter-dark .select.is-loading::after,html.theme--documenter-dark .loader,html.theme--documenter-dark .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdee0;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--documenter-dark .hero-video,html.theme--documenter-dark .modal-background,html.theme--documenter-dark .modal,html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--documenter-dark .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#ecf0f1 !important}a.has-text-light:hover,a.has-text-light:focus{color:#cfd9db !important}.has-background-light{background-color:#ecf0f1 !important}.has-text-dark{color:#282f2f !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#111414 !important}.has-background-dark{background-color:#282f2f !important}.has-text-primary{color:#375a7f !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#28415b !important}.has-background-primary{background-color:#375a7f !important}.has-text-primary-light{color:#f1f5f9 !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#cddbe9 !important}.has-background-primary-light{background-color:#f1f5f9 !important}.has-text-primary-dark{color:#4d7eb2 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#7198c1 !important}.has-background-primary-dark{background-color:#4d7eb2 !important}.has-text-link{color:#1abc9c !important}a.has-text-link:hover,a.has-text-link:focus{color:#148f77 !important}.has-background-link{background-color:#1abc9c !important}.has-text-link-light{color:#edfdf9 !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c0f6ec !important}.has-background-link-light{background-color:#edfdf9 !important}.has-text-link-dark{color:#15987e !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#1bc5a4 !important}.has-background-link-dark{background-color:#15987e !important}.has-text-info{color:#024c7d !important}a.has-text-info:hover,a.has-text-info:focus{color:#012d4b !important}.has-background-info{background-color:#024c7d !important}.has-text-info-light{color:#ebf7ff !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#b9e2fe !important}.has-background-info-light{background-color:#ebf7ff !important}.has-text-info-dark{color:#0e9dfb !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#40b1fc !important}.has-background-info-dark{background-color:#0e9dfb !important}.has-text-success{color:#008438 !important}a.has-text-success:hover,a.has-text-success:focus{color:#005122 !important}.has-background-success{background-color:#008438 !important}.has-text-success-light{color:#ebfff3 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#b8ffd6 !important}.has-background-success-light{background-color:#ebfff3 !important}.has-text-success-dark{color:#00eb64 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#1fff7e !important}.has-background-success-dark{background-color:#00eb64 !important}.has-text-warning{color:#ad8100 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#7a5b00 !important}.has-background-warning{background-color:#ad8100 !important}.has-text-warning-light{color:#fffaeb !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#ffedb8 !important}.has-background-warning-light{background-color:#fffaeb !important}.has-text-warning-dark{color:#d19c00 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#ffbf05 !important}.has-background-warning-dark{background-color:#d19c00 !important}.has-text-danger{color:#9e1b0d !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#6f1309 !important}.has-background-danger{background-color:#9e1b0d !important}.has-text-danger-light{color:#fdeeec !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#fac3bd !important}.has-background-danger-light{background-color:#fdeeec !important}.has-text-danger-dark{color:#ec311d !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#f05c4c !important}.has-background-danger-dark{background-color:#ec311d !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#282f2f !important}.has-background-grey-darker{background-color:#282f2f !important}.has-text-grey-dark{color:#343c3d !important}.has-background-grey-dark{background-color:#343c3d !important}.has-text-grey{color:#5e6d6f !important}.has-background-grey{background-color:#5e6d6f !important}.has-text-grey-light{color:#8c9b9d !important}.has-background-grey-light{background-color:#8c9b9d !important}.has-text-grey-lighter{color:#dbdee0 !important}.has-background-grey-lighter{background-color:#dbdee0 !important}.has-text-white-ter{color:#ecf0f1 !important}.has-background-white-ter{background-color:#ecf0f1 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--documenter-dark .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--documenter-dark{/*! + Theme: a11y-dark + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/}html.theme--documenter-dark html{background-color:#1f2424;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark article,html.theme--documenter-dark aside,html.theme--documenter-dark figure,html.theme--documenter-dark footer,html.theme--documenter-dark header,html.theme--documenter-dark hgroup,html.theme--documenter-dark section{display:block}html.theme--documenter-dark body,html.theme--documenter-dark button,html.theme--documenter-dark input,html.theme--documenter-dark optgroup,html.theme--documenter-dark select,html.theme--documenter-dark textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--documenter-dark code,html.theme--documenter-dark pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark body{color:#fff;font-size:1em;font-weight:400;line-height:1.5}html.theme--documenter-dark a{color:#1abc9c;cursor:pointer;text-decoration:none}html.theme--documenter-dark a strong{color:currentColor}html.theme--documenter-dark a:hover{color:#1dd2af}html.theme--documenter-dark code{background-color:rgba(255,255,255,0.05);color:#ececec;font-size:.875em;font-weight:normal;padding:.1em}html.theme--documenter-dark hr{background-color:#282f2f;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--documenter-dark img{height:auto;max-width:100%}html.theme--documenter-dark input[type="checkbox"],html.theme--documenter-dark input[type="radio"]{vertical-align:baseline}html.theme--documenter-dark small{font-size:.875em}html.theme--documenter-dark span{font-style:inherit;font-weight:inherit}html.theme--documenter-dark strong{color:#f2f2f2;font-weight:700}html.theme--documenter-dark fieldset{border:none}html.theme--documenter-dark pre{-webkit-overflow-scrolling:touch;background-color:#282f2f;color:#fff;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--documenter-dark pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--documenter-dark table td,html.theme--documenter-dark table th{vertical-align:top}html.theme--documenter-dark table td:not([align]),html.theme--documenter-dark table th:not([align]){text-align:inherit}html.theme--documenter-dark table th{color:#f2f2f2}html.theme--documenter-dark .box{background-color:#343c3d;border-radius:8px;box-shadow:none;color:#fff;display:block;padding:1.25rem}html.theme--documenter-dark a.box:hover,html.theme--documenter-dark a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #1abc9c}html.theme--documenter-dark a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #1abc9c}html.theme--documenter-dark .button{background-color:#282f2f;border-color:#4c5759;border-width:1px;color:#375a7f;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--documenter-dark .button strong{color:inherit}html.theme--documenter-dark .button .icon,html.theme--documenter-dark .button .icon.is-small,html.theme--documenter-dark .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--documenter-dark #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--documenter-dark .button .icon.is-medium,html.theme--documenter-dark .button .icon.is-large{height:1.5em;width:1.5em}html.theme--documenter-dark .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--documenter-dark .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button:hover,html.theme--documenter-dark .button.is-hovered{border-color:#8c9b9d;color:#f2f2f2}html.theme--documenter-dark .button:focus,html.theme--documenter-dark .button.is-focused{border-color:#8c9b9d;color:#17a689}html.theme--documenter-dark .button:focus:not(:active),html.theme--documenter-dark .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button:active,html.theme--documenter-dark .button.is-active{border-color:#343c3d;color:#f2f2f2}html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;color:#fff;text-decoration:underline}html.theme--documenter-dark .button.is-text:hover,html.theme--documenter-dark .button.is-text.is-hovered,html.theme--documenter-dark .button.is-text:focus,html.theme--documenter-dark .button.is-text.is-focused{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .button.is-text:active,html.theme--documenter-dark .button.is-text.is-active{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .button.is-text[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--documenter-dark .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#1abc9c;text-decoration:none}html.theme--documenter-dark .button.is-ghost:hover,html.theme--documenter-dark .button.is-ghost.is-hovered{color:#1abc9c;text-decoration:underline}html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:hover,html.theme--documenter-dark .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus,html.theme--documenter-dark .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus:not(:active),html.theme--documenter-dark .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--documenter-dark .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-white.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:hover,html.theme--documenter-dark .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus,html.theme--documenter-dark .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus:not(:active),html.theme--documenter-dark .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:hover,html.theme--documenter-dark .button.is-light.is-hovered{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus,html.theme--documenter-dark .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus:not(:active),html.theme--documenter-dark .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light.is-active{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:#ecf0f1;box-shadow:none}html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-outlined.is-focused{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-dark,html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover,html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus:not(:active),html.theme--documenter-dark .content kbd.button:focus:not(:active),html.theme--documenter-dark .button.is-dark.is-focused:not(:active),html.theme--documenter-dark .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark[disabled],html.theme--documenter-dark .content kbd.button[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark,fieldset[disabled] html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:#282f2f;box-shadow:none}html.theme--documenter-dark .button.is-dark.is-inverted,html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted:hover,html.theme--documenter-dark .content kbd.button.is-inverted:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-dark.is-inverted[disabled],html.theme--documenter-dark .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-loading::after,html.theme--documenter-dark .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined,html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-outlined.is-focused{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus:not(:active),html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--documenter-dark .button.is-primary.is-focused:not(:active),html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark .docstring>section>a.button.is-active.docs-sourcelink{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary[disabled],html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;box-shadow:none}html.theme--documenter-dark .button.is-primary.is-inverted,html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted:hover,html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--documenter-dark .button.is-primary.is-inverted[disabled],html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-loading::after,html.theme--documenter-dark .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-outlined:hover,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-outlined.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-outlined:focus,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-outlined.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:hover::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:focus::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined[disabled],html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-outlined,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:hover,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:focus,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined[disabled],html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary.is-light,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:hover,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-light.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e8eef5;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:active,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-light.is-active,html.theme--documenter-dark .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#dfe8f1;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:hover,html.theme--documenter-dark .button.is-link.is-hovered{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus,html.theme--documenter-dark .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus:not(:active),html.theme--documenter-dark .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link.is-active{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:#1abc9c;box-shadow:none}html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-outlined.is-focused{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:hover,html.theme--documenter-dark .button.is-link.is-light.is-hovered{background-color:#e2fbf6;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:active,html.theme--documenter-dark .button.is-link.is-light.is-active{background-color:#d7f9f3;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-info{background-color:#024c7d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:hover,html.theme--documenter-dark .button.is-info.is-hovered{background-color:#024470;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus,html.theme--documenter-dark .button.is-info.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus:not(:active),html.theme--documenter-dark .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(2,76,125,0.25)}html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info.is-active{background-color:#023d64;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info{background-color:#024c7d;border-color:#024c7d;box-shadow:none}html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;color:#024c7d}html.theme--documenter-dark .button.is-info.is-inverted:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#024c7d}html.theme--documenter-dark .button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#024c7d;color:#024c7d}html.theme--documenter-dark .button.is-info.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-outlined.is-focused{background-color:#024c7d;border-color:#024c7d;color:#fff}html.theme--documenter-dark .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #024c7d #024c7d !important}html.theme--documenter-dark .button.is-info.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#024c7d;box-shadow:none;color:#024c7d}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#024c7d}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #024c7d #024c7d !important}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-info.is-light{background-color:#ebf7ff;color:#0e9dfb}html.theme--documenter-dark .button.is-info.is-light:hover,html.theme--documenter-dark .button.is-info.is-light.is-hovered{background-color:#def2fe;border-color:transparent;color:#0e9dfb}html.theme--documenter-dark .button.is-info.is-light:active,html.theme--documenter-dark .button.is-info.is-light.is-active{background-color:#d2edfe;border-color:transparent;color:#0e9dfb}html.theme--documenter-dark .button.is-success{background-color:#008438;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:hover,html.theme--documenter-dark .button.is-success.is-hovered{background-color:#073;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus,html.theme--documenter-dark .button.is-success.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus:not(:active),html.theme--documenter-dark .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(0,132,56,0.25)}html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success.is-active{background-color:#006b2d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success{background-color:#008438;border-color:#008438;box-shadow:none}html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;color:#008438}html.theme--documenter-dark .button.is-success.is-inverted:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#008438}html.theme--documenter-dark .button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#008438;color:#008438}html.theme--documenter-dark .button.is-success.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-outlined.is-focused{background-color:#008438;border-color:#008438;color:#fff}html.theme--documenter-dark .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #008438 #008438 !important}html.theme--documenter-dark .button.is-success.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#008438;box-shadow:none;color:#008438}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#008438}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #008438 #008438 !important}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-success.is-light{background-color:#ebfff3;color:#00eb64}html.theme--documenter-dark .button.is-success.is-light:hover,html.theme--documenter-dark .button.is-success.is-light.is-hovered{background-color:#deffec;border-color:transparent;color:#00eb64}html.theme--documenter-dark .button.is-success.is-light:active,html.theme--documenter-dark .button.is-success.is-light.is-active{background-color:#d1ffe5;border-color:transparent;color:#00eb64}html.theme--documenter-dark .button.is-warning{background-color:#ad8100;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-warning:hover,html.theme--documenter-dark .button.is-warning.is-hovered{background-color:#a07700;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-warning:focus,html.theme--documenter-dark .button.is-warning.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-warning:focus:not(:active),html.theme--documenter-dark .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(173,129,0,0.25)}html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning.is-active{background-color:#946e00;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-warning[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning{background-color:#ad8100;border-color:#ad8100;box-shadow:none}html.theme--documenter-dark .button.is-warning.is-inverted{background-color:#fff;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-inverted:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#ad8100;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-outlined.is-focused{background-color:#ad8100;border-color:#ad8100;color:#fff}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ad8100 #ad8100 !important}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#ad8100;box-shadow:none;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-focused{background-color:#fff;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ad8100 #ad8100 !important}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-warning.is-light{background-color:#fffaeb;color:#d19c00}html.theme--documenter-dark .button.is-warning.is-light:hover,html.theme--documenter-dark .button.is-warning.is-light.is-hovered{background-color:#fff7de;border-color:transparent;color:#d19c00}html.theme--documenter-dark .button.is-warning.is-light:active,html.theme--documenter-dark .button.is-warning.is-light.is-active{background-color:#fff3d1;border-color:transparent;color:#d19c00}html.theme--documenter-dark .button.is-danger{background-color:#9e1b0d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:hover,html.theme--documenter-dark .button.is-danger.is-hovered{background-color:#92190c;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus,html.theme--documenter-dark .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus:not(:active),html.theme--documenter-dark .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(158,27,13,0.25)}html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger.is-active{background-color:#86170b;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger{background-color:#9e1b0d;border-color:#9e1b0d;box-shadow:none}html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-inverted:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#9e1b0d;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-outlined.is-focused{background-color:#9e1b0d;border-color:#9e1b0d;color:#fff}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #9e1b0d #9e1b0d !important}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#9e1b0d;box-shadow:none;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #9e1b0d #9e1b0d !important}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-danger.is-light{background-color:#fdeeec;color:#ec311d}html.theme--documenter-dark .button.is-danger.is-light:hover,html.theme--documenter-dark .button.is-danger.is-light.is-hovered{background-color:#fce3e0;border-color:transparent;color:#ec311d}html.theme--documenter-dark .button.is-danger.is-light:active,html.theme--documenter-dark .button.is-danger.is-light.is-active{background-color:#fcd8d5;border-color:transparent;color:#ec311d}html.theme--documenter-dark .button.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--documenter-dark .button.is-small:not(.is-rounded),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--documenter-dark .button.is-normal{font-size:1rem}html.theme--documenter-dark .button.is-medium{font-size:1.25rem}html.theme--documenter-dark .button.is-large{font-size:1.5rem}html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .button{background-color:#8c9b9d;border-color:#5e6d6f;box-shadow:none;opacity:.5}html.theme--documenter-dark .button.is-fullwidth{display:flex;width:100%}html.theme--documenter-dark .button.is-loading{color:transparent !important;pointer-events:none}html.theme--documenter-dark .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--documenter-dark .button.is-static{background-color:#282f2f;border-color:#5e6d6f;color:#dbdee0;box-shadow:none;pointer-events:none}html.theme--documenter-dark .button.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--documenter-dark .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .buttons .button{margin-bottom:0.5rem}html.theme--documenter-dark .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--documenter-dark .buttons:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .buttons:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--documenter-dark .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--documenter-dark .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--documenter-dark .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--documenter-dark .buttons.has-addons .button:last-child{margin-right:0}html.theme--documenter-dark .buttons.has-addons .button:hover,html.theme--documenter-dark .buttons.has-addons .button.is-hovered{z-index:2}html.theme--documenter-dark .buttons.has-addons .button:focus,html.theme--documenter-dark .buttons.has-addons .button.is-focused,html.theme--documenter-dark .buttons.has-addons .button:active,html.theme--documenter-dark .buttons.has-addons .button.is-active,html.theme--documenter-dark .buttons.has-addons .button.is-selected{z-index:3}html.theme--documenter-dark .buttons.has-addons .button:focus:hover,html.theme--documenter-dark .buttons.has-addons .button.is-focused:hover,html.theme--documenter-dark .buttons.has-addons .button:active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--documenter-dark .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .buttons.is-centered{justify-content:center}html.theme--documenter-dark .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--documenter-dark .buttons.is-right{justify-content:flex-end}html.theme--documenter-dark .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:1rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1.25rem}}html.theme--documenter-dark .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--documenter-dark .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--documenter-dark .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--documenter-dark .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--documenter-dark .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--documenter-dark .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--documenter-dark .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--documenter-dark .content li+li{margin-top:0.25em}html.theme--documenter-dark .content p:not(:last-child),html.theme--documenter-dark .content dl:not(:last-child),html.theme--documenter-dark .content ol:not(:last-child),html.theme--documenter-dark .content ul:not(:last-child),html.theme--documenter-dark .content blockquote:not(:last-child),html.theme--documenter-dark .content pre:not(:last-child),html.theme--documenter-dark .content table:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .content h1,html.theme--documenter-dark .content h2,html.theme--documenter-dark .content h3,html.theme--documenter-dark .content h4,html.theme--documenter-dark .content h5,html.theme--documenter-dark .content h6{color:#f2f2f2;font-weight:600;line-height:1.125}html.theme--documenter-dark .content h1{font-size:2em;margin-bottom:0.5em}html.theme--documenter-dark .content h1:not(:first-child){margin-top:1em}html.theme--documenter-dark .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--documenter-dark .content h2:not(:first-child){margin-top:1.1428em}html.theme--documenter-dark .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--documenter-dark .content h3:not(:first-child){margin-top:1.3333em}html.theme--documenter-dark .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--documenter-dark .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--documenter-dark .content h6{font-size:1em;margin-bottom:1em}html.theme--documenter-dark .content blockquote{background-color:#282f2f;border-left:5px solid #5e6d6f;padding:1.25em 1.5em}html.theme--documenter-dark .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ol:not([type]){list-style-type:decimal}html.theme--documenter-dark .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--documenter-dark .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--documenter-dark .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--documenter-dark .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--documenter-dark .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--documenter-dark .content ul ul ul{list-style-type:square}html.theme--documenter-dark .content dd{margin-left:2em}html.theme--documenter-dark .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--documenter-dark .content figure:not(:first-child){margin-top:2em}html.theme--documenter-dark .content figure:not(:last-child){margin-bottom:2em}html.theme--documenter-dark .content figure img{display:inline-block}html.theme--documenter-dark .content figure figcaption{font-style:italic}html.theme--documenter-dark .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--documenter-dark .content sup,html.theme--documenter-dark .content sub{font-size:75%}html.theme--documenter-dark .content table{width:100%}html.theme--documenter-dark .content table td,html.theme--documenter-dark .content table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .content table th{color:#f2f2f2}html.theme--documenter-dark .content table th:not([align]){text-align:inherit}html.theme--documenter-dark .content table thead td,html.theme--documenter-dark .content table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .content table tfoot td,html.theme--documenter-dark .content table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .content table tbody tr:last-child td,html.theme--documenter-dark .content table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .content .tabs li+li{margin-top:0}html.theme--documenter-dark .content.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--documenter-dark .content.is-normal{font-size:1rem}html.theme--documenter-dark .content.is-medium{font-size:1.25rem}html.theme--documenter-dark .content.is-large{font-size:1.5rem}html.theme--documenter-dark .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--documenter-dark .icon.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--documenter-dark .icon.is-medium{height:2rem;width:2rem}html.theme--documenter-dark .icon.is-large{height:3rem;width:3rem}html.theme--documenter-dark .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--documenter-dark .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--documenter-dark .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--documenter-dark div.icon-text{display:flex}html.theme--documenter-dark .image,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--documenter-dark .image img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--documenter-dark .image img.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--documenter-dark .image.is-fullwidth,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--documenter-dark .image.is-square,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--documenter-dark .image.is-1by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--documenter-dark .image.is-5by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--documenter-dark .image.is-4by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--documenter-dark .image.is-3by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--documenter-dark .image.is-5by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--documenter-dark .image.is-16by9,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--documenter-dark .image.is-2by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--documenter-dark .image.is-3by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--documenter-dark .image.is-4by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--documenter-dark .image.is-3by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--documenter-dark .image.is-2by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--documenter-dark .image.is-3by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--documenter-dark .image.is-9by16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--documenter-dark .image.is-1by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--documenter-dark .image.is-1by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--documenter-dark .image.is-16x16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--documenter-dark .image.is-24x24,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--documenter-dark .image.is-32x32,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--documenter-dark .image.is-48x48,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--documenter-dark .image.is-64x64,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--documenter-dark .image.is-96x96,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--documenter-dark .image.is-128x128,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--documenter-dark .notification{background-color:#282f2f;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--documenter-dark .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .notification strong{color:currentColor}html.theme--documenter-dark .notification code,html.theme--documenter-dark .notification pre{background:#fff}html.theme--documenter-dark .notification pre code{background:transparent}html.theme--documenter-dark .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--documenter-dark .notification .title,html.theme--documenter-dark .notification .subtitle,html.theme--documenter-dark .notification .content{color:currentColor}html.theme--documenter-dark .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .notification.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .notification.is-dark,html.theme--documenter-dark .content kbd.notification{background-color:#282f2f;color:#fff}html.theme--documenter-dark .notification.is-primary,html.theme--documenter-dark .docstring>section>a.notification.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .notification.is-primary.is-light,html.theme--documenter-dark .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .notification.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .notification.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .notification.is-info{background-color:#024c7d;color:#fff}html.theme--documenter-dark .notification.is-info.is-light{background-color:#ebf7ff;color:#0e9dfb}html.theme--documenter-dark .notification.is-success{background-color:#008438;color:#fff}html.theme--documenter-dark .notification.is-success.is-light{background-color:#ebfff3;color:#00eb64}html.theme--documenter-dark .notification.is-warning{background-color:#ad8100;color:#fff}html.theme--documenter-dark .notification.is-warning.is-light{background-color:#fffaeb;color:#d19c00}html.theme--documenter-dark .notification.is-danger{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .notification.is-danger.is-light{background-color:#fdeeec;color:#ec311d}html.theme--documenter-dark .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--documenter-dark .progress::-webkit-progress-bar{background-color:#343c3d}html.theme--documenter-dark .progress::-webkit-progress-value{background-color:#dbdee0}html.theme--documenter-dark .progress::-moz-progress-bar{background-color:#dbdee0}html.theme--documenter-dark .progress::-ms-fill{background-color:#dbdee0;border:none}html.theme--documenter-dark .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--documenter-dark .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--documenter-dark .progress.is-white::-ms-fill{background-color:#fff}html.theme--documenter-dark .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-light::-webkit-progress-value{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-moz-progress-bar{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-ms-fill{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light:indeterminate{background-image:linear-gradient(to right, #ecf0f1 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-dark::-webkit-progress-value,html.theme--documenter-dark .content kbd.progress::-webkit-progress-value{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-moz-progress-bar,html.theme--documenter-dark .content kbd.progress::-moz-progress-bar{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-ms-fill,html.theme--documenter-dark .content kbd.progress::-ms-fill{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark:indeterminate,html.theme--documenter-dark .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #282f2f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-primary::-webkit-progress-value,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-moz-progress-bar,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-ms-fill,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary:indeterminate,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #375a7f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-link::-webkit-progress-value{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-moz-progress-bar{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-ms-fill{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link:indeterminate{background-image:linear-gradient(to right, #1abc9c 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-info::-webkit-progress-value{background-color:#024c7d}html.theme--documenter-dark .progress.is-info::-moz-progress-bar{background-color:#024c7d}html.theme--documenter-dark .progress.is-info::-ms-fill{background-color:#024c7d}html.theme--documenter-dark .progress.is-info:indeterminate{background-image:linear-gradient(to right, #024c7d 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-success::-webkit-progress-value{background-color:#008438}html.theme--documenter-dark .progress.is-success::-moz-progress-bar{background-color:#008438}html.theme--documenter-dark .progress.is-success::-ms-fill{background-color:#008438}html.theme--documenter-dark .progress.is-success:indeterminate{background-image:linear-gradient(to right, #008438 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-warning::-webkit-progress-value{background-color:#ad8100}html.theme--documenter-dark .progress.is-warning::-moz-progress-bar{background-color:#ad8100}html.theme--documenter-dark .progress.is-warning::-ms-fill{background-color:#ad8100}html.theme--documenter-dark .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #ad8100 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-danger::-webkit-progress-value{background-color:#9e1b0d}html.theme--documenter-dark .progress.is-danger::-moz-progress-bar{background-color:#9e1b0d}html.theme--documenter-dark .progress.is-danger::-ms-fill{background-color:#9e1b0d}html.theme--documenter-dark .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #9e1b0d 30%, #343c3d 30%)}html.theme--documenter-dark .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#343c3d;background-image:linear-gradient(to right, #fff 30%, #343c3d 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--documenter-dark .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-ms-fill{animation-name:none}html.theme--documenter-dark .progress.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--documenter-dark .progress.is-medium{height:1.25rem}html.theme--documenter-dark .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--documenter-dark .table{background-color:#343c3d;color:#fff}html.theme--documenter-dark .table td,html.theme--documenter-dark .table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .table td.is-white,html.theme--documenter-dark .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .table td.is-black,html.theme--documenter-dark .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .table td.is-light,html.theme--documenter-dark .table th.is-light{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .table td.is-dark,html.theme--documenter-dark .table th.is-dark{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .table td.is-primary,html.theme--documenter-dark .table th.is-primary{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-link,html.theme--documenter-dark .table th.is-link{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .table td.is-info,html.theme--documenter-dark .table th.is-info{background-color:#024c7d;border-color:#024c7d;color:#fff}html.theme--documenter-dark .table td.is-success,html.theme--documenter-dark .table th.is-success{background-color:#008438;border-color:#008438;color:#fff}html.theme--documenter-dark .table td.is-warning,html.theme--documenter-dark .table th.is-warning{background-color:#ad8100;border-color:#ad8100;color:#fff}html.theme--documenter-dark .table td.is-danger,html.theme--documenter-dark .table th.is-danger{background-color:#9e1b0d;border-color:#9e1b0d;color:#fff}html.theme--documenter-dark .table td.is-narrow,html.theme--documenter-dark .table th.is-narrow{white-space:nowrap;width:1%}html.theme--documenter-dark .table td.is-selected,html.theme--documenter-dark .table th.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-selected a,html.theme--documenter-dark .table td.is-selected strong,html.theme--documenter-dark .table th.is-selected a,html.theme--documenter-dark .table th.is-selected strong{color:currentColor}html.theme--documenter-dark .table td.is-vcentered,html.theme--documenter-dark .table th.is-vcentered{vertical-align:middle}html.theme--documenter-dark .table th{color:#f2f2f2}html.theme--documenter-dark .table th:not([align]){text-align:left}html.theme--documenter-dark .table tr.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table tr.is-selected a,html.theme--documenter-dark .table tr.is-selected strong{color:currentColor}html.theme--documenter-dark .table tr.is-selected td,html.theme--documenter-dark .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--documenter-dark .table thead{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table thead td,html.theme--documenter-dark .table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .table tfoot{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tfoot td,html.theme--documenter-dark .table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .table tbody{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tbody tr:last-child td,html.theme--documenter-dark .table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .table.is-bordered td,html.theme--documenter-dark .table.is-bordered th{border-width:1px}html.theme--documenter-dark .table.is-bordered tr:last-child td,html.theme--documenter-dark .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--documenter-dark .table.is-fullwidth{width:100%}html.theme--documenter-dark .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#2d3435}html.theme--documenter-dark .table.is-narrow td,html.theme--documenter-dark .table.is-narrow th{padding:0.25em 0.5em}html.theme--documenter-dark .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#282f2f}html.theme--documenter-dark .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--documenter-dark .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .tags .tag,html.theme--documenter-dark .tags .content kbd,html.theme--documenter-dark .content .tags kbd,html.theme--documenter-dark .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--documenter-dark .tags .tag:not(:last-child),html.theme--documenter-dark .tags .content kbd:not(:last-child),html.theme--documenter-dark .content .tags kbd:not(:last-child),html.theme--documenter-dark .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--documenter-dark .tags:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .tags:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--documenter-dark .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--documenter-dark .tags.is-centered{justify-content:center}html.theme--documenter-dark .tags.is-centered .tag,html.theme--documenter-dark .tags.is-centered .content kbd,html.theme--documenter-dark .content .tags.is-centered kbd,html.theme--documenter-dark .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--documenter-dark .tags.is-right{justify-content:flex-end}html.theme--documenter-dark .tags.is-right .tag:not(:first-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:first-child),html.theme--documenter-dark .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--documenter-dark .tags.is-right .tag:not(:last-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:last-child),html.theme--documenter-dark .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--documenter-dark .tags.has-addons .tag,html.theme--documenter-dark .tags.has-addons .content kbd,html.theme--documenter-dark .content .tags.has-addons kbd,html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--documenter-dark .tags.has-addons .tag:not(:first-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:first-child),html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--documenter-dark .tags.has-addons .tag:not(:last-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:last-child),html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--documenter-dark .tag:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#282f2f;border-radius:.4em;color:#fff;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--documenter-dark .tag:not(body) .delete,html.theme--documenter-dark .content kbd:not(body) .delete,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--documenter-dark .tag.is-white:not(body),html.theme--documenter-dark .content kbd.is-white:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .tag.is-black:not(body),html.theme--documenter-dark .content kbd.is-black:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .tag.is-light:not(body),html.theme--documenter-dark .content kbd.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .tag.is-dark:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--documenter-dark .content .docstring>section>kbd:not(body){background-color:#282f2f;color:#fff}html.theme--documenter-dark .tag.is-primary:not(body),html.theme--documenter-dark .content kbd.is-primary:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body){background-color:#375a7f;color:#fff}html.theme--documenter-dark .tag.is-primary.is-light:not(body),html.theme--documenter-dark .content kbd.is-primary.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .tag.is-link:not(body),html.theme--documenter-dark .content kbd.is-link:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#1abc9c;color:#fff}html.theme--documenter-dark .tag.is-link.is-light:not(body),html.theme--documenter-dark .content kbd.is-link.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .tag.is-info:not(body),html.theme--documenter-dark .content kbd.is-info:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#024c7d;color:#fff}html.theme--documenter-dark .tag.is-info.is-light:not(body),html.theme--documenter-dark .content kbd.is-info.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#ebf7ff;color:#0e9dfb}html.theme--documenter-dark .tag.is-success:not(body),html.theme--documenter-dark .content kbd.is-success:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#008438;color:#fff}html.theme--documenter-dark .tag.is-success.is-light:not(body),html.theme--documenter-dark .content kbd.is-success.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#ebfff3;color:#00eb64}html.theme--documenter-dark .tag.is-warning:not(body),html.theme--documenter-dark .content kbd.is-warning:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#ad8100;color:#fff}html.theme--documenter-dark .tag.is-warning.is-light:not(body),html.theme--documenter-dark .content kbd.is-warning.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fffaeb;color:#d19c00}html.theme--documenter-dark .tag.is-danger:not(body),html.theme--documenter-dark .content kbd.is-danger:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .tag.is-danger.is-light:not(body),html.theme--documenter-dark .content kbd.is-danger.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fdeeec;color:#ec311d}html.theme--documenter-dark .tag.is-normal:not(body),html.theme--documenter-dark .content kbd.is-normal:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--documenter-dark .tag.is-medium:not(body),html.theme--documenter-dark .content kbd.is-medium:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--documenter-dark .tag.is-large:not(body),html.theme--documenter-dark .content kbd.is-large:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--documenter-dark .tag:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--documenter-dark .tag:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--documenter-dark .tag:not(body) .icon:first-child:last-child,html.theme--documenter-dark .content kbd:not(body) .icon:first-child:last-child,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--documenter-dark .tag.is-delete:not(body),html.theme--documenter-dark .content kbd.is-delete:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--documenter-dark .tag.is-delete:not(body):hover,html.theme--documenter-dark .content kbd.is-delete:not(body):hover,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--documenter-dark .tag.is-delete:not(body):focus,html.theme--documenter-dark .content kbd.is-delete:not(body):focus,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#1d2122}html.theme--documenter-dark .tag.is-delete:not(body):active,html.theme--documenter-dark .content kbd.is-delete:not(body):active,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#111414}html.theme--documenter-dark .tag.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--documenter-dark .content kbd.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--documenter-dark a.tag:hover,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--documenter-dark .title,html.theme--documenter-dark .subtitle{word-break:break-word}html.theme--documenter-dark .title em,html.theme--documenter-dark .title span,html.theme--documenter-dark .subtitle em,html.theme--documenter-dark .subtitle span{font-weight:inherit}html.theme--documenter-dark .title sub,html.theme--documenter-dark .subtitle sub{font-size:.75em}html.theme--documenter-dark .title sup,html.theme--documenter-dark .subtitle sup{font-size:.75em}html.theme--documenter-dark .title .tag,html.theme--documenter-dark .title .content kbd,html.theme--documenter-dark .content .title kbd,html.theme--documenter-dark .title .docstring>section>a.docs-sourcelink,html.theme--documenter-dark .subtitle .tag,html.theme--documenter-dark .subtitle .content kbd,html.theme--documenter-dark .content .subtitle kbd,html.theme--documenter-dark .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--documenter-dark .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--documenter-dark .title strong{color:inherit;font-weight:inherit}html.theme--documenter-dark .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--documenter-dark .title.is-1{font-size:3rem}html.theme--documenter-dark .title.is-2{font-size:2.5rem}html.theme--documenter-dark .title.is-3{font-size:2rem}html.theme--documenter-dark .title.is-4{font-size:1.5rem}html.theme--documenter-dark .title.is-5{font-size:1.25rem}html.theme--documenter-dark .title.is-6{font-size:1rem}html.theme--documenter-dark .title.is-7{font-size:.75rem}html.theme--documenter-dark .subtitle{color:#8c9b9d;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--documenter-dark .subtitle strong{color:#8c9b9d;font-weight:600}html.theme--documenter-dark .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--documenter-dark .subtitle.is-1{font-size:3rem}html.theme--documenter-dark .subtitle.is-2{font-size:2.5rem}html.theme--documenter-dark .subtitle.is-3{font-size:2rem}html.theme--documenter-dark .subtitle.is-4{font-size:1.5rem}html.theme--documenter-dark .subtitle.is-5{font-size:1.25rem}html.theme--documenter-dark .subtitle.is-6{font-size:1rem}html.theme--documenter-dark .subtitle.is-7{font-size:.75rem}html.theme--documenter-dark .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--documenter-dark .number{align-items:center;background-color:#282f2f;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#1f2424;border-color:#5e6d6f;border-radius:.4em;color:#dbdee0}html.theme--documenter-dark .select select::-moz-placeholder,html.theme--documenter-dark .textarea::-moz-placeholder,html.theme--documenter-dark .input::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select::-webkit-input-placeholder,html.theme--documenter-dark .textarea::-webkit-input-placeholder,html.theme--documenter-dark .input::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:-moz-placeholder,html.theme--documenter-dark .textarea:-moz-placeholder,html.theme--documenter-dark .input:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select:-ms-input-placeholder,html.theme--documenter-dark .textarea:-ms-input-placeholder,html.theme--documenter-dark .input:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:hover,html.theme--documenter-dark .textarea:hover,html.theme--documenter-dark .input:hover,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:hover,html.theme--documenter-dark .select select.is-hovered,html.theme--documenter-dark .is-hovered.textarea,html.theme--documenter-dark .is-hovered.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#8c9b9d}html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#1abc9c;box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#8c9b9d;border-color:#282f2f;box-shadow:none;color:#fff}html.theme--documenter-dark .select select[disabled]::-moz-placeholder,html.theme--documenter-dark .textarea[disabled]::-moz-placeholder,html.theme--documenter-dark .input[disabled]::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .textarea[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .input[disabled]::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-moz-placeholder,html.theme--documenter-dark .textarea[disabled]:-moz-placeholder,html.theme--documenter-dark .input[disabled]:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-ms-input-placeholder,html.theme--documenter-dark .textarea[disabled]:-ms-input-placeholder,html.theme--documenter-dark .input[disabled]:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--documenter-dark .textarea[readonly],html.theme--documenter-dark .input[readonly],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--documenter-dark .is-white.textarea,html.theme--documenter-dark .is-white.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--documenter-dark .is-white.textarea:focus,html.theme--documenter-dark .is-white.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--documenter-dark .is-white.is-focused.textarea,html.theme--documenter-dark .is-white.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-white.textarea:active,html.theme--documenter-dark .is-white.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--documenter-dark .is-white.is-active.textarea,html.theme--documenter-dark .is-white.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .is-black.textarea,html.theme--documenter-dark .is-black.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--documenter-dark .is-black.textarea:focus,html.theme--documenter-dark .is-black.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--documenter-dark .is-black.is-focused.textarea,html.theme--documenter-dark .is-black.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-black.textarea:active,html.theme--documenter-dark .is-black.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--documenter-dark .is-black.is-active.textarea,html.theme--documenter-dark .is-black.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .is-light.textarea,html.theme--documenter-dark .is-light.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#ecf0f1}html.theme--documenter-dark .is-light.textarea:focus,html.theme--documenter-dark .is-light.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--documenter-dark .is-light.is-focused.textarea,html.theme--documenter-dark .is-light.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-light.textarea:active,html.theme--documenter-dark .is-light.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--documenter-dark .is-light.is-active.textarea,html.theme--documenter-dark .is-light.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .is-dark.textarea,html.theme--documenter-dark .content kbd.textarea,html.theme--documenter-dark .is-dark.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--documenter-dark .content kbd.input{border-color:#282f2f}html.theme--documenter-dark .is-dark.textarea:focus,html.theme--documenter-dark .content kbd.textarea:focus,html.theme--documenter-dark .is-dark.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--documenter-dark .content kbd.input:focus,html.theme--documenter-dark .is-dark.is-focused.textarea,html.theme--documenter-dark .content kbd.is-focused.textarea,html.theme--documenter-dark .is-dark.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .content kbd.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--documenter-dark .is-dark.textarea:active,html.theme--documenter-dark .content kbd.textarea:active,html.theme--documenter-dark .is-dark.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--documenter-dark .content kbd.input:active,html.theme--documenter-dark .is-dark.is-active.textarea,html.theme--documenter-dark .content kbd.is-active.textarea,html.theme--documenter-dark .is-dark.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .content kbd.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .is-primary.textarea,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink{border-color:#375a7f}html.theme--documenter-dark .is-primary.textarea:focus,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.is-focused.textarea,html.theme--documenter-dark .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--documenter-dark .is-primary.textarea:active,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink:active,html.theme--documenter-dark .is-primary.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink:active,html.theme--documenter-dark .is-primary.is-active.textarea,html.theme--documenter-dark .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .is-link.textarea,html.theme--documenter-dark .is-link.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#1abc9c}html.theme--documenter-dark .is-link.textarea:focus,html.theme--documenter-dark .is-link.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--documenter-dark .is-link.is-focused.textarea,html.theme--documenter-dark .is-link.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-link.textarea:active,html.theme--documenter-dark .is-link.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--documenter-dark .is-link.is-active.textarea,html.theme--documenter-dark .is-link.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .is-info.textarea,html.theme--documenter-dark .is-info.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#024c7d}html.theme--documenter-dark .is-info.textarea:focus,html.theme--documenter-dark .is-info.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--documenter-dark .is-info.is-focused.textarea,html.theme--documenter-dark .is-info.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-info.textarea:active,html.theme--documenter-dark .is-info.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--documenter-dark .is-info.is-active.textarea,html.theme--documenter-dark .is-info.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(2,76,125,0.25)}html.theme--documenter-dark .is-success.textarea,html.theme--documenter-dark .is-success.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#008438}html.theme--documenter-dark .is-success.textarea:focus,html.theme--documenter-dark .is-success.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--documenter-dark .is-success.is-focused.textarea,html.theme--documenter-dark .is-success.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-success.textarea:active,html.theme--documenter-dark .is-success.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--documenter-dark .is-success.is-active.textarea,html.theme--documenter-dark .is-success.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(0,132,56,0.25)}html.theme--documenter-dark .is-warning.textarea,html.theme--documenter-dark .is-warning.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#ad8100}html.theme--documenter-dark .is-warning.textarea:focus,html.theme--documenter-dark .is-warning.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--documenter-dark .is-warning.is-focused.textarea,html.theme--documenter-dark .is-warning.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-warning.textarea:active,html.theme--documenter-dark .is-warning.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--documenter-dark .is-warning.is-active.textarea,html.theme--documenter-dark .is-warning.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(173,129,0,0.25)}html.theme--documenter-dark .is-danger.textarea,html.theme--documenter-dark .is-danger.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#9e1b0d}html.theme--documenter-dark .is-danger.textarea:focus,html.theme--documenter-dark .is-danger.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--documenter-dark .is-danger.is-focused.textarea,html.theme--documenter-dark .is-danger.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-danger.textarea:active,html.theme--documenter-dark .is-danger.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--documenter-dark .is-danger.is-active.textarea,html.theme--documenter-dark .is-danger.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(158,27,13,0.25)}html.theme--documenter-dark .is-small.textarea,html.theme--documenter-dark .is-small.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .is-medium.textarea,html.theme--documenter-dark .is-medium.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--documenter-dark .is-large.textarea,html.theme--documenter-dark .is-large.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--documenter-dark .is-fullwidth.textarea,html.theme--documenter-dark .is-fullwidth.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--documenter-dark .is-inline.textarea,html.theme--documenter-dark .is-inline.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--documenter-dark .input.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--documenter-dark .input.is-static,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--documenter-dark .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--documenter-dark .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--documenter-dark .textarea[rows]{height:initial}html.theme--documenter-dark .textarea.has-fixed-size{resize:none}html.theme--documenter-dark .radio,html.theme--documenter-dark .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--documenter-dark .radio input,html.theme--documenter-dark .checkbox input{cursor:pointer}html.theme--documenter-dark .radio:hover,html.theme--documenter-dark .checkbox:hover{color:#8c9b9d}html.theme--documenter-dark .radio[disabled],html.theme--documenter-dark .checkbox[disabled],fieldset[disabled] html.theme--documenter-dark .radio,fieldset[disabled] html.theme--documenter-dark .checkbox,html.theme--documenter-dark .radio input[disabled],html.theme--documenter-dark .checkbox input[disabled]{color:#fff;cursor:not-allowed}html.theme--documenter-dark .radio+.radio{margin-left:.5em}html.theme--documenter-dark .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--documenter-dark .select:not(.is-multiple){height:2.5em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border-color:#1abc9c;right:1.125em;z-index:4}html.theme--documenter-dark .select.is-rounded select,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--documenter-dark .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--documenter-dark .select select::-ms-expand{display:none}html.theme--documenter-dark .select select[disabled]:hover,fieldset[disabled] html.theme--documenter-dark .select select:hover{border-color:#282f2f}html.theme--documenter-dark .select select:not([multiple]){padding-right:2.5em}html.theme--documenter-dark .select select[multiple]{height:auto;padding:0}html.theme--documenter-dark .select select[multiple] option{padding:0.5em 1em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#8c9b9d}html.theme--documenter-dark .select.is-white:not(:hover)::after{border-color:#fff}html.theme--documenter-dark .select.is-white select{border-color:#fff}html.theme--documenter-dark .select.is-white select:hover,html.theme--documenter-dark .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--documenter-dark .select.is-white select:focus,html.theme--documenter-dark .select.is-white select.is-focused,html.theme--documenter-dark .select.is-white select:active,html.theme--documenter-dark .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select:hover,html.theme--documenter-dark .select.is-black select.is-hovered{border-color:#000}html.theme--documenter-dark .select.is-black select:focus,html.theme--documenter-dark .select.is-black select.is-focused,html.theme--documenter-dark .select.is-black select:active,html.theme--documenter-dark .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .select.is-light:not(:hover)::after{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select:hover,html.theme--documenter-dark .select.is-light select.is-hovered{border-color:#dde4e6}html.theme--documenter-dark .select.is-light select:focus,html.theme--documenter-dark .select.is-light select.is-focused,html.theme--documenter-dark .select.is-light select:active,html.theme--documenter-dark .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .select.is-dark:not(:hover)::after,html.theme--documenter-dark .content kbd.select:not(:hover)::after{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select,html.theme--documenter-dark .content kbd.select select{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select:hover,html.theme--documenter-dark .content kbd.select select:hover,html.theme--documenter-dark .select.is-dark select.is-hovered,html.theme--documenter-dark .content kbd.select select.is-hovered{border-color:#1d2122}html.theme--documenter-dark .select.is-dark select:focus,html.theme--documenter-dark .content kbd.select select:focus,html.theme--documenter-dark .select.is-dark select.is-focused,html.theme--documenter-dark .content kbd.select select.is-focused,html.theme--documenter-dark .select.is-dark select:active,html.theme--documenter-dark .content kbd.select select:active,html.theme--documenter-dark .select.is-dark select.is-active,html.theme--documenter-dark .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .select.is-primary:not(:hover)::after,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select:hover,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:hover,html.theme--documenter-dark .select.is-primary select.is-hovered,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#2f4d6d}html.theme--documenter-dark .select.is-primary select:focus,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:focus,html.theme--documenter-dark .select.is-primary select.is-focused,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--documenter-dark .select.is-primary select:active,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:active,html.theme--documenter-dark .select.is-primary select.is-active,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .select.is-link:not(:hover)::after{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select:hover,html.theme--documenter-dark .select.is-link select.is-hovered{border-color:#17a689}html.theme--documenter-dark .select.is-link select:focus,html.theme--documenter-dark .select.is-link select.is-focused,html.theme--documenter-dark .select.is-link select:active,html.theme--documenter-dark .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select.is-info:not(:hover)::after{border-color:#024c7d}html.theme--documenter-dark .select.is-info select{border-color:#024c7d}html.theme--documenter-dark .select.is-info select:hover,html.theme--documenter-dark .select.is-info select.is-hovered{border-color:#023d64}html.theme--documenter-dark .select.is-info select:focus,html.theme--documenter-dark .select.is-info select.is-focused,html.theme--documenter-dark .select.is-info select:active,html.theme--documenter-dark .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(2,76,125,0.25)}html.theme--documenter-dark .select.is-success:not(:hover)::after{border-color:#008438}html.theme--documenter-dark .select.is-success select{border-color:#008438}html.theme--documenter-dark .select.is-success select:hover,html.theme--documenter-dark .select.is-success select.is-hovered{border-color:#006b2d}html.theme--documenter-dark .select.is-success select:focus,html.theme--documenter-dark .select.is-success select.is-focused,html.theme--documenter-dark .select.is-success select:active,html.theme--documenter-dark .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(0,132,56,0.25)}html.theme--documenter-dark .select.is-warning:not(:hover)::after{border-color:#ad8100}html.theme--documenter-dark .select.is-warning select{border-color:#ad8100}html.theme--documenter-dark .select.is-warning select:hover,html.theme--documenter-dark .select.is-warning select.is-hovered{border-color:#946e00}html.theme--documenter-dark .select.is-warning select:focus,html.theme--documenter-dark .select.is-warning select.is-focused,html.theme--documenter-dark .select.is-warning select:active,html.theme--documenter-dark .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(173,129,0,0.25)}html.theme--documenter-dark .select.is-danger:not(:hover)::after{border-color:#9e1b0d}html.theme--documenter-dark .select.is-danger select{border-color:#9e1b0d}html.theme--documenter-dark .select.is-danger select:hover,html.theme--documenter-dark .select.is-danger select.is-hovered{border-color:#86170b}html.theme--documenter-dark .select.is-danger select:focus,html.theme--documenter-dark .select.is-danger select.is-focused,html.theme--documenter-dark .select.is-danger select:active,html.theme--documenter-dark .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(158,27,13,0.25)}html.theme--documenter-dark .select.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .select.is-medium{font-size:1.25rem}html.theme--documenter-dark .select.is-large{font-size:1.5rem}html.theme--documenter-dark .select.is-disabled::after{border-color:#fff !important;opacity:0.5}html.theme--documenter-dark .select.is-fullwidth{width:100%}html.theme--documenter-dark .select.is-fullwidth select{width:100%}html.theme--documenter-dark .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--documenter-dark .select.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .select.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--documenter-dark .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:hover .file-cta,html.theme--documenter-dark .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:focus .file-cta,html.theme--documenter-dark .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--documenter-dark .file.is-white:active .file-cta,html.theme--documenter-dark .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:hover .file-cta,html.theme--documenter-dark .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:focus .file-cta,html.theme--documenter-dark .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--documenter-dark .file.is-black:active .file-cta,html.theme--documenter-dark .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-light .file-cta{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:hover .file-cta,html.theme--documenter-dark .file.is-light.is-hovered .file-cta{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:focus .file-cta,html.theme--documenter-dark .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(236,240,241,0.25);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:active .file-cta,html.theme--documenter-dark .file.is-light.is-active .file-cta{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-dark .file-cta,html.theme--documenter-dark .content kbd.file .file-cta{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:hover .file-cta,html.theme--documenter-dark .content kbd.file:hover .file-cta,html.theme--documenter-dark .file.is-dark.is-hovered .file-cta,html.theme--documenter-dark .content kbd.file.is-hovered .file-cta{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:focus .file-cta,html.theme--documenter-dark .content kbd.file:focus .file-cta,html.theme--documenter-dark .file.is-dark.is-focused .file-cta,html.theme--documenter-dark .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(40,47,47,0.25);color:#fff}html.theme--documenter-dark .file.is-dark:active .file-cta,html.theme--documenter-dark .content kbd.file:active .file-cta,html.theme--documenter-dark .file.is-dark.is-active .file-cta,html.theme--documenter-dark .content kbd.file.is-active .file-cta{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:hover .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--documenter-dark .file.is-primary.is-hovered .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:focus .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--documenter-dark .file.is-primary.is-focused .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(55,90,127,0.25);color:#fff}html.theme--documenter-dark .file.is-primary:active .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--documenter-dark .file.is-primary.is-active .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link .file-cta{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:hover .file-cta,html.theme--documenter-dark .file.is-link.is-hovered .file-cta{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:focus .file-cta,html.theme--documenter-dark .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(26,188,156,0.25);color:#fff}html.theme--documenter-dark .file.is-link:active .file-cta,html.theme--documenter-dark .file.is-link.is-active .file-cta{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info .file-cta{background-color:#024c7d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:hover .file-cta,html.theme--documenter-dark .file.is-info.is-hovered .file-cta{background-color:#024470;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:focus .file-cta,html.theme--documenter-dark .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(2,76,125,0.25);color:#fff}html.theme--documenter-dark .file.is-info:active .file-cta,html.theme--documenter-dark .file.is-info.is-active .file-cta{background-color:#023d64;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success .file-cta{background-color:#008438;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:hover .file-cta,html.theme--documenter-dark .file.is-success.is-hovered .file-cta{background-color:#073;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:focus .file-cta,html.theme--documenter-dark .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(0,132,56,0.25);color:#fff}html.theme--documenter-dark .file.is-success:active .file-cta,html.theme--documenter-dark .file.is-success.is-active .file-cta{background-color:#006b2d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-warning .file-cta{background-color:#ad8100;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-warning:hover .file-cta,html.theme--documenter-dark .file.is-warning.is-hovered .file-cta{background-color:#a07700;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-warning:focus .file-cta,html.theme--documenter-dark .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(173,129,0,0.25);color:#fff}html.theme--documenter-dark .file.is-warning:active .file-cta,html.theme--documenter-dark .file.is-warning.is-active .file-cta{background-color:#946e00;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger .file-cta{background-color:#9e1b0d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:hover .file-cta,html.theme--documenter-dark .file.is-danger.is-hovered .file-cta{background-color:#92190c;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:focus .file-cta,html.theme--documenter-dark .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(158,27,13,0.25);color:#fff}html.theme--documenter-dark .file.is-danger:active .file-cta,html.theme--documenter-dark .file.is-danger.is-active .file-cta{background-color:#86170b;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--documenter-dark .file.is-normal{font-size:1rem}html.theme--documenter-dark .file.is-medium{font-size:1.25rem}html.theme--documenter-dark .file.is-medium .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-large{font-size:1.5rem}html.theme--documenter-dark .file.is-large .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--documenter-dark .file.has-name.is-empty .file-name{display:none}html.theme--documenter-dark .file.is-boxed .file-label{flex-direction:column}html.theme--documenter-dark .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--documenter-dark .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--documenter-dark .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--documenter-dark .file.is-boxed .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-boxed.is-small .file-icon .fa,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--documenter-dark .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--documenter-dark .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--documenter-dark .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--documenter-dark .file.is-centered{justify-content:center}html.theme--documenter-dark .file.is-fullwidth .file-label{width:100%}html.theme--documenter-dark .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--documenter-dark .file.is-right{justify-content:flex-end}html.theme--documenter-dark .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--documenter-dark .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--documenter-dark .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--documenter-dark .file-label:hover .file-cta{background-color:#232829;color:#f2f2f2}html.theme--documenter-dark .file-label:hover .file-name{border-color:#596668}html.theme--documenter-dark .file-label:active .file-cta{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .file-label:active .file-name{border-color:#535f61}html.theme--documenter-dark .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--documenter-dark .file-cta{background-color:#282f2f;color:#fff}html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--documenter-dark .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--documenter-dark .file-icon .fa{font-size:14px}html.theme--documenter-dark .label{color:#f2f2f2;display:block;font-size:1rem;font-weight:700}html.theme--documenter-dark .label:not(:last-child){margin-bottom:0.5em}html.theme--documenter-dark .label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--documenter-dark .label.is-medium{font-size:1.25rem}html.theme--documenter-dark .label.is-large{font-size:1.5rem}html.theme--documenter-dark .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--documenter-dark .help.is-white{color:#fff}html.theme--documenter-dark .help.is-black{color:#0a0a0a}html.theme--documenter-dark .help.is-light{color:#ecf0f1}html.theme--documenter-dark .help.is-dark,html.theme--documenter-dark .content kbd.help{color:#282f2f}html.theme--documenter-dark .help.is-primary,html.theme--documenter-dark .docstring>section>a.help.docs-sourcelink{color:#375a7f}html.theme--documenter-dark .help.is-link{color:#1abc9c}html.theme--documenter-dark .help.is-info{color:#024c7d}html.theme--documenter-dark .help.is-success{color:#008438}html.theme--documenter-dark .help.is-warning{color:#ad8100}html.theme--documenter-dark .help.is-danger{color:#9e1b0d}html.theme--documenter-dark .field:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.has-addons{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--documenter-dark .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.has-addons.has-addons-centered{justify-content:center}html.theme--documenter-dark .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--documenter-dark .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .field.is-grouped{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.is-grouped>.control{flex-shrink:0}html.theme--documenter-dark .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--documenter-dark .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field.is-horizontal{display:flex}}html.theme--documenter-dark .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--documenter-dark .field-label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-normal{padding-top:0.375em}html.theme--documenter-dark .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--documenter-dark .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--documenter-dark .field-body .field{margin-bottom:0}html.theme--documenter-dark .field-body>.field{flex-shrink:1}html.theme--documenter-dark .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--documenter-dark .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--documenter-dark .control.has-icons-left .input:focus~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-left .select:focus~.icon,html.theme--documenter-dark .control.has-icons-right .input:focus~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-right .select:focus~.icon{color:#282f2f}html.theme--documenter-dark .control.has-icons-left .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-small~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--documenter-dark .control.has-icons-left .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--documenter-dark .control.has-icons-left .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon{color:#5e6d6f;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--documenter-dark .control.has-icons-left .input,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--documenter-dark .control.has-icons-left .select select{padding-left:2.5em}html.theme--documenter-dark .control.has-icons-left .icon.is-left{left:0}html.theme--documenter-dark .control.has-icons-right .input,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--documenter-dark .control.has-icons-right .select select{padding-right:2.5em}html.theme--documenter-dark .control.has-icons-right .icon.is-right{right:0}html.theme--documenter-dark .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--documenter-dark .control.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .control.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--documenter-dark .breadcrumb a{align-items:center;color:#1abc9c;display:flex;justify-content:center;padding:0 .75em}html.theme--documenter-dark .breadcrumb a:hover{color:#1dd2af}html.theme--documenter-dark .breadcrumb li{align-items:center;display:flex}html.theme--documenter-dark .breadcrumb li:first-child a{padding-left:0}html.theme--documenter-dark .breadcrumb li.is-active a{color:#f2f2f2;cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb li+li::before{color:#8c9b9d;content:"\0002f"}html.theme--documenter-dark .breadcrumb ul,html.theme--documenter-dark .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .breadcrumb .icon:first-child{margin-right:.5em}html.theme--documenter-dark .breadcrumb .icon:last-child{margin-left:.5em}html.theme--documenter-dark .breadcrumb.is-centered ol,html.theme--documenter-dark .breadcrumb.is-centered ul{justify-content:center}html.theme--documenter-dark .breadcrumb.is-right ol,html.theme--documenter-dark .breadcrumb.is-right ul{justify-content:flex-end}html.theme--documenter-dark .breadcrumb.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--documenter-dark .breadcrumb.is-medium{font-size:1.25rem}html.theme--documenter-dark .breadcrumb.is-large{font-size:1.5rem}html.theme--documenter-dark .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--documenter-dark .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--documenter-dark .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--documenter-dark .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--documenter-dark .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#fff;max-width:100%;position:relative}html.theme--documenter-dark .card-footer:first-child,html.theme--documenter-dark .card-content:first-child,html.theme--documenter-dark .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-footer:last-child,html.theme--documenter-dark .card-content:last-child,html.theme--documenter-dark .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--documenter-dark .card-header-title{align-items:center;color:#f2f2f2;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--documenter-dark .card-header-title.is-centered{justify-content:center}html.theme--documenter-dark .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--documenter-dark .card-image{display:block;position:relative}html.theme--documenter-dark .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--documenter-dark .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--documenter-dark .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--documenter-dark .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--documenter-dark .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--documenter-dark .dropdown.is-active .dropdown-menu,html.theme--documenter-dark .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--documenter-dark .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--documenter-dark .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--documenter-dark .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .dropdown-content{background-color:#282f2f;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--documenter-dark .dropdown-item{color:#fff;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--documenter-dark a.dropdown-item,html.theme--documenter-dark button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--documenter-dark a.dropdown-item:hover,html.theme--documenter-dark button.dropdown-item:hover{background-color:#282f2f;color:#0a0a0a}html.theme--documenter-dark a.dropdown-item.is-active,html.theme--documenter-dark button.dropdown-item.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--documenter-dark .level{align-items:center;justify-content:space-between}html.theme--documenter-dark .level code{border-radius:.4em}html.theme--documenter-dark .level img{display:inline-block;vertical-align:top}html.theme--documenter-dark .level.is-mobile{display:flex}html.theme--documenter-dark .level.is-mobile .level-left,html.theme--documenter-dark .level.is-mobile .level-right{display:flex}html.theme--documenter-dark .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--documenter-dark .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level{display:flex}html.theme--documenter-dark .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--documenter-dark .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--documenter-dark .level-item .title,html.theme--documenter-dark .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--documenter-dark .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--documenter-dark .level-left,html.theme--documenter-dark .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .level-left .level-item.is-flexible,html.theme--documenter-dark .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left .level-item:not(:last-child),html.theme--documenter-dark .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--documenter-dark .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left{display:flex}}html.theme--documenter-dark .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-right{display:flex}}html.theme--documenter-dark .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--documenter-dark .media .content:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .media .media{border-top:1px solid rgba(94,109,111,0.5);display:flex;padding-top:.75rem}html.theme--documenter-dark .media .media .content:not(:last-child),html.theme--documenter-dark .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--documenter-dark .media .media .media{padding-top:.5rem}html.theme--documenter-dark .media .media .media+.media{margin-top:.5rem}html.theme--documenter-dark .media+.media{border-top:1px solid rgba(94,109,111,0.5);margin-top:1rem;padding-top:1rem}html.theme--documenter-dark .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--documenter-dark .media-left,html.theme--documenter-dark .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .media-left{margin-right:1rem}html.theme--documenter-dark .media-right{margin-left:1rem}html.theme--documenter-dark .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .media-content{overflow-x:auto}}html.theme--documenter-dark .menu{font-size:1rem}html.theme--documenter-dark .menu.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--documenter-dark .menu.is-medium{font-size:1.25rem}html.theme--documenter-dark .menu.is-large{font-size:1.5rem}html.theme--documenter-dark .menu-list{line-height:1.25}html.theme--documenter-dark .menu-list a{border-radius:3px;color:#fff;display:block;padding:0.5em 0.75em}html.theme--documenter-dark .menu-list a:hover{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .menu-list a.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .menu-list li ul{border-left:1px solid #5e6d6f;margin:.75em;padding-left:.75em}html.theme--documenter-dark .menu-label{color:#fff;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--documenter-dark .menu-label:not(:first-child){margin-top:1em}html.theme--documenter-dark .menu-label:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .message{background-color:#282f2f;border-radius:.4em;font-size:1rem}html.theme--documenter-dark .message strong{color:currentColor}html.theme--documenter-dark .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .message.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--documenter-dark .message.is-medium{font-size:1.25rem}html.theme--documenter-dark .message.is-large{font-size:1.5rem}html.theme--documenter-dark .message.is-white{background-color:#fff}html.theme--documenter-dark .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .message.is-white .message-body{border-color:#fff}html.theme--documenter-dark .message.is-black{background-color:#fafafa}html.theme--documenter-dark .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .message.is-black .message-body{border-color:#0a0a0a}html.theme--documenter-dark .message.is-light{background-color:#f9fafb}html.theme--documenter-dark .message.is-light .message-header{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .message.is-light .message-body{border-color:#ecf0f1}html.theme--documenter-dark .message.is-dark,html.theme--documenter-dark .content kbd.message{background-color:#f9fafa}html.theme--documenter-dark .message.is-dark .message-header,html.theme--documenter-dark .content kbd.message .message-header{background-color:#282f2f;color:#fff}html.theme--documenter-dark .message.is-dark .message-body,html.theme--documenter-dark .content kbd.message .message-body{border-color:#282f2f}html.theme--documenter-dark .message.is-primary,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink{background-color:#f1f5f9}html.theme--documenter-dark .message.is-primary .message-header,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink .message-header{background-color:#375a7f;color:#fff}html.theme--documenter-dark .message.is-primary .message-body,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink .message-body{border-color:#375a7f;color:#4d7eb2}html.theme--documenter-dark .message.is-link{background-color:#edfdf9}html.theme--documenter-dark .message.is-link .message-header{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .message.is-link .message-body{border-color:#1abc9c;color:#15987e}html.theme--documenter-dark .message.is-info{background-color:#ebf7ff}html.theme--documenter-dark .message.is-info .message-header{background-color:#024c7d;color:#fff}html.theme--documenter-dark .message.is-info .message-body{border-color:#024c7d;color:#0e9dfb}html.theme--documenter-dark .message.is-success{background-color:#ebfff3}html.theme--documenter-dark .message.is-success .message-header{background-color:#008438;color:#fff}html.theme--documenter-dark .message.is-success .message-body{border-color:#008438;color:#00eb64}html.theme--documenter-dark .message.is-warning{background-color:#fffaeb}html.theme--documenter-dark .message.is-warning .message-header{background-color:#ad8100;color:#fff}html.theme--documenter-dark .message.is-warning .message-body{border-color:#ad8100;color:#d19c00}html.theme--documenter-dark .message.is-danger{background-color:#fdeeec}html.theme--documenter-dark .message.is-danger .message-header{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .message.is-danger .message-body{border-color:#9e1b0d;color:#ec311d}html.theme--documenter-dark .message-header{align-items:center;background-color:#fff;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--documenter-dark .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--documenter-dark .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--documenter-dark .message-body{border-color:#5e6d6f;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#fff;padding:1.25em 1.5em}html.theme--documenter-dark .message-body code,html.theme--documenter-dark .message-body pre{background-color:#fff}html.theme--documenter-dark .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--documenter-dark .modal.is-active{display:flex}html.theme--documenter-dark .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--documenter-dark .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--documenter-dark .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--documenter-dark .modal-card-head,html.theme--documenter-dark .modal-card-foot{align-items:center;background-color:#282f2f;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--documenter-dark .modal-card-head{border-bottom:1px solid #5e6d6f;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--documenter-dark .modal-card-title{color:#f2f2f2;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--documenter-dark .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #5e6d6f}html.theme--documenter-dark .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--documenter-dark .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--documenter-dark .navbar{background-color:#375a7f;min-height:4rem;position:relative;z-index:30}html.theme--documenter-dark .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-white .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--documenter-dark .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-black .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--documenter-dark .navbar.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-light .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}}html.theme--documenter-dark .navbar.is-dark,html.theme--documenter-dark .content kbd.navbar{background-color:#282f2f;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-burger,html.theme--documenter-dark .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-dark .navbar-start>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-end>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#282f2f;color:#fff}}html.theme--documenter-dark .navbar.is-primary,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-burger,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-primary .navbar-start>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-end>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#375a7f;color:#fff}}html.theme--documenter-dark .navbar.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-link .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c;color:#fff}}html.theme--documenter-dark .navbar.is-info{background-color:#024c7d;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#023d64;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-info .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#023d64;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#023d64;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#024c7d;color:#fff}}html.theme--documenter-dark .navbar.is-success{background-color:#008438;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#006b2d;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-success .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#006b2d;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#006b2d;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#008438;color:#fff}}html.theme--documenter-dark .navbar.is-warning{background-color:#ad8100;color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#946e00;color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-warning .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#946e00;color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#946e00;color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ad8100;color:#fff}}html.theme--documenter-dark .navbar.is-danger{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#86170b;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-danger .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#86170b;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#86170b;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#9e1b0d;color:#fff}}html.theme--documenter-dark .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--documenter-dark .navbar.has-shadow{box-shadow:0 2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-bottom,html.theme--documenter-dark .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-top{top:0}html.theme--documenter-dark html.has-navbar-fixed-top,html.theme--documenter-dark body.has-navbar-fixed-top{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom,html.theme--documenter-dark body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--documenter-dark .navbar-brand,html.theme--documenter-dark .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--documenter-dark .navbar-brand a.navbar-item:focus,html.theme--documenter-dark .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--documenter-dark .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--documenter-dark .navbar-burger{color:#fff;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--documenter-dark .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--documenter-dark .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--documenter-dark .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--documenter-dark .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--documenter-dark .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--documenter-dark .navbar-menu{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{color:#fff;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--documenter-dark .navbar-item .icon:only-child,html.theme--documenter-dark .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--documenter-dark a.navbar-item,html.theme--documenter-dark .navbar-link{cursor:pointer}html.theme--documenter-dark a.navbar-item:focus,html.theme--documenter-dark a.navbar-item:focus-within,html.theme--documenter-dark a.navbar-item:hover,html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link:focus,html.theme--documenter-dark .navbar-link:focus-within,html.theme--documenter-dark .navbar-link:hover,html.theme--documenter-dark .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-item{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .navbar-item img{max-height:1.75rem}html.theme--documenter-dark .navbar-item.has-dropdown{padding:0}html.theme--documenter-dark .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--documenter-dark .navbar-item.is-tab:focus,html.theme--documenter-dark .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c}html.theme--documenter-dark .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c;border-bottom-style:solid;border-bottom-width:3px;color:#1abc9c;padding-bottom:calc(0.5rem - 3px)}html.theme--documenter-dark .navbar-content{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--documenter-dark .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--documenter-dark .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar>.container{display:block}html.theme--documenter-dark .navbar-brand .navbar-item,html.theme--documenter-dark .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--documenter-dark .navbar-link::after{display:none}html.theme--documenter-dark .navbar-menu{background-color:#375a7f;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--documenter-dark .navbar-menu.is-active{display:block}html.theme--documenter-dark .navbar.is-fixed-bottom-touch,html.theme--documenter-dark .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-touch{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-touch{top:0}html.theme--documenter-dark .navbar.is-fixed-top .navbar-menu,html.theme--documenter-dark .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--documenter-dark html.has-navbar-fixed-top-touch,html.theme--documenter-dark body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-touch,html.theme--documenter-dark body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar,html.theme--documenter-dark .navbar-menu,html.theme--documenter-dark .navbar-start,html.theme--documenter-dark .navbar-end{align-items:stretch;display:flex}html.theme--documenter-dark .navbar{min-height:4rem}html.theme--documenter-dark .navbar.is-spaced{padding:1rem 2rem}html.theme--documenter-dark .navbar.is-spaced .navbar-start,html.theme--documenter-dark .navbar.is-spaced .navbar-end{align-items:center}html.theme--documenter-dark .navbar.is-spaced a.navbar-item,html.theme--documenter-dark .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent a.navbar-item:hover,html.theme--documenter-dark .navbar.is-transparent a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-transparent .navbar-link:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-link:hover,html.theme--documenter-dark .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-burger{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{align-items:center;display:flex}html.theme--documenter-dark .navbar-item.has-dropdown{align-items:stretch}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--documenter-dark .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--documenter-dark .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--documenter-dark .navbar-dropdown{background-color:#375a7f;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--documenter-dark .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--documenter-dark .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}.navbar.is-spaced html.theme--documenter-dark .navbar-dropdown,html.theme--documenter-dark .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--documenter-dark .navbar-dropdown.is-right{left:auto;right:0}html.theme--documenter-dark .navbar-divider{display:block}html.theme--documenter-dark .navbar>.container .navbar-brand,html.theme--documenter-dark .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--documenter-dark .navbar>.container .navbar-menu,html.theme--documenter-dark .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop,html.theme--documenter-dark .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-desktop{top:0}html.theme--documenter-dark html.has-navbar-fixed-top-desktop,html.theme--documenter-dark body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-desktop,html.theme--documenter-dark body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-top,html.theme--documenter-dark body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-bottom,html.theme--documenter-dark body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link.is-active{color:#1abc9c}html.theme--documenter-dark a.navbar-item.is-active:not(:focus):not(:hover),html.theme--documenter-dark .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--documenter-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--documenter-dark .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--documenter-dark .pagination{font-size:1rem;margin:-.25rem}html.theme--documenter-dark .pagination.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--documenter-dark .pagination.is-medium{font-size:1.25rem}html.theme--documenter-dark .pagination.is-large{font-size:1.5rem}html.theme--documenter-dark .pagination.is-rounded .pagination-previous,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--documenter-dark .pagination.is-rounded .pagination-next,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--documenter-dark .pagination.is-rounded .pagination-link,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--documenter-dark .pagination,html.theme--documenter-dark .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link{border-color:#5e6d6f;color:#1abc9c;min-width:2.5em}html.theme--documenter-dark .pagination-previous:hover,html.theme--documenter-dark .pagination-next:hover,html.theme--documenter-dark .pagination-link:hover{border-color:#8c9b9d;color:#1dd2af}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus{border-color:#8c9b9d}html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-previous.is-disabled,html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-next.is-disabled,html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-link.is-disabled{background-color:#5e6d6f;border-color:#5e6d6f;box-shadow:none;color:#fff;opacity:0.5}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--documenter-dark .pagination-link.is-current{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .pagination-ellipsis{color:#8c9b9d;pointer-events:none}html.theme--documenter-dark .pagination-list{flex-wrap:wrap}html.theme--documenter-dark .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--documenter-dark .pagination{flex-wrap:wrap}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination-previous{order:2}html.theme--documenter-dark .pagination-next{order:3}html.theme--documenter-dark .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination.is-centered .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--documenter-dark .pagination.is-centered .pagination-next{order:3}html.theme--documenter-dark .pagination.is-right .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-right .pagination-next{order:2}html.theme--documenter-dark .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--documenter-dark .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--documenter-dark .panel:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--documenter-dark .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--documenter-dark .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--documenter-dark .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--documenter-dark .panel.is-light .panel-heading{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .panel.is-light .panel-tabs a.is-active{border-bottom-color:#ecf0f1}html.theme--documenter-dark .panel.is-light .panel-block.is-active .panel-icon{color:#ecf0f1}html.theme--documenter-dark .panel.is-dark .panel-heading,html.theme--documenter-dark .content kbd.panel .panel-heading{background-color:#282f2f;color:#fff}html.theme--documenter-dark .panel.is-dark .panel-tabs a.is-active,html.theme--documenter-dark .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#282f2f}html.theme--documenter-dark .panel.is-dark .panel-block.is-active .panel-icon,html.theme--documenter-dark .content kbd.panel .panel-block.is-active .panel-icon{color:#282f2f}html.theme--documenter-dark .panel.is-primary .panel-heading,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#375a7f;color:#fff}html.theme--documenter-dark .panel.is-primary .panel-tabs a.is-active,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#375a7f}html.theme--documenter-dark .panel.is-primary .panel-block.is-active .panel-icon,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#375a7f}html.theme--documenter-dark .panel.is-link .panel-heading{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .panel.is-link .panel-tabs a.is-active{border-bottom-color:#1abc9c}html.theme--documenter-dark .panel.is-link .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel.is-info .panel-heading{background-color:#024c7d;color:#fff}html.theme--documenter-dark .panel.is-info .panel-tabs a.is-active{border-bottom-color:#024c7d}html.theme--documenter-dark .panel.is-info .panel-block.is-active .panel-icon{color:#024c7d}html.theme--documenter-dark .panel.is-success .panel-heading{background-color:#008438;color:#fff}html.theme--documenter-dark .panel.is-success .panel-tabs a.is-active{border-bottom-color:#008438}html.theme--documenter-dark .panel.is-success .panel-block.is-active .panel-icon{color:#008438}html.theme--documenter-dark .panel.is-warning .panel-heading{background-color:#ad8100;color:#fff}html.theme--documenter-dark .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ad8100}html.theme--documenter-dark .panel.is-warning .panel-block.is-active .panel-icon{color:#ad8100}html.theme--documenter-dark .panel.is-danger .panel-heading{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#9e1b0d}html.theme--documenter-dark .panel.is-danger .panel-block.is-active .panel-icon{color:#9e1b0d}html.theme--documenter-dark .panel-tabs:not(:last-child),html.theme--documenter-dark .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--documenter-dark .panel-heading{background-color:#343c3d;border-radius:8px 8px 0 0;color:#f2f2f2;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--documenter-dark .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--documenter-dark .panel-tabs a{border-bottom:1px solid #5e6d6f;margin-bottom:-1px;padding:0.5em}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#343c3d;color:#17a689}html.theme--documenter-dark .panel-list a{color:#fff}html.theme--documenter-dark .panel-list a:hover{color:#1abc9c}html.theme--documenter-dark .panel-block{align-items:center;color:#f2f2f2;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--documenter-dark .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--documenter-dark .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--documenter-dark .panel-block.is-wrapped{flex-wrap:wrap}html.theme--documenter-dark .panel-block.is-active{border-left-color:#1abc9c;color:#17a689}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--documenter-dark a.panel-block,html.theme--documenter-dark label.panel-block{cursor:pointer}html.theme--documenter-dark a.panel-block:hover,html.theme--documenter-dark label.panel-block:hover{background-color:#282f2f}html.theme--documenter-dark .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#fff;margin-right:.75em}html.theme--documenter-dark .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--documenter-dark .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--documenter-dark .tabs a{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;color:#fff;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--documenter-dark .tabs a:hover{border-bottom-color:#f2f2f2;color:#f2f2f2}html.theme--documenter-dark .tabs li{display:block}html.theme--documenter-dark .tabs li.is-active a{border-bottom-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .tabs ul{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--documenter-dark .tabs ul.is-left{padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--documenter-dark .tabs .icon:first-child{margin-right:.5em}html.theme--documenter-dark .tabs .icon:last-child{margin-left:.5em}html.theme--documenter-dark .tabs.is-centered ul{justify-content:center}html.theme--documenter-dark .tabs.is-right ul{justify-content:flex-end}html.theme--documenter-dark .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--documenter-dark .tabs.is-boxed a:hover{background-color:#282f2f;border-bottom-color:#5e6d6f}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#5e6d6f;border-bottom-color:rgba(0,0,0,0) !important}html.theme--documenter-dark .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .tabs.is-toggle a{border-color:#5e6d6f;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--documenter-dark .tabs.is-toggle a:hover{background-color:#282f2f;border-color:#8c9b9d;z-index:2}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li.is-active a{background-color:#1abc9c;border-color:#1abc9c;color:#fff;z-index:1}html.theme--documenter-dark .tabs.is-toggle ul{border-bottom:none}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--documenter-dark .tabs.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--documenter-dark .tabs.is-medium{font-size:1.25rem}html.theme--documenter-dark .tabs.is-large{font-size:1.5rem}html.theme--documenter-dark .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--documenter-dark .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--documenter-dark .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--documenter-dark .column.is-narrow-mobile{flex:none;width:unset}html.theme--documenter-dark .column.is-full-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-mobile{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--documenter-dark .column.is-0-mobile{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-mobile{margin-left:0%}html.theme--documenter-dark .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-mobile{margin-left:25%}html.theme--documenter-dark .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-mobile{margin-left:50%}html.theme--documenter-dark .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-mobile{margin-left:75%}html.theme--documenter-dark .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .column.is-narrow,html.theme--documenter-dark .column.is-narrow-tablet{flex:none;width:unset}html.theme--documenter-dark .column.is-full,html.theme--documenter-dark .column.is-full-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters,html.theme--documenter-dark .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds,html.theme--documenter-dark .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half,html.theme--documenter-dark .column.is-half-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third,html.theme--documenter-dark .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter,html.theme--documenter-dark .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth,html.theme--documenter-dark .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths,html.theme--documenter-dark .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths,html.theme--documenter-dark .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths,html.theme--documenter-dark .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters,html.theme--documenter-dark .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds,html.theme--documenter-dark .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half,html.theme--documenter-dark .column.is-offset-half-tablet{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third,html.theme--documenter-dark .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter,html.theme--documenter-dark .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth,html.theme--documenter-dark .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths,html.theme--documenter-dark .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths,html.theme--documenter-dark .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths,html.theme--documenter-dark .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--documenter-dark .column.is-0,html.theme--documenter-dark .column.is-0-tablet{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0,html.theme--documenter-dark .column.is-offset-0-tablet{margin-left:0%}html.theme--documenter-dark .column.is-1,html.theme--documenter-dark .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1,html.theme--documenter-dark .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2,html.theme--documenter-dark .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2,html.theme--documenter-dark .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3,html.theme--documenter-dark .column.is-3-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3,html.theme--documenter-dark .column.is-offset-3-tablet{margin-left:25%}html.theme--documenter-dark .column.is-4,html.theme--documenter-dark .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4,html.theme--documenter-dark .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5,html.theme--documenter-dark .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5,html.theme--documenter-dark .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6,html.theme--documenter-dark .column.is-6-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6,html.theme--documenter-dark .column.is-offset-6-tablet{margin-left:50%}html.theme--documenter-dark .column.is-7,html.theme--documenter-dark .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7,html.theme--documenter-dark .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8,html.theme--documenter-dark .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8,html.theme--documenter-dark .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9,html.theme--documenter-dark .column.is-9-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9,html.theme--documenter-dark .column.is-offset-9-tablet{margin-left:75%}html.theme--documenter-dark .column.is-10,html.theme--documenter-dark .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10,html.theme--documenter-dark .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11,html.theme--documenter-dark .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11,html.theme--documenter-dark .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12,html.theme--documenter-dark .column.is-12-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12,html.theme--documenter-dark .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--documenter-dark .column.is-narrow-touch{flex:none;width:unset}html.theme--documenter-dark .column.is-full-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-touch{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-touch{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-touch{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-touch{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-touch{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--documenter-dark .column.is-0-touch{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-touch{margin-left:0%}html.theme--documenter-dark .column.is-1-touch{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-touch{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-touch{margin-left:25%}html.theme--documenter-dark .column.is-4-touch{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-touch{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-touch{margin-left:50%}html.theme--documenter-dark .column.is-7-touch{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-touch{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-touch{margin-left:75%}html.theme--documenter-dark .column.is-10-touch{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-touch{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--documenter-dark .column.is-narrow-desktop{flex:none;width:unset}html.theme--documenter-dark .column.is-full-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-desktop{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--documenter-dark .column.is-0-desktop{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-desktop{margin-left:0%}html.theme--documenter-dark .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-desktop{margin-left:25%}html.theme--documenter-dark .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-desktop{margin-left:50%}html.theme--documenter-dark .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-desktop{margin-left:75%}html.theme--documenter-dark .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--documenter-dark .column.is-narrow-widescreen{flex:none;width:unset}html.theme--documenter-dark .column.is-full-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--documenter-dark .column.is-0-widescreen{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-widescreen{margin-left:0%}html.theme--documenter-dark .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--documenter-dark .column.is-narrow-fullhd{flex:none;width:unset}html.theme--documenter-dark .column.is-full-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--documenter-dark .column.is-0-fullhd{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-fullhd{margin-left:0%}html.theme--documenter-dark .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-fullhd{margin-left:100%}}html.theme--documenter-dark .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .columns:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--documenter-dark .columns.is-centered{justify-content:center}html.theme--documenter-dark .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--documenter-dark .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--documenter-dark .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .columns.is-gapless:last-child{margin-bottom:0}html.theme--documenter-dark .columns.is-mobile{display:flex}html.theme--documenter-dark .columns.is-multiline{flex-wrap:wrap}html.theme--documenter-dark .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-desktop{display:flex}}html.theme--documenter-dark .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--documenter-dark .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--documenter-dark .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--documenter-dark .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--documenter-dark .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--documenter-dark .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--documenter-dark .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--documenter-dark .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--documenter-dark .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--documenter-dark .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--documenter-dark .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--documenter-dark .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--documenter-dark .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .tile.is-child{margin:0 !important}html.theme--documenter-dark .tile.is-parent{padding:.75rem}html.theme--documenter-dark .tile.is-vertical{flex-direction:column}html.theme--documenter-dark .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--documenter-dark .tile:not(.is-child){display:flex}html.theme--documenter-dark .tile.is-1{flex:none;width:8.33333337%}html.theme--documenter-dark .tile.is-2{flex:none;width:16.66666674%}html.theme--documenter-dark .tile.is-3{flex:none;width:25%}html.theme--documenter-dark .tile.is-4{flex:none;width:33.33333337%}html.theme--documenter-dark .tile.is-5{flex:none;width:41.66666674%}html.theme--documenter-dark .tile.is-6{flex:none;width:50%}html.theme--documenter-dark .tile.is-7{flex:none;width:58.33333337%}html.theme--documenter-dark .tile.is-8{flex:none;width:66.66666674%}html.theme--documenter-dark .tile.is-9{flex:none;width:75%}html.theme--documenter-dark .tile.is-10{flex:none;width:83.33333337%}html.theme--documenter-dark .tile.is-11{flex:none;width:91.66666674%}html.theme--documenter-dark .tile.is-12{flex:none;width:100%}}html.theme--documenter-dark .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--documenter-dark .hero .navbar{background:none}html.theme--documenter-dark .hero .tabs ul{border-bottom:none}html.theme--documenter-dark .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-white strong{color:inherit}html.theme--documenter-dark .hero.is-white .title{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--documenter-dark .hero.is-white .subtitle a:not(.button),html.theme--documenter-dark .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-white .navbar-menu{background-color:#fff}}html.theme--documenter-dark .hero.is-white .navbar-item,html.theme--documenter-dark .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--documenter-dark .hero.is-white a.navbar-item:hover,html.theme--documenter-dark .hero.is-white a.navbar-item.is-active,html.theme--documenter-dark .hero.is-white .navbar-link:hover,html.theme--documenter-dark .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--documenter-dark .hero.is-white .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--documenter-dark .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-black strong{color:inherit}html.theme--documenter-dark .hero.is-black .title{color:#fff}html.theme--documenter-dark .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-black .subtitle a:not(.button),html.theme--documenter-dark .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--documenter-dark .hero.is-black .navbar-item,html.theme--documenter-dark .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-black a.navbar-item:hover,html.theme--documenter-dark .hero.is-black a.navbar-item.is-active,html.theme--documenter-dark .hero.is-black .navbar-link:hover,html.theme--documenter-dark .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-black .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--documenter-dark .hero.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-light strong{color:inherit}html.theme--documenter-dark .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--documenter-dark .hero.is-light .subtitle a:not(.button),html.theme--documenter-dark .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-light .navbar-menu{background-color:#ecf0f1}}html.theme--documenter-dark .hero.is-light .navbar-item,html.theme--documenter-dark .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a.navbar-item:hover,html.theme--documenter-dark .hero.is-light a.navbar-item.is-active,html.theme--documenter-dark .hero.is-light .navbar-link:hover,html.theme--documenter-dark .hero.is-light .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--documenter-dark .hero.is-light .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-light .tabs li.is-active a{color:#ecf0f1 !important;opacity:1}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .hero.is-light.is-bold{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}}html.theme--documenter-dark .hero.is-dark,html.theme--documenter-dark .content kbd.hero{background-color:#282f2f;color:#fff}html.theme--documenter-dark .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-dark strong,html.theme--documenter-dark .content kbd.hero strong{color:inherit}html.theme--documenter-dark .hero.is-dark .title,html.theme--documenter-dark .content kbd.hero .title{color:#fff}html.theme--documenter-dark .hero.is-dark .subtitle,html.theme--documenter-dark .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-dark .subtitle a:not(.button),html.theme--documenter-dark .content kbd.hero .subtitle a:not(.button),html.theme--documenter-dark .hero.is-dark .subtitle strong,html.theme--documenter-dark .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-dark .navbar-menu,html.theme--documenter-dark .content kbd.hero .navbar-menu{background-color:#282f2f}}html.theme--documenter-dark .hero.is-dark .navbar-item,html.theme--documenter-dark .content kbd.hero .navbar-item,html.theme--documenter-dark .hero.is-dark .navbar-link,html.theme--documenter-dark .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-dark a.navbar-item:hover,html.theme--documenter-dark .content kbd.hero a.navbar-item:hover,html.theme--documenter-dark .hero.is-dark a.navbar-item.is-active,html.theme--documenter-dark .content kbd.hero a.navbar-item.is-active,html.theme--documenter-dark .hero.is-dark .navbar-link:hover,html.theme--documenter-dark .content kbd.hero .navbar-link:hover,html.theme--documenter-dark .hero.is-dark .navbar-link.is-active,html.theme--documenter-dark .content kbd.hero .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .hero.is-dark .tabs a,html.theme--documenter-dark .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-dark .tabs a:hover,html.theme--documenter-dark .content kbd.hero .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-dark .tabs li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs li.is-active a{color:#282f2f !important;opacity:1}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#282f2f}html.theme--documenter-dark .hero.is-dark.is-bold,html.theme--documenter-dark .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-dark.is-bold .navbar-menu,html.theme--documenter-dark .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}}html.theme--documenter-dark .hero.is-primary,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-primary strong,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--documenter-dark .hero.is-primary .title,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--documenter-dark .hero.is-primary .subtitle,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-primary .subtitle a:not(.button),html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--documenter-dark .hero.is-primary .subtitle strong,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-primary .navbar-menu,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#375a7f}}html.theme--documenter-dark .hero.is-primary .navbar-item,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--documenter-dark .hero.is-primary .navbar-link,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-primary a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--documenter-dark .hero.is-primary a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--documenter-dark .hero.is-primary .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--documenter-dark .hero.is-primary .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .hero.is-primary .tabs a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-primary .tabs a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-primary .tabs li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#375a7f !important;opacity:1}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#375a7f}html.theme--documenter-dark .hero.is-primary.is-bold,html.theme--documenter-dark .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-primary.is-bold .navbar-menu,html.theme--documenter-dark .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}}html.theme--documenter-dark .hero.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-link strong{color:inherit}html.theme--documenter-dark .hero.is-link .title{color:#fff}html.theme--documenter-dark .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-link .subtitle a:not(.button),html.theme--documenter-dark .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-link .navbar-menu{background-color:#1abc9c}}html.theme--documenter-dark .hero.is-link .navbar-item,html.theme--documenter-dark .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-link a.navbar-item:hover,html.theme--documenter-dark .hero.is-link a.navbar-item.is-active,html.theme--documenter-dark .hero.is-link .navbar-link:hover,html.theme--documenter-dark .hero.is-link .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-link .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-link .tabs li.is-active a{color:#1abc9c !important;opacity:1}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1abc9c}html.theme--documenter-dark .hero.is-link.is-bold{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}}html.theme--documenter-dark .hero.is-info{background-color:#024c7d;color:#fff}html.theme--documenter-dark .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-info strong{color:inherit}html.theme--documenter-dark .hero.is-info .title{color:#fff}html.theme--documenter-dark .hero.is-info .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-info .subtitle a:not(.button),html.theme--documenter-dark .hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-info .navbar-menu{background-color:#024c7d}}html.theme--documenter-dark .hero.is-info .navbar-item,html.theme--documenter-dark .hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-info a.navbar-item:hover,html.theme--documenter-dark .hero.is-info a.navbar-item.is-active,html.theme--documenter-dark .hero.is-info .navbar-link:hover,html.theme--documenter-dark .hero.is-info .navbar-link.is-active{background-color:#023d64;color:#fff}html.theme--documenter-dark .hero.is-info .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-info .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-info .tabs li.is-active a{color:#024c7d !important;opacity:1}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#024c7d}html.theme--documenter-dark .hero.is-info.is-bold{background-image:linear-gradient(141deg, #003a4c 0%, #024c7d 71%, #004299 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #003a4c 0%, #024c7d 71%, #004299 100%)}}html.theme--documenter-dark .hero.is-success{background-color:#008438;color:#fff}html.theme--documenter-dark .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-success strong{color:inherit}html.theme--documenter-dark .hero.is-success .title{color:#fff}html.theme--documenter-dark .hero.is-success .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-success .subtitle a:not(.button),html.theme--documenter-dark .hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-success .navbar-menu{background-color:#008438}}html.theme--documenter-dark .hero.is-success .navbar-item,html.theme--documenter-dark .hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-success a.navbar-item:hover,html.theme--documenter-dark .hero.is-success a.navbar-item.is-active,html.theme--documenter-dark .hero.is-success .navbar-link:hover,html.theme--documenter-dark .hero.is-success .navbar-link.is-active{background-color:#006b2d;color:#fff}html.theme--documenter-dark .hero.is-success .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-success .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-success .tabs li.is-active a{color:#008438 !important;opacity:1}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#008438}html.theme--documenter-dark .hero.is-success.is-bold{background-image:linear-gradient(141deg, #005115 0%, #008438 71%, #009e5d 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #005115 0%, #008438 71%, #009e5d 100%)}}html.theme--documenter-dark .hero.is-warning{background-color:#ad8100;color:#fff}html.theme--documenter-dark .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-warning strong{color:inherit}html.theme--documenter-dark .hero.is-warning .title{color:#fff}html.theme--documenter-dark .hero.is-warning .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-warning .subtitle a:not(.button),html.theme--documenter-dark .hero.is-warning .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-warning .navbar-menu{background-color:#ad8100}}html.theme--documenter-dark .hero.is-warning .navbar-item,html.theme--documenter-dark .hero.is-warning .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-warning a.navbar-item:hover,html.theme--documenter-dark .hero.is-warning a.navbar-item.is-active,html.theme--documenter-dark .hero.is-warning .navbar-link:hover,html.theme--documenter-dark .hero.is-warning .navbar-link.is-active{background-color:#946e00;color:#fff}html.theme--documenter-dark .hero.is-warning .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-warning .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-warning .tabs li.is-active a{color:#ad8100 !important;opacity:1}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#ad8100}html.theme--documenter-dark .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #7a4700 0%, #ad8100 71%, #c7b500 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #7a4700 0%, #ad8100 71%, #c7b500 100%)}}html.theme--documenter-dark .hero.is-danger{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-danger strong{color:inherit}html.theme--documenter-dark .hero.is-danger .title{color:#fff}html.theme--documenter-dark .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-danger .subtitle a:not(.button),html.theme--documenter-dark .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-danger .navbar-menu{background-color:#9e1b0d}}html.theme--documenter-dark .hero.is-danger .navbar-item,html.theme--documenter-dark .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-danger a.navbar-item:hover,html.theme--documenter-dark .hero.is-danger a.navbar-item.is-active,html.theme--documenter-dark .hero.is-danger .navbar-link:hover,html.theme--documenter-dark .hero.is-danger .navbar-link.is-active{background-color:#86170b;color:#fff}html.theme--documenter-dark .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-danger .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-danger .tabs li.is-active a{color:#9e1b0d !important;opacity:1}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#9e1b0d}html.theme--documenter-dark .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #75030b 0%, #9e1b0d 71%, #ba380a 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #75030b 0%, #9e1b0d 71%, #ba380a 100%)}}html.theme--documenter-dark .hero.is-small .hero-body,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--documenter-dark .hero.is-halfheight .hero-body,html.theme--documenter-dark .hero.is-fullheight .hero-body,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--documenter-dark .hero.is-halfheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .hero.is-halfheight{min-height:50vh}html.theme--documenter-dark .hero.is-fullheight{min-height:100vh}html.theme--documenter-dark .hero-video{overflow:hidden}html.theme--documenter-dark .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--documenter-dark .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-video{display:none}}html.theme--documenter-dark .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-buttons .button{display:flex}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-buttons{display:flex;justify-content:center}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--documenter-dark .hero-head,html.theme--documenter-dark .hero-foot{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-body{padding:3rem 3rem}}html.theme--documenter-dark .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--documenter-dark .section{padding:3rem 3rem}html.theme--documenter-dark .section.is-medium{padding:9rem 4.5rem}html.theme--documenter-dark .section.is-large{padding:18rem 6rem}}html.theme--documenter-dark .footer{background-color:#282f2f;padding:3rem 1.5rem 6rem}html.theme--documenter-dark hr{height:1px}html.theme--documenter-dark h6{text-transform:uppercase;letter-spacing:0.5px}html.theme--documenter-dark .hero{background-color:#343c3d}html.theme--documenter-dark a{transition:all 200ms ease}html.theme--documenter-dark .button{transition:all 200ms ease;border-width:1px;color:#fff}html.theme--documenter-dark .button.is-active,html.theme--documenter-dark .button.is-focused,html.theme--documenter-dark .button:active,html.theme--documenter-dark .button:focus{box-shadow:0 0 0 2px rgba(140,155,157,0.5)}html.theme--documenter-dark .button.is-white.is-hovered,html.theme--documenter-dark .button.is-white:hover{background-color:#fff}html.theme--documenter-dark .button.is-white.is-active,html.theme--documenter-dark .button.is-white.is-focused,html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white:focus{border-color:#fff;box-shadow:0 0 0 2px rgba(255,255,255,0.5)}html.theme--documenter-dark .button.is-black.is-hovered,html.theme--documenter-dark .button.is-black:hover{background-color:#1d1d1d}html.theme--documenter-dark .button.is-black.is-active,html.theme--documenter-dark .button.is-black.is-focused,html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black:focus{border-color:#0a0a0a;box-shadow:0 0 0 2px rgba(10,10,10,0.5)}html.theme--documenter-dark .button.is-light.is-hovered,html.theme--documenter-dark .button.is-light:hover{background-color:#fff}html.theme--documenter-dark .button.is-light.is-active,html.theme--documenter-dark .button.is-light.is-focused,html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light:focus{border-color:#ecf0f1;box-shadow:0 0 0 2px rgba(236,240,241,0.5)}html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered,html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover{background-color:#3a4344}html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused,html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus{border-color:#282f2f;box-shadow:0 0 0 2px rgba(40,47,47,0.5)}html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:hover{background-color:#436d9a}html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark .docstring>section>a.button.is-active.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink,html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus{border-color:#375a7f;box-shadow:0 0 0 2px rgba(55,90,127,0.5)}html.theme--documenter-dark .button.is-link.is-hovered,html.theme--documenter-dark .button.is-link:hover{background-color:#1fdeb8}html.theme--documenter-dark .button.is-link.is-active,html.theme--documenter-dark .button.is-link.is-focused,html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link:focus{border-color:#1abc9c;box-shadow:0 0 0 2px rgba(26,188,156,0.5)}html.theme--documenter-dark .button.is-info.is-hovered,html.theme--documenter-dark .button.is-info:hover{background-color:#0363a3}html.theme--documenter-dark .button.is-info.is-active,html.theme--documenter-dark .button.is-info.is-focused,html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info:focus{border-color:#024c7d;box-shadow:0 0 0 2px rgba(2,76,125,0.5)}html.theme--documenter-dark .button.is-success.is-hovered,html.theme--documenter-dark .button.is-success:hover{background-color:#00aa48}html.theme--documenter-dark .button.is-success.is-active,html.theme--documenter-dark .button.is-success.is-focused,html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success:focus{border-color:#008438;box-shadow:0 0 0 2px rgba(0,132,56,0.5)}html.theme--documenter-dark .button.is-warning.is-hovered,html.theme--documenter-dark .button.is-warning:hover{background-color:#d39e00}html.theme--documenter-dark .button.is-warning.is-active,html.theme--documenter-dark .button.is-warning.is-focused,html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning:focus{border-color:#ad8100;box-shadow:0 0 0 2px rgba(173,129,0,0.5)}html.theme--documenter-dark .button.is-danger.is-hovered,html.theme--documenter-dark .button.is-danger:hover{background-color:#c12110}html.theme--documenter-dark .button.is-danger.is-active,html.theme--documenter-dark .button.is-danger.is-focused,html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger:focus{border-color:#9e1b0d;box-shadow:0 0 0 2px rgba(158,27,13,0.5)}html.theme--documenter-dark .label{color:#dbdee0}html.theme--documenter-dark .button,html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .select,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea{height:2.5em}html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em}html.theme--documenter-dark .select:after,html.theme--documenter-dark .select select{border-width:1px}html.theme--documenter-dark .control.has-addons .button,html.theme--documenter-dark .control.has-addons .input,html.theme--documenter-dark .control.has-addons #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-addons form.docs-search>input,html.theme--documenter-dark .control.has-addons .select{margin-right:-1px}html.theme--documenter-dark .notification{background-color:#343c3d}html.theme--documenter-dark .card{box-shadow:none;border:1px solid #343c3d;background-color:#282f2f;border-radius:.4em}html.theme--documenter-dark .card .card-image img{border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-header{box-shadow:none;background-color:rgba(18,18,18,0.2);border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-footer{background-color:rgba(18,18,18,0.2)}html.theme--documenter-dark .card .card-footer,html.theme--documenter-dark .card .card-footer-item{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .notification.is-white a:not(.button){color:#0a0a0a;text-decoration:underline}html.theme--documenter-dark .notification.is-black a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-light a:not(.button){color:rgba(0,0,0,0.7);text-decoration:underline}html.theme--documenter-dark .notification.is-dark a:not(.button),html.theme--documenter-dark .content kbd.notification a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-primary a:not(.button),html.theme--documenter-dark .docstring>section>a.notification.docs-sourcelink a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-link a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-info a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-success a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-warning a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-danger a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .tag,html.theme--documenter-dark .content kbd,html.theme--documenter-dark .docstring>section>a.docs-sourcelink{border-radius:.4em}html.theme--documenter-dark .menu-list a{transition:all 300ms ease}html.theme--documenter-dark .modal-card-body{background-color:#282f2f}html.theme--documenter-dark .modal-card-foot,html.theme--documenter-dark .modal-card-head{border-color:#343c3d}html.theme--documenter-dark .message-header{font-weight:700;background-color:#343c3d;color:#fff}html.theme--documenter-dark .message-body{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .navbar{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent{background:none}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar .navbar-menu{background-color:#375a7f;border-radius:0 0 .4em .4em}}html.theme--documenter-dark .hero .navbar,html.theme--documenter-dark body>.navbar{border-radius:0}html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous{border-width:1px}html.theme--documenter-dark .panel-block,html.theme--documenter-dark .panel-heading,html.theme--documenter-dark .panel-tabs{border-width:1px}html.theme--documenter-dark .panel-block:first-child,html.theme--documenter-dark .panel-heading:first-child,html.theme--documenter-dark .panel-tabs:first-child{border-top-width:1px}html.theme--documenter-dark .panel-heading{font-weight:700}html.theme--documenter-dark .panel-tabs a{border-width:1px;margin-bottom:-1px}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#17a689}html.theme--documenter-dark .panel-block:hover{color:#1dd2af}html.theme--documenter-dark .panel-block:hover .panel-icon{color:#1dd2af}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#17a689}html.theme--documenter-dark .tabs a{border-bottom-width:1px;margin-bottom:-1px}html.theme--documenter-dark .tabs ul{border-bottom-width:1px}html.theme--documenter-dark .tabs.is-boxed a{border-width:1px}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#1f2424}html.theme--documenter-dark .tabs.is-toggle li a{border-width:1px;margin-bottom:0}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .hero.is-white .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-black .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-light .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-dark .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark .content kbd.hero .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-primary .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-link .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-info .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-success .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-warning .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-danger .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark h1 .docs-heading-anchor,html.theme--documenter-dark h1 .docs-heading-anchor:hover,html.theme--documenter-dark h1 .docs-heading-anchor:visited,html.theme--documenter-dark h2 .docs-heading-anchor,html.theme--documenter-dark h2 .docs-heading-anchor:hover,html.theme--documenter-dark h2 .docs-heading-anchor:visited,html.theme--documenter-dark h3 .docs-heading-anchor,html.theme--documenter-dark h3 .docs-heading-anchor:hover,html.theme--documenter-dark h3 .docs-heading-anchor:visited,html.theme--documenter-dark h4 .docs-heading-anchor,html.theme--documenter-dark h4 .docs-heading-anchor:hover,html.theme--documenter-dark h4 .docs-heading-anchor:visited,html.theme--documenter-dark h5 .docs-heading-anchor,html.theme--documenter-dark h5 .docs-heading-anchor:hover,html.theme--documenter-dark h5 .docs-heading-anchor:visited,html.theme--documenter-dark h6 .docs-heading-anchor,html.theme--documenter-dark h6 .docs-heading-anchor:hover,html.theme--documenter-dark h6 .docs-heading-anchor:visited{color:#f2f2f2}html.theme--documenter-dark h1 .docs-heading-anchor-permalink,html.theme--documenter-dark h2 .docs-heading-anchor-permalink,html.theme--documenter-dark h3 .docs-heading-anchor-permalink,html.theme--documenter-dark h4 .docs-heading-anchor-permalink,html.theme--documenter-dark h5 .docs-heading-anchor-permalink,html.theme--documenter-dark h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--documenter-dark h1 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h2 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h3 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h4 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h5 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--documenter-dark h1:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h2:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h3:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h4:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h5:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--documenter-dark .docs-light-only{display:none !important}html.theme--documenter-dark pre{position:relative;overflow:hidden}html.theme--documenter-dark pre code,html.theme--documenter-dark pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--documenter-dark pre code:first-of-type,html.theme--documenter-dark pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--documenter-dark pre code:last-of-type,html.theme--documenter-dark pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--documenter-dark pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#fff;cursor:pointer;text-align:center}html.theme--documenter-dark pre .copy-button:focus,html.theme--documenter-dark pre .copy-button:hover{opacity:1;background:rgba(255,255,255,0.1);color:#1abc9c}html.theme--documenter-dark pre .copy-button.success{color:#259a12;opacity:1}html.theme--documenter-dark pre .copy-button.error{color:#cb3c33;opacity:1}html.theme--documenter-dark pre:hover .copy-button{opacity:1}html.theme--documenter-dark .admonition{background-color:#282f2f;border-style:solid;border-width:1px;border-color:#5e6d6f;border-radius:.4em;font-size:1rem}html.theme--documenter-dark .admonition strong{color:currentColor}html.theme--documenter-dark .admonition.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--documenter-dark .admonition.is-medium{font-size:1.25rem}html.theme--documenter-dark .admonition.is-large{font-size:1.5rem}html.theme--documenter-dark .admonition.is-default{background-color:#282f2f;border-color:#5e6d6f}html.theme--documenter-dark .admonition.is-default>.admonition-header{background-color:#5e6d6f;color:#fff}html.theme--documenter-dark .admonition.is-default>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-info{background-color:#282f2f;border-color:#024c7d}html.theme--documenter-dark .admonition.is-info>.admonition-header{background-color:#024c7d;color:#fff}html.theme--documenter-dark .admonition.is-info>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-success{background-color:#282f2f;border-color:#008438}html.theme--documenter-dark .admonition.is-success>.admonition-header{background-color:#008438;color:#fff}html.theme--documenter-dark .admonition.is-success>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-warning{background-color:#282f2f;border-color:#ad8100}html.theme--documenter-dark .admonition.is-warning>.admonition-header{background-color:#ad8100;color:#fff}html.theme--documenter-dark .admonition.is-warning>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-danger{background-color:#282f2f;border-color:#9e1b0d}html.theme--documenter-dark .admonition.is-danger>.admonition-header{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .admonition.is-danger>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-compat{background-color:#282f2f;border-color:#137886}html.theme--documenter-dark .admonition.is-compat>.admonition-header{background-color:#137886;color:#fff}html.theme--documenter-dark .admonition.is-compat>.admonition-body{color:#fff}html.theme--documenter-dark .admonition-header{color:#fff;background-color:#5e6d6f;align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--documenter-dark .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--documenter-dark details.admonition.is-details>.admonition-header{list-style:none}html.theme--documenter-dark details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--documenter-dark details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--documenter-dark .admonition-body{color:#fff;padding:0.5rem .75rem}html.theme--documenter-dark .admonition-body pre{background-color:#282f2f}html.theme--documenter-dark .admonition-body code{background-color:rgba(255,255,255,0.05)}html.theme--documenter-dark .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:1px solid #5e6d6f;box-shadow:none;max-width:100%}html.theme--documenter-dark .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#282f2f;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .docstring>header code{background-color:transparent}html.theme--documenter-dark .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--documenter-dark .docstring>header .docstring-binding{margin-right:0.3em}html.theme--documenter-dark .docstring>header .docstring-category{margin-left:0.3em}html.theme--documenter-dark .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .docstring>section:last-child{border-bottom:none}html.theme--documenter-dark .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--documenter-dark .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--documenter-dark .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--documenter-dark .documenter-example-output{background-color:#1f2424}html.theme--documenter-dark .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#282f2f;color:#fff;border-bottom:3px solid #9e1b0d;padding:10px 35px;text-align:center;font-size:15px}html.theme--documenter-dark .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--documenter-dark .outdated-warning-overlay a{color:#1abc9c}html.theme--documenter-dark .outdated-warning-overlay a:hover{color:#1dd2af}html.theme--documenter-dark .content pre{border:1px solid #5e6d6f}html.theme--documenter-dark .content code{font-weight:inherit}html.theme--documenter-dark .content a code{color:#1abc9c}html.theme--documenter-dark .content h1 code,html.theme--documenter-dark .content h2 code,html.theme--documenter-dark .content h3 code,html.theme--documenter-dark .content h4 code,html.theme--documenter-dark .content h5 code,html.theme--documenter-dark .content h6 code{color:#f2f2f2}html.theme--documenter-dark .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--documenter-dark .content blockquote>ul:first-child,html.theme--documenter-dark .content blockquote>ol:first-child,html.theme--documenter-dark .content .admonition-body>ul:first-child,html.theme--documenter-dark .content .admonition-body>ol:first-child{margin-top:0}html.theme--documenter-dark pre,html.theme--documenter-dark code{font-variant-ligatures:no-contextual}html.theme--documenter-dark .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb a.is-disabled,html.theme--documenter-dark .breadcrumb a.is-disabled:hover{color:#f2f2f2}html.theme--documenter-dark .hljs{background:initial !important}html.theme--documenter-dark .katex .katex-mathml{top:0;right:0}html.theme--documenter-dark .katex-display,html.theme--documenter-dark mjx-container,html.theme--documenter-dark .MathJax_Display{margin:0.5em 0 !important}html.theme--documenter-dark html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--documenter-dark li.no-marker{list-style:none}html.theme--documenter-dark #documenter .docs-main>article{overflow-wrap:break-word}html.theme--documenter-dark #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main{width:100%}html.theme--documenter-dark #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-main>header,html.theme--documenter-dark #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar{background-color:#1f2424;border-bottom:1px solid #5e6d6f;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--documenter-dark #documenter .docs-main section.footnotes{border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-main section.footnotes li .tag:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--documenter-dark .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--documenter-dark #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #5e6d6f;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--documenter-dark #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--documenter-dark #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--documenter-dark #documenter .docs-sidebar{display:flex;flex-direction:column;color:#fff;background-color:#282f2f;border-right:1px solid #5e6d6f;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--documenter-dark #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar{left:0;top:0}}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a,html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a:hover{color:#fff}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #5e6d6f;display:none;padding:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #5e6d6f;padding-bottom:1.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#fff;background:#282f2f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#fff;background-color:#32393a}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #5e6d6f;border-bottom:1px solid #5e6d6f;background-color:#1f2424}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#1f2424;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#32393a;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--documenter-dark #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}html.theme--documenter-dark kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--documenter-dark .search-min-width-50{min-width:50%}html.theme--documenter-dark .search-min-height-100{min-height:100%}html.theme--documenter-dark .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--documenter-dark .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--documenter-dark .search-result-link:hover,html.theme--documenter-dark .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--documenter-dark .search-result-link .property-search-result-badge,html.theme--documenter-dark .search-result-link .search-filter{transition:all 300ms}html.theme--documenter-dark .property-search-result-badge,html.theme--documenter-dark .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--documenter-dark .search-result-link:hover .property-search-result-badge,html.theme--documenter-dark .search-result-link:hover .search-filter,html.theme--documenter-dark .search-result-link:focus .property-search-result-badge,html.theme--documenter-dark .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--documenter-dark .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--documenter-dark .search-filter:hover,html.theme--documenter-dark .search-filter:focus{color:#333}html.theme--documenter-dark .search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}html.theme--documenter-dark .search-filter-selected:hover,html.theme--documenter-dark .search-filter-selected:focus{color:#f5f5f5}html.theme--documenter-dark .search-result-highlight{background-color:#ffdd57;color:black}html.theme--documenter-dark .search-divider{border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .search-result-title{width:85%;color:#f5f5f5}html.theme--documenter-dark .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--documenter-dark .w-100{width:100%}html.theme--documenter-dark .gap-2{gap:0.5rem}html.theme--documenter-dark .gap-4{gap:1rem}html.theme--documenter-dark .gap-8{gap:2rem}html.theme--documenter-dark{background-color:#1f2424;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark .ansi span.sgr1{font-weight:bolder}html.theme--documenter-dark .ansi span.sgr2{font-weight:lighter}html.theme--documenter-dark .ansi span.sgr3{font-style:italic}html.theme--documenter-dark .ansi span.sgr4{text-decoration:underline}html.theme--documenter-dark .ansi span.sgr7{color:#1f2424;background-color:#fff}html.theme--documenter-dark .ansi span.sgr8{color:transparent}html.theme--documenter-dark .ansi span.sgr8 span{color:transparent}html.theme--documenter-dark .ansi span.sgr9{text-decoration:line-through}html.theme--documenter-dark .ansi span.sgr30{color:#242424}html.theme--documenter-dark .ansi span.sgr31{color:#f6705f}html.theme--documenter-dark .ansi span.sgr32{color:#4fb43a}html.theme--documenter-dark .ansi span.sgr33{color:#f4c72f}html.theme--documenter-dark .ansi span.sgr34{color:#7587f0}html.theme--documenter-dark .ansi span.sgr35{color:#bc89d3}html.theme--documenter-dark .ansi span.sgr36{color:#49b6ca}html.theme--documenter-dark .ansi span.sgr37{color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr40{background-color:#242424}html.theme--documenter-dark .ansi span.sgr41{background-color:#f6705f}html.theme--documenter-dark .ansi span.sgr42{background-color:#4fb43a}html.theme--documenter-dark .ansi span.sgr43{background-color:#f4c72f}html.theme--documenter-dark .ansi span.sgr44{background-color:#7587f0}html.theme--documenter-dark .ansi span.sgr45{background-color:#bc89d3}html.theme--documenter-dark .ansi span.sgr46{background-color:#49b6ca}html.theme--documenter-dark .ansi span.sgr47{background-color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr90{color:#92a0a2}html.theme--documenter-dark .ansi span.sgr91{color:#ff8674}html.theme--documenter-dark .ansi span.sgr92{color:#79d462}html.theme--documenter-dark .ansi span.sgr93{color:#ffe76b}html.theme--documenter-dark .ansi span.sgr94{color:#8a98ff}html.theme--documenter-dark .ansi span.sgr95{color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr96{color:#6bc8db}html.theme--documenter-dark .ansi span.sgr97{color:#ecf0f1}html.theme--documenter-dark .ansi span.sgr100{background-color:#92a0a2}html.theme--documenter-dark .ansi span.sgr101{background-color:#ff8674}html.theme--documenter-dark .ansi span.sgr102{background-color:#79d462}html.theme--documenter-dark .ansi span.sgr103{background-color:#ffe76b}html.theme--documenter-dark .ansi span.sgr104{background-color:#8a98ff}html.theme--documenter-dark .ansi span.sgr105{background-color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr106{background-color:#6bc8db}html.theme--documenter-dark .ansi span.sgr107{background-color:#ecf0f1}html.theme--documenter-dark code.language-julia-repl>span.hljs-meta{color:#4fb43a;font-weight:bolder}html.theme--documenter-dark .hljs{background:#2b2b2b;color:#f8f8f2}html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-quote{color:#d4d0ab}html.theme--documenter-dark .hljs-variable,html.theme--documenter-dark .hljs-template-variable,html.theme--documenter-dark .hljs-tag,html.theme--documenter-dark .hljs-name,html.theme--documenter-dark .hljs-selector-id,html.theme--documenter-dark .hljs-selector-class,html.theme--documenter-dark .hljs-regexp,html.theme--documenter-dark .hljs-deletion{color:#ffa07a}html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-link{color:#f5ab35}html.theme--documenter-dark .hljs-attribute{color:#ffd700}html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-addition{color:#abe338}html.theme--documenter-dark .hljs-title,html.theme--documenter-dark .hljs-section{color:#00e0e0}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{color:#dcc6e0}html.theme--documenter-dark .hljs-emphasis{font-style:italic}html.theme--documenter-dark .hljs-strong{font-weight:bold}@media screen and (-ms-high-contrast: active){html.theme--documenter-dark .hljs-addition,html.theme--documenter-dark .hljs-attribute,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-link,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-quote{color:highlight}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{font-weight:bold}}html.theme--documenter-dark .hljs-subst{color:#f8f8f2} diff --git a/previews/PR3536/assets/themes/documenter-light.css b/previews/PR3536/assets/themes/documenter-light.css new file mode 100644 index 00000000000..60a317a4c53 --- /dev/null +++ b/previews/PR3536/assets/themes/documenter-light.css @@ -0,0 +1,9 @@ +.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.file-cta,.file-name,.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input,.button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus,.pagination-ellipsis:focus,.file-cta:focus,.file-name:focus,.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.button:focus,.is-focused.pagination-previous,.is-focused.pagination-next,.is-focused.pagination-link,.is-focused.pagination-ellipsis,.is-focused.file-cta,.is-focused.file-name,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-focused.button,.pagination-previous:active,.pagination-next:active,.pagination-link:active,.pagination-ellipsis:active,.file-cta:active,.file-name:active,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.button:active,.is-active.pagination-previous,.is-active.pagination-next,.is-active.pagination-link,.is-active.pagination-ellipsis,.is-active.file-cta,.is-active.file-name,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.is-active.button{outline:none}.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled],.pagination-ellipsis[disabled],.file-cta[disabled],.file-name[disabled],.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],.button[disabled],fieldset[disabled] .pagination-previous,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] .button{cursor:not-allowed}.tabs,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.breadcrumb,.file,.button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}.admonition:not(:last-child),.tabs:not(:last-child),.pagination:not(:last-child),.message:not(:last-child),.level:not(:last-child),.breadcrumb:not(:last-child),.block:not(:last-child),.title:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.progress:not(:last-child),.notification:not(:last-child),.content:not(:last-child),.box:not(:last-child){margin-bottom:1.5rem}.modal-close,.delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}.modal-close::before,.delete::before,.modal-close::after,.delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.modal-close::before,.delete::before{height:2px;width:50%}.modal-close::after,.delete::after{height:50%;width:2px}.modal-close:hover,.delete:hover,.modal-close:focus,.delete:focus{background-color:rgba(10,10,10,0.3)}.modal-close:active,.delete:active{background-color:rgba(10,10,10,0.4)}.is-small.modal-close,#documenter .docs-sidebar form.docs-search>input.modal-close,.is-small.delete,#documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.modal-close,.is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.modal-close,.is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.control.is-loading::after,.select.is-loading::after,.loader,.button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.modal-background,.modal,.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363636 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c1c !important}.has-background-dark{background-color:#363636 !important}.has-text-primary{color:#4eb5de !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#27a1d2 !important}.has-background-primary{background-color:#4eb5de !important}.has-text-primary-light{color:#eef8fc !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#c3e6f4 !important}.has-background-primary-light{background-color:#eef8fc !important}.has-text-primary-dark{color:#1a6d8e !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#228eb9 !important}.has-background-primary-dark{background-color:#1a6d8e !important}.has-text-link{color:#2e63b8 !important}a.has-text-link:hover,a.has-text-link:focus{color:#244d8f !important}.has-background-link{background-color:#2e63b8 !important}.has-text-link-light{color:#eff3fb !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c6d6f1 !important}.has-background-link-light{background-color:#eff3fb !important}.has-text-link-dark{color:#3169c4 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#5485d4 !important}.has-background-link-dark{background-color:#3169c4 !important}.has-text-info{color:#209cee !important}a.has-text-info:hover,a.has-text-info:focus{color:#1081cb !important}.has-background-info{background-color:#209cee !important}.has-text-info-light{color:#ecf7fe !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#bde2fa !important}.has-background-info-light{background-color:#ecf7fe !important}.has-text-info-dark{color:#0e72b4 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#1190e3 !important}.has-background-info-dark{background-color:#0e72b4 !important}.has-text-success{color:#22c35b !important}a.has-text-success:hover,a.has-text-success:focus{color:#1a9847 !important}.has-background-success{background-color:#22c35b !important}.has-text-success-light{color:#eefcf3 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c2f4d4 !important}.has-background-success-light{background-color:#eefcf3 !important}.has-text-success-dark{color:#198f43 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#21bb57 !important}.has-background-success-dark{background-color:#198f43 !important}.has-text-warning{color:#ffdd57 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#ffd324 !important}.has-background-warning{background-color:#ffdd57 !important}.has-text-warning-light{color:#fffbeb !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fff1b8 !important}.has-background-warning-light{background-color:#fffbeb !important}.has-text-warning-dark{color:#947600 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#c79f00 !important}.has-background-warning-dark{background-color:#947600 !important}.has-text-danger{color:#da0b00 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a70800 !important}.has-background-danger{background-color:#da0b00 !important}.has-text-danger-light{color:#ffeceb !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#ffbbb8 !important}.has-background-danger-light{background-color:#ffeceb !important}.has-text-danger-dark{color:#f50c00 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#ff3429 !important}.has-background-danger-dark{background-color:#f50c00 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363636 !important}.has-background-grey-darker{background-color:#363636 !important}.has-text-grey-dark{color:#4a4a4a !important}.has-background-grey-dark{background-color:#4a4a4a !important}.has-text-grey{color:#6b6b6b !important}.has-background-grey{background-color:#6b6b6b !important}.has-text-grey-light{color:#b5b5b5 !important}.has-background-grey-light{background-color:#b5b5b5 !important}.has-text-grey-lighter{color:#dbdbdb !important}.has-background-grey-lighter{background-color:#dbdbdb !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}body{color:#222;font-size:1em;font-weight:400;line-height:1.5}a{color:#2e63b8;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:rgba(0,0,0,0.05);color:#000;font-size:.875em;font-weight:normal;padding:.1em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type="checkbox"],input[type="radio"]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#222;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#222;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#222}@keyframes spinAround{from{transform:rotate(0deg)}to{transform:rotate(359deg)}}.box{background-color:#fff;border-radius:6px;box-shadow:#bbb;color:#222;display:block;padding:1.25rem}a.box:hover,a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #2e63b8}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #2e63b8}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#222;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-small,.button #documenter .docs-sidebar form.docs-search>input.icon,#documenter .docs-sidebar .button form.docs-search>input.icon,.button .icon.is-medium,.button .icon.is-large{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}.button:hover,.button.is-hovered{border-color:#b5b5b5;color:#363636}.button:focus,.button.is-focused{border-color:#3c5dcd;color:#363636}.button:focus:not(:active),.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button:active,.button.is-active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#222;text-decoration:underline}.button.is-text:hover,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text.is-focused{background-color:#f5f5f5;color:#222}.button.is-text:active,.button.is-text.is-active{background-color:#e8e8e8;color:#222}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#2e63b8;text-decoration:none}.button.is-ghost:hover,.button.is-ghost.is-hovered{color:#2e63b8;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white:hover,.button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white:focus,.button.is-white.is-focused{border-color:transparent;color:#0a0a0a}.button.is-white:focus:not(:active),.button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.button.is-white:active,.button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted:hover,.button.is-white.is-inverted.is-hovered{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined:hover,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-outlined.is-loading:hover::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined:hover,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading:hover::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black:hover,.button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}.button.is-black:focus,.button.is-black.is-focused{border-color:transparent;color:#fff}.button.is-black:focus:not(:active),.button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.button.is-black:active,.button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted:hover,.button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined:hover,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-outlined.is-loading:hover::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined:hover,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading:hover::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:hover,.button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus,.button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus:not(:active),.button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.button.is-light:active,.button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted:hover,.button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined:hover,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-outlined.is-loading:hover::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}.button.is-light.is-inverted.is-outlined:hover,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading:hover::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}.button.is-dark,.content kbd.button{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark:hover,.content kbd.button:hover,.button.is-dark.is-hovered,.content kbd.button.is-hovered{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark:focus,.content kbd.button:focus,.button.is-dark.is-focused,.content kbd.button.is-focused{border-color:transparent;color:#fff}.button.is-dark:focus:not(:active),.content kbd.button:focus:not(:active),.button.is-dark.is-focused:not(:active),.content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.button.is-dark:active,.content kbd.button:active,.button.is-dark.is-active,.content kbd.button.is-active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],.content kbd.button[disabled],fieldset[disabled] .button.is-dark,fieldset[disabled] .content kbd.button,.content fieldset[disabled] kbd.button{background-color:#363636;border-color:#363636;box-shadow:none}.button.is-dark.is-inverted,.content kbd.button.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted:hover,.content kbd.button.is-inverted:hover,.button.is-dark.is-inverted.is-hovered,.content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],.content kbd.button.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted,fieldset[disabled] .content kbd.button.is-inverted,.content fieldset[disabled] kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after,.content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined,.content kbd.button.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined:hover,.content kbd.button.is-outlined:hover,.button.is-dark.is-outlined.is-hovered,.content kbd.button.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.content kbd.button.is-outlined:focus,.button.is-dark.is-outlined.is-focused,.content kbd.button.is-outlined.is-focused{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after,.content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-outlined.is-loading:hover::after,.content kbd.button.is-outlined.is-loading:hover::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.content kbd.button.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading.is-focused::after,.content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined[disabled],.content kbd.button.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined,fieldset[disabled] .content kbd.button.is-outlined,.content fieldset[disabled] kbd.button.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined,.content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined:hover,.content kbd.button.is-inverted.is-outlined:hover,.button.is-dark.is-inverted.is-outlined.is-hovered,.content kbd.button.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.content kbd.button.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined.is-focused,.content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading:hover::after,.content kbd.button.is-inverted.is-outlined.is-loading:hover::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.content kbd.button.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-inverted.is-outlined[disabled],.content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined,fieldset[disabled] .content kbd.button.is-inverted.is-outlined,.content fieldset[disabled] kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary,.docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:transparent;color:#fff}.button.is-primary:hover,.docstring>section>a.button.docs-sourcelink:hover,.button.is-primary.is-hovered,.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#43b1dc;border-color:transparent;color:#fff}.button.is-primary:focus,.docstring>section>a.button.docs-sourcelink:focus,.button.is-primary.is-focused,.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}.button.is-primary:focus:not(:active),.docstring>section>a.button.docs-sourcelink:focus:not(:active),.button.is-primary.is-focused:not(:active),.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.button.is-primary:active,.docstring>section>a.button.docs-sourcelink:active,.button.is-primary.is-active,.docstring>section>a.button.is-active.docs-sourcelink{background-color:#39acda;border-color:transparent;color:#fff}.button.is-primary[disabled],.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary,fieldset[disabled] .docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;box-shadow:none}.button.is-primary.is-inverted,.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted:hover,.docstring>section>a.button.is-inverted.docs-sourcelink:hover,.button.is-primary.is-inverted.is-hovered,.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted,fieldset[disabled] .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#4eb5de}.button.is-primary.is-loading::after,.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined,.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;color:#4eb5de}.button.is-primary.is-outlined:hover,.docstring>section>a.button.is-outlined.docs-sourcelink:hover,.button.is-primary.is-outlined.is-hovered,.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-outlined:focus,.docstring>section>a.button.is-outlined.docs-sourcelink:focus,.button.is-primary.is-outlined.is-focused,.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.button.is-primary.is-outlined.is-loading::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-outlined.is-loading:hover::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-outlined.is-loading:focus::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-outlined.is-loading.is-focused::after,.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined[disabled],.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-outlined,fieldset[disabled] .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;box-shadow:none;color:#4eb5de}.button.is-primary.is-inverted.is-outlined,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,.button.is-primary.is-inverted.is-outlined.is-hovered,.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-inverted.is-outlined:focus,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,.button.is-primary.is-inverted.is-outlined.is-focused,.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted.is-outlined.is-loading:hover::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-inverted.is-outlined[disabled],.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined,fieldset[disabled] .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light,.docstring>section>a.button.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.button.is-primary.is-light:hover,.docstring>section>a.button.is-light.docs-sourcelink:hover,.button.is-primary.is-light.is-hovered,.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e3f3fa;border-color:transparent;color:#1a6d8e}.button.is-primary.is-light:active,.docstring>section>a.button.is-light.docs-sourcelink:active,.button.is-primary.is-light.is-active,.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d8eff8;border-color:transparent;color:#1a6d8e}.button.is-link{background-color:#2e63b8;border-color:transparent;color:#fff}.button.is-link:hover,.button.is-link.is-hovered{background-color:#2b5eae;border-color:transparent;color:#fff}.button.is-link:focus,.button.is-link.is-focused{border-color:transparent;color:#fff}.button.is-link:focus:not(:active),.button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button.is-link:active,.button.is-link.is-active{background-color:#2958a4;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#2e63b8;border-color:#2e63b8;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted:hover,.button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#2e63b8}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;color:#2e63b8}.button.is-link.is-outlined:hover,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined.is-focused{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-outlined.is-loading:hover::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;box-shadow:none;color:#2e63b8}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined:hover,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted.is-outlined.is-loading:hover::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eff3fb;color:#3169c4}.button.is-link.is-light:hover,.button.is-link.is-light.is-hovered{background-color:#e4ecf8;border-color:transparent;color:#3169c4}.button.is-link.is-light:active,.button.is-link.is-light.is-active{background-color:#dae5f6;border-color:transparent;color:#3169c4}.button.is-info{background-color:#209cee;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info.is-hovered{background-color:#1497ed;border-color:transparent;color:#fff}.button.is-info:focus,.button.is-info.is-focused{border-color:transparent;color:#fff}.button.is-info:focus:not(:active),.button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(32,156,238,0.25)}.button.is-info:active,.button.is-info.is-active{background-color:#1190e3;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#209cee;border-color:#209cee;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#209cee}.button.is-info.is-inverted:hover,.button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#209cee}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined{background-color:transparent;border-color:#209cee;color:#209cee}.button.is-info.is-outlined:hover,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined.is-focused{background-color:#209cee;border-color:#209cee;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #209cee #209cee !important}.button.is-info.is-outlined.is-loading:hover::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#209cee;box-shadow:none;color:#209cee}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#209cee}.button.is-info.is-inverted.is-outlined.is-loading:hover::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #209cee #209cee !important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#ecf7fe;color:#0e72b4}.button.is-info.is-light:hover,.button.is-info.is-light.is-hovered{background-color:#e0f1fd;border-color:transparent;color:#0e72b4}.button.is-info.is-light:active,.button.is-info.is-light.is-active{background-color:#d4ecfc;border-color:transparent;color:#0e72b4}.button.is-success{background-color:#22c35b;border-color:transparent;color:#fff}.button.is-success:hover,.button.is-success.is-hovered{background-color:#20b856;border-color:transparent;color:#fff}.button.is-success:focus,.button.is-success.is-focused{border-color:transparent;color:#fff}.button.is-success:focus:not(:active),.button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(34,195,91,0.25)}.button.is-success:active,.button.is-success.is-active{background-color:#1ead51;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#22c35b;border-color:#22c35b;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#22c35b}.button.is-success.is-inverted:hover,.button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#22c35b}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined{background-color:transparent;border-color:#22c35b;color:#22c35b}.button.is-success.is-outlined:hover,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined.is-focused{background-color:#22c35b;border-color:#22c35b;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #22c35b #22c35b !important}.button.is-success.is-outlined.is-loading:hover::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#22c35b;box-shadow:none;color:#22c35b}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined:hover,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#22c35b}.button.is-success.is-inverted.is-outlined.is-loading:hover::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #22c35b #22c35b !important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#eefcf3;color:#198f43}.button.is-success.is-light:hover,.button.is-success.is-light.is-hovered{background-color:#e3faeb;border-color:transparent;color:#198f43}.button.is-success.is-light:active,.button.is-success.is-light.is-active{background-color:#d8f8e3;border-color:transparent;color:#198f43}.button.is-warning{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning:hover,.button.is-warning.is-hovered{background-color:#ffda4a;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning:focus,.button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning:focus:not(:active),.button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,221,87,0.25)}.button.is-warning:active,.button.is-warning.is-active{background-color:#ffd83e;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffdd57;border-color:#ffdd57;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#ffdd57}.button.is-warning.is-inverted:hover,.button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ffdd57}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;color:#ffdd57}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined.is-focused{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,0.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffdd57 #ffdd57 !important}.button.is-warning.is-outlined.is-loading:hover::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;box-shadow:none;color:#ffdd57}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}.button.is-warning.is-inverted.is-outlined:hover,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ffdd57}.button.is-warning.is-inverted.is-outlined.is-loading:hover::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ffdd57 #ffdd57 !important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}.button.is-warning.is-light{background-color:#fffbeb;color:#947600}.button.is-warning.is-light:hover,.button.is-warning.is-light.is-hovered{background-color:#fff8de;border-color:transparent;color:#947600}.button.is-warning.is-light:active,.button.is-warning.is-light.is-active{background-color:#fff6d1;border-color:transparent;color:#947600}.button.is-danger{background-color:#da0b00;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger.is-hovered{background-color:#cd0a00;border-color:transparent;color:#fff}.button.is-danger:focus,.button.is-danger.is-focused{border-color:transparent;color:#fff}.button.is-danger:focus:not(:active),.button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(218,11,0,0.25)}.button.is-danger:active,.button.is-danger.is-active{background-color:#c10a00;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#da0b00;border-color:#da0b00;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#da0b00}.button.is-danger.is-inverted:hover,.button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#da0b00}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined{background-color:transparent;border-color:#da0b00;color:#da0b00}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined.is-focused{background-color:#da0b00;border-color:#da0b00;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #da0b00 #da0b00 !important}.button.is-danger.is-outlined.is-loading:hover::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#da0b00;box-shadow:none;color:#da0b00}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#da0b00}.button.is-danger.is-inverted.is-outlined.is-loading:hover::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #da0b00 #da0b00 !important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#ffeceb;color:#f50c00}.button.is-danger.is-light:hover,.button.is-danger.is-light.is-hovered{background-color:#ffe0de;border-color:transparent;color:#f50c00}.button.is-danger.is-light:active,.button.is-danger.is-light.is-active{background-color:#ffd3d1;border-color:transparent;color:#f50c00}.button.is-small,#documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}.button.is-small:not(.is-rounded),#documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent !important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#6b6b6b;box-shadow:none;pointer-events:none}.button.is-rounded,#documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:0.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-0.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button:hover,.buttons.has-addons .button.is-hovered{z-index:2}.buttons.has-addons .button:focus,.buttons.has-addons .button.is-focused,.buttons.has-addons .button:active,.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-selected{z-index:3}.buttons.has-addons .button:focus:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-selected:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.65625rem}.button.is-responsive.is-medium{font-size:.75rem}.button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.75rem}.button.is-responsive.is-medium{font-size:1rem}.button.is-responsive.is-large{font-size:1.25rem}}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){.container{max-width:992px}}@media screen and (max-width: 1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:0.25em}.content p:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content ul:not(:last-child),.content blockquote:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#222;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:0.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:0.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:0.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:0.8em}.content h5{font-size:1.125em;margin-bottom:0.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}.content ol.is-lower-roman:not([type]){list-style-type:lower-roman}.content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}.content ol.is-upper-roman:not([type]){list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:0.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}.content sup,.content sub{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.content table th{color:#222}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#222}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#222}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small,#documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small,#documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image,#documenter .docs-sidebar .docs-logo>img{display:block;position:relative}.image img,#documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}.image img.is-rounded,#documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}.image.is-fullwidth,#documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}.image.is-square,#documenter .docs-sidebar .docs-logo>img.is-square,.image.is-1by1,#documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}.image.is-5by4,#documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}.image.is-4by3,#documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}.image.is-3by2,#documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}.image.is-5by3,#documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}.image.is-16by9,#documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}.image.is-2by1,#documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}.image.is-3by1,#documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}.image.is-4by5,#documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}.image.is-3by4,#documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}.image.is-2by3,#documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}.image.is-3by5,#documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}.image.is-9by16,#documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}.image.is-1by2,#documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}.image.is-1by3,#documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}.image.is-16x16,#documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}.image.is-24x24,#documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}.image.is-32x32,#documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}.image.is-48x48,#documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}.image.is-64x64,#documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}.image.is-96x96,#documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}.image.is-128x128,#documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:transparent}.notification>.delete{right:.5rem;position:absolute;top:0.5rem}.notification .title,.notification .subtitle,.notification .content{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.notification.is-dark,.content kbd.notification{background-color:#363636;color:#fff}.notification.is-primary,.docstring>section>a.notification.docs-sourcelink{background-color:#4eb5de;color:#fff}.notification.is-primary.is-light,.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.notification.is-link{background-color:#2e63b8;color:#fff}.notification.is-link.is-light{background-color:#eff3fb;color:#3169c4}.notification.is-info{background-color:#209cee;color:#fff}.notification.is-info.is-light{background-color:#ecf7fe;color:#0e72b4}.notification.is-success{background-color:#22c35b;color:#fff}.notification.is-success.is-light{background-color:#eefcf3;color:#198f43}.notification.is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.notification.is-warning.is-light{background-color:#fffbeb;color:#947600}.notification.is-danger{background-color:#da0b00;color:#fff}.notification.is-danger.is-light{background-color:#ffeceb;color:#f50c00}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#222}.progress::-moz-progress-bar{background-color:#222}.progress::-ms-fill{background-color:#222;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #ededed 30%)}.progress.is-dark::-webkit-progress-value,.content kbd.progress::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar,.content kbd.progress::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill,.content kbd.progress::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate,.content kbd.progress:indeterminate{background-image:linear-gradient(to right, #363636 30%, #ededed 30%)}.progress.is-primary::-webkit-progress-value,.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#4eb5de}.progress.is-primary::-moz-progress-bar,.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#4eb5de}.progress.is-primary::-ms-fill,.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#4eb5de}.progress.is-primary:indeterminate,.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #4eb5de 30%, #ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#2e63b8}.progress.is-link::-moz-progress-bar{background-color:#2e63b8}.progress.is-link::-ms-fill{background-color:#2e63b8}.progress.is-link:indeterminate{background-image:linear-gradient(to right, #2e63b8 30%, #ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#209cee}.progress.is-info::-moz-progress-bar{background-color:#209cee}.progress.is-info::-ms-fill{background-color:#209cee}.progress.is-info:indeterminate{background-image:linear-gradient(to right, #209cee 30%, #ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#22c35b}.progress.is-success::-moz-progress-bar{background-color:#22c35b}.progress.is-success::-ms-fill{background-color:#22c35b}.progress.is-success:indeterminate{background-image:linear-gradient(to right, #22c35b 30%, #ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffdd57}.progress.is-warning::-moz-progress-bar{background-color:#ffdd57}.progress.is-warning::-ms-fill{background-color:#ffdd57}.progress.is-warning:indeterminate{background-image:linear-gradient(to right, #ffdd57 30%, #ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#da0b00}.progress.is-danger::-moz-progress-bar{background-color:#da0b00}.progress.is-danger::-ms-fill{background-color:#da0b00}.progress.is-danger:indeterminate{background-image:linear-gradient(to right, #da0b00 30%, #ededed 30%)}.progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right, #222 30%, #ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small,#documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#222}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.table td.is-link,.table th.is-link{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.table td.is-info,.table th.is-info{background-color:#209cee;border-color:#209cee;color:#fff}.table td.is-success,.table th.is-success{background-color:#22c35b;border-color:#22c35b;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,0.7)}.table td.is-danger,.table th.is-danger{background-color:#da0b00;border-color:#da0b00;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#4eb5de;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#222}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#4eb5de;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:rgba(0,0,0,0)}.table thead td,.table thead th{border-width:0 0 2px;color:#222}.table tfoot{background-color:rgba(0,0,0,0)}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#222}.table tbody{background-color:rgba(0,0,0,0)}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:0.25em 0.5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag,.tags .content kbd,.content .tags kbd,.tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}.tags .tag:not(:last-child),.tags .content kbd:not(:last-child),.content .tags kbd:not(:last-child),.tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-0.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large),.tags.are-medium .content kbd:not(.is-normal):not(.is-large),.content .tags.are-medium kbd:not(.is-normal):not(.is-large),.tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium),.tags.are-large .content kbd:not(.is-normal):not(.is-medium),.content .tags.are-large kbd:not(.is-normal):not(.is-medium),.tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag,.tags.is-centered .content kbd,.content .tags.is-centered kbd,.tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child),.tags.is-right .content kbd:not(:first-child),.content .tags.is-right kbd:not(:first-child),.tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}.tags.is-right .tag:not(:last-child),.tags.is-right .content kbd:not(:last-child),.content .tags.is-right kbd:not(:last-child),.tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}.tags.has-addons .tag,.tags.has-addons .content kbd,.content .tags.has-addons kbd,.tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}.tags.has-addons .tag:not(:first-child),.tags.has-addons .content kbd:not(:first-child),.content .tags.has-addons kbd:not(:first-child),.tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child),.tags.has-addons .content kbd:not(:last-child),.content .tags.has-addons kbd:not(:last-child),.tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body),.content kbd:not(body),.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#222;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}.tag:not(body) .delete,.content kbd:not(body) .delete,.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag.is-white:not(body),.content kbd.is-white:not(body),.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}.tag.is-black:not(body),.content kbd.is-black:not(body),.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}.tag.is-light:not(body),.content kbd.is-light:not(body),.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.tag.is-dark:not(body),.content kbd:not(body),.docstring>section>a.docs-sourcelink.is-dark:not(body),.content .docstring>section>kbd:not(body){background-color:#363636;color:#fff}.tag.is-primary:not(body),.content kbd.is-primary:not(body),.docstring>section>a.docs-sourcelink:not(body){background-color:#4eb5de;color:#fff}.tag.is-primary.is-light:not(body),.content kbd.is-primary.is-light:not(body),.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#eef8fc;color:#1a6d8e}.tag.is-link:not(body),.content kbd.is-link:not(body),.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#2e63b8;color:#fff}.tag.is-link.is-light:not(body),.content kbd.is-link.is-light:not(body),.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#eff3fb;color:#3169c4}.tag.is-info:not(body),.content kbd.is-info:not(body),.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#209cee;color:#fff}.tag.is-info.is-light:not(body),.content kbd.is-info.is-light:not(body),.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#ecf7fe;color:#0e72b4}.tag.is-success:not(body),.content kbd.is-success:not(body),.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#22c35b;color:#fff}.tag.is-success.is-light:not(body),.content kbd.is-success.is-light:not(body),.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#eefcf3;color:#198f43}.tag.is-warning:not(body),.content kbd.is-warning:not(body),.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#ffdd57;color:rgba(0,0,0,0.7)}.tag.is-warning.is-light:not(body),.content kbd.is-warning.is-light:not(body),.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fffbeb;color:#947600}.tag.is-danger:not(body),.content kbd.is-danger:not(body),.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#da0b00;color:#fff}.tag.is-danger.is-light:not(body),.content kbd.is-danger.is-light:not(body),.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#ffeceb;color:#f50c00}.tag.is-normal:not(body),.content kbd.is-normal:not(body),.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}.tag.is-medium:not(body),.content kbd.is-medium:not(body),.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}.tag.is-large:not(body),.content kbd.is-large:not(body),.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child),.content kbd:not(body) .icon:first-child:not(:last-child),.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child),.content kbd:not(body) .icon:last-child:not(:first-child),.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child,.content kbd:not(body) .icon:first-child:last-child,.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag.is-delete:not(body),.content kbd.is-delete:not(body),.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}.tag.is-delete:not(body):hover,.content kbd.is-delete:not(body):hover,.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,.tag.is-delete:not(body):focus,.content kbd.is-delete:not(body):focus,.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#e8e8e8}.tag.is-delete:not(body):active,.content kbd.is-delete:not(body):active,.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#dbdbdb}.tag.is-rounded:not(body),#documenter .docs-sidebar form.docs-search>input:not(body),.content kbd.is-rounded:not(body),#documenter .docs-sidebar .content form.docs-search>input:not(body),.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}a.tag:hover,.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}.title,.subtitle{word-break:break-word}.title em,.title span,.subtitle em,.subtitle span{font-weight:inherit}.title sub,.subtitle sub{font-size:.75em}.title sup,.subtitle sup{font-size:.75em}.title .tag,.title .content kbd,.content .title kbd,.title .docstring>section>a.docs-sourcelink,.subtitle .tag,.subtitle .content kbd,.content .subtitle kbd,.subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}.title{color:#222;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#222;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#222;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#222}.select select::-moz-placeholder,.textarea::-moz-placeholder,.input::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#707070}.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder,.input::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#707070}.select select:-moz-placeholder,.textarea:-moz-placeholder,.input:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#707070}.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder,.input:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#707070}.select select:hover,.textarea:hover,.input:hover,#documenter .docs-sidebar form.docs-search>input:hover,.select select.is-hovered,.is-hovered.textarea,.is-hovered.input,#documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#b5b5b5}.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{border-color:#2e63b8;box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#6b6b6b}.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,.input[disabled]::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,.input[disabled]::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-webkit-input-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,.input[disabled]:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,.input[disabled]:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-ms-input-placeholder{color:rgba(107,107,107,0.3)}.textarea,.input,#documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}.textarea[readonly],.input[readonly],#documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}.is-white.textarea,.is-white.input,#documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}.is-white.textarea:focus,.is-white.input:focus,#documenter .docs-sidebar form.docs-search>input.is-white:focus,.is-white.is-focused.textarea,.is-white.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-white.textarea:active,.is-white.input:active,#documenter .docs-sidebar form.docs-search>input.is-white:active,.is-white.is-active.textarea,.is-white.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.is-black.textarea,.is-black.input,#documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}.is-black.textarea:focus,.is-black.input:focus,#documenter .docs-sidebar form.docs-search>input.is-black:focus,.is-black.is-focused.textarea,.is-black.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-black.textarea:active,.is-black.input:active,#documenter .docs-sidebar form.docs-search>input.is-black:active,.is-black.is-active.textarea,.is-black.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.is-light.textarea,.is-light.input,#documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}.is-light.textarea:focus,.is-light.input:focus,#documenter .docs-sidebar form.docs-search>input.is-light:focus,.is-light.is-focused.textarea,.is-light.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-light.textarea:active,.is-light.input:active,#documenter .docs-sidebar form.docs-search>input.is-light:active,.is-light.is-active.textarea,.is-light.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.is-dark.textarea,.content kbd.textarea,.is-dark.input,#documenter .docs-sidebar form.docs-search>input.is-dark,.content kbd.input{border-color:#363636}.is-dark.textarea:focus,.content kbd.textarea:focus,.is-dark.input:focus,#documenter .docs-sidebar form.docs-search>input.is-dark:focus,.content kbd.input:focus,.is-dark.is-focused.textarea,.content kbd.is-focused.textarea,.is-dark.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.content kbd.is-focused.input,#documenter .docs-sidebar .content form.docs-search>input.is-focused,.is-dark.textarea:active,.content kbd.textarea:active,.is-dark.input:active,#documenter .docs-sidebar form.docs-search>input.is-dark:active,.content kbd.input:active,.is-dark.is-active.textarea,.content kbd.is-active.textarea,.is-dark.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.content kbd.is-active.input,#documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.is-primary.textarea,.docstring>section>a.textarea.docs-sourcelink,.is-primary.input,#documenter .docs-sidebar form.docs-search>input.is-primary,.docstring>section>a.input.docs-sourcelink{border-color:#4eb5de}.is-primary.textarea:focus,.docstring>section>a.textarea.docs-sourcelink:focus,.is-primary.input:focus,#documenter .docs-sidebar form.docs-search>input.is-primary:focus,.docstring>section>a.input.docs-sourcelink:focus,.is-primary.is-focused.textarea,.docstring>section>a.is-focused.textarea.docs-sourcelink,.is-primary.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.docstring>section>a.is-focused.input.docs-sourcelink,.is-primary.textarea:active,.docstring>section>a.textarea.docs-sourcelink:active,.is-primary.input:active,#documenter .docs-sidebar form.docs-search>input.is-primary:active,.docstring>section>a.input.docs-sourcelink:active,.is-primary.is-active.textarea,.docstring>section>a.is-active.textarea.docs-sourcelink,.is-primary.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.is-link.textarea,.is-link.input,#documenter .docs-sidebar form.docs-search>input.is-link{border-color:#2e63b8}.is-link.textarea:focus,.is-link.input:focus,#documenter .docs-sidebar form.docs-search>input.is-link:focus,.is-link.is-focused.textarea,.is-link.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-link.textarea:active,.is-link.input:active,#documenter .docs-sidebar form.docs-search>input.is-link:active,.is-link.is-active.textarea,.is-link.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.is-info.textarea,.is-info.input,#documenter .docs-sidebar form.docs-search>input.is-info{border-color:#209cee}.is-info.textarea:focus,.is-info.input:focus,#documenter .docs-sidebar form.docs-search>input.is-info:focus,.is-info.is-focused.textarea,.is-info.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-info.textarea:active,.is-info.input:active,#documenter .docs-sidebar form.docs-search>input.is-info:active,.is-info.is-active.textarea,.is-info.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(32,156,238,0.25)}.is-success.textarea,.is-success.input,#documenter .docs-sidebar form.docs-search>input.is-success{border-color:#22c35b}.is-success.textarea:focus,.is-success.input:focus,#documenter .docs-sidebar form.docs-search>input.is-success:focus,.is-success.is-focused.textarea,.is-success.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-success.textarea:active,.is-success.input:active,#documenter .docs-sidebar form.docs-search>input.is-success:active,.is-success.is-active.textarea,.is-success.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(34,195,91,0.25)}.is-warning.textarea,.is-warning.input,#documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#ffdd57}.is-warning.textarea:focus,.is-warning.input:focus,#documenter .docs-sidebar form.docs-search>input.is-warning:focus,.is-warning.is-focused.textarea,.is-warning.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-warning.textarea:active,.is-warning.input:active,#documenter .docs-sidebar form.docs-search>input.is-warning:active,.is-warning.is-active.textarea,.is-warning.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,221,87,0.25)}.is-danger.textarea,.is-danger.input,#documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#da0b00}.is-danger.textarea:focus,.is-danger.input:focus,#documenter .docs-sidebar form.docs-search>input.is-danger:focus,.is-danger.is-focused.textarea,.is-danger.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-danger.textarea:active,.is-danger.input:active,#documenter .docs-sidebar form.docs-search>input.is-danger:active,.is-danger.is-active.textarea,.is-danger.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(218,11,0,0.25)}.is-small.textarea,.is-small.input,#documenter .docs-sidebar form.docs-search>input{border-radius:2px;font-size:.75rem}.is-medium.textarea,.is-medium.input,#documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}.is-large.textarea,.is-large.input,#documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}.is-fullwidth.textarea,.is-fullwidth.input,#documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}.is-inline.textarea,.is-inline.input,#documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}.input.is-rounded,#documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}.input.is-static,#documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.radio,.checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.radio input,.checkbox input{cursor:pointer}.radio:hover,.checkbox:hover{color:#222}.radio[disabled],.checkbox[disabled],fieldset[disabled] .radio,fieldset[disabled] .checkbox,.radio input[disabled],.checkbox input[disabled]{color:#6b6b6b;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#2e63b8;right:1.125em;z-index:4}.select.is-rounded select,#documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:0.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#222}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select:hover,.select.is-white select.is-hovered{border-color:#f2f2f2}.select.is-white select:focus,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select:hover,.select.is-black select.is-hovered{border-color:#000}.select.is-black select:focus,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select:hover,.select.is-light select.is-hovered{border-color:#e8e8e8}.select.is-light select:focus,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.select.is-dark:not(:hover)::after,.content kbd.select:not(:hover)::after{border-color:#363636}.select.is-dark select,.content kbd.select select{border-color:#363636}.select.is-dark select:hover,.content kbd.select select:hover,.select.is-dark select.is-hovered,.content kbd.select select.is-hovered{border-color:#292929}.select.is-dark select:focus,.content kbd.select select:focus,.select.is-dark select.is-focused,.content kbd.select select.is-focused,.select.is-dark select:active,.content kbd.select select:active,.select.is-dark select.is-active,.content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.select.is-primary:not(:hover)::after,.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#4eb5de}.select.is-primary select,.docstring>section>a.select.docs-sourcelink select{border-color:#4eb5de}.select.is-primary select:hover,.docstring>section>a.select.docs-sourcelink select:hover,.select.is-primary select.is-hovered,.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#39acda}.select.is-primary select:focus,.docstring>section>a.select.docs-sourcelink select:focus,.select.is-primary select.is-focused,.docstring>section>a.select.docs-sourcelink select.is-focused,.select.is-primary select:active,.docstring>section>a.select.docs-sourcelink select:active,.select.is-primary select.is-active,.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.select.is-link:not(:hover)::after{border-color:#2e63b8}.select.is-link select{border-color:#2e63b8}.select.is-link select:hover,.select.is-link select.is-hovered{border-color:#2958a4}.select.is-link select:focus,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select.is-info:not(:hover)::after{border-color:#209cee}.select.is-info select{border-color:#209cee}.select.is-info select:hover,.select.is-info select.is-hovered{border-color:#1190e3}.select.is-info select:focus,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(32,156,238,0.25)}.select.is-success:not(:hover)::after{border-color:#22c35b}.select.is-success select{border-color:#22c35b}.select.is-success select:hover,.select.is-success select.is-hovered{border-color:#1ead51}.select.is-success select:focus,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(34,195,91,0.25)}.select.is-warning:not(:hover)::after{border-color:#ffdd57}.select.is-warning select{border-color:#ffdd57}.select.is-warning select:hover,.select.is-warning select.is-hovered{border-color:#ffd83e}.select.is-warning select:focus,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(255,221,87,0.25)}.select.is-danger:not(:hover)::after{border-color:#da0b00}.select.is-danger select{border-color:#da0b00}.select.is-danger select:hover,.select.is-danger select.is-hovered{border-color:#c10a00}.select.is-danger select:focus,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(218,11,0,0.25)}.select.is-small,#documenter .docs-sidebar form.docs-search>input.select{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#6b6b6b !important;opacity:0.5}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}.select.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white:hover .file-cta,.file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white:focus .file-cta,.file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}.file.is-white:active .file-cta,.file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black:hover .file-cta,.file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black:focus .file-cta,.file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}.file.is-black:active .file-cta,.file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:hover .file-cta,.file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:focus .file-cta,.file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}.file.is-light:active .file-cta,.file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-dark .file-cta,.content kbd.file .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark:hover .file-cta,.content kbd.file:hover .file-cta,.file.is-dark.is-hovered .file-cta,.content kbd.file.is-hovered .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark:focus .file-cta,.content kbd.file:focus .file-cta,.file.is-dark.is-focused .file-cta,.content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(54,54,54,0.25);color:#fff}.file.is-dark:active .file-cta,.content kbd.file:active .file-cta,.file.is-dark.is-active .file-cta,.content kbd.file.is-active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta,.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#4eb5de;border-color:transparent;color:#fff}.file.is-primary:hover .file-cta,.docstring>section>a.file.docs-sourcelink:hover .file-cta,.file.is-primary.is-hovered .file-cta,.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#43b1dc;border-color:transparent;color:#fff}.file.is-primary:focus .file-cta,.docstring>section>a.file.docs-sourcelink:focus .file-cta,.file.is-primary.is-focused .file-cta,.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(78,181,222,0.25);color:#fff}.file.is-primary:active .file-cta,.docstring>section>a.file.docs-sourcelink:active .file-cta,.file.is-primary.is-active .file-cta,.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#39acda;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#2e63b8;border-color:transparent;color:#fff}.file.is-link:hover .file-cta,.file.is-link.is-hovered .file-cta{background-color:#2b5eae;border-color:transparent;color:#fff}.file.is-link:focus .file-cta,.file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(46,99,184,0.25);color:#fff}.file.is-link:active .file-cta,.file.is-link.is-active .file-cta{background-color:#2958a4;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#209cee;border-color:transparent;color:#fff}.file.is-info:hover .file-cta,.file.is-info.is-hovered .file-cta{background-color:#1497ed;border-color:transparent;color:#fff}.file.is-info:focus .file-cta,.file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(32,156,238,0.25);color:#fff}.file.is-info:active .file-cta,.file.is-info.is-active .file-cta{background-color:#1190e3;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#22c35b;border-color:transparent;color:#fff}.file.is-success:hover .file-cta,.file.is-success.is-hovered .file-cta{background-color:#20b856;border-color:transparent;color:#fff}.file.is-success:focus .file-cta,.file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(34,195,91,0.25);color:#fff}.file.is-success:active .file-cta,.file.is-success.is-active .file-cta{background-color:#1ead51;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-warning:hover .file-cta,.file.is-warning.is-hovered .file-cta{background-color:#ffda4a;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-warning:focus .file-cta,.file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,221,87,0.25);color:rgba(0,0,0,0.7)}.file.is-warning:active .file-cta,.file.is-warning.is-active .file-cta{background-color:#ffd83e;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-danger .file-cta{background-color:#da0b00;border-color:transparent;color:#fff}.file.is-danger:hover .file-cta,.file.is-danger.is-hovered .file-cta{background-color:#cd0a00;border-color:transparent;color:#fff}.file.is-danger:focus .file-cta,.file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(218,11,0,0.25);color:#fff}.file.is-danger:active .file-cta,.file.is-danger.is-active .file-cta{background-color:#c10a00;border-color:transparent;color:#fff}.file.is-small,#documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa,#documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#222}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#222}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#222}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#222;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:0.5em}.label.is-small,#documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:0.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark,.content kbd.help{color:#363636}.help.is-primary,.docstring>section>a.help.docs-sourcelink{color:#4eb5de}.help.is-link{color:#2e63b8}.help.is-info{color:#209cee}.help.is-success{color:#22c35b}.help.is-warning{color:#ffdd57}.help.is-danger{color:#da0b00}.field:not(:last-child){margin-bottom:0.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .button.is-hovered:not([disabled]),.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,.field.has-addons .control .input.is-hovered:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),.field.has-addons .control .select select:not([disabled]):hover,.field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .button.is-focused:not([disabled]),.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button.is-active:not([disabled]),.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,.field.has-addons .control .input.is-focused:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,.field.has-addons .control .input.is-active:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),.field.has-addons .control .select select:not([disabled]):focus,.field.has-addons .control .select select.is-focused:not([disabled]),.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select.is-active:not([disabled]){z-index:3}.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .button.is-focused:not([disabled]):hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button.is-active:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,.field.has-addons .control .input.is-focused:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,.field.has-addons .control .input.is-active:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):focus:hover,.field.has-addons .control .select select.is-focused:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width: 768px){.field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small,#documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}.field-label.is-normal{padding-top:0.375em}.field-label.is-medium{font-size:1.25rem;padding-top:0.375em}.field-label.is-large{font-size:1.5rem;padding-top:0.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#222}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}.control.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#2e63b8;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#222;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ul,.breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small,#documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:#bbb;color:#222;max-width:100%;position:relative}.card-footer:first-child,.card-content:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-footer:last-child,.card-content:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}.card-header-title{align-items:center;color:#222;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:rgba(0,0,0,0);padding:1.5rem}.card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:#bbb;padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#222;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#2e63b8;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .title,.level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,0.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,0.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small,#documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#222;display:block;padding:0.5em 0.75em}.menu-list a:hover{background-color:#f5f5f5;color:#222}.menu-list a.is-active{background-color:#2e63b8;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#6b6b6b;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small,#documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark,.content kbd.message{background-color:#fafafa}.message.is-dark .message-header,.content kbd.message .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body,.content kbd.message .message-body{border-color:#363636}.message.is-primary,.docstring>section>a.message.docs-sourcelink{background-color:#eef8fc}.message.is-primary .message-header,.docstring>section>a.message.docs-sourcelink .message-header{background-color:#4eb5de;color:#fff}.message.is-primary .message-body,.docstring>section>a.message.docs-sourcelink .message-body{border-color:#4eb5de;color:#1a6d8e}.message.is-link{background-color:#eff3fb}.message.is-link .message-header{background-color:#2e63b8;color:#fff}.message.is-link .message-body{border-color:#2e63b8;color:#3169c4}.message.is-info{background-color:#ecf7fe}.message.is-info .message-header{background-color:#209cee;color:#fff}.message.is-info .message-body{border-color:#209cee;color:#0e72b4}.message.is-success{background-color:#eefcf3}.message.is-success .message-header{background-color:#22c35b;color:#fff}.message.is-success .message-body{border-color:#22c35b;color:#198f43}.message.is-warning{background-color:#fffbeb}.message.is-warning .message-header{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.message.is-warning .message-body{border-color:#ffdd57;color:#947600}.message.is-danger{background-color:#ffeceb}.message.is-danger .message-header{background-color:#da0b00;color:#fff}.message.is-danger .message-body{border-color:#da0b00;color:#f50c00}.message-header{align-items:center;background-color:#222;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#222;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:rgba(0,0,0,0)}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,0.86)}.modal-content,.modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){.modal-content,.modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-head,.modal-card-foot{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#222;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand>.navbar-item,.navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){.navbar.is-white .navbar-start>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-start .navbar-link::after,.navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand>.navbar-item,.navbar.is-black .navbar-brand .navbar-link{color:#fff}.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-black .navbar-start>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-end .navbar-link{color:#fff}.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-start .navbar-link::after,.navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>.navbar-item,.navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){.navbar.is-light .navbar-start>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start .navbar-link::after,.navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}.navbar.is-dark,.content kbd.navbar{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand>.navbar-item,.content kbd.navbar .navbar-brand>.navbar-item,.navbar.is-dark .navbar-brand .navbar-link,.content kbd.navbar .navbar-brand .navbar-link{color:#fff}.navbar.is-dark .navbar-brand>a.navbar-item:focus,.content kbd.navbar .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover,.content kbd.navbar .navbar-brand>a.navbar-item:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.content kbd.navbar .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.content kbd.navbar .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.content kbd.navbar .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand .navbar-link.is-active,.content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after,.content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger,.content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-dark .navbar-start>.navbar-item,.content kbd.navbar .navbar-start>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.content kbd.navbar .navbar-start .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.content kbd.navbar .navbar-end>.navbar-item,.navbar.is-dark .navbar-end .navbar-link,.content kbd.navbar .navbar-end .navbar-link{color:#fff}.navbar.is-dark .navbar-start>a.navbar-item:focus,.content kbd.navbar .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover,.content kbd.navbar .navbar-start>a.navbar-item:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.content kbd.navbar .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.content kbd.navbar .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.content kbd.navbar .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.content kbd.navbar .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.content kbd.navbar .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.content kbd.navbar .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.content kbd.navbar .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.content kbd.navbar .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.content kbd.navbar .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end .navbar-link.is-active,.content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-start .navbar-link::after,.content kbd.navbar .navbar-start .navbar-link::after,.navbar.is-dark .navbar-end .navbar-link::after,.content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active,.content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary,.docstring>section>a.navbar.docs-sourcelink{background-color:#4eb5de;color:#fff}.navbar.is-primary .navbar-brand>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,.navbar.is-primary .navbar-brand .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}.navbar.is-primary .navbar-brand>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger,.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-primary .navbar-start>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,.navbar.is-primary .navbar-end .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}.navbar.is-primary .navbar-start>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-start .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,.navbar.is-primary .navbar-end .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#4eb5de;color:#fff}}.navbar.is-link{background-color:#2e63b8;color:#fff}.navbar.is-link .navbar-brand>.navbar-item,.navbar.is-link .navbar-brand .navbar-link{color:#fff}.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-link .navbar-start>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-end .navbar-link{color:#fff}.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-start .navbar-link::after,.navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#2e63b8;color:#fff}}.navbar.is-info{background-color:#209cee;color:#fff}.navbar.is-info .navbar-brand>.navbar-item,.navbar.is-info .navbar-brand .navbar-link{color:#fff}.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#1190e3;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-info .navbar-start>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-end .navbar-link{color:#fff}.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end .navbar-link.is-active{background-color:#1190e3;color:#fff}.navbar.is-info .navbar-start .navbar-link::after,.navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#1190e3;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#209cee;color:#fff}}.navbar.is-success{background-color:#22c35b;color:#fff}.navbar.is-success .navbar-brand>.navbar-item,.navbar.is-success .navbar-brand .navbar-link{color:#fff}.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#1ead51;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-success .navbar-start>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-end .navbar-link{color:#fff}.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end .navbar-link.is-active{background-color:#1ead51;color:#fff}.navbar.is-success .navbar-start .navbar-link::after,.navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#1ead51;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#22c35b;color:#fff}}.navbar.is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-brand>.navbar-item,.navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#ffd83e;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){.navbar.is-warning .navbar-start>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#ffd83e;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-start .navbar-link::after,.navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ffd83e;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffdd57;color:rgba(0,0,0,0.7)}}.navbar.is-danger{background-color:#da0b00;color:#fff}.navbar.is-danger .navbar-brand>.navbar-item,.navbar.is-danger .navbar-brand .navbar-link{color:#fff}.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#c10a00;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-danger .navbar-start>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-end .navbar-link{color:#fff}.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#c10a00;color:#fff}.navbar.is-danger .navbar-start .navbar-link::after,.navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#c10a00;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#da0b00;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}html.has-navbar-fixed-top,body.has-navbar-fixed-top{padding-top:3.25rem}html.has-navbar-fixed-bottom,body.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#222;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,0.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#222;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}a.navbar-item,.navbar-link{cursor:pointer}a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover,a.navbar-item.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,.navbar-link.is-active{background-color:#fafafa;color:#2e63b8}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(0.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8}.navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8;border-bottom-style:solid;border-bottom-width:3px;color:#2e63b8;padding-bottom:calc(0.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#2e63b8;margin-top:-0.375em;right:1.125em}.navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}html.has-navbar-fixed-top-touch,body.has-navbar-fixed-top-touch{padding-top:3.25rem}html.has-navbar-fixed-bottom-touch,body.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width: 1056px){.navbar,.navbar-menu,.navbar-start,.navbar-end{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-start,.navbar.is-spaced .navbar-end{align-items:center}.navbar.is-spaced a.navbar-item,.navbar.is-spaced .navbar-link{border-radius:4px}.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar.is-spaced .navbar-dropdown,.navbar-dropdown.is-boxed{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.navbar>.container .navbar-brand,.container>.navbar .navbar-brand{margin-left:-.75rem}.navbar>.container .navbar-menu,.container>.navbar .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-desktop{top:0}html.has-navbar-fixed-top-desktop,body.has-navbar-fixed-top-desktop{padding-top:3.25rem}html.has-navbar-fixed-bottom-desktop,body.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}html.has-spaced-navbar-fixed-top,body.has-spaced-navbar-fixed-top{padding-top:5.25rem}html.has-spaced-navbar-fixed-bottom,body.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}a.navbar-item.is-active,.navbar-link.is-active{color:#0a0a0a}a.navbar-item.is-active:not(:focus):not(:hover),.navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link,.navbar-item.has-dropdown.is-active .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small,#documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-previous,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,.pagination.is-rounded .pagination-next,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-previous,.pagination-next,.pagination-link{border-color:#dbdbdb;color:#222;min-width:2.5em}.pagination-previous:hover,.pagination-next:hover,.pagination-link:hover{border-color:#b5b5b5;color:#363636}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus{border-color:#3c5dcd}.pagination-previous:active,.pagination-next:active,.pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}.pagination-previous[disabled],.pagination-previous.is-disabled,.pagination-next[disabled],.pagination-next.is-disabled,.pagination-link[disabled],.pagination-link.is-disabled{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#6b6b6b;opacity:0.5}.pagination-previous,.pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width: 768px){.pagination{flex-wrap:wrap}.pagination-previous,.pagination-next{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:#bbb;font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading,.content kbd.panel .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active,.content kbd.panel .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon,.content kbd.panel .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading,.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#4eb5de;color:#fff}.panel.is-primary .panel-tabs a.is-active,.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#4eb5de}.panel.is-primary .panel-block.is-active .panel-icon,.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#4eb5de}.panel.is-link .panel-heading{background-color:#2e63b8;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#2e63b8}.panel.is-link .panel-block.is-active .panel-icon{color:#2e63b8}.panel.is-info .panel-heading{background-color:#209cee;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#209cee}.panel.is-info .panel-block.is-active .panel-icon{color:#209cee}.panel.is-success .panel-heading{background-color:#22c35b;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#22c35b}.panel.is-success .panel-block.is-active .panel-icon{color:#22c35b}.panel.is-warning .panel-heading{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffdd57}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffdd57}.panel.is-danger .panel-heading{background-color:#da0b00;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#da0b00}.panel.is-danger .panel-block.is-active .panel-icon{color:#da0b00}.panel-tabs:not(:last-child),.panel-block:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#222;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:0.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#222}.panel-list a:hover{color:#2e63b8}.panel-block{align-items:center;color:#222;display:flex;justify-content:flex-start;padding:0.5em 0.75em}.panel-block input[type="checkbox"]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#2e63b8;color:#363636}.panel-block.is-active .panel-icon{color:#2e63b8}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#6b6b6b;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#222;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#222;color:#222}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#2e63b8;color:#2e63b8}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:0.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:rgba(0,0,0,0) !important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#2e63b8;border-color:#2e63b8;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small,#documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0%}.columns.is-mobile>.column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>.column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>.column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>.column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>.column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0%}.column.is-1-mobile{flex:none;width:8.33333337%}.column.is-offset-1-mobile{margin-left:8.33333337%}.column.is-2-mobile{flex:none;width:16.66666674%}.column.is-offset-2-mobile{margin-left:16.66666674%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333337%}.column.is-offset-4-mobile{margin-left:33.33333337%}.column.is-5-mobile{flex:none;width:41.66666674%}.column.is-offset-5-mobile{margin-left:41.66666674%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333337%}.column.is-offset-7-mobile{margin-left:58.33333337%}.column.is-8-mobile{flex:none;width:66.66666674%}.column.is-offset-8-mobile{margin-left:66.66666674%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333337%}.column.is-offset-10-mobile{margin-left:83.33333337%}.column.is-11-mobile{flex:none;width:91.66666674%}.column.is-offset-11-mobile{margin-left:91.66666674%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0%}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333337%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333337%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66666674%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66666674%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333337%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333337%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66666674%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66666674%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333337%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333337%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66666674%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66666674%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333337%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333337%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66666674%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66666674%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0%}.column.is-1-touch{flex:none;width:8.33333337%}.column.is-offset-1-touch{margin-left:8.33333337%}.column.is-2-touch{flex:none;width:16.66666674%}.column.is-offset-2-touch{margin-left:16.66666674%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333337%}.column.is-offset-4-touch{margin-left:33.33333337%}.column.is-5-touch{flex:none;width:41.66666674%}.column.is-offset-5-touch{margin-left:41.66666674%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333337%}.column.is-offset-7-touch{margin-left:58.33333337%}.column.is-8-touch{flex:none;width:66.66666674%}.column.is-offset-8-touch{margin-left:66.66666674%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333337%}.column.is-offset-10-touch{margin-left:83.33333337%}.column.is-11-touch{flex:none;width:91.66666674%}.column.is-offset-11-touch{margin-left:91.66666674%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0%}.column.is-1-desktop{flex:none;width:8.33333337%}.column.is-offset-1-desktop{margin-left:8.33333337%}.column.is-2-desktop{flex:none;width:16.66666674%}.column.is-offset-2-desktop{margin-left:16.66666674%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333337%}.column.is-offset-4-desktop{margin-left:33.33333337%}.column.is-5-desktop{flex:none;width:41.66666674%}.column.is-offset-5-desktop{margin-left:41.66666674%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333337%}.column.is-offset-7-desktop{margin-left:58.33333337%}.column.is-8-desktop{flex:none;width:66.66666674%}.column.is-offset-8-desktop{margin-left:66.66666674%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333337%}.column.is-offset-10-desktop{margin-left:83.33333337%}.column.is-11-desktop{flex:none;width:91.66666674%}.column.is-offset-11-desktop{margin-left:91.66666674%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0%}.column.is-1-widescreen{flex:none;width:8.33333337%}.column.is-offset-1-widescreen{margin-left:8.33333337%}.column.is-2-widescreen{flex:none;width:16.66666674%}.column.is-offset-2-widescreen{margin-left:16.66666674%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333337%}.column.is-offset-4-widescreen{margin-left:33.33333337%}.column.is-5-widescreen{flex:none;width:41.66666674%}.column.is-offset-5-widescreen{margin-left:41.66666674%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333337%}.column.is-offset-7-widescreen{margin-left:58.33333337%}.column.is-8-widescreen{flex:none;width:66.66666674%}.column.is-offset-8-widescreen{margin-left:66.66666674%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333337%}.column.is-offset-10-widescreen{margin-left:83.33333337%}.column.is-11-widescreen{flex:none;width:91.66666674%}.column.is-offset-11-widescreen{margin-left:91.66666674%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){.column.is-narrow-fullhd{flex:none;width:unset}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0%}.column.is-1-fullhd{flex:none;width:8.33333337%}.column.is-offset-1-fullhd{margin-left:8.33333337%}.column.is-2-fullhd{flex:none;width:16.66666674%}.column.is-offset-2-fullhd{margin-left:16.66666674%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333337%}.column.is-offset-4-fullhd{margin-left:33.33333337%}.column.is-5-fullhd{flex:none;width:41.66666674%}.column.is-offset-5-fullhd{margin-left:41.66666674%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333337%}.column.is-offset-7-fullhd{margin-left:58.33333337%}.column.is-8-fullhd{flex:none;width:66.66666674%}.column.is-offset-8-fullhd{margin-left:66.66666674%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333337%}.column.is-offset-10-fullhd{margin-left:83.33333337%}.column.is-11-fullhd{flex:none;width:91.66666674%}.column.is-offset-11-fullhd{margin-left:91.66666674%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0 !important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){.columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-0-fullhd{--columnGap: 0rem}}.columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){.columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-1-fullhd{--columnGap: .25rem}}.columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){.columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-2-fullhd{--columnGap: .5rem}}.columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){.columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-3-fullhd{--columnGap: .75rem}}.columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){.columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-4-fullhd{--columnGap: 1rem}}.columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}.columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}.columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}.columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){.columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-8-fullhd{--columnGap: 2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0 !important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333337%}.tile.is-2{flex:none;width:16.66666674%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333337%}.tile.is-5{flex:none;width:41.66666674%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333337%}.tile.is-8{flex:none;width:66.66666674%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333337%}.tile.is-11{flex:none;width:91.66666674%}.tile.is-12{flex:none;width:100%}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:none}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,0.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}.hero.is-white a.navbar-item:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white .navbar-link:hover,.hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,0.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-black a.navbar-item:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black .navbar-link:hover,.hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:0.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,0.7)}.hero.is-light .subtitle{color:rgba(0,0,0,0.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}.hero.is-light a.navbar-item:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light .navbar-link:hover,.hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}.hero.is-dark,.content kbd.hero{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong,.content kbd.hero strong{color:inherit}.hero.is-dark .title,.content kbd.hero .title{color:#fff}.hero.is-dark .subtitle,.content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}.hero.is-dark .subtitle a:not(.button),.content kbd.hero .subtitle a:not(.button),.hero.is-dark .subtitle strong,.content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-dark .navbar-menu,.content kbd.hero .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.content kbd.hero .navbar-item,.hero.is-dark .navbar-link,.content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-dark a.navbar-item:hover,.content kbd.hero a.navbar-item:hover,.hero.is-dark a.navbar-item.is-active,.content kbd.hero a.navbar-item.is-active,.hero.is-dark .navbar-link:hover,.content kbd.hero .navbar-link:hover,.hero.is-dark .navbar-link.is-active,.content kbd.hero .navbar-link.is-active{background-color:#292929;color:#fff}.hero.is-dark .tabs a,.content kbd.hero .tabs a{color:#fff;opacity:0.9}.hero.is-dark .tabs a:hover,.content kbd.hero .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a,.content kbd.hero .tabs li.is-active a{color:#363636 !important;opacity:1}.hero.is-dark .tabs.is-boxed a,.content kbd.hero .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a,.content kbd.hero .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.content kbd.hero .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover,.content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.content kbd.hero .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.content kbd.hero .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold,.content kbd.hero.is-bold{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}@media screen and (max-width: 768px){.hero.is-dark.is-bold .navbar-menu,.content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}}.hero.is-primary,.docstring>section>a.hero.docs-sourcelink{background-color:#4eb5de;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong,.docstring>section>a.hero.docs-sourcelink strong{color:inherit}.hero.is-primary .title,.docstring>section>a.hero.docs-sourcelink .title{color:#fff}.hero.is-primary .subtitle,.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}.hero.is-primary .subtitle a:not(.button),.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),.hero.is-primary .subtitle strong,.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-primary .navbar-menu,.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#4eb5de}}.hero.is-primary .navbar-item,.docstring>section>a.hero.docs-sourcelink .navbar-item,.hero.is-primary .navbar-link,.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-primary a.navbar-item:hover,.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,.hero.is-primary a.navbar-item.is-active,.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,.hero.is-primary .navbar-link:hover,.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,.hero.is-primary .navbar-link.is-active,.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#39acda;color:#fff}.hero.is-primary .tabs a,.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}.hero.is-primary .tabs a:hover,.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#4eb5de !important;opacity:1}.hero.is-primary .tabs.is-boxed a,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#4eb5de}.hero.is-primary.is-bold,.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}@media screen and (max-width: 768px){.hero.is-primary.is-bold .navbar-menu,.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}}.hero.is-link{background-color:#2e63b8;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,0.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-link .navbar-menu{background-color:#2e63b8}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-link a.navbar-item:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link .navbar-link:hover,.hero.is-link .navbar-link.is-active{background-color:#2958a4;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:0.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#2e63b8 !important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#2e63b8}.hero.is-link.is-bold{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}@media screen and (max-width: 768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}}.hero.is-info{background-color:#209cee;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,0.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-info .navbar-menu{background-color:#209cee}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-info a.navbar-item:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info .navbar-link:hover,.hero.is-info .navbar-link.is-active{background-color:#1190e3;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:0.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#209cee !important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#209cee}.hero.is-info.is-bold{background-image:linear-gradient(141deg, #05a6d6 0%, #209cee 71%, #3287f5 100%)}@media screen and (max-width: 768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #05a6d6 0%, #209cee 71%, #3287f5 100%)}}.hero.is-success{background-color:#22c35b;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,0.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-success .navbar-menu{background-color:#22c35b}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-success a.navbar-item:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success .navbar-link:hover,.hero.is-success .navbar-link.is-active{background-color:#1ead51;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:0.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#22c35b !important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#22c35b}.hero.is-success.is-bold{background-image:linear-gradient(141deg, #12a02c 0%, #22c35b 71%, #1fdf83 100%)}@media screen and (max-width: 768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #12a02c 0%, #22c35b 71%, #1fdf83 100%)}}.hero.is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,0.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){.hero.is-warning .navbar-menu{background-color:#ffdd57}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}.hero.is-warning a.navbar-item:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning .navbar-link.is-active{background-color:#ffd83e;color:rgba(0,0,0,0.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#ffdd57 !important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ffdd57}.hero.is-warning.is-bold{background-image:linear-gradient(141deg, #ffae24 0%, #ffdd57 71%, #fffa71 100%)}@media screen and (max-width: 768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ffae24 0%, #ffdd57 71%, #fffa71 100%)}}.hero.is-danger{background-color:#da0b00;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-danger .navbar-menu{background-color:#da0b00}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-danger a.navbar-item:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger .navbar-link.is-active{background-color:#c10a00;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:0.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#da0b00 !important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#da0b00}.hero.is-danger.is-bold{background-image:linear-gradient(141deg, #a70013 0%, #da0b00 71%, #f43500 100%)}@media screen and (max-width: 768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #a70013 0%, #da0b00 71%, #f43500 100%)}}.hero.is-small .hero-body,#documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-halfheight .hero-body,.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}.hero.is-halfheight .hero-body>.container,.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}.hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-head,.hero-foot{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}h1 .docs-heading-anchor,h1 .docs-heading-anchor:hover,h1 .docs-heading-anchor:visited,h2 .docs-heading-anchor,h2 .docs-heading-anchor:hover,h2 .docs-heading-anchor:visited,h3 .docs-heading-anchor,h3 .docs-heading-anchor:hover,h3 .docs-heading-anchor:visited,h4 .docs-heading-anchor,h4 .docs-heading-anchor:hover,h4 .docs-heading-anchor:visited,h5 .docs-heading-anchor,h5 .docs-heading-anchor:hover,h5 .docs-heading-anchor:visited,h6 .docs-heading-anchor,h6 .docs-heading-anchor:hover,h6 .docs-heading-anchor:visited{color:#222}h1 .docs-heading-anchor-permalink,h2 .docs-heading-anchor-permalink,h3 .docs-heading-anchor-permalink,h4 .docs-heading-anchor-permalink,h5 .docs-heading-anchor-permalink,h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}h1 .docs-heading-anchor-permalink::before,h2 .docs-heading-anchor-permalink::before,h3 .docs-heading-anchor-permalink::before,h4 .docs-heading-anchor-permalink::before,h5 .docs-heading-anchor-permalink::before,h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}h1:hover .docs-heading-anchor-permalink,h2:hover .docs-heading-anchor-permalink,h3:hover .docs-heading-anchor-permalink,h4:hover .docs-heading-anchor-permalink,h5:hover .docs-heading-anchor-permalink,h6:hover .docs-heading-anchor-permalink{visibility:visible}.docs-dark-only{display:none !important}pre{position:relative;overflow:hidden}pre code,pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}pre code:first-of-type,pre code.hljs:first-of-type{padding-top:0.5rem !important}pre code:last-of-type,pre code.hljs:last-of-type{padding-bottom:0.5rem !important}pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#222;cursor:pointer;text-align:center}pre .copy-button:focus,pre .copy-button:hover{opacity:1;background:rgba(34,34,34,0.1);color:#2e63b8}pre .copy-button.success{color:#259a12;opacity:1}pre .copy-button.error{color:#cb3c33;opacity:1}pre:hover .copy-button{opacity:1}.admonition{background-color:#b5b5b5;border-style:solid;border-width:1px;border-color:#363636;border-radius:4px;font-size:1rem}.admonition strong{color:currentColor}.admonition.is-small,#documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}.admonition.is-medium{font-size:1.25rem}.admonition.is-large{font-size:1.5rem}.admonition.is-default{background-color:#b5b5b5;border-color:#363636}.admonition.is-default>.admonition-header{background-color:#363636;color:#fff}.admonition.is-default>.admonition-body{color:#fff}.admonition.is-info{background-color:#def0fc;border-color:#209cee}.admonition.is-info>.admonition-header{background-color:#209cee;color:#fff}.admonition.is-info>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-success{background-color:#bdf4d1;border-color:#22c35b}.admonition.is-success>.admonition-header{background-color:#22c35b;color:#fff}.admonition.is-success>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-warning{background-color:#fff3c5;border-color:#ffdd57}.admonition.is-warning>.admonition-header{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.admonition.is-warning>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-danger{background-color:#ffaba7;border-color:#da0b00}.admonition.is-danger>.admonition-header{background-color:#da0b00;color:#fff}.admonition.is-danger>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-compat{background-color:#bdeff5;border-color:#1db5c9}.admonition.is-compat>.admonition-header{background-color:#1db5c9;color:#fff}.admonition.is-compat>.admonition-body{color:rgba(0,0,0,0.7)}.admonition-header{color:#fff;background-color:#363636;align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}details.admonition.is-details>.admonition-header{list-style:none}details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}.admonition-body{color:#222;padding:0.5rem .75rem}.admonition-body pre{background-color:#f5f5f5}.admonition-body code{background-color:rgba(0,0,0,0.05)}.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:1px solid #dbdbdb;box-shadow:2px 2px 3px rgba(10,10,10,0.1);max-width:100%}.docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#f5f5f5;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #dbdbdb}.docstring>header code{background-color:transparent}.docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}.docstring>header .docstring-binding{margin-right:0.3em}.docstring>header .docstring-category{margin-left:0.3em}.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #dbdbdb}.docstring>section:last-child{border-bottom:none}.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}.docstring:hover>section>a.docs-sourcelink{opacity:0.2}.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}.docstring>section:hover a.docs-sourcelink{opacity:1}.documenter-example-output{background-color:#fff}.outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#ffaba7;color:rgba(0,0,0,0.7);border-bottom:3px solid #da0b00;padding:10px 35px;text-align:center;font-size:15px}.outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}.outdated-warning-overlay a{color:#2e63b8}.outdated-warning-overlay a:hover{color:#363636}.content pre{border:1px solid #dbdbdb}.content code{font-weight:inherit}.content a code{color:#2e63b8}.content h1 code,.content h2 code,.content h3 code,.content h4 code,.content h5 code,.content h6 code{color:#222}.content table{display:block;width:initial;max-width:100%;overflow-x:auto}.content blockquote>ul:first-child,.content blockquote>ol:first-child,.content .admonition-body>ul:first-child,.content .admonition-body>ol:first-child{margin-top:0}pre,code{font-variant-ligatures:no-contextual}.breadcrumb a.is-disabled{cursor:default;pointer-events:none}.breadcrumb a.is-disabled,.breadcrumb a.is-disabled:hover{color:#222}.hljs{background:initial !important}.katex .katex-mathml{top:0;right:0}.katex-display,mjx-container,.MathJax_Display{margin:0.5em 0 !important}html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}li.no-marker{list-style:none}#documenter .docs-main>article{overflow-wrap:break-word}#documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){#documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){#documenter .docs-main{width:100%}#documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}#documenter .docs-main>header,#documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}#documenter .docs-main header.docs-navbar{background-color:#fff;border-bottom:1px solid #dbdbdb;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}#documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1}#documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}#documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}#documenter .docs-main header.docs-navbar .docs-right .docs-icon,#documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}#documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}#documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}#documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #bbb;transition-duration:0.7s;-webkit-transition-duration:0.7s}#documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}#documenter .docs-main section.footnotes{border-top:1px solid #dbdbdb}#documenter .docs-main section.footnotes li .tag:first-child,#documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,#documenter .docs-main section.footnotes li .content kbd:first-child,.content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}#documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #dbdbdb;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){#documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}#documenter .docs-main .docs-footer .docs-footer-nextpage,#documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}#documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}#documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}#documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}#documenter .docs-sidebar{display:flex;flex-direction:column;color:#0a0a0a;background-color:#f5f5f5;border-right:1px solid #dbdbdb;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}#documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #bbb}@media screen and (min-width: 1056px){#documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){#documenter .docs-sidebar{left:0;top:0}}#documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}#documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}#documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}#documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}#documenter .docs-sidebar .docs-package-name a,#documenter .docs-sidebar .docs-package-name a:hover{color:#0a0a0a}#documenter .docs-sidebar .docs-version-selector{border-top:1px solid #dbdbdb;display:none;padding:0.5rem}#documenter .docs-sidebar .docs-version-selector.visible{display:flex}#documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #dbdbdb;padding-bottom:1.5rem}#documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}#documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}#documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}#documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}#documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}#documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}#documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}#documenter .docs-sidebar ul.docs-menu .tocitem,#documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#0a0a0a;background:#f5f5f5}#documenter .docs-sidebar ul.docs-menu a.tocitem:hover,#documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#0a0a0a;background-color:#ebebeb}#documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #dbdbdb;border-bottom:1px solid #dbdbdb;background-color:#fff}#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#fff;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#ebebeb;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}#documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}#documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}#documenter .docs-sidebar form.docs-search>input{width:14.4rem}#documenter .docs-sidebar #documenter-search-query{color:#707070;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){#documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#ccc}}@media screen and (max-width: 1055px){#documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#ccc}}kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(0,0,0,0.6);box-shadow:0 2px 0 1px rgba(0,0,0,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}.search-min-width-50{min-width:50%}.search-min-height-100{min-height:100%}.search-modal-card-body{max-height:calc(100vh - 15rem)}.search-result-link{border-radius:0.7em;transition:all 300ms}.search-result-link:hover,.search-result-link:focus{background-color:rgba(0,128,128,0.1)}.search-result-link .property-search-result-badge,.search-result-link .search-filter{transition:all 300ms}.property-search-result-badge,.search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}.search-result-link:hover .property-search-result-badge,.search-result-link:hover .search-filter,.search-result-link:focus .property-search-result-badge,.search-result-link:focus .search-filter{color:#f1f5f9;background-color:#333}.search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}.search-filter:hover,.search-filter:focus{color:#333}.search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}.search-filter-selected:hover,.search-filter-selected:focus{color:#f5f5f5}.search-result-highlight{background-color:#ffdd57;color:black}.search-divider{border-bottom:1px solid #dbdbdb}.search-result-title{width:85%;color:#333}.search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}#search-modal .modal-card-body::-webkit-scrollbar,#search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}#search-modal .modal-card-body::-webkit-scrollbar-thumb,#search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}#search-modal .modal-card-body::-webkit-scrollbar-track,#search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}.w-100{width:100%}.gap-2{gap:0.5rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.ansi span.sgr1{font-weight:bolder}.ansi span.sgr2{font-weight:lighter}.ansi span.sgr3{font-style:italic}.ansi span.sgr4{text-decoration:underline}.ansi span.sgr7{color:#fff;background-color:#222}.ansi span.sgr8{color:transparent}.ansi span.sgr8 span{color:transparent}.ansi span.sgr9{text-decoration:line-through}.ansi span.sgr30{color:#242424}.ansi span.sgr31{color:#a7201f}.ansi span.sgr32{color:#066f00}.ansi span.sgr33{color:#856b00}.ansi span.sgr34{color:#2149b0}.ansi span.sgr35{color:#7d4498}.ansi span.sgr36{color:#007989}.ansi span.sgr37{color:gray}.ansi span.sgr40{background-color:#242424}.ansi span.sgr41{background-color:#a7201f}.ansi span.sgr42{background-color:#066f00}.ansi span.sgr43{background-color:#856b00}.ansi span.sgr44{background-color:#2149b0}.ansi span.sgr45{background-color:#7d4498}.ansi span.sgr46{background-color:#007989}.ansi span.sgr47{background-color:gray}.ansi span.sgr90{color:#616161}.ansi span.sgr91{color:#cb3c33}.ansi span.sgr92{color:#0e8300}.ansi span.sgr93{color:#a98800}.ansi span.sgr94{color:#3c5dcd}.ansi span.sgr95{color:#9256af}.ansi span.sgr96{color:#008fa3}.ansi span.sgr97{color:#f5f5f5}.ansi span.sgr100{background-color:#616161}.ansi span.sgr101{background-color:#cb3c33}.ansi span.sgr102{background-color:#0e8300}.ansi span.sgr103{background-color:#a98800}.ansi span.sgr104{background-color:#3c5dcd}.ansi span.sgr105{background-color:#9256af}.ansi span.sgr106{background-color:#008fa3}.ansi span.sgr107{background-color:#f5f5f5}code.language-julia-repl>span.hljs-meta{color:#066f00;font-weight:bolder}/*! + Theme: Default + Description: Original highlight.js style + Author: (c) Ivan Sagalaev + Maintainer: @highlightjs/core-team + Website: https://highlightjs.org/ + License: see project LICENSE + Touched: 2021 +*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#F3F3F3;color:#444}.hljs-comment{color:#697070}.hljs-tag,.hljs-punctuation{color:#444a}.hljs-tag .hljs-name,.hljs-tag .hljs-attr{color:#444}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta .hljs-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-operator,.hljs-selector-pseudo{color:#ab5656}.hljs-literal{color:#695}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} diff --git a/previews/PR3536/assets/themeswap.js b/previews/PR3536/assets/themeswap.js new file mode 100644 index 00000000000..9f5eebe6aa2 --- /dev/null +++ b/previews/PR3536/assets/themeswap.js @@ -0,0 +1,84 @@ +// Small function to quickly swap out themes. Gets put into the tag.. +function set_theme_from_local_storage() { + // Initialize the theme to null, which means default + var theme = null; + // If the browser supports the localstorage and is not disabled then try to get the + // documenter theme + if (window.localStorage != null) { + // Get the user-picked theme from localStorage. May be `null`, which means the default + // theme. + theme = window.localStorage.getItem("documenter-theme"); + } + // Check if the users preference is for dark color scheme + var darkPreference = + window.matchMedia("(prefers-color-scheme: dark)").matches === true; + // Initialize a few variables for the loop: + // + // - active: will contain the index of the theme that should be active. Note that there + // is no guarantee that localStorage contains sane values. If `active` stays `null` + // we either could not find the theme or it is the default (primary) theme anyway. + // Either way, we then need to stick to the primary theme. + // + // - disabled: style sheets that should be disabled (i.e. all the theme style sheets + // that are not the currently active theme) + var active = null; + var disabled = []; + var primaryLightTheme = null; + var primaryDarkTheme = null; + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // To distinguish the default (primary) theme, it needs to have the data-theme-primary + // attribute set. + if (ss.ownerNode.getAttribute("data-theme-primary") !== null) { + primaryLightTheme = themename; + } + // Check if the theme is primary dark theme so that we could store its name in darkTheme + if (ss.ownerNode.getAttribute("data-theme-primary-dark") !== null) { + primaryDarkTheme = themename; + } + // If we find a matching theme (and it's not the default), we'll set active to non-null + if (themename === theme) active = i; + // Store the style sheets of inactive themes so that we could disable them + if (themename !== theme) disabled.push(ss); + } + var activeTheme = null; + if (active !== null) { + // If we did find an active theme, we'll (1) add the theme--$(theme) class to + document.getElementsByTagName("html")[0].className = "theme--" + theme; + activeTheme = theme; + } else { + // If we did _not_ find an active theme, then we need to fall back to the primary theme + // which can either be dark or light, depending on the user's OS preference. + var activeTheme = darkPreference ? primaryDarkTheme : primaryLightTheme; + // In case it somehow happens that the relevant primary theme was not found in the + // preceding loop, we abort without doing anything. + if (activeTheme === null) { + console.error("Unable to determine primary theme."); + return; + } + // When switching to the primary light theme, then we must not have a class name + // for the tag. That's only for non-primary or the primary dark theme. + if (darkPreference) { + document.getElementsByTagName("html")[0].className = + "theme--" + activeTheme; + } else { + document.getElementsByTagName("html")[0].className = ""; + } + } + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // we'll disable all the stylesheets, except for the active one + ss.disabled = !(themename == activeTheme); + } +} +set_theme_from_local_storage(); diff --git a/previews/PR3536/assets/warner.js b/previews/PR3536/assets/warner.js new file mode 100644 index 00000000000..3f6f5d0083a --- /dev/null +++ b/previews/PR3536/assets/warner.js @@ -0,0 +1,52 @@ +function maybeAddWarning() { + // DOCUMENTER_NEWEST is defined in versions.js, DOCUMENTER_CURRENT_VERSION and DOCUMENTER_STABLE + // in siteinfo.js. + // If either of these are undefined something went horribly wrong, so we abort. + if ( + window.DOCUMENTER_NEWEST === undefined || + window.DOCUMENTER_CURRENT_VERSION === undefined || + window.DOCUMENTER_STABLE === undefined + ) { + return; + } + + // Current version is not a version number, so we can't tell if it's the newest version. Abort. + if (!/v(\d+\.)*\d+/.test(window.DOCUMENTER_CURRENT_VERSION)) { + return; + } + + // Current version is newest version, so no need to add a warning. + if (window.DOCUMENTER_NEWEST === window.DOCUMENTER_CURRENT_VERSION) { + return; + } + + // Add a noindex meta tag (unless one exists) so that search engines don't index this version of the docs. + if (document.body.querySelector('meta[name="robots"]') === null) { + const meta = document.createElement("meta"); + meta.name = "robots"; + meta.content = "noindex"; + + document.getElementsByTagName("head")[0].appendChild(meta); + } + + const div = document.createElement("div"); + div.classList.add("outdated-warning-overlay"); + const closer = document.createElement("button"); + closer.classList.add("outdated-warning-closer", "delete"); + closer.addEventListener("click", function () { + document.body.removeChild(div); + }); + const href = window.documenterBaseURL + "/../" + window.DOCUMENTER_STABLE; + div.innerHTML = + 'This documentation is not for the latest stable release, but for either the development version or an older release.
Click here to go to the documentation for the latest stable release.'; + div.appendChild(closer); + document.body.appendChild(div); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", maybeAddWarning); +} else { + maybeAddWarning(); +} diff --git a/previews/PR3536/background/algebraic_modeling_languages/index.html b/previews/PR3536/background/algebraic_modeling_languages/index.html new file mode 100644 index 00000000000..1a788aaf140 --- /dev/null +++ b/previews/PR3536/background/algebraic_modeling_languages/index.html @@ -0,0 +1,129 @@ + +Algebraic modeling languages · JuMP

Algebraic modeling languages

JuMP is an algebraic modeling language for mathematical optimization written in the Julia language. In this page, we explain what an algebraic modeling language actually is.

What is an algebraic modeling language?

If you have taken a class in mixed-integer linear programming, you will have seen a formulation like:

\[\begin{aligned} +\min \; & c^\top x \\ +\text{s.t.} & A x = b \\ + & x \ge 0 \\ + & x_i \in \mathbb{Z}, \quad \forall i \in \mathcal{I} +\end{aligned}\]

where c, A, and b are appropriately sized vectors and matrices of data, and $\mathcal{I}$ denotes the set of variables that are integer.

Solvers expect problems in a standard form like this because it limits the types of constraints that they need to consider. This makes writing a solver much easier.

What is a solver?

A solver is a software package that computes solutions to one or more classes of problems.

For example, HiGHS is a solver for linear programming (LP) and mixed integer programming (MIP) problems. It incorporates algorithms such as the simplex method and the interior-point method.

JuMP currently supports a number of open-source and commercial solvers, which can be viewed in the Supported-solvers table.

Despite the textbook view of a linear program, you probably formulated problems algebraically like so:

\[\begin{aligned} +\max \; & \sum\limits_{i = 1}^n c_i x_i \\ +\text{s.t.} & \sum\limits_{i = 1}^n w_i x_i \le b \\ + & x_i \ge 0 \quad \forall i = 1,\ldots,n \\ + & x_i \in \mathbb{Z} \quad \forall i = 1,\ldots,n. +\end{aligned}\]

Info

Do you recognize this formulation? It's the knapsack problem.

Users prefer to write problems in algebraic form because it is more convenient. For example, we used $\le b$, even though the standard form only supported constraints of the form $Ax = b$.

We could convert our knapsack problem into the standard form by adding a new slack variable $x_0$:

\[\begin{aligned} +\max \; & \sum\limits_{i = 1}^n c_i x_i \\ +\text{s.t.} & x_0 + \sum\limits_{i = 1}^n w_i x_i = b \\ + & x_i \ge 0 \quad \forall i = 0,\ldots,n \\ + & x_i \in \mathbb{Z} \quad \forall i = 1,\ldots,n. +\end{aligned}\]

However, as models get more complicated, this manual conversion becomes more and more error-prone.

An algebraic modeling language is a tool that simplifies the translation between the algebraic form of the modeler, and the standard form of the solver.

Each algebraic modeling language has two main parts:

  1. A domain specific language for the user to write down problems in algebraic form.
  2. A converter from the algebraic form into a standard form supported by the solver (and back again).

Part 2 is less trivial than it might seem, because each solver has a unique application programming interface (API) and data structure for representing optimization models and obtaining results.

JuMP uses the MathOptInterface.jl package to abstract these differences between solvers.

What is MathOptInterface?

MathOptInterface (MOI) is an abstraction layer designed to provide an interface to mathematical optimization solvers so that users do not need to understand multiple solver-specific APIs. MOI can be used directly, or through a higher-level modeling interface like JuMP.

There are three main parts to MathOptInterface:

  1. A solver-independent API that abstracts concepts such as adding and deleting variables and constraints, setting and getting parameters, and querying results. For more information on the MathOptInterface API, read the documentation.

  2. An automatic rewriting system based on equivalent formulations of a constraint. For more information on this rewriting system, read the LazyBridgeOptimizer section of the manual, and our paper on arXiv.

  3. Utilities for managing how and when models are copied to solvers. For more information on this, read the CachingOptimizer section of the manual.

From user to solver

This section provides a brief summary of the steps that happen in order to translate the model that the user writes into a model that the solver understands.

Step I: writing in algebraic form

JuMP provides the first part of an algebraic modeling language using the @variable, @objective, and @constraint macros.

For example, here's how we write the knapsack problem in JuMP:

julia> using JuMP, HiGHS
+
+julia> function algebraic_knapsack(c, w, b)
+           n = length(c)
+           model = Model(HiGHS.Optimizer)
+           set_silent(model)
+           @variable(model, x[1:n] >= 0, Int)
+           @objective(model, Max, sum(c[i] * x[i] for i = 1:n))
+           @constraint(model, sum(w[i] * x[i] for i = 1:n) <= b)
+           optimize!(model)
+           return value.(x)
+       end
+algebraic_knapsack (generic function with 1 method)
+
+julia> algebraic_knapsack([1, 2], [0.5, 0.5], 1.25)
+2-element Vector{Float64}:
+ 0.0
+ 2.0

This formulation is compact, and it closely matches the algebraic formulation of the model we wrote out above.

Step II: algebraic to functional

For the next step, JuMP's macros re-write the variables and constraints into a functional form. Here's what the JuMP code looks like after this step:

julia> using JuMP, HiGHS
+
+julia> function nonalgebraic_knapsack(c, w, b)
+           n = length(c)
+           model = Model(HiGHS.Optimizer)
+           set_silent(model)
+           x = [VariableRef(model) for i = 1:n]
+           for i = 1:n
+               set_lower_bound(x[i], 0)
+               set_integer(x[i])
+               set_name(x[i], "x[$i]")
+           end
+           obj = AffExpr(0.0)
+           for i = 1:n
+               add_to_expression!(obj, c[i], x[i])
+           end
+           set_objective(model, MAX_SENSE, obj)
+           lhs = AffExpr(0.0)
+           for i = 1:n
+               add_to_expression!(lhs, w[i], x[i])
+           end
+           con = build_constraint(error, lhs, MOI.LessThan(b))
+           add_constraint(model, con)
+           optimize!(model)
+           return value.(x)
+       end
+nonalgebraic_knapsack (generic function with 1 method)
+
+julia> nonalgebraic_knapsack([1, 2], [0.5, 0.5], 1.25)
+2-element Vector{Float64}:
+ 0.0
+ 2.0

Hopefully you agree that the macro version is much easier to read.

Part III: JuMP to MathOptInterface

In the third step, JuMP converts the functional form of the problem, that is, nonalgebraic_knapsack, into the MathOptInterface API:

julia> import MathOptInterface as MOI
+
+julia> import HiGHS
+
+julia> function mathoptinterface_knapsack(optimizer, c, w, b)
+           n = length(c)
+           model = MOI.instantiate(optimizer)
+           MOI.set(model, MOI.Silent(), true)
+           x = MOI.add_variables(model, n)
+           for i in 1:n
+               MOI.add_constraint(model, x[i], MOI.GreaterThan(0.0))
+               MOI.add_constraint(model, x[i], MOI.Integer())
+               MOI.set(model, MOI.VariableName(), x[i], "x[$i]")
+           end
+           MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
+           obj = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(c, x), 0.0)
+           MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj)
+           MOI.add_constraint(
+               model,
+               MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(w, x), 0.0),
+               MOI.LessThan(b),
+           )
+           MOI.optimize!(model)
+           return MOI.get.(model, MOI.VariablePrimal(), x)
+       end
+mathoptinterface_knapsack (generic function with 1 method)
+
+julia> mathoptinterface_knapsack(HiGHS.Optimizer, [1.0, 2.0], [0.5, 0.5], 1.25)
+2-element Vector{Float64}:
+ 0.0
+ 2.0

The code is becoming more verbose and looking less like the mathematical formulation that we started with.

Step IV: MathOptInterface to HiGHS

As a final step, the HiGHS.jl package converts the MathOptInterface form, that is, mathoptinterface_knapsack, into a HiGHS-specific API:

julia> using HiGHS
+
+julia> function highs_knapsack(c, w, b)
+           n = length(c)
+           model = Highs_create()
+           Highs_setBoolOptionValue(model, "output_flag", false)
+           for i in 1:n
+               Highs_addCol(model, c[i], 0.0, Inf, 0, C_NULL, C_NULL)
+               Highs_changeColIntegrality(model, i-1, 1)
+           end
+           Highs_changeObjectiveSense(model, -1)
+           Highs_addRow(
+               model,
+               -Inf,
+               b,
+               Cint(length(w)),
+               collect(Cint(0):Cint(n-1)),
+               w,
+           )
+           Highs_run(model)
+           x = fill(NaN, 2)
+           Highs_getSolution(model, x, C_NULL, C_NULL, C_NULL)
+           Highs_destroy(model)
+           return x
+       end
+highs_knapsack (generic function with 1 method)
+
+julia> highs_knapsack([1.0, 2.0], [0.5, 0.5], 1.25)
+2-element Vector{Float64}:
+ 0.0
+ 2.0

We've now gone from a algebraic model that looked identical to the mathematical model we started with, to a verbose function that uses HiGHS-specific functionality.

The difference between algebraic_knapsack and highs_knapsack highlights the benefit that algebraic modeling languages provide to users. Moreover, if we used a different solver, the solver-specific function would be entirely different. A key benefit of an algebraic modeling language is that you can change the solver without needing to rewrite the model.

diff --git a/previews/PR3536/changelog/index.html b/previews/PR3536/changelog/index.html new file mode 100644 index 00000000000..bc2374ed991 --- /dev/null +++ b/previews/PR3536/changelog/index.html @@ -0,0 +1,15 @@ + +Release notes · JuMP

Release notes

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Version 1.15.1 (September 24, 2023)

Fixed

  • Fixed support for single argument min and max operators (#3522)
  • Fixed error message for add_to_expression! when called with a GenericNonlinearExpr (#3506)
  • Fixed constraint tags with broadcasted constraints (#3515)
  • Fixed MethodError in MA.scaling (#3518)
  • Fixed support for arrays of Parameter variables (#3524)

Other

  • Updated to Documenter@1 (#3501)
  • Fixed links to data in tutorials (#3512)
  • Fixed typo in TSP tutorial (#3516)
  • Improved error message for VariableNotOwned errors (#3520)
  • Fixed various JET errors (#3519)

Version 1.15.0 (September 15, 2023)

This is a large minor release because it adds an entirely new data structure and API path for working with nonlinear programs. The previous nonlinear interface remains unchanged and is documented at Nonlinear Modeling (Legacy). The new interface is a treated as a non-breaking feature addition and is documented at Nonlinear Modeling.

Breaking

Although the new nonlinear interface is a feature addition, there are two changes which might be breaking for a very small number of users.

  • The syntax inside JuMP macros is parsed using a different code path, even for linear and quadratic expressions. We made this change to unify how we parse linear, quadratic, and nonlinear expressions. In all cases, the new code returns equivalent expressions, but because of the different order of operations, there are three changes to be aware of when updating:
    • The printed form of the expression may change, for example from x * y to y * x. This can cause tests which test the String representation of a model to fail.
    • Some coefficients may change slightly due to floating point round-off error.
    • Particularly when working with a JuMP extension, you may encounter a MethodError due to a missing or ambiguous method. These errors are due to previously existing bugs that were not triggered by the previous parsing code. If you encounter such an error, please open a GitHub issue.
  • The methods for Base.:^(x::VariableRef, n::Integer) and Base.:^(x::AffExpr, n::Integer) have changed. Previously, these methods supported only n = 0, 1, 2 and they always returned a QuadExpr, even for the case when n = 0 or n = 1. Now:
    • x^0 returns one(T), where T is the value_type of the model (defaults to Float64)
    • x^1 returns x
    • x^2 returns a QuadExpr
    • x^n where !(0 <= n <= 2) returns a NonlinearExpr.
    We made this change to support nonlinear expressions and to align the mathematical definition of the operation with their return type. (Previously, users were surprised that x^1 returned a QuadExpr.) As a consequence of this change, the methods are now not type-stable. This means that the compiler cannot prove that x^2 returns a QuadExpr. If benchmarking shows that this is a performance problem, you can use the type-stable x * x instead of x^2.

Added

Fixed

  • Fixed uses of @nospecialize which cause precompilation failures in Julia v1.6.0 and v1.6.1. (#3464)
  • Fixed adding a container of Parameter (#3473)
  • Fixed return type of x^0 and x^1 to no longer return QuadExpr (see note in Breaking section above) (#3474)
  • Fixed error messages in LowerBoundRef, UpperBoundRef, FixRef, IntegerRef, BinaryRef, ParameterRef and related functions (#3494)
  • Fixed type inference of empty containers in JuMP macros (#3500)

Other

  • Added GAMS to solver documentation (#3357)
  • Updated various tutorials (#3459) (#3460) (#3462) (#3463) (#3465) (#3490) (#3492) (#3503)
  • Added The network multi-commodity flow problem tutorial (#3491)
  • Added Two-stage stochastic programs tutorial (#3466)
  • Added better error messages for unsupported operations in LinearAlgebra (#3476)
  • Updated to the latest version of Documenter (#3484) (#3495) (#3497)
  • Updated GitHub action versions (#3507)

Version 1.14.1 (September 2, 2023)

Fixed

  • Fix links in Documentation (#3478)

Version 1.14.0 (August 27, 2023)

Added

Fixed

  • Fixed model_convert for BridgeableConstraint (#3437)
  • Fixed printing models with integer coefficients larger than typemax(Int) (#3447)
  • Fixed support for constant left-hand side functions in a complementarity constraint (#3452)

Other

  • Updated packages used in documentation (#3444) (#3455)
  • Fixed docstring tests (#3445)
  • Fixed printing change for MathOptInterface (#3446)
  • Fixed typos in documentation (#3448) (#3457)
  • Added SCIP to callback documentation (#3449)

Version 1.13.0 (July 27, 2023)

Added

Fixed

Other

  • Added Loraine.jl to the installation table (#3426)
  • Removed Penopt.jl from packages.toml (#3428)
  • Improved problem statement in cannery example of tutorial (#3430)
  • Minor cleanups in Containers.DenseAxisArray implementation (#3429)
  • Changed nested_problems.jl: outer/inner to upper/lower (#3433)
  • Removed second SDP relaxation in OPF tutorial (#3432)

Version 1.12.0 (June 19, 2023)

Added

Fixed

  • Fixed error message for matrix in HermitianPSDCone (#3369)
  • Fixed EditURL for custom documentation pages (#3373)
  • Fixed return type annotations for MOI.ConstraintPrimal and MOI.ConstraintDual (#3381)
  • Fixed printing change in Julia nightly (#3391)
  • Fixed printing of Complex coefficients (#3397)
  • Fixed printing of constraints in text/latex mode (#3405)
  • Fixed performance issue in Containers.rowtable (#3410)
  • Fixed bug when variables added to set of wrong dimension (#3411)

Other

  • Added more solver READMEs to the documentation (#3358) (#3360) (#3364) (#3365) (#3366) (#3368) (#3372) (#3374) (#3376) (#3379) (#3387) (#3389)
  • Added StatusSwitchingQP.jl to the installation table (#3354)
  • Updated checklist for adding a new solver (#3370)
  • Updated extension-tests.yml action (#3371) (#3375)
  • Color logs in GitHub actions (#3392)
  • Added new tutorials
  • Updated JuMP paper citation (#3400)
  • Changed GitHub action to upload LaTeX logs when building documentation (#3403)
  • Fixed printing of SCS log in documentation (#3406)
  • Updated solver versions (#3407)
  • Updated documentation to use Julia v1.9 (#3398)
  • Replaced _value_type with MOI.Utilities.value_type (#3414)
  • Fixed a typo in docstring (#3415)
  • Refactored API documentation (#3386)
  • Updated SCIP license (#3420)

Version 1.11.1 (May 19, 2023)

Fixed

  • Fixed a poor error message when sum(::DenseAxisArray; dims) was called (#3338)
  • Fixed support for dependent sets in the @variable macro (#3344)
  • Fixed a performance bug in constraints with sparse symmetric matrices (#3349)

Other

  • Improved the printing of complex numbers (#3332)
  • When printing, sets which contain constants ending in .0 now print as integers. This follows the behavior of constants in functions (#3341)
  • Added InfiniteOpt to the extensions documentation (#3343)
  • Added more documentation for the exponential cone (#3345) (#3347)
  • Added checklists for developers (#3346) (#3355)
  • Fixed test support upcoming Julia nightly (#3351)
  • Fixed extension-tests.yml action (#3353)
  • Add more solvers to the documentation (#3359) (#3361) (#3362)

Version 1.11.0 (May 3, 2023)

Added

  • Added new methods to print_active_bridges for printing a particular objective, constraint, or variable (#3316)

Fixed

  • Fixed tests for MOI v1.14.0 release (#3312)
  • Fixed indexing containers when an axis is Vector{Any} that contains a Vector{Any} element (#3280)
  • Fixed getindex(::AbstractJuMPScalar) which is called for an expression like x[] (#3314)
  • Fixed bug in set_string_names_on_creation with a vector of variables (#3322)
  • Fixed bug in memoize function in nonlinear documentation (#3337)

Other

  • Fixed typos in the documentation (#3317) (#3318) (#3328)
  • Added a test for the order of setting start values (#3315)
  • Added READMEs of solvers and extensions to the docs (#3309) (#3320) (#3327) (#3329) (#3333)
  • Style improvements to src/variables.jl (#3324)
  • Clarify that column generation does not find global optimum (#3325)
  • Add a GitHub actions workflow for testing extensions prior to release (#3331)
  • Document the release process for JuMP (#3334)
  • Fix links to discourse and chatroom (#3335)

Version 1.10.0 (April 3, 2023)

Added

  • Added Nonnegatives, Nonpositives and Zeros, and support vector-valued inequality syntax in the JuMP macros (#3273)
  • Added special support for LinearAlgebra.Symmetric and LinearAlgebra.Hermitian matrices in Zeros constraints (#3281) (#3296)
  • Added HermitianMatrixSpace and the Hermitian tag for generating a matrix of variables that is Hermitian (#3292) (#3293)
  • Added Semicontinuous and Semiinteger (#3302)
  • Added support for keyword indexing of containers (#3237)

Fixed

  • Fixed [compat] bound for MathOptInterface in Project.toml (#3272)

Other

  • Split out the Nested optimization problems tutorial (#3274)
  • Updated doctests to ensure none have hidden state (#3275) (#3276)
  • Clarified how lazy constraints may revisit points (#3278)
  • Added P-Norm example (#3282)
  • Clarified docs that macros create new bindings (#3284)
  • Fixed threading example (#3283)
  • Added plot to The minimum distortion problem (#3288)
  • Added Google style rules for Vale and fixed warnings (#3285)
  • Added citation for the JuMP 1.0 paper (#3294)
  • Updated package versions in the documentation (#3298)
  • Added comment for the order in which start values must be set (#3303)
  • Improved error message for unrecognized constraint operators (#3311)

Version 1.9.0 (March 7, 2023)

Added

Fixed

  • The matrix returned by a variable in HermitianPSDCone is now a LinearAlgebra.Hermitian matrix. This is potentially breaking if you have written code to assume the return is a Matrix. (#3245) (#3246)
  • Fixed missing support for Base.isreal of expressions (#3252)

Other

  • Fixed a thread safety issue in the Parallelism tutorial (#3240) (#3243)
  • Improved the error message when unsupported operators are used in @NL macros (#3236)
  • Clarified the documentation to say that matrices in HermitianPSDCone must be LinearAlgebra.Hermitian (#3241)
  • Minor style fixes to internal macro code (#3247)
  • Add Quantum state discrimination tutorial (#3250)
  • Improve error message when begin...end not passed to plural macros (#3255)
  • Document how to register function with varying number of input arguments (#3258)
  • Tidy tests by removing unneeded JuMP. prefixes (#3260)
  • Clarified the introduction to the Complex number support tutorial (#3262)
  • Fixed typos in the Documentation (#3263) (#3266) (#3268) (#3269)

Version 1.8.2 (February 27, 2023)

Fixed

  • Fixed dot product between complex JuMP expression and number (#3244)

Other

  • Polish simple SDP examples (#3232)

Version 1.8.1 (February 23, 2023)

Fixed

  • Fixed support for init in nonlinear generator expressions (#3226)

Other

  • Use and document import MathOptInterface as MOI (#3222)
  • Removed references in documentation to multiobjective optimization being unsupported (#3223)
  • Added tutorial on multi-objective portfolio optimization (#3227)
  • Refactored some of the conic tutorials (#3229)
  • Fixed typos in the documentation (#3230)
  • Added tutorial on parallelism (#3231)

Version 1.8.0 (February 16, 2023)

Added

  • Added --> syntax support for indicator constraints. The old syntax of => remains supported (#3207)
  • Added <--> syntax for reified constraints. For now, few solvers support reified constraints (#3206)
  • Added fix_discrete_variables. This is most useful for computing the dual of a mixed-integer program (#3208)
  • Added support for vector-valued objectives. For details, see the Multi-objective knapsack tutorial (#3176)

Fixed

  • Fixed a bug in lp_sensitivity_report by switching to an explicit LU factorization of the basis matrix (#3182)
  • Fixed a bug that prevented [; kwarg] arguments in macros (#3220)

Other

Version 1.7.0 (January 25, 2023)

Added

  • Added support for view of a Containers.DenseAxisArray (#3152) (#3180)
  • Added support for containers of variables in ComplexPlane (#3184)
  • Added support for minimum and maximum generators in nonlinear expressions (#3189)
  • Added SnoopPrecompile statements that reduce the time-to-first-solve in Julia 1.9 (#3193) (#3195) (#3196) (#3197)

Other

  • Large refactoring of the tests (#3166) (#3167) (#3168) (#3169) (#3170) (#3171)
  • Remove unreachable code due to VERSION checks (#3172)
  • Document how to test JuMP extensions (#3174)
  • Fix method ambiguities in Containers (#3173)
  • Improve error message that is thrown when = is used instead of == in the @constraint macro (#3178)
  • Improve the error message when Bool is used instead of Bin in the @variable macro (#3180)
  • Update versions of the documentation (#3185)
  • Tidy the import of packages and remove unnecessary prefixes (#3186) (#3187)
  • Refactor src/JuMP.jl by moving methods into more relevant files (#3188)
  • Fix docstring of Model not appearing in the documentation (#3198)

Version 1.6.0 (January 1, 2023)

Added

Fixed

  • Fixed promotion of complex expressions (#3150) (#3164)

Other

  • Added Benders tutorial with in-place resolves (#3145)
  • Added more Tips and tricks for linear programs (#3144) (#3163)
  • Clarified documentation that start can depend on the indices of a variable container (#3148)
  • Replace instances of length and size by the recommended eachindex and axes (#3149)
  • Added a warning explaining why the model is dirty when accessing solution results from a modified model (#3156)
  • Clarify documentation that PSD ensures a symmetric matrix (#3159)
  • Maintenance of the JuMP test suite (#3146) (#3158) (#3162)

Version 1.5.0 (December 8, 2022)

Added

Fixed

  • Fixed error message for vectorized interval constraints (#3123)
  • Fixed passing AbstractString to set_optimizer_attribute (#3127)

Other

  • Update package versions used in docs (#3119) (#3133) (#3139)
  • Fixed output of diet tutorial (#3120)
  • Explain how to use Dates.period in set_time_limit_sec (#3121)
  • Update to JuliaFormatter v1.0.15 (#3130)
  • Fixed HTTP server example in web_app.jl (#3131)
  • Update docs to build with Documenter#master (#3094)
  • Add tests for LinearAlgebra operations (#3132)
  • Tidy these release notes (#3135)
  • Added documentation for Complex number support (#3141)
  • Removed the "workforce scheduling" and "steelT3" tutorials (#3143)

Version 1.4.0 (October 29, 2022)

Added

Fixed

  • Fixed a bug in copy_to(dest::Model, src::MOI.ModelLike) when src has nonlinear components (#3101)
  • Fixed the printing of (-1.0 + 0.0im) coefficients in complex expressions (#3112)
  • Fixed a parsing bug in nonlinear expressions with generator statements that contain multiple for statements (#3116)

Other

  • Converted the multi-commodity flow tutorial to use an SQLite database (#3098)
  • Fixed a number of typos in the documentation (#3103) (#3107) (#3018)
  • Improved various style aspects of the PDF documentation (#3095) (#3098) (#3102)

Version 1.3.1 (September 28, 2022)

Fixed

  • Fixed a performance issue in relax_integrality (#3087)
  • Fixed the type stability of operators with Complex arguments (#3072)
  • Fixed a bug which added additional +() terms to some nonlinear expressions (#3091)
  • Fixed potential method ambiguities with AffExpr and QuadExpr objects (#3092)

Other

  • Added vale as a linter for the documentation (#3080)
  • Added a tutorial on debugging JuMP models (#3043)
  • Fixed a number of typos in the documentation (#3079) (#3083)
  • Many other small tweaks to the documentation (#3068) (#3073) (#3074) (#3075) (#3076) (#3077) (#3078) (#3081) (#3082) (#3084) (#3085) (#3089)

Version 1.3.0 (September 5, 2022)

Added

  • Support slicing in SparseAxisArray (#3031)

Fixed

  • Fixed a bug introduced in v1.2.0 that prevented DenseAxisArrays with Vector keys (#3064)

Other

  • Released the JuMP logos under the CC BY 4.0 license (#3063)
  • Minor tweaks to the documentation (#3054) (#3056) (#3057) (#3060) (#3061) (#3065)
  • Improved code coverage of a number of files (#3048) (#3049) (#3050) (#3051) (#3052) (#3053) (#3058) (#3059)

Version 1.2.1 (August 22, 2022)

Fixed

  • Fixed a bug when parsing two-sided nonlinear constraints (#3045)

Version 1.2.0 (August 16, 2022)

Breaking

This is a large minor release because it significantly refactors the internal code for handling nonlinear programs to use the MathOptInterface.Nonlinear submodule that was introduced in MathOptInterface v1.3.0. As a consequence, the internal datastructure in model.nlp_data has been removed, as has the JuMP._Derivatives submodule. Despite the changes, the public API for nonlinear programming has not changed, and any code that uses only the public API and that worked with v1.1.1 will continue to work with v1.2.0.

Added

  • Added all_constraints(model; include_variable_in_set_constraints) which simplifies returning a list of all constraint indices in the model.
  • Added the ability to delete nonlinear constraints via delete(::Model, ::NonlinearConstraintRef).
  • Added the ability to provide an explicit Hessian for a multivariate user-defined function.
  • Added support for querying the primal value of a nonlinear constraint via value(::NonlinearConstraintRef)

Fixed

  • Fixed a bug in Containers.DenseAxisArray so that it now supports indexing with keys that hash to the same value, even if they are different types, for example, Int32 and Int64.
  • Fixed a bug printing the model when the solver does not support MOI.Name.

Other

  • Added a constraint programming formulation to the Sudoku tutorial.
  • Added newly supported solvers Pajarito, Clarabel, and COPT to the installation table.
  • Fixed a variety of other miscellaneous issues in the documentation.

Version 1.1.1 (June 14, 2022)

Other

  • Fixed problem displaying LaTeX in the documentation
  • Minor updates to the style guide
  • Updated to MOI v1.4.0 in the documentation

Version 1.1.0 (May 25, 2022)

Added

  • Added num_constraints(::Model; count_variable_in_set_constraints) to simplify the process of counting the number of constraints in a model
  • Added VariableRef(::ConstraintRef) for querying the variable associated with a bound or integrality constraint.
  • Added set_normalized_coefficients for modifying the variable coefficients of a vector-valued constraint.
  • Added set_string_names_on_creation to disable creating String names for variables and constraints. This can improve performance.

Fixed

  • Fixed a bug passing nothing to the start keyword of @variable

Other

  • New tutorials:
    • Sensitivity analysis of a linear program
    • Serving web apps
  • Minimal ellipse SDP tutorial refactored and improved
  • Docs updated to the latest version of each package
  • Lots of minor fixes and improvements to the documentation

Version 1.0.0 (March 24, 2022)

Read more about this release, along with an acknowledgement of all the contributors in our JuMP 1.0.0 is released blog post.

Breaking

  • The previously deprecated functions (v0.23.0, v0.23.1) have been removed. Deprecation was to improve consistency of function names:
    • num_nl_constraints (see num_nonlinear_constraints)
    • all_nl_constraints (see all_nonlinear_constraints)
    • add_NL_expression (see add_nonlinear_expression)
    • set_NL_objective (see set_nonlinear_objective)
    • add_NL_constraint (see add_nonlinear_constraint)
    • nl_expr_string (see nonlinear_expr_string)
    • nl_constraint_string (see nonlinear_constraint_string)
    • SymMatrixSpace (see SymmetricMatrixSpace)
  • The unintentionally exported variable JuMP.op_hint has been renamed to the unexported JuMP._OP_HINT

Fixed

  • Fixed a bug writing .nl files
  • Fixed a bug broadcasting SparseAxisArrays

Version 0.23.2 (March 14, 2022)

Added

  • Added relative_gap to solution_summary
  • register now throws an informative error if the function is not differentiable using ForwardDiff. In some cases, the check in register will encounter a false negative, and the informative error will be thrown at run-time. This usually happens when the function is non-differentiable in a subset of the domain.

Fixed

  • Fixed a scoping issue when extending the container keyword of containers

Other

  • Docs updated to the latest version of each package

Version 0.23.1 (March 2, 2022)

Deprecated

  • nl_expr_string and nl_constraint_string have been renamed to nonlinear_expr_string and nonlinear_constraint_string. The old methods still exist with deprecation warnings. This change should impact very few users because to call them you must rely on private internals of the nonlinear API. Users are encouraged to use sprint(show, x) instead, where x is the nonlinear expression or constraint of interest.

Added

  • Added support for Base.abs2(x) where x is a variable or affine expression. This is mainly useful for complex-valued constraints.

Fixed

  • Fixed addition of complex and real affine expressions
  • Fixed arithmetic for Complex-valued quadratic expressions
  • Fixed variable bounds passed as Rational{Int}(Inf)
  • Fixed printing of the coefficient (0 + 1im)
  • Fixed a bug when solution_summary is called prior to optimize!

Version 0.23.0 (February 25, 2022)

JuMP v0.23.0 is a breaking release. It is also a release-candidate for JuMP v1.0.0. That is, if no issues are found with the v0.23.0 release, then it will be re-tagged as v1.0.0.

Breaking

  • Julia 1.6 is now the minimum supported version
  • MathOptInterface has been updated to v1.0.0
  • All previously deprecated functionality has been removed
  • PrintMode, REPLMode and IJuliaMode have been removed in favor of the MIME types MIME"text/plain" and MIME"text/latex". Replace instances of ::Type{REPLMode} with ::MIME"text/plain", REPLMode with MIME("text/plain"), ::Type{IJuliaMode} with ::MIME"text/latex", and IJuliaMode with MIME("text/latex").
  • Functions containing the nl_ acronym have been renamed to the more explicit nonlinear_. For example, num_nl_constraints is now num_nonlinear_constraints and set_NL_objective is now set_nonlinear_objective. Calls to the old functions throw an error explaining the new name.
  • SymMatrixSpace has been renamed to SymmetricMatrixSpace

Added

  • Added nonlinear_dual_start_value and set_nonlinear_dual_start_value
  • Added preliminary support for Complex coefficient types

Fixed

  • Fixed a bug in solution_summary

Other

  • MILP examples have been migrated from GLPK to HiGHS
  • Fixed various typos
  • Improved section on setting constraint start values

Troubleshooting problems when updating

If you experience problems when updating, you are likely using previously deprecated functionality. (By default, Julia does not warn when you use deprecated features.)

To find the deprecated features you are using, start Julia with --depwarn=yes:

$ julia --depwarn=yes

Then install JuMP v0.22.3:

julia> using Pkg
+julia> pkg"add JuMP@0.22.3"

And then run your code. Apply any suggestions, or search the release notes below for advice on updating a specific deprecated feature.

Version 0.22.3 (February 10, 2022)

Fixed

  • Fixed a reproducibility issue in the TSP tutorial
  • Fixed a reproducibility issue in the max_cut_sdp tutorial
  • Fixed a bug broadcasting an empty SparseAxisArray

Other

  • Added a warning and improved documentation for the modify-then-query case
  • Fixed a typo in the docstring of RotatedSecondOrderCone
  • Added Aqua.jl as a check for code health
  • Added introductions to each section of the tutorials
  • Improved the column generation and Benders decomposition tutorials
  • Updated documentation to MOI v0.10.8
  • Updated JuliaFormatter to v0.22.2

Version 0.22.2 (January 10, 2022)

Added

  • The function all_nl_constraints now returns all nonlinear constraints in a model
  • start_value and set_start_value can now be used to get and set the primal start for constraint references
  • Plural macros now return a tuple containing the elements that were defined instead of nothing
  • Anonymous variables are now printed as _[i] where i is the index of the variable instead of noname. Calling name(x) still returns "" so this is non-breaking.

Fixed

  • Fixed handling of min and max in nonlinear expressions
  • CartesianIndex is no longer allowed as a key for DenseAxisArrays.

Other

  • Improved the performance of GenericAffExpr
  • Added a tutorial on the Travelling Salesperson Problem
  • Added a tutorial on querying the Hessian of a nonlinear program
  • Added documentation on using custom solver binaries.

Version 0.22.1 (November 29, 2021)

Added

  • Export OptimizationSense enum, with instances: MIN_SENSE, MAX_SENSE, and FEASIBILITY_SENSE
  • Add Base.isempty(::Model) to match Base.empty(::Model)

Fixed

  • Fix bug in container with tuples as indices
  • Fix bug in set_time_limit_sec

Other

  • Add tutorial "Design patterns for larger models"
  • Remove release notes section from PDF
  • General edits of the documentation and error messages

Version 0.22.0 (November 10, 2021)

JuMP v0.22 is a breaking release

Breaking

JuMP 0.22 contains a number of breaking changes. However, these should be invisible for the majority of users. You will mostly encounter these breaking changes if you: wrote a JuMP extension, accessed backend(model), or called @SDconstraint.

The breaking changes are as follows:

  • MathOptInterface has been updated to v0.10.4. For users who have interacted with the MOI backend, this contains a large number of breaking changes. Read the MathOptInterface release notes for more details.
  • The bridge_constraints keyword argument to Model and set_optimizer has been renamed add_bridges to reflect that more thing were bridged than just constraints.
  • The backend(model) field now contains a concrete instance of a MOI.Utilities.CachingOptimizer instead of one with an abstractly typed optimizer field. In most cases, this will lead to improved performance. However, calling set_optimizer after backend invalidates the old backend. For example:
    model = Model()
    +b = backend(model)
    +set_optimizer(model, GLPK.Optimizer)
    +@variable(model, x)
    +# b is not updated with `x`! Get a new b by calling `backend` again.
    +new_b = backend(model)
  • All usages of @SDconstraint are deprecated. The new syntax is @constraint(model, X >= Y, PSDCone()).
  • Creating a DenseAxisArray with a Number as an axis will now display a warning. This catches a common error in which users write @variable(model, x[length(S)]) instead of @variable(model, x[1:length(S)]).
  • The caching_mode argument to Model, for example, Model(caching_mode = MOIU.MANUAL) mode has been removed. For more control over the optimizer, use direct_model instead.
  • The previously deprecated lp_objective_perturbation_range and lp_rhs_perturbation_range functions have been removed. Use lp_sensitivity_report instead.
  • The .m fields of NonlinearExpression and NonlinearParameter have been renamed to .model.
  • Infinite variable bounds are now ignored. Thus, @variable(model, x <= Inf) will show has_upper_bound(x) == false. Previously, these bounds were passed through to the solvers which caused numerical issues for solvers expecting finite bounds.
  • The variable_type and constraint_type functions were removed. This should only affect users who previously wrote JuMP extensions. The functions can be deleted without consequence.
  • The internal functions moi_mode, moi_bridge_constraints, moi_add_constraint, and moi_add_to_function_constant are no longer exported.
  • The un-used method Containers.generate_container has been deleted.
  • The Containers API has been refactored, and _build_ref_sets is now public as Containers.build_ref_sets.
  • The parse_constraint_ methods for extending @constraint at parse time have been refactored in a breaking way. Consult the Extensions documentation for more details and examples.

Added

  • The TerminationStatusCode and ResultStatusCode enums are now exported by JuMP. Prefer termination_status(model) == OPTIMAL instead of == MOI.OPTIMAL, although the MOI. prefix way still works.
  • Copy a x::DenseAxisArray to an Array by calling Array(x).
  • NonlinearExpression is now a subtype of AbstractJuMPScalar
  • Constraints such as @constraint(model, x + 1 in MOI.Integer()) are now supported.
  • primal_feasibility_report now accepts a function as the first argument.
  • Scalar variables @variable(model, x[1:2] in MOI.Integer()) creates two variables, both of which are constrained to be in the set MOI.Integer.
  • Conic constraints can now be specified as inequalities under a different partial ordering. So @constraint(model, x - y in MOI.Nonnegatives()) can now be written as @constraint(model, x >= y, MOI.Nonnegatives()).
  • Names are now set for vectorized constraints.

Fixed

  • Fixed a performance issue when show was called on a SparseAxisArray with a large number of elements.
  • Fixed a bug displaying barrier and simplex iterations in solution_summary.
  • Fixed a bug by implementing hash for DenseAxisArray and SparseAxisArray.
  • Names are now only set if the solver supports them. Previously, this prevented solvers such as Ipopt from being used with direct_model.
  • MutableArithmetics.Zero is converted into a 0.0 before being returned to the user. Previously, some calls to @expression would return the undocumented MutableArithmetics.Zero() object. One example is summing over an empty set @expression(model, sum(x[i] for i in 1:0)). You will now get 0.0 instead.
  • AffExpr and QuadExpr can now be used with == 0 instead of iszero. This fixes a number of issues relating to Julia standard libraries such as LinearAlgebra and SparseArrays.
  • Fixed a bug when registering a user-defined function with splatting.

Other

  • The documentation is now available as a PDF.
  • The documentation now includes a full copy of the MathOptInterface documentation to make it easy to link concepts between the docs. (The MathOptInterface documentation has also been significantly improved.)
  • The documentation contains a large number of improvements and clarifications on a range of topics. Thanks to @sshin23, @DilumAluthge, and @jlwether.
  • The documentation is now built with Julia 1.6 instead of 1.0.
  • Various error messages have been improved to be more readable.

Version 0.21.10 (September 4, 2021)

Added

  • Added add_NL_expression
  • add_NL_xxx functions now support AffExpr and QuadExpr as terms

Fixed

  • Fixed a bug in solution_summary
  • Fixed a bug in relax_integrality

Other

  • Improved error message in lp_sensitivity_report

Version 0.21.9 (August 1, 2021)

Added

  • Containers now support arbitrary container types by passing the type to the container keyword and overloading Containers.container.
  • is_valid now supports nonlinear constraints
  • Added unsafe_backend for querying the inner-most optimizer of a JuMP model.
  • Nonlinear parameters now support the plural @NLparameters macro.
  • Containers (for example, DenseAxisArray) can now be used in vector-valued constraints.

Other

  • Various improvements to the documentation.

Version 0.21.8 (May 8, 2021)

Added

  • The @constraint macro is now extendable in the same way as @variable.
  • AffExpr and QuadExpr can now be used in nonlinear macros.

Fixed

  • Fixed a bug in lp_sensitivity_report.
  • Fixed an inference issue when creating empty SparseAxisArrays.

Version 0.21.7 (April 12, 2021)

Added

  • Added primal_feasibility_report, which can be used to check whether a primal point satisfies primal feasibility.
  • Added coefficient, which returns the coefficient associated with a variable in affine and quadratic expressions.
  • Added copy_conflict, which returns the IIS of an infeasible model.
  • Added solution_summary, which returns (and prints) a struct containing a summary of the solution.
  • Allow AbstractVector in vector constraints instead of just Vector.
  • Added latex_formulation(model) which returns an object representing the latex formulation of a model. Use print(latex_formulation(model)) to print the formulation as a string.
  • User-defined functions in nonlinear expressions are now automatically registered to aid quick model prototyping. However, a warning is printed to encourage the manual registration.
  • DenseAxisArray's now support broadcasting over multiple arrays.
  • Container indices can now be iterators of Base.SizeUnknown.

Fixed

  • Fixed bug in rad2deg and deg2rad in nonlinear expressions.
  • Fixed a MethodError bug in Containers when forcing container type.
  • Allow partial slicing of a DenseAxisArray, resolving an issue from 2014.
  • Fixed a bug printing variable names in IJulia.
  • Ending an IJulia cell with model now prints a summary of the model (like in the REPL) not the latex formulation. Use print(model) to print the latex formulation.
  • Fixed a bug when copying models containing nested arrays.

Other

  • Tutorials are now part of the documentation, and more refactoring has taken place.
  • Added JuliaFormatter added as a code formatter.
  • Added some precompilation statements to reduce initial latency.
  • Various improvements to error messages to make them more helpful.
  • Improved performance of value(::NonlinearExpression).
  • Improved performance of fix(::VariableRef).

Version 0.21.6 (January 29, 2021)

Added

  • Added support for skew symmetric variables via @variable(model, X[1:2, 1:2] in SkewSymmetricMatrixSpace()).
  • lp_sensitivity_report has been added which significantly improves the performance of querying the sensitivity summary of an LP. lp_objective_perturbation_range and lp_rhs_perturbation_range are deprecated.
  • Dual warm-starts are now supported with set_dual_start_value and dual_start_value.
  • (\in<tab>) can now be used in macros instead of = or in.
  • Use haskey(model::Model, key::Symbol) to check if a name key is registered in a model.
  • Added unregister(model::Model, key::Symbol) to unregister a name key from model.
  • Added callback_node_status for use in callbacks.
  • Added print_bridge_graph to visualize the bridging graph generated by MathOptInterface.
  • Improved error message for containers with duplicate indices.

Fixed

  • Various fixes to pass tests on Julia 1.6.
  • Fixed a bug in the printing of nonlinear expressions in IJulia.
  • Fixed a bug when nonlinear expressions are passed to user-defined functions.
  • Some internal functions that were previously exported are now no longer exported.
  • Fixed a bug when relaxing a fixed binary variable.
  • Fixed a StackOverflowError that occurred when SparseAxisArrays had a large number of elements.
  • Removed an unnecessary type assertion in list_of_constraint_types.
  • Fixed a bug when copying models with registered expressions.

Other

  • The documentation has been significantly overhauled. It now has distinct sections for the manual, API reference, and examples. The existing examples in /examples have now been moved to /docs/src/examples and rewritten using Literate.jl, and they are now included in the documentation.
  • JuliaFormatter has been applied to most of the codebase. This will continue to roll out over time, as we fix upstream issues in the formatter, and will eventually become compulsory.
  • The root cause of a large number of method invalidations has been resolved.
  • We switched continuous integration from Travis and Appveyor to GitHub Actions.

Version 0.21.5 (September 18, 2020)

Fixed

  • Fixed deprecation warnings
  • Throw DimensionMismatch for incompatibly sized functions and sets
  • Unify treatment of keys(x) on JuMP containers

Version 0.21.4 (September 14, 2020)

Added

  • Add debug info when adding unsupported constraints
  • Add relax_integrality for solving continuous relaxation
  • Allow querying constraint conflicts

Fixed

  • Dispatch on Real for MOI.submit
  • Implement copy for CustomSet in tests
  • Don't export private macros
  • Fix invalid assertion in nonlinear
  • Error if constraint has NaN right-hand side
  • Improve speed of tests
  • Lots of work modularizing files in /test
  • Improve line numbers in macro error messages
  • Print nonlinear subexpressions
  • Various documentation updates
  • Dependency updates:
    • Datastructures 0.18
    • MathOptFormat v0.5
    • Prep for MathOptInterface 0.9.15

Version 0.21.3 (June 18, 2020)

  • Added Special Order Sets (SOS1 and SOS2) to JuMP with default weights to ease the creation of such constraints (#2212).
  • Added functions simplex_iterations, barrier_iterations and node_count (#2201).
  • Added function reduced_cost (#2205).
  • Implemented callback_value for affine and quadratic expressions (#2231).
  • Support MutableArithmetics.Zero in objective and constraints (#2219).
  • Documentation improvements:
    • Mention tutorials in the docs (#2223).
    • Update COIN-OR links (#2242).
    • Explicit link to the documentation of MOI.FileFormats (#2253).
    • Typo fixes (#2261).
  • Containers improvements:
    • Fix Base.map for DenseAxisArray (#2235).
    • Throw BoundsError if number of indices is incorrect for DenseAxisArray and SparseAxisArray (#2240).
  • Extensibility improvements:
    • Implement a set_objective method fallback that redirects to set_objective_sense and set_objective_function (#2247).
    • Add parse_constraint method with arbitrary number of arguments (#2051).
    • Add parse_constraint_expr and parse_constraint_head (#2228).

Version 0.21.2 (April 2, 2020)

  • Added relative_gap() to access MOI.RelativeGap() attribute (#2199).
  • Documentation fixes:
    • Added link to source for docstrings in the documentation (#2207).
    • Added docstring for @variables macro (#2216).
    • Typo fixes (#2177, #2184, #2182).
  • Implementation of methods for Base functions:
    • Implemented Base.empty! for JuMP.Model (#2198).
    • Implemented Base.conj for JuMP scalar types (#2209).

Fixed

  • Fixed sum of expression with scalar product in macro (#2178).
  • Fixed writing of nonlinear models to MathOptFormat (#2181).
  • Fixed construction of empty SparseAxisArray (#2179).
  • Fixed constraint with zero function (#2188).

Version 0.21.1 (Feb 18, 2020)

  • Improved the clarity of the with_optimizer deprecation warning.

Version 0.21.0 (Feb 16, 2020)

Breaking

  • Deprecated with_optimizer (#2090, #2084, #2141). You can replace with_optimizer by either nothing, optimizer_with_attributes or a closure:

    • replace with_optimizer(Ipopt.Optimizer) by Ipopt.Optimizer.
    • replace with_optimizer(Ipopt.Optimizer, max_cpu_time=60.0) by optimizer_with_attributes(Ipopt.Optimizer, "max_cpu_time" => 60.0).
    • replace with_optimizer(Gurobi.Optimizer, env) by () -> Gurobi.Optimizer(env).
    • replace with_optimizer(Gurobi.Optimizer, env, Presolve=0) by optimizer_with_attributes(() -> Gurobi.Optimizer(env), "Presolve" => 0).

    alternatively to optimizer_with_attributes, you can also set the attributes separately with set_optimizer_attribute.

  • Renamed set_parameter and set_parameters to set_optimizer_attribute and set_optimizer_attributes (#2150).

  • Broadcast should now be explicit inside macros. @SDconstraint(model, x >= 1) and @constraint(model, x + 1 in SecondOrderCone()) now throw an error instead of broadcasting 1 along the dimension of x (#2107).

  • @SDconstraint(model, x >= 0) is now equivalent to @constraint(model, x in PSDCone()) instead of @constraint(model, (x .- 0) in PSDCone()) (#2107).

  • The macros now create the containers with map instead of for loops, as a consequence, containers created by @expression can now have any element type and containers of constraint references now have concrete element types when possible. This fixes a long-standing issue where @expression could only be used to generate a collection of linear expressions. Now it works for quadratic expressions as well (#2070).

  • Calling deepcopy(::AbstractModel) now throws an error.

  • The constraint name is now printed in the model string (#2108).

Added

  • Added support for solver-independent and solver-specific callbacks (#2101).
  • Added write_to_file and read_from_file, supported formats are CBF, LP, MathOptFormat, MPS and SDPA (#2114).
  • Added support for complementarity constraints (#2132).
  • Added support for indicator constraints (#2092).
  • Added support for querying multiple solutions with the result keyword (#2100).
  • Added support for constraining variables on creation (#2128).
  • Added method delete that deletes a vector of variables at once if it is supported by the underlying solver (#2135).
  • The arithmetic between JuMP expression has be refactored into the MutableArithmetics package (#2107).
  • Improved error on complex values in NLP (#1978).
  • Added an example of column generation (#2010).

Fixed

  • Incorrect coefficients generated when using Symmetric variables (#2102)

Version 0.20.1 (Oct 18, 2019)

  • Add sections on @variables and @constraints in the documentation (#2062).
  • Fixed product of sparse matrices for Julia v1.3 (#2063).
  • Added set_objective_coefficient to modify the coefficient of a linear term of the objective function (#2008).
  • Added set_time_limit_sec, unset_time_limit_sec and time_limit_sec to set and query the time limit for the solver in seconds (#2053).

Version 0.20.0 (Aug 24, 2019)

  • Documentation updates.
  • Numerous bug fixes.
  • Better error messages (#1977, #1978, #1997, #2017).
  • Performance improvements (#1947, #2032).
  • Added LP sensitivity summary functions lp_objective_perturbation_range and lp_rhs_perturbation_range (#1917).
  • Added functions dual_objective_value, raw_status and set_parameter.
  • Added function set_objective_coefficient to modify the coefficient of a linear term of the objective (#2008).
  • Added functions set_normalized_rhs, normalized_rhs, and add_to_function_constant to modify and get the constant part of a constraint (#1935, #1960).
  • Added functions set_normalized_coefficient and normalized_coefficient to modify and get the coefficient of a linear term of a constraint (#1935, #1960).
  • Numerous other improvements in MOI 0.9, see the NEWS.md file of MOI for more details.

Version 0.19.2 (June 8, 2019)

  • Fix a bug in derivatives that could arise in models with nested nonlinear subexpressions.

Version 0.19.1 (May 12, 2019)

  • Usability and performance improvements.
  • Bug fixes.

Version 0.19.0 (February 15, 2019)

JuMP 0.19 contains significant breaking changes.

Breaking

  • JuMP's abstraction layer for communicating with solvers changed from MathProgBase (MPB) to MathOptInterface (MOI). MOI addresses many longstanding design issues. (See @mlubin's slides from JuMP-dev 2018.) JuMP 0.19 is compatible only with solvers that have been updated for MOI. See the installation guide for a list of solvers that have and have not yet been updated.

  • Most solvers have been renamed to PackageName.Optimizer. For example, GurobiSolver() is now Gurobi.Optimizer.

  • Solvers are no longer added to a model via Model(solver = XXX(kwargs...)). Instead use Model(with_optimizer(XXX, kwargs...)). For example, Model(with_optimizer(Gurobi.Optimizer, OutputFlag=0)).

  • JuMP containers (for example, the objects returned by @variable) have been redesigned. Containers.SparseAxisArray replaces JuMPDict, JuMPArray was rewritten (inspired by AxisArrays) and renamed Containers.DenseAxisArray, and you can now request a container type with the container= keyword to the macros. See the corresponding documentation for more details.

  • The statuses returned by solvers have changed. See the possible status values here. The MOI statuses are much richer than the MPB statuses and can be used to distinguish between previously indistinguishable cases (for example, did the solver have a feasible solution when it stopped because of the time limit?).

  • Starting values are separate from result values. Use value to query the value of a variable in a solution. Use start_value and set_start_value to get and set an initial starting point provided to the solver. The solutions from previous solves are no longer automatically set as the starting points for the next solve.

  • The data structures for affine and quadratic expressions AffExpr and QuadExpr have changed. Internally, terms are stored in dictionaries instead of lists. Duplicate coefficients can no longer exist. Accessors and iteration methods have changed.

  • JuMPNLPEvaluator no longer includes the linear and quadratic parts of the model in the evaluation calls. These are now handled separately to allow NLP solvers that support various types of constraints.

  • JuMP solver-independent callbacks have been replaced by solver-specific callbacks. See your favorite solver for more details. (See the note below: No solver-specific callbacks are implemented yet.)

  • The norm() syntax is no longer recognized inside macros. Use the SecondOrderCone() set instead.

  • JuMP no longer performs automatic transformation between special quadratic forms and second-order cone constraints. Support for these constraint classes depends on the solver.

  • The symbols :Min and :Max are no longer used as optimization senses. Instead, JuMP uses the OptimizationSense enum from MathOptInterface. @objective(model, Max, ...), @objective(model, Min, ...), @NLobjective(model, Max, ...), and @objective(model, Min, ...) remain valid, but @objective(m, :Max, ...) is no longer accepted.

  • The sign conventions for duals has changed in some cases for consistency with conic duality (see the documentation). The shadow_price helper method returns duals with signs that match conventional LP interpretations of dual values as sensitivities of the objective value to relaxations of constraints.

  • @constraintref is no longer defined. Instead, create the appropriate container to hold constraint references manually. For example,

    constraints = Dict() # Optionally, specify types for improved performance.
    +for i in 1:N
    +  constraints[i] = @constraint(model, ...)
    +end
  • The lowerbound, upperbound, and basename keyword arguments to the @variable macro have been renamed to lower_bound, upper_bound, and base_name, for consistency with JuMP's new style recommendations.

  • We rely on broadcasting syntax to apply accessors to collections of variables, for example, value.(x) instead of getvalue(x) for collections. (Use value(x) when x is a scalar object.)

Added

  • Splatting (like f(x...)) is recognized in restricted settings in nonlinear expressions.

  • Support for deleting constraints and variables.

  • The documentation has been completely rewritten using docstrings and Documenter.

  • Support for modeling mixed conic and quadratic models (for example, conic models with quadratic objectives and bi-linear matrix inequalities).

  • Significantly improved support for modeling new types of constraints and for extending JuMP's macros.

  • Support for providing dual warm starts.

  • Improved support for accessing solver-specific attributes (for example, the irreducible inconsistent subsystem).

  • Explicit control of whether symmetry-enforcing constraints are added to PSD constraints.

  • Support for modeling exponential cones.

  • Significant improvements in internal code quality and testing.

  • Style and naming guidelines.

  • Direct mode and manual mode provide explicit control over when copies of a model are stored or regenerated. See the corresponding documentation.

Regressions

There are known regressions from JuMP 0.18 that will be addressed in a future release (0.19.x or later):

  • Performance regressions in model generation (issue). Please file an issue anyway if you notice a significant performance regression. We have plans to address a number of performance issues, but we might not be aware of all of them.

  • Fast incremental NLP solves are not yet reimplemented (issue).

  • We do not yet have an implementation of solver-specific callbacks.

  • The column generation syntax in @variable has been removed (that is, the objective, coefficients, and inconstraints keyword arguments). Support for column generation will be re-introduced in a future release.

  • The ability to solve the continuous relaxation (that is, via solve(model; relaxation = true)) is not yet reimplemented (issue).

Version 0.18.5 (December 1, 2018)

  • Support views in some derivative evaluation functions.
  • Improved compatibility with PackageCompiler.

Version 0.18.4 (October 8, 2018)

  • Fix a bug in model printing on Julia 0.7 and 1.0.

Version 0.18.3 (October 1, 2018)

  • Add support for Julia v1.0 (Thanks @ExpandingMan)
  • Fix matrix expressions with quadratic functions (#1508)

Version 0.18.2 (June 10, 2018)

  • Fix a bug in second-order derivatives when expressions are present (#1319)
  • Fix a bug in @constraintref (#1330)

Version 0.18.1 (April 9, 2018)

  • Fix for nested tuple destructuring (#1193)
  • Preserve internal model when relaxation=true (#1209)
  • Minor bug fixes and updates for example

Version 0.18.0 (July 27, 2017)

  • Drop support for Julia 0.5.
  • Update for ForwardDiff 0.5.
  • Minor bug fixes.

Version 0.17.1 (June 9, 2017)

  • Use of constructconstraint! in @SDconstraint.
  • Minor bug fixes.

Version 0.17.0 (May 27, 2017)

  • Breaking change: Mixing quadratic and conic constraints is no longer supported.
  • Breaking change: The getvariable and getconstraint functions are replaced by indexing on the corresponding symbol. For instance, to access the variable with name x, one should now write m[:x] instead of getvariable(m, :x). As a consequence, creating a variable and constraint with the same name now triggers a warning, and accessing one of them afterwards throws an error. This change is breaking only in the latter case.
  • Addition of the getobjectivebound function that mirrors the functionality of the MathProgBase getobjbound function except that it takes into account transformations performed by JuMP.
  • Minor bug fixes.

The following changes are primarily of interest to developers of JuMP extensions:

  • The new syntax @constraint(model, expr in Cone) creates the constraint ensuring that expr is inside Cone. The Cone argument is passed to constructconstraint! which enables the call to the dispatched to an extension.
  • The @variable macro now calls constructvariable! instead of directly calling the Variable constructor. Extra arguments and keyword arguments passed to @variable are passed to constructvariable! which enables the call to be dispatched to an extension.
  • Refactor the internal function conicdata (used build the MathProgBase conic model) into smaller sub-functions to make these parts reusable by extensions.

Version 0.16.2 (March 28, 2017)

  • Minor bug fixes and printing tweaks
  • Address deprecation warnings for Julia 0.6

Version 0.16.1 (March 7, 2017)

  • Better support for AbstractArray in JuMP (Thanks @tkoolen)
  • Minor bug fixes

Version 0.16.0 (February 23, 2017)

  • Breaking change: JuMP no longer has a mechanism for selecting solvers by default (the previous mechanism was flawed and incompatible with Julia 0.6). Not specifying a solver before calling solve() will result in an error.
  • Breaking change: User-defined functions are no longer global. The first argument to JuMP.register is now a JuMP Model object within whose scope the function will be registered. Calling JuMP.register without a Model now produces an error.
  • Breaking change: Use the new JuMP.fix method to fix a variable to a value or to update the value to which a variable is fixed. Calling setvalue on a fixed variable now results in an error in order to avoid silent behavior changes. (Thanks @joaquimg)
  • Nonlinear expressions now print out similarly to linear/quadratic expressions (useful for debugging!)
  • New category keyword to @variable. Used for specifying categories of anonymous variables.
  • Compatibility with Julia 0.6-dev.
  • Minor fixes and improvements (Thanks @cossio, @ccoffrin, @blegat)

Version 0.15.1 (January 31, 2017)

  • Bugfix for @LinearConstraints and friends

Version 0.15.0 (December 22, 2016)

  • Julia 0.5.0 is the minimum required version for this release.
  • Document support for BARON solver
  • Enable info callbacks in more states than before, for example, for recording solutions. New when argument to addinfocallback (#814, thanks @yeesian)
  • Improved support for anonymous variables. This includes new warnings for potentially confusing use of the traditional non-anonymous syntax:
    • When multiple variables in a model are given the same name
    • When non-symbols are used as names, for example, @variable(m, x[1][1:N])
  • Improvements in iterating over JuMP containers (#836, thanks @IssamT)
  • Support for writing variable names in .lp file output (Thanks @leethargo)
  • Support for querying duals to SDP problems (Thanks @blegat)
  • The comprehension syntax with curly braces sum{}, prod{}, and norm2{} has been deprecated in favor of Julia's native comprehension syntax sum(), prod() and norm() as previously announced. (For early adopters of the new syntax, norm2() was renamed to norm() without deprecation.)
  • Unit tests rewritten to use Base.Test instead of FactCheck
  • Improved support for operations with matrices of JuMP types (Thanks @ExpandingMan)
  • The syntax to halt a solver from inside a callback has changed from throw(CallbackAbort()) to return JuMP.StopTheSolver
  • Minor bug fixes

Version 0.14.2 (December 12, 2016)

  • Allow singleton anonymous variables (includes bugfix)

Version 0.14.1 (September 12, 2016)

  • More consistent handling of states in informational callbacks, includes a new when parameter to addinfocallback for specifying in which state an informational callback should be called.

Version 0.14.0 (August 7, 2016)

  • Compatibility with Julia 0.5 and ForwardDiff 0.2
  • Support for "anonymous" variables, constraints, expressions, and parameters, for example, x = @variable(m, [1:N]) instead of @variable(m, x[1:N])
  • Support for retrieving constraints from a model by name via getconstraint
  • @NLconstraint now returns constraint references (as expected).
  • Support for vectorized expressions within lazy constraints
  • On Julia 0.5, parse new comprehension syntax sum(x[i] for i in 1:N if isodd(i)) instead of sum{ x[i], i in 1:N; isodd(i) }. The old syntax with curly braces will be deprecated in JuMP 0.15.
  • Now possible to provide nonlinear expressions as "raw" Julia Expr objects instead of using JuMP's nonlinear macros. This input format is useful for programmatically generated expressions.
  • s/Mathematical Programming/Mathematical Optimization/
  • Support for local cuts (Thanks to @madanim, Mehdi Madani)
  • Document Xpress interface developed by @joaquimg, Joaquim Dias Garcia
  • Minor bug and deprecation fixes (Thanks @odow, @jrevels)

Version 0.13.2 (May 16, 2016)

  • Compatibility update for MathProgBase

Version 0.13.1 (May 3, 2016)

  • Fix broken deprecation for registerNLfunction.

Version 0.13.0 (April 29, 2016)

  • Most exported methods and macros have been renamed to avoid camelCase. See the list of changes here. There is a 1-1 mapping from the old names to the new, and it is safe to simply replace the names to update existing models.
  • Specify variable lower/upper bounds in @variable using the lowerbound and upperbound keyword arguments.
  • Change name printed for variable using the basename keyword argument to @variable.
  • New @variables macro allows multi-line declaration of groups of variables.
  • A number of solver methods previously available only through MathProgBase are now exposed directly in JuMP. The fix was recorded live.
  • Compatibility fixes with Julia 0.5.
  • The "end" indexing syntax is no longer supported within JuMPArrays which do not use 1-based indexing until upstream issues are resolved, see here.

Version 0.12.2 (March 9, 2016)

  • Small fixes for nonlinear optimization

Version 0.12.1 (March 1, 2016)

  • Fix a regression in slicing for JuMPArrays (when not using 1-based indexing)

Version 0.12.0 (February 27, 2016)

  • The automatic differentiation functionality has been completely rewritten with a number of user-facing changes:
    • @defExpr and @defNLExpr now take the model as the first argument. The previous one-argument version of @defExpr is deprecated; all expressions should be named. For example, replace @defExpr(2x+y) with @defExpr(jump_model, my_expr, 2x+y).
    • JuMP no longer uses Julia's variable binding rules for efficiently re-solving a sequence of nonlinear models. Instead, we have introduced nonlinear parameters. This is a breaking change, so we have added a warning message when we detect models that may depend on the old behavior.
    • Support for user-defined functions integrated within nonlinear JuMP expressions.
  • Replaced iteration over AffExpr with Number-like scalar iteration; previous iteration behavior is now available via linearterms(::AffExpr).
  • Stopping the solver via throw(CallbackAbort()) from a callback no longer triggers an exception. Instead, solve() returns UserLimit status.
  • getDual() now works for conic problems (Thanks @emreyamangil.)

Version 0.11.3 (February 4, 2016)

  • Bug-fix for problems with quadratic objectives and semidefinite constraints

Version 0.11.2 (January 14, 2016)

  • Compatibility update for Mosek

Version 0.11.1 (December 1, 2015)

  • Remove usage of @compat in tests.
  • Fix updating quadratic objectives for nonlinear models.

Version 0.11.0 (November 30, 2015)

  • Julia 0.4.0 is the minimum required version for this release.
  • Fix for scoping semantics of index variables in sum{}. Index variables no longer leak into the surrounding scope.
  • Addition of the solve(m::Model, relaxation=true) keyword argument to solve the standard continuous relaxation of model m
  • The getConstraintBounds() method allows access to the lower and upper bounds of all constraints in a (nonlinear) model.
  • Update for breaking changes in MathProgBase

Version 0.10.3 (November 20, 2015)

  • Fix a rare error when parsing quadratic expressions
  • Fix Variable() constructor with default arguments
  • Detect unrecognized keywords in solve()

Version 0.10.2 (September 28, 2015)

  • Fix for deprecation warnings

Version 0.10.1 (September 3, 2015)

  • Fixes for ambiguity warnings.
  • Fix for breaking change in precompilation syntax in Julia 0.4-pre

Version 0.10.0 (August 31, 2015)

  • Support (on Julia 0.4 and later) for conditions in indexing @defVar and @addConstraint constructs, for example, @defVar(m, x[i=1:5,j=1:5; i+j >= 3])
  • Support for vectorized operations on Variables and expressions. See the documentation for details.
  • New getVar() method to access variables in a model by name
  • Support for semidefinite programming.
  • Dual solutions are now available for general nonlinear problems. You may call getDual on a reference object for a nonlinear constraint, and getDual on a variable object for Lagrange multipliers from active bounds.
  • Introduce warnings for two common performance traps: too many calls to getValue() on a collection of variables and use of the + operator in a loop to sum expressions.
  • Second-order cone constraints can be written directly with the norm() and norm2{} syntax.
  • Implement MathProgBase interface for querying Hessian-vector products.
  • Iteration over JuMPContainers is deprecated; instead, use the keys and values functions, and zip(keys(d),values(d)) for the old behavior.
  • @defVar returns Array{Variable,N} when each of N index sets are of the form 1:nᵢ.
  • Module precompilation: on Julia 0.4 and later, using JuMP is now much faster.

Version 0.9.3 (August 11, 2015)

  • Fixes for FactCheck testing on julia v0.4.

Version 0.9.2 (June 27, 2015)

  • Fix bug in @addConstraints.

Version 0.9.1 (April 25, 2015)

  • Fix for Julia 0.4-dev.
  • Small infrastructure improvements for extensions.

Version 0.9.0 (April 18, 2015)

  • Comparison operators for constructing constraints (for example, 2x >= 1) have been deprecated. Instead, construct the constraints explicitly in the @addConstraint macro to add them to the model, or in the @LinearConstraint macro to create a stand-alone linear constraint instance.
  • getValue() method implemented to compute the value of a nonlinear subexpression
  • JuMP is now released under the Mozilla Public License version 2.0 (was previously LGPL). MPL is a copyleft license which is less restrictive than LGPL, especially for embedding JuMP within other applications.
  • A number of performance improvements in ReverseDiffSparse for computing derivatives.
  • MathProgBase.getsolvetime(m) now returns the solution time reported by the solver, if available. (Thanks @odow, Oscar Dowson)
  • Formatting fix for LP format output. (Thanks @sbebo, Leonardo Taccari).

Version 0.8.0 (February 17, 2015)

  • Nonlinear subexpressions now supported with the @defNLExpr macro.
  • SCS supported for solving second-order conic problems.
  • setXXXCallback family deprecated in favor of addXXXCallback.
  • Multiple callbacks of the same type can be registered.
  • Added support for informational callbacks via addInfoCallback.
  • A CallbackAbort exception can be thrown from callback to safely exit optimization.

Version 0.7.4 (February 4, 2015)

  • Reduced costs and linear constraint duals are now accessible when quadratic constraints are present.
  • Two-sided nonlinear constraints are supported.
  • Methods for accessing the number of variables and constraints in a model are renamed.
  • New default procedure for setting initial values in nonlinear optimization: project zero onto the variable bounds.
  • Small bug fixes.

Version 0.7.3 (January 14, 2015)

  • Fix a method ambiguity conflict with Compose.jl (cosmetic fix)

Version 0.7.2 (January 9, 2015)

  • Fix a bug in sum(::JuMPDict)
  • Added the setCategory function to change a variables category (for example, continuous or binary)

after construction, and getCategory to retrieve the variable category.

Version 0.7.1 (January 2, 2015)

  • Fix a bug in parsing linear expressions in macros. Affects only Julia 0.4 and later.

Version 0.7.0 (December 29, 2014)

Linear/quadratic/conic programming

  • Breaking change: The syntax for column-wise model generation has been changed to use keyword arguments in @defVar.
  • On Julia 0.4 and later, variables and coefficients may be multiplied in any order within macros. That is, variable*coefficient is now valid syntax.
  • ECOS supported for solving second-order conic problems.

Nonlinear programming

  • Support for skipping model generation when solving a sequence of nonlinear models with changing data.
  • Fix a memory leak when solving a sequence of nonlinear models.
  • The @addNLConstraint macro now supports the three-argument version to define sets of nonlinear constraints.
  • KNITRO supported as a nonlinear solver.
  • Speed improvements for model generation.
  • The @addNLConstraints macro supports adding multiple (groups of) constraints at once. Syntax is similar to @addConstraints.
  • Discrete variables allowed in nonlinear problems for solvers which support them (currently only KNITRO).

General

  • Starting values for variables may now be specified with @defVar(m, x, start=value).
  • The setSolver function allows users to change the solver subsequent to model creation.
  • Support for "fixed" variables via the @defVar(m, x == 1) syntax.
  • Unit tests rewritten to use FactCheck.jl, improved testing across solvers.

Version 0.6.3 (October 19, 2014)

  • Fix a bug in multiplying two AffExpr objects.

Version 0.6.2 (October 11, 2014)

  • Further improvements and bug fixes for printing.
  • Fixed a bug in @defExpr.
  • Support for accessing expression graphs through the MathProgBase NLP interface.

Version 0.6.1 (September 19, 2014)

  • Improvements and bug fixes for printing.

Version 0.6.0 (September 9, 2014)

  • Julia 0.3.0 is the minimum required version for this release.
  • buildInternalModel(m::Model) added to build solver-level model in memory without optimizing.
  • Deprecate load_model_only keyword argument to solve.
  • Add groups of constraints with @addConstraints macro.
  • Unicode operators now supported, including for sum, for prod, and /
  • Quadratic constraints supported in @addConstraint macro.
  • Quadratic objectives supported in @setObjective macro.
  • MathProgBase solver-independent interface replaces Ipopt-specific interface for nonlinear problems
    • Breaking change: IpoptOptions no longer supported to specify solver options, use m = Model(solver=IpoptSolver(options...)) instead.
  • New solver interfaces: ECOS, NLopt, and nonlinear support for MOSEK
  • New option to control whether the lazy constraint callback is executed at each node in the B&B tree or just when feasible solutions are found
  • Add support for semicontinuous and semi-integer variables for those solvers that support them.
  • Add support for index dependencies (for example, triangular indexing) in @defVar, @addConstraint, and @defExpr (for example, @defVar(m, x[i=1:10,j=i:10])).
    • This required some changes to the internal structure of JuMP containers, which may break code that explicitly stored JuMPDict objects.

Version 0.5.8 (September 24, 2014)

  • Fix a bug with specifying solvers (affects Julia 0.2 only)

Version 0.5.7 (September 5, 2014)

  • Fix a bug in printing models

Version 0.5.6 (September 2, 2014)

  • Add support for semicontinuous and semi-integer variables for those solvers that support them.
    • Breaking change: Syntax for Variable() constructor has changed (use of this interface remains discouraged)
  • Update for breaking changes in MathProgBase

Version 0.5.5 (July 6, 2014)

  • Fix bug with problem modification: adding variables that did not appear in existing constraints or objective.

Version 0.5.4 (June 19, 2014)

  • Update for breaking change in MathProgBase which reduces loading times for using JuMP
  • Fix error when MIPs not solved to optimality

Version 0.5.3 (May 21, 2014)

  • Update for breaking change in ReverseDiffSparse

Version 0.5.2 (May 9, 2014)

  • Fix compatibility with Julia 0.3 prerelease

Version 0.5.1 (May 5, 2014)

  • Fix a bug in coefficient handling inside lazy constraints and user cuts

Version 0.5.0 (May 2, 2014)

  • Support for nonlinear optimization with exact, sparse second-order derivatives automatically computed. Ipopt is currently the only solver supported.
  • getValue for AffExpr and QuadExpr
  • Breaking change: getSolverModel replaced by getInternalModel, which returns the internal MathProgBase-level model
  • Groups of constraints can be specified with @addConstraint (see documentation for details). This is not a breaking change.
  • dot(::JuMPDict{Variable},::JuMPDict{Variable}) now returns the corresponding quadratic expression.

Version 0.4.1 (March 24, 2014)

  • Fix bug where change in objective sense was ignored when re-solving a model.
  • Fix issue with handling zero coefficients in AffExpr.

Version 0.4.0 (March 10, 2014)

  • Support for SOS1 and SOS2 constraints.
  • Solver-independent callback for user heuristics.
  • dot and sum implemented for JuMPDict objects. Now you can say @addConstraint(m, dot(a,x) <= b).
  • Developers: support for extensions to JuMP. See definition of Model in src/JuMP.jl for more details.
  • Option to construct the low-level model before optimizing.

Version 0.3.2 (February 17, 2014)

  • Improved model printing
    • Preliminary support for IJulia output

Version 0.3.1 (January 30, 2014)

  • Documentation updates
  • Support for MOSEK
  • CPLEXLink renamed to CPLEX

Version 0.3.0 (January 21, 2014)

  • Unbounded/infeasibility rays: getValue() will return the corresponding components of an unbounded ray when a model is unbounded, if supported by the selected solver. getDual() will return an infeasibility ray (Farkas proof) if a model is infeasible and the selected solver supports this feature.
  • Solver-independent callbacks for user generated cuts.
  • Use new interface for solver-independent QCQP.
  • setlazycallback renamed to setLazyCallback for consistency.

Version 0.2.0 (December 15, 2013)

Breaking

  • Objective sense is specified in setObjective instead of in the Model constructor.
  • lpsolver and mipsolver merged into single solver option.

Added

  • Problem modification with efficient LP restarts and MIP warm-starts.
  • Relatedly, column-wise modeling now supported.
  • Solver-independent callbacks supported. Currently we support only a "lazy constraint" callback, which works with Gurobi, CPLEX, and GLPK. More callbacks coming soon.

Version 0.1.2 (November 16, 2013)

  • Bug fixes for printing, improved error messages.
  • Allow AffExpr to be used in macros; for example, ex = y + z; @addConstraint(m, x + 2*ex <= 3)

Version 0.1.1 (October 23, 2013)

  • Update for solver specification API changes in MathProgBase.

Version 0.1.0 (October 3, 2013)

  • Initial public release.
diff --git a/previews/PR3536/developers/checklists/index.html b/previews/PR3536/developers/checklists/index.html new file mode 100644 index 00000000000..54b31285fb8 --- /dev/null +++ b/previews/PR3536/developers/checklists/index.html @@ -0,0 +1,65 @@ + +Checklists · JuMP

Checklists

The purpose of this page is to collate a series of checklists for commonly performed changes to the source code of JuMP.

In each case, copy the checklist into the description of the pull request.

Making a release

In preparation for a release, use the following checklist. These steps can be done in the same commit, or separately. The last commit should have the message "Prep for vX.Y.Z."

## Pre-release
+
+ - [ ] Check that the pinned packages in `docs/Project.toml` are updated. We pin
+       the versions so that changes in the solvers (changes in printing, small
+       numeric changes) do not break the printing of the JuMP docs in arbitrary
+       commits.
+ - [ ] Check that the `rev` fields in `docs/packages.toml` are updated. We pin
+       the versions of solvers and extensions to ensure that changes to their
+       READMEs do not break the JuMP docs in arbitrary commits, and to ensure
+       that the versions are compatible with the latest JuMP and
+       MathOptInterface releases.
+ - [ ] Update `docs/src/changelog.md`
+ - [ ] Run https://github.com/jump-dev/JuMP.jl/actions/workflows/extension-tests.yml
+       using a `workflow_dispatch` trigger to check for any changes in JuMP that
+       broke extensions.
+ - [ ] Change the version number in `Project.toml`
+ - [ ] Update the links in README.md
+ - [ ] The commit messages in this PR do not contain `[ci skip]`
+
+## The release
+
+ - [ ] After merging this pull request, comment `[at]JuliaRegistrator register` in
+       the GitHub commit. This should automatically publish a new version to the
+       Julia registry, as well as create a tag, and rebuild the documentation
+       for this tag.
+
+       These steps can take quite a bit of time (1 hour or more), so don't be
+       surprised if the new documentation takes a while to appear. In addition,
+       the links in the README will be broken until JuliaHub fetches the new
+       version on their servers.
+
+## Post-release
+
+ - [ ] Once the tag is created, update the relevant `release-` branch. The latest
+       release branch at the time of writing is `release-1.0` (we haven't
+       back-ported any patches that needed to create a `release-1.Y` branch). To
+       to update the release branch with the v1.10.0 tag, do:
+       ```
+       git checkout release-1.0
+       git pull
+       git merge v1.10.0
+       git push
+       ```

Adding a new solver to the documentation

Use the following checklist when adding a new solver to the JuMP documentation.

## Basic
+
+ - [ ] Check that the solver is a registered Julia package
+ - [ ] Check that the solver supports the long-term support release of Julia
+ - [ ] Check that the solver has a MathOptInterface wrapper
+ - [ ] Check that the tests call `MOI.Test.runtests`. Some test excludes are
+       permissible, but the reason for skipping a particular test should be
+       documented.
+ - [ ] Check that the README and/or documentation provides an example of how to
+       use the solver with JuMP
+
+## Documentation
+
+ - [ ] Add a new row to the table in `docs/src/installation.md`
+
+## Optional
+
+ - [ ] Add package metadata to `docs/packages.toml`
diff --git a/previews/PR3536/developers/contributing/index.html b/previews/PR3536/developers/contributing/index.html new file mode 100644 index 00000000000..e6a08744b37 --- /dev/null +++ b/previews/PR3536/developers/contributing/index.html @@ -0,0 +1,28 @@ + +Contributing · JuMP

How to contribute to JuMP

Welcome, this document explains some ways you can contribute to JuMP.

Code of Conduct

This project and everyone participating in it is governed by the JuMP Code of Conduct. By participating, you are expected to uphold this code.

Join the community forum

First up, join the community forum.

The forum is a good place to ask questions about how to use JuMP. You can also use the forum to discuss possible feature requests and bugs before raising a GitHub issue (more on this below).

Aside from asking questions, the easiest way you can contribute to JuMP is to help answer questions on the forum.

Join the developer chatroom

If you're interested in contributing code to JuMP, the next place to join is the developer chatroom. Let us know what you have in mind, and we can point you in the right direction.

Improve the documentation

Chances are, if you asked (or answered) a question on the community forum, then it is a sign that the documentation could be improved. Moreover, since it is your question, you are probably the best-placed person to improve it.

The docs are written in Markdown and are built using Documenter.jl. You can find the source of all the docs here.

If your change is small (like fixing typos, or one or two sentence corrections), the easiest way to do this is via GitHub's online editor. (GitHub has help on how to do this.)

If your change is larger, or touches multiple files, you will need to make the change locally and then use Git to submit a pull request. (See Contribute code to JuMP below for more on this.)

Tip

If you need any help, come join the developer chatroom and we will walk you through the process.

File a bug report

Another way to contribute to JuMP is to file bug reports.

Make sure you read the info in the box where you write the body of the issue before posting. You can also find a copy of that info here.

Tip

If you're unsure whether you have a real bug, post on the community forum first. Someone will either help you fix the problem, or let you know the most appropriate place to open a bug report.

Contribute code to JuMP

Finally, you can also contribute code to JuMP.

Warning

If you do not have experience with Git, GitHub, and Julia development, the first steps can be a little daunting. However, there are lots of tutorials available online, including these for:

If you need any help, come join the developer chatroom and we will walk you through the process.

Once you are familiar with Git and GitHub, the workflow for contributing code to JuMP is similar to the following:

Step 1: decide what to work on

The first step is to find an open issue (or open a new one) for the problem you want to solve. Then, before spending too much time on it, discuss what you are planning to do in the issue to see if other contributors are fine with your proposed changes. Getting feedback early can improve code quality, and avoid time spent writing code that does not get merged into JuMP.

Tip

At this point, remember to be patient and polite; you may get a lot of comments on your issue. However, do not be afraid. Comments mean that people are willing to help you improve the code that you are contributing to JuMP.

Step 2: fork JuMP

Go to https://github.com/jump-dev/JuMP.jl and click the "Fork" button in the top-right corner. This will create a copy of JuMP under your GitHub account.

Step 3: install JuMP locally

Open Julia and run:

] dev JuMP

This will download the JuMP Git repository to ~/.julia/dev/JuMP. If you're on Windows, this will be C:\\Users\\<my_name>\\.julia\\dev\\JuMP.

Warning

] command means "first type ] to enter the Julia pkg mode, then type the rest. Don't copy-paste the code directly.

Step 4: checkout a new branch

Note

In the following, replace any instance of GITHUB_ACCOUNT with your GitHub user name.

The next step is to checkout a development branch. In a terminal (or command prompt on Windows), run:

$ cd ~/.julia/dev/JuMP
+
+$ git remote add GITHUB_ACCOUNT https://github.com/GITHUB_ACCOUNT/JuMP.jl.git
+
+$ git checkout master
+
+$ git pull
+
+$ git checkout -b my_new_branch
Tip

Lines starting with $ mean "run these in a terminal (command prompt on Windows)."

Step 5: make changes

Now make any changes to the source code inside the ~/.julia/dev/JuMP directory.

Make sure you:

Tip

When you change the source code, you'll need to restart Julia for the changes to take effect. This is a pain, so install Revise.jl.

Step 6a: test your code changes

To test that your changes work, run the JuMP test-suite by opening Julia and running:

cd("~/.julia/dev/JuMP")
+] activate .
+] test
Warning

Running the tests might take a long time (~10–15 minutes).

Tip

If you're using Revise.jl, you can also run the tests by calling include:

include("test/runtests.jl")

This can be faster if you want to re-run the tests multiple times.

Step 6b: test your documentation changes

Open Julia, then run:

cd("~/.julia/dev/JuMP/docs")
+] activate .
+include("src/make.jl")
Warning

Building the documentation might take a long time (~10 minutes).

Tip

If there's a problem with the tests that you don't know how to fix, don't worry. Continue to step 5, and one of the JuMP contributors will comment on your pull request telling you how to fix things.

Step 7: make a pull request

Once you've made changes, you're ready to push the changes to GitHub. Run:

$ cd ~/.julia/dev/JuMP
+
+$ git add .
+
+$ git commit -m "A descriptive message of the changes"
+
+$ git push -u GITHUB_ACCOUNT my_new_branch

Then go to https://github.com/jump-dev/JuMP.jl and follow the instructions that pop up to open a pull request.

Step 8: respond to comments

At this point, remember to be patient and polite; you may get a lot of comments on your pull request. However, do not be afraid. A lot of comments means that people are willing to help you improve the code that you are contributing to JuMP.

To respond to the comments, go back to step 5, make any changes, test the changes in step 6, and then make a new commit in step 7. Your PR will automatically update.

Step 9: cleaning up

Once the PR is merged, clean-up your Git repository ready for the next contribution.

$ cd ~/.julia/dev/JuMP
+
+$ git checkout master
+
+$ git pull
Note

If you have suggestions to improve this guide, please make a pull request. It's particularly helpful if you do this after your first pull request because you'll know all the parts that could be explained better.

diff --git a/previews/PR3536/developers/custom_solver_binaries/index.html b/previews/PR3536/developers/custom_solver_binaries/index.html new file mode 100644 index 00000000000..3647795886f --- /dev/null +++ b/previews/PR3536/developers/custom_solver_binaries/index.html @@ -0,0 +1,93 @@ + +Custom binaries · JuMP

How to use a custom binary

Many solvers are not written in Julia, but instead in languages like C or C++. JuMP interacts with these solvers through binary dependencies.

For many open-source solvers, we automatically install the appropriate binary when you run Pkg.add("Solver"). For example, Pkg.add("ECOS") will also install the ECOS binary.

This page explains how this installation works, and how you can use a custom binary.

Compat

These instructions require Julia 1.6 or later.

Background

Each solver that JuMP supports is structured as a Julia package. For example, the interface for the ECOS solver is provided by the ECOS.jl package.

Tip

This page uses the example of ECOS.jl because it is simple to compile. Other solvers follow similar conventions. For example, the interface to the Clp solver is provided by Clp.jl.

The ECOS.jl package provides an interface between the C API of ECOS and MathOptInterface. However, it does not handle the installation of the solver binary; that is the job for a JLL package.

A JLL is a Julia package that wraps a pre-compiled binary. Binaries are built using Yggdrasil (for example, ECOS) and hosted in the JuliaBinaryWrappers GitHub repository (for example, ECOS_jll.jl).

JLL packages contain little code. Their only job is to dlopen a dynamic library, along with any dependencies.

JLL packages manage their binary dependencies using Julia's artifact system. Each JLL package has an Artifacts.toml file which describes where to find each binary artifact for each different platform that it might be installed on. Here is the Artifacts.toml file for ECOS_jll.jl.

The binaries installed by the JLL package should be sufficient for most users. In rare cases, however, you may require a custom binary. The two main reasons to use a custom binary are:

  • You want a binary with custom compilation settings (for example, debugging)
  • You want a binary with a set of dependencies that are not available on Yggdrasil (for example, a commercial solver like Gurobi or CPLEX).

The following sections explain how to replace the binaries provided by a JLL package with the custom ones you have compiled. As a reminder, we use ECOS as an example for simplicity, but the steps are the same for other solvers.

Explore the JLL you want to override

The first step is to explore the structure and filenames of the JLL package we want to override.

Find the location of the files using .artifact_dir:

julia> using ECOS_jll
+
+julia> ECOS_jll.artifact_dir
+"/Users/oscar/.julia/artifacts/2addb75332eff5a1657b46bb6bf30d2410bc7ecf"
Tip

This path may be different on other machines.

Here is what it contains:

julia> readdir(ECOS_jll.artifact_dir)
+4-element Vector{String}:
+ "include"
+ "lib"
+ "logs"
+ "share"
+
+julia> readdir(joinpath(ECOS_jll.artifact_dir, "lib"))
+1-element Vector{String}:
+ "libecos.dylib"

Other solvers may have a bin directory containing executables. To use a custom binary of ECOS, we need to replace /lib/libecos.dylib with our custom binary.

Compile a custom binary

The next step is to compile a custom binary. Because ECOS is written in C with no dependencies, this is easy to do if you have a C compiler:

oscar@Oscars-MBP jll_example % git clone https://github.com/embotech/ecos.git
+[... lines omitted ...]
+oscar@Oscars-MBP jll_example % cd ecos
+oscar@Oscars-MBP ecos % make shared
+[... many lines omitted...]
+oscar@Oscars-MBP ecos % mkdir lib
+oscar@Oscars-MBP ecos % cp libecos.dylib lib
Warning

Compiling custom solver binaries is an advanced operation. Due to the complexities of compiling various solvers, the JuMP community is unable to help you diagnose and fix compilation issues.

After this compilation step, we now have a folder /tmp/jll_example/ecos that contains lib and include directories with the same files as ECOS_jll:

julia> readdir(joinpath("ecos", "lib"))
+1-element Vector{String}:
+ "libecos.dylib"

Overriding a single library

To override the libecos library, we need to know what ECOS_jll calls it. (In most cases, it will also be libecos, but not always.)

There are two ways you can check.

  1. Check the bottom of the JLL's GitHub README. For example, ECOS_jll has a single LibraryProduct called libecos.
  2. Type ECOS_jll. and the press the [TAB] key twice to auto-complete available options:
    julia> ECOS_jll.
    +LIBPATH           PATH_list          best_wrapper       get_libecos_path   libecos_handle
    +LIBPATH_list      __init__           dev_jll            is_available       libecos_path
    +PATH              artifact_dir       find_artifact_dir  libecos
    Here you can see there is libecos, and more usefully for us, libecos_path.

Once you know the name of the variable to override (the one that ends in _path), use Preferences.jl to specify a new path:

using Preferences
+set_preferences!(
+    "LocalPreferences.toml",
+    "ECOS_jll",
+    "libecos_path" => "/tmp/jll_example/ecos/lib/libecos"
+)

This will create a file in your current directory called LocalPreferences.toml with the contents:

[ECOS_jll]
+libecos_path = "/tmp/jll_example/ecos/lib/libecos"

Now if you restart Julia, you will see:

julia> using ECOS_jll
+
+julia> ECOS_jll.libecos
+"/tmp/jll_example/ecos/lib/libecos"

To go back to using the default library, just delete the LocalPreferences.toml file.

Overriding an entire artifact

Sometimes a solver may provide a number of libraries and executables, and specifying the path for each of the becomes tedious. In this case, we can use Julia's Override.toml to replace an entire artifact.

Overriding an entire artifact requires you to replicate the structure and contents of the JLL package that we explored above.

In most cases you need only reproduce the include, lib, and bin directories (if they exist). You can safely ignore any logs or share directories. Take careful note of what files each directory contains and what they are called.

For our ECOS example, we already reproduced the structure when we compiled ECOS.

So, now we need to tell Julia to use our custom installation instead of the default. We can do this by making an override file at ~/.julia/artifacts/Overrides.toml.

Overrides.toml has the following content:

# Override for ECOS_jll
+2addb75332eff5a1657b46bb6bf30d2410bc7ecf = "/tmp/jll_example/ecos"

where 2addb75332eff5a1657b46bb6bf30d2410bc7ecf is the folder from the original ECOS_jll.artifact_dir and "/tmp/jll_example/ecos" is the location of our new installation. Replace these as appropriate for your system.

If you restart Julia after creating the override file, you will see:

julia> using ECOS_jll
+
+julia> ECOS_jll.artifact_dir
+"/tmp/jll_example/ecos"

Now when we use ECOS it will use our custom binary.

Using Cbc with a custom binary

As a second example, we demonstrate how to use Cbc.jl with a custom binary.

Explore the JLL you want to override

First, let's check where Cbc_jll is installed:

julia> using Cbc_jll
+
+julia> Cbc_jll.artifact_dir
+"/Users/oscar/.julia/artifacts/e481bc81db5e229ba1f52b2b4bd57484204b1b06"
+
+julia> readdir(Cbc_jll.artifact_dir)
+5-element Vector{String}:
+ "bin"
+ "include"
+ "lib"
+ "logs"
+ "share"
+
+julia> readdir(joinpath(Cbc_jll.artifact_dir, "bin"))
+1-element Vector{String}:
+ "cbc"
+
+julia> readdir(joinpath(Cbc_jll.artifact_dir, "lib"))
+10-element Vector{String}:
+ "libCbc.3.10.5.dylib"
+ "libCbc.3.dylib"
+ "libCbc.dylib"
+ "libCbcSolver.3.10.5.dylib"
+ "libCbcSolver.3.dylib"
+ "libCbcSolver.dylib"
+ "libOsiCbc.3.10.5.dylib"
+ "libOsiCbc.3.dylib"
+ "libOsiCbc.dylib"
+ "pkgconfig"

Compile a custom binary

Next, we need to compile Cbc. Cbc can be difficult to compile (it has a lot of dependencies), but for macOS users there is a homebrew recipe:

(base) oscar@Oscars-MBP jll_example % brew install cbc
+[ ... lines omitted ... ]
+(base) oscar@Oscars-MBP jll_example % brew list cbc
+/usr/local/Cellar/cbc/2.10.5/bin/cbc
+/usr/local/Cellar/cbc/2.10.5/include/cbc/ (76 files)
+/usr/local/Cellar/cbc/2.10.5/lib/libCbc.3.10.5.dylib
+/usr/local/Cellar/cbc/2.10.5/lib/libCbcSolver.3.10.5.dylib
+/usr/local/Cellar/cbc/2.10.5/lib/libOsiCbc.3.10.5.dylib
+/usr/local/Cellar/cbc/2.10.5/lib/pkgconfig/ (2 files)
+/usr/local/Cellar/cbc/2.10.5/lib/ (6 other files)
+/usr/local/Cellar/cbc/2.10.5/share/cbc/ (59 files)
+/usr/local/Cellar/cbc/2.10.5/share/coin/ (4 files)

Override single libraries

To use Preferences.jl to override specific libraries we first check the names of each library in Cbc_jll:

julia> Cbc_jll.
+LIBPATH               cbc                    get_libcbcsolver_path  libOsiCbc_path
+LIBPATH_list          cbc_path               is_available           libcbcsolver
+PATH                  dev_jll                libCbc                 libcbcsolver_handle
+PATH_list             find_artifact_dir      libCbc_handle          libcbcsolver_path
+__init__              get_cbc_path           libCbc_path
+artifact_dir          get_libCbc_path        libOsiCbc
+best_wrapper          get_libOsiCbc_path     libOsiCbc_handle

Then we add the following to LocalPreferences.toml:

[Cbc_jll]
+cbc_path = "/usr/local/Cellar/cbc/2.10.5/bin/cbc"
+libCbc_path = "/usr/local/Cellar/cbc/2.10.5/lib/libCbc.3.10.5"
+libOsiCbc_path = "/usr/local/Cellar/cbc/2.10.5/lib/libOsiCbc.3.10.5"
+libcbcsolver_path = "/usr/local/Cellar/cbc/2.10.5/lib/libCbcSolver.3.10.5"
Info

Note that capitalization matters, so libcbcsolver_path corresponds to libCbcSolver.3.10.5.

Override entire artifact

To use the homebrew install as our custom binary we add the following to ~/.julia/artifacts/Overrides.toml:

# Override for Cbc_jll
+e481bc81db5e229ba1f52b2b4bd57484204b1b06 = "/usr/local/Cellar/cbc/2.10.5"
diff --git a/previews/PR3536/developers/extensions/index.html b/previews/PR3536/developers/extensions/index.html new file mode 100644 index 00000000000..b48c7049939 --- /dev/null +++ b/previews/PR3536/developers/extensions/index.html @@ -0,0 +1,304 @@ + +Extensions · JuMP

Extensions

JuMP provides a variety of ways to extend the basic modeling functionality.

Tip

This documentation in this section is still a work-in-progress. The best place to look for ideas and help when writing a new JuMP extension are existing JuMP extensions. Examples include:

Compatibility

When writing JuMP extensions, you should carefully consider the compatibility guarantees that JuMP makes. In particular:

  • All functions, structs, and constants which do not begin with an underscore (_) are public. These are always safe to use, and they should all have corresponding documentation.
  • All identifiers beginning with an underscore (_) are private. These are not safe to use, because they may break in any JuMP release, including patch releases.
  • Unless explicitly mentioned in the documentation, all fields of a struct are private. These are not safe to use, because they may break in any JuMP release, including patch releases. An example of a field which is safe to use is the model.ext extension dictionary, which is documented in The extension dictionary.

In general, we strongly encourage you to use only the public API of JuMP. If you are missing a feature, please open a GitHub issue.

However, if you do use the private API (for example, because your feature request has not been implemented yet), then you must carefully restrict the versions of JuMP that your package is compatible with in the Project.toml file. The easiest way to do this is via the hyphen specifiers. For example, if your package supports all JuMP versions between v1.0.0 and v1.1.1, do:

JuMP = "1.0.0 - 1.1.1"

Then, whenever JuMP releases a new version, you should check if your package is still compatible and update the bound accordingly.

Define a new set

To define a new set for JuMP, subtype MOI.AbstractScalarSet or MOI.AbstractVectorSet and implement Base.copy for the set.

julia> struct NewMOIVectorSet <: MOI.AbstractVectorSet
+           dimension::Int
+       end
+
+julia> Base.copy(x::NewMOIVectorSet) = x
+
+julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @constraint(model, x in NewMOIVectorSet(2))
+[x[1], x[2]] ∈ NewMOIVectorSet(2)

However, for vector-sets, this requires the user to specify the dimension argument to their set, even though we could infer it from the length of x!

You can make a more user-friendly set by subtyping AbstractVectorSet and implementing moi_set.

julia> struct NewVectorSet <: JuMP.AbstractVectorSet end
+
+julia> JuMP.moi_set(::NewVectorSet, dim::Int) = NewMOIVectorSet(dim)
+
+julia> @constraint(model, x in NewVectorSet())
+[x[1], x[2]] ∈ NewMOIVectorSet(2)

Extend @variable

Just as Bin and Int create binary and integer variables, you can extend the @variable macro to create new types of variables. Here is an explanation by example, where we create a AddTwice type, that creates a tuple of two JuMP variables instead of a single variable.

First, create a new struct. This can be anything. Our struct holds a VariableInfo object that stores bound information, and whether the variable is binary or integer.

julia> struct AddTwice
+           info::JuMP.VariableInfo
+       end

Second, implement build_variable, which takes ::Type{AddTwice} as an argument, and returns an instance of AddTwice. Note that you can also receive keyword arguments.

julia> function JuMP.build_variable(
+           _err::Function,
+           info::JuMP.VariableInfo,
+           ::Type{AddTwice};
+           kwargs...
+       )
+           println("Can also use $kwargs here.")
+           return AddTwice(info)
+       end

Third, implement add_variable, which takes the instance of AddTwice from the previous step, and returns something. Typically, you will want to call add_variable here. For example, our AddTwice call is going to add two JuMP variables.

julia> function JuMP.add_variable(
+           model::JuMP.Model,
+           duplicate::AddTwice,
+           name::String,
+       )
+           a = JuMP.add_variable(
+               model,
+               JuMP.ScalarVariable(duplicate.info),
+               "$(name)_a",
+            )
+           b = JuMP.add_variable(
+               model,
+               JuMP.ScalarVariable(duplicate.info),
+               "$(name)_b",
+            )
+           return (a, b)
+       end

Now AddTwice can be passed to @variable similar to Bin or Int, or through the variable_type keyword. However, now it adds two variables instead of one.

julia> model = Model();
+
+julia> @variable(model, x[i=1:2], variable_type = AddTwice, kw = i)
+Can also use Base.Pairs(:kw => 1) here.
+Can also use Base.Pairs(:kw => 2) here.
+2-element Vector{Tuple{VariableRef, VariableRef}}:
+ (x[1]_a, x[1]_b)
+ (x[2]_a, x[2]_b)
+
+julia> num_variables(model)
+4
+
+julia> first(x[1])
+x[1]_a
+
+julia> last(x[2])
+x[2]_b

Extend @constraint

The @constraint macro has three steps that can be intercepted and extended: parse time, build time, and add time.

Parse

To extend the @constraint macro at parse time, implement one of the following methods:

Warning

Extending the constraint macro at parse time is an advanced operation and has the potential to interfere with existing JuMP syntax. Please discuss with the developer chatroom before publishing any code that implements these methods.

parse_constraint_head should be implemented to intercept an expression based on the .head field of Base.Expr. For example:

julia> using JuMP
+
+julia> const MutableArithmetics = JuMP._MA;
+
+julia> model = Model(); @variable(model, x);
+
+julia> function JuMP.parse_constraint_head(
+           _error::Function,
+           ::Val{:(:=)},
+           lhs,
+           rhs,
+       )
+           println("Rewriting := as ==")
+           new_lhs, parse_code = MutableArithmetics.rewrite(lhs)
+           build_code = :(
+               build_constraint($(_error), $(new_lhs), MOI.EqualTo($(rhs)))
+           )
+           return false, parse_code, build_code
+       end
+
+julia> @constraint(model, x + x := 1.0)
+Rewriting := as ==
+2 x = 1

parse_constraint_call should be implemented to intercept an expression of the form Expr(:call, op, args...). For example:

julia> using JuMP
+
+julia> const MutableArithmetics = JuMP._MA;
+
+julia> model = Model(); @variable(model, x);
+
+julia> function JuMP.parse_constraint_call(
+           _error::Function,
+           is_vectorized::Bool,
+           ::Val{:my_equal_to},
+           lhs,
+           rhs,
+       )
+           println("Rewriting my_equal_to to ==")
+           new_lhs, parse_code = MutableArithmetics.rewrite(lhs)
+           build_code = if is_vectorized
+               :(build_constraint($(_error), $(new_lhs), MOI.EqualTo($(rhs)))
+           )
+           else
+               :(build_constraint.($(_error), $(new_lhs), MOI.EqualTo($(rhs))))
+           end
+           return parse_code, build_code
+       end
+
+julia> @constraint(model, my_equal_to(x + x, 1.0))
+Rewriting my_equal_to to ==
+2 x = 1
Tip

When parsing a constraint you can recurse into sub-constraint (for example, the {expr} in z => {x <= 1}) by calling parse_constraint.

Build

To extend the @constraint macro at build time, implement a new build_constraint method.

This may mean implementing a method for a specific function or set created at parse time, or it may mean implementing a method which handles additional positional arguments.

build_constraint must return an AbstractConstraint, which can either be an AbstractConstraint already supported by JuMP, for example, ScalarConstraint or VectorConstraint, or a custom AbstractConstraint with a corresponding add_constraint method (see Add).

Tip

The easiest way to extend @constraint is via an additional positional argument to build_constraint.

Here is an example of adding extra arguments to build_constraint:

julia> model = Model(); @variable(model, x);
+
+julia> struct MyConstrType end
+
+julia> function JuMP.build_constraint(
+            _error::Function,
+            f::JuMP.GenericAffExpr,
+            set::MOI.EqualTo,
+            extra::Type{MyConstrType};
+            d = 0,
+       )
+            new_set = MOI.LessThan(set.value + d)
+            return JuMP.build_constraint(_error, f, new_set)
+       end
+
+julia> @constraint(model, my_con, x == 0, MyConstrType, d = 2)
+my_con : x ≤ 2
Note

Only a single positional argument can be given to a particular constraint. Extensions that seek to pass multiple arguments (for example, Foo and Bar) should combine them into one argument type (for example, FooBar).

Add

build_constraint returns an AbstractConstraint object. To extend @constraint at add time, define a subtype of AbstractConstraint, implement build_constraint to return an instance of the new type, and then implement add_constraint.

Here is an example:

julia> model = Model(); @variable(model, x);
+
+julia> struct MyTag
+           name::String
+       end
+
+julia> struct MyConstraint{S} <: AbstractConstraint
+           name::String
+           f::AffExpr
+           s::S
+       end
+
+julia> function JuMP.build_constraint(
+            _error::Function,
+            f::AffExpr,
+            set::MOI.AbstractScalarSet,
+            extra::MyTag,
+       )
+            return MyConstraint(extra.name, f, set)
+       end
+
+julia> function JuMP.add_constraint(
+            model::Model,
+            con::MyConstraint,
+            name::String,
+       )
+            return add_constraint(
+                model,
+                ScalarConstraint(con.f, con.s),
+                "$(con.name)[$(name)]",
+            )
+       end
+
+julia> @constraint(model, my_con, 2x <= 1, MyTag("my_prefix"))
+my_prefix[my_con] : 2 x - 1 ≤ 0

The extension dictionary

Every JuMP model has a field .ext::Dict{Symbol,Any} that can be used by extensions. This is useful if your extensions to @variable and @constraint need to store information between calls.

The most common way to initialize a model with information in the .ext dictionary is to provide a new constructor:

julia> function MyModel()
+           model = Model()
+           model.ext[:MyModel] = 1
+           return model
+       end
+MyModel (generic function with 1 method)
+
+julia> model = MyModel()
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+
+julia> model.ext
+Dict{Symbol, Any} with 1 entry:
+  :MyModel => 1

If you define extension data, implement copy_extension_data to support copy_model.

Defining new JuMP models

If extending individual calls to @variable and @constraint is not sufficient, it is possible to implement a new model via a subtype of AbstractModel. You can also define new AbstractVariableRefs to create different types of JuMP variables.

Warning

Extending JuMP in this manner is an advanced operation. We strongly encourage you to consider how you can use the methods mentioned in the previous sections to achieve your aims instead of defining new model and variable types. Consult the developer chatroom before starting work on this.

If you define new types, you will need to implement a considerable number of methods, and doing so will require a detailed understanding of the JuMP internals. Therefore, the list of methods to implement is currently undocumented.

The easiest way to extend JuMP by defining a new model type is to follow an existing example. A simple example to follow is the JuMPExtension module in the JuMP test suite. The best example of an external JuMP extension that implements an AbstractModel is InfiniteOpt.jl.

Testing JuMP extensions

The JuMP test suite contains a large number of tests for JuMP extensions. You can run these tests by copying the MIT-licensed Kokako.jl file in the JuMP tests into your /test folder, and then adding this snippet to your /test/runtests.jl file:

using MyJuMPExtension
+import JuMP
+include("Kokako.jl")
+const MODULES_TO_TEST = Kokako.include_modules_to_test(JuMP)
+Kokako.run_tests(
+    MODULES_TO_TEST,
+    MyJuMPExtension.MyModel,
+    MyJuMPExtension.MyVariableRef;
+    test_prefix = "test_extension_",
+)

Set an optimize! hook

Some extensions require modification to the problem after the user has finished constructing the problem, but before optimize! is called. For these situations, JuMP provides set_optimize_hook, which lets you intercept the optimize! call.

Here's a simple example of adding an optimize hook that extends optimize! to take a keyword argument silent:

julia> using JuMP, HiGHS
+
+julia> model = Model(HiGHS.Optimizer);
+
+julia> @variable(model, x >= 1.5, Int);
+
+julia> @objective(model, Min, x);
+
+julia> function silent_hook(model; silent::Bool)
+           if silent
+               set_silent(model)
+           else
+               unset_silent(model)
+           end
+           ## Make sure you set ignore_optimize_hook = true, or we'll
+           ## recursively enter the optimize hook!
+           return optimize!(model; ignore_optimize_hook = true)
+       end
+silent_hook (generic function with 1 method)
+
+julia> set_optimize_hook(model, silent_hook)
+silent_hook (generic function with 1 method)
+
+julia> optimize!(model; silent = true)
+
+julia> optimize!(model; silent = false)
+Solution has               num          max          sum
+Col     infeasibilities      0            0            0
+Integer infeasibilities      0            0            0
+Row     infeasibilities      0            0            0
+Row     residuals            0            0            0
+Presolving model
+0 rows, 0 cols, 0 nonzeros
+0 rows, 0 cols, 0 nonzeros
+Presolve: Optimal
+
+Solving report
+  Status            Optimal
+  Primal bound      2
+  Dual bound        2
+  Gap               0% (tolerance: 0.01%)
+  Solution status   feasible
+                    2 (objective)
+                    0 (bound viol.)
+                    0 (int. viol.)
+                    0 (row viol.)
+  Timing            0.00 (total)
+                    0.00 (presolve)
+                    0.00 (postsolve)
+  Nodes             0
+  LP iterations     0 (total)
+                    0 (strong br.)
+                    0 (separation)
+                    0 (heuristics)

Creating new container types

JuMP macros (for example, @variable) accept a container keyword argument to force the type of container that is chosen. By default, JuMP supports container = Array, container = DenseAxisArray, container = SparseAxisArray and container = Auto. You can extend support to user-defined types by implementing Containers.container.

For example, here is a container that reverses the order of the indices:

julia> struct Foo end
+
+julia> function Containers.container(f::Function, indices, ::Type{Foo})
+           return reverse([f(i...) for i in indices])
+       end
+
+julia> model = Model();
+
+julia> @variable(model, x[1:3], container = Foo)
+3-element Vector{VariableRef}:
+ x[3]
+ x[2]
+ x[1]
+
+julia> x[1]
+x[3]
+
+julia> @variable(model, y[1:3, 1:2], container = Foo)
+3×2 Matrix{VariableRef}:
+ y[3,2]  y[3,1]
+ y[2,2]  y[2,1]
+ y[1,2]  y[1,1]
+
+julia> y[1, 1]
+y[3,2]
+
+julia> @variable(model, z[i=1:3; isodd(i)], container = Foo)
+2-element Vector{VariableRef}:
+ z[3]
+ z[1]
+
+julia> z[2]
+z[1]
Warning

If you are a general user, you should not need to create a new container type. Instead, consider following User-defined containers and create a new container using standard Julia syntax. For example:

julia> model = Model();
+
+julia> @variable(model, x[1:3])
+3-element Vector{VariableRef}:
+ x[1]
+ x[2]
+ x[3]
+
+julia> y = reverse(x)
+3-element Vector{VariableRef}:
+ x[3]
+ x[2]
+ x[1]

Performance tips for extensions

The function-in-set design of MathOptInterface causes type stability issues in Julia if you try to iterate over all of the constraints in a model. The easiest way to fix this is to use a function barrier.

For example, instead of:

function all_names_slow(model)
+    names = Set{String}()
+    for ci in all_constraints(model)
+        push!(names, name(ci))
+    end
+    return names
+end

use:

function _function_barrier(names, model, ::Type{F}, ::Type{S}) where {F,S}
+    for ci in all_constraints(model, F, S)
+        push!(names, name(ci))
+    end
+    return
+end
+
+function all_names_fast(model)
+    names = Set{String}()
+    for (F, S) in list_of_constraint_types(model)
+        _function_barrier(names, model, F, S)
+    end
+    return names
+end
Note

It is important to explicitly type the F and S arguments. If you leave them untyped, for example, function _function_barrier(names, model, F, S), Julia will not specialize the function calls and performance will not be improved.

diff --git a/previews/PR3536/developers/roadmap/index.html b/previews/PR3536/developers/roadmap/index.html new file mode 100644 index 00000000000..6ec7214a096 --- /dev/null +++ b/previews/PR3536/developers/roadmap/index.html @@ -0,0 +1,6 @@ + +Roadmap · JuMP

Development roadmap

The JuMP developers have compiled this roadmap document to share their plans and goals with the JuMP community. Contributions to roadmap issues are especially invited.

Most of these issues will require changes to both JuMP and MathOptInterface, and are non-trivial in their implementation. They are in no particular order, but represent broad themes that we see as areas in which JuMP could be improved.

diff --git a/previews/PR3536/developers/style/index.html b/previews/PR3536/developers/style/index.html new file mode 100644 index 00000000000..d9babd577f6 --- /dev/null +++ b/previews/PR3536/developers/style/index.html @@ -0,0 +1,185 @@ + +Style Guide · JuMP

Style guide and design principles

Style guide

This section describes the coding style rules that apply to JuMP code and that we recommend for JuMP models and surrounding Julia code. The motivations for a style guide include:

  • conveying best practices for writing readable and maintainable code
  • reducing the amount of time spent on bike-shedding by establishing basic naming and formatting conventions
  • lowering the barrier for new contributors by codifying the existing practices (for example, you can be more confident your code will pass review if you follow the style guide)

In some cases, the JuMP style guide diverges from the Julia style guide. All such cases will be explicitly noted and justified.

The JuMP style guide adopts many recommendations from the Google style guides.

Info

The style guide is always a work in progress, and not all JuMP code follows the rules. When modifying JuMP, please fix the style violations of the surrounding code (that is, leave the code tidier than when you started). If large changes are needed, consider separating them into another PR.

JuliaFormatter

JuMP uses JuliaFormatter.jl as an auto-formatting tool.

We use the options contained in .JuliaFormatter.toml.

To format code, cd to the JuMP directory, then run:

] add JuliaFormatter@1
+using JuliaFormatter
+format("docs")
+format("src")
+format("test")
Info

A continuous integration check verifies that all PRs made to JuMP have passed the formatter.

The following sections outline extra style guide points that are not fixed automatically by JuliaFormatter.

Abstract types and composition

Specifying types for method arguments is mostly optional in Julia. The benefit of abstract method arguments is that it enables functions and types from one package to be used with functions and types from another package via multiple dispatch.

However, abstractly typed methods have two main drawbacks:

  1. It's possible to find out that you are working with unexpected types deep in the call chain, potentially leading to hard-to-diagnose MethodErrors.
  2. Untyped function arguments can lead to correctness problems if the user's choice of input type does not satisfy the assumptions made by the author of the function.

As a motivating example, consider the following function:

julia> function my_sum(x)
+           y = 0.0
+           for i in 1:length(x)
+               y += x[i]
+           end
+           return y
+       end
+my_sum (generic function with 1 method)

This function contains a number of implicit assumptions about the type of x:

  • x supports 1-based getindex and implements length
  • The element type of x supports addition with 0.0, and then with the result of x + 0.0.
Info

As a motivating example for the second point, VariableRef plus Float64 produces an AffExpr. Do not assume that +(::A, ::B) produces an instance of the type A or B.

my_sum works as expected if the user passes in Vector{Float64}:

julia> my_sum([1.0, 2.0, 3.0])
+6.0

but it doesn't respect input types, for example returning a Float64 if the user passes Vector{Int}:

julia> my_sum([1, 2, 3])
+6.0

but it throws a MethodError if the user passes String:

julia> my_sum("abc")
+ERROR: MethodError: no method matching +(::Float64, ::Char)
+[...]

This particular MethodError is hard to debug, particularly for new users, because it mentions +, Float64, and Char, none of which were called or passed by the user.

Dealing with MethodErrors

This section diverges from the Julia style guide, as well as other common guides like SciML. The following suggestions are intended to provide a friendlier experience for novice Julia programmers, at the cost of limiting the power and flexibility of advanced Julia programmers.

Code should follow the MethodError principle:

The MethodError principle

A user should see a MethodError only for methods that they called directly.

Bad:

_internal_function(x::Integer) = x + 1
+# The user sees a MethodError for _internal_function when calling
+# public_function("a string"). This is not very helpful.
+public_function(x) = _internal_function(x)

Good:

_internal_function(x::Integer) = x + 1
+# The user sees a MethodError for public_function when calling
+# public_function("a string"). This is easy to understand.
+public_function(x::Integer) = _internal_function(x)

If it is hard to provide an error message at the top of the call chain, then the following pattern is also ok:

_internal_function(x::Integer) = x + 1
+function _internal_function(x)
+    error(
+        "Internal error. This probably means that you called " *
+        "`public_function()`s with the wrong type.",
+    )
+end
+public_function(x) = _internal_function(x)

Dealing with correctness

Dealing with correctness is harder, because Julia has no way of formally specifying interfaces that abstract types must implement. Instead, here are two options that you can use when writing and interacting with generic code:

Option 1: use concrete types and let users extend new methods.

In this option, explicitly restrict input arguments to concrete types that are tested and have been validated for correctness. For example:

julia> function my_sum_option_1(x::Vector{Float64})
+           y = 0.0
+           for i in 1:length(x)
+               y += x[i]
+           end
+           return y
+       end
+my_sum_option_1 (generic function with 1 method)
+
+julia> my_sum_option_1([1.0, 2.0, 3.0])
+6.0

Using concrete types satisfies the MethodError principle:

julia> my_sum_option_1("abc")
+ERROR: MethodError: no method matching my_sum_option_1(::String)

and it allows other types to be supported in future by defining new methods:

julia> function my_sum_option_1(x::Array{T,N}) where {T<:Number,N}
+           y = zero(T)
+           for i in eachindex(x)
+               y += x[i]
+           end
+           return y
+       end
+my_sum_option_1 (generic function with 2 methods)

Importantly, these methods do not have to be defined in the original package.

Info

Some usage of abstract types is okay. For example, in my_sum_option_1, we allowed the element type, T, to be a subtype of Number. This is fairly safe, but it still has an implicit assumption that T supports zero(T) and +(::T, ::T).

Option 2: program defensively, and validate all assumptions.

An alternative is to program defensively, and to rigorously document and validate all assumptions that the code makes. In particular:

  1. All assumptions on abstract types that aren't guaranteed by the definition of the abstract type (for example, optional methods without a fallback) should be documented.
  2. If practical, the assumptions should be checked in code, and informative error messages should be provided to the user if the assumptions are not met. In general, these checks may be expensive, so you should prefer to do this once, at the highest level of the call-chain.
  3. Tests should cover for a range of corner cases and argument types.

For example:

"""
+    test_my_sum_defensive_assumptions(x::AbstractArray{T}) where {T}
+
+Test the assumptions made by `my_sum_defensive`.
+"""
+function test_my_sum_defensive_assumptions(x::AbstractArray{T}) where {T}
+    try
+        # Some types may not define zero.
+        @assert zero(T) isa T
+        # Check iteration supported
+        @assert iterate(x) isa Union{Nothing,Tuple{T,Int}}
+        # Check that + is defined
+        @assert +(zero(T), zero(T)) isa Any
+    catch err
+        error(
+            "Unable to call my_sum_defensive(::$(typeof(x))) because " *
+            "it failed an internal assumption",
+        )
+    end
+    return
+end
+
+"""
+    my_sum_defensive(x::AbstractArray{T}) where {T}
+
+Return the sum of the elements in the abstract array `x`.
+
+## Assumptions
+
+This function makes the following assumptions:
+
+ * That `zero(T)` is defined
+ * That `x` supports the iteration interface
+ * That  `+(::T, ::T)` is defined
+"""
+function my_sum_defensive(x::AbstractArray{T}) where {T}
+    test_my_sum_defensive_assumptions(x)
+    y = zero(T)
+    for xi in x
+        y += xi
+    end
+    return y
+end
+
+# output
+
+my_sum_defensive

This function works on Vector{Float64}:

julia> my_sum_defensive([1.0, 2.0, 3.0])
+6.0

as well as Matrix{Rational{Int}}:

julia> my_sum_defensive([(1//2) + (4//3)im; (6//5) + (7//11)im])
+17//10 + 65//33*im

and it throws an error when the assumptions aren't met:

julia> my_sum_defensive(['a', 'b', 'c'])
+ERROR: Unable to call my_sum_defensive(::Vector{Char}) because it failed an internal assumption
+[...]

As an alternative, you may choose not to call test_my_sum_defensive_assumptions within my_sum_defensive, and instead ask users of my_sum_defensive to call it in their tests.

Juxtaposed multiplication

Only use juxtaposed multiplication when the right-hand side is a symbol.

Good:

2x  # Acceptable if there are space constraints.
+2 * x  # This is preferred if space is not an issue.
+2 * (x + 1)

Bad:

2(x + 1)

Empty vectors

For a type T, T[] and Vector{T}() are equivalent ways to create an empty vector with element type T. Prefer T[] because it is more concise.

Comments

For non-native speakers and for general clarity, comments in code must be proper English sentences with appropriate punctuation.

Good:

# This is a comment demonstrating a good comment.

Bad:

# a bad comment

JuMP macro syntax

For consistency, always use parentheses.

Good:

@variable(model, x >= 0)

Bad:

@variable model x >= 0

For consistency, always use constant * variable as opposed to variable * constant. This makes it easier to read models in ambiguous cases like a * x.

Good:

a = 4
+@constraint(model, 3 * x <= 1)
+@constraint(model, a * x <= 1)

Bad:

a = 4
+@constraint(model, x * 3 <= 1)
+@constraint(model, x * a <= 1)

In order to reduce boilerplate code, prefer the plural form of macros over lots of repeated calls to singular forms.

Good:

@variables(model, begin
+    x >= 0
+    y >= 1
+    z <= 2
+end)

Bad:

@variable(model, x >= 0)
+@variable(model, y >= 1)
+@variable(model, z <= 2)

An exception is made for calls with many keyword arguments, since these need to be enclosed in parentheses in order to parse properly.

Acceptable:

@variable(model, x >= 0, start = 0.0, base_name = "my_x")
+@variable(model, y >= 1, start = 2.0)
+@variable(model, z <= 2, start = -1.0)

Also acceptable:

@variables(model, begin
+    x >= 0, (start = 0.0, base_name = "my_x")
+    y >= 1, (start = 2.0)
+    z <= 2, (start = -1.0)
+end)

While we always use in for for-loops, it is acceptable to use = in the container declarations of JuMP macros.

Okay:

@variable(model, x[i=1:3])

Also okay:

@variable(model, x[i in 1:3])

Naming

module SomeModule end
+function some_function end
+const SOME_CONSTANT = ...
+struct SomeStruct
+  some_field::SomeType
+end
+@enum SomeEnum ENUM_VALUE_A ENUM_VALUE_B
+some_local_variable = ...
+some_file.jl # Except for ModuleName.jl.

Exported and non-exported names

Begin private module level functions and constants with an underscore. All other objects in the scope of a module should be exported. (See JuMP.jl for an example of how to do this.)

Names beginning with an underscore should only be used for distinguishing between exported (public) and non-exported (private) objects. Therefore, never begin the name of a local variable with an underscore.

module MyModule
+
+export public_function, PUBLIC_CONSTANT
+
+function _private_function()
+    local_variable = 1
+    return
+end
+
+function public_function end
+
+const _PRIVATE_CONSTANT = 3.14159
+const PUBLIC_CONSTANT = 1.41421
+
+end

Use of underscores within names

The Julia style guide recommends avoiding underscores "when readable," for example, haskey, isequal, remotecall, and remotecall_fetch. This convention creates the potential for unnecessary bikeshedding and also forces the user to recall the presence/absence of an underscore, for example, "was that argument named basename or base_name?". For consistency, always use underscores in variable names and function names to separate words.

Use of !

Julia has a convention of appending ! to a function name if the function modifies its arguments. We recommend to:

  • Omit ! when the name itself makes it clear that modification is taking place, for example, add_constraint and set_name. We depart from the Julia style guide because ! does not provide a reader with any additional information in this case, and adherence to this convention is not uniform even in base Julia itself (consider Base.println and Base.finalize).
  • Use ! in all other cases. In particular it can be used to distinguish between modifying and non-modifying variants of the same function like scale and scale!.

Note that ! is not a self-documenting feature because it is still ambiguous which arguments are modified when multiple arguments are present. Be sure to document which arguments are modified in the method's docstring.

See also the Julia style guide recommendations for ordering of function arguments.

Abbreviations

Abbreviate names to make the code more readable, not to save typing. Don't arbitrarily delete letters from a word to abbreviate it (for example, indx). Use abbreviations consistently within a body of code (for example, do not mix con and constr, idx and indx).

Common abbreviations:

  • num for number
  • con for constraint

No one-letter variable names

Where possible, avoid one-letter variable names.

Use model = Model() instead of m = Model()

Exceptions are made for indices in loops.

@enum vs. Symbol

The @enum macro lets you define types with a finite number of values that are explicitly enumerated (like enum in C/C++). Symbols are lightweight strings that are used to represent identifiers in Julia (for example, :x).

@enum provides type safety and can have docstrings attached to explain the possible values. Use @enums when applicable, for example, for reporting statuses. Use strings to provide long-form additional information like error messages.

Use of Symbol should typically be reserved for identifiers, for example, for lookup in the JuMP model (model[:my_variable]).

using vs. import

using ModuleName brings all symbols exported by the module ModuleName into scope, while import ModuleName brings only the module itself into scope. (See the Julia manual) for examples and more details.

For the same reason that from <module> import * is not recommended in python (PEP 8), avoid using ModuleName except in throw-away scripts or at the REPL. The using statement makes it harder to track where symbols come from and exposes the code to ambiguities when two modules export the same symbol.

Prefer using ModuleName: x, p to import ModuleName.x, ModuleName.p and import MyModule: x, p because the import versions allow method extension without qualifying with the module name.

Similarly, using ModuleName: ModuleName is an acceptable substitute for import ModuleName, because it does not bring all symbols exported by ModuleName into scope. However, we prefer import ModuleName for consistency.

Documentation

This section describes the writing style that should be used when writing documentation for JuMP (and supporting packages).

We can recommend the documentation style guides by Divio, Google, and Write the Docs as general reading for those writing documentation. This guide delegates a thorough handling of the topic to those guides and instead elaborates on the points more specific to Julia and documentation that use Documenter.

  • Be concise
  • Use lists instead of long sentences
  • Use numbered lists when describing a sequence, for example, (1) do X, (2) then Y
  • Use bullet points when the items are not ordered
  • Example code should be covered by doctests
  • When a word is a Julia symbol and not an English word, enclose it with backticks. In addition, if it has a docstring in this doc add a link using @ref. If it is a plural, add the "s" after the closing backtick. For example,
    [`VariableRef`](@ref)s
  • Use @meta blocks for TODOs and other comments that shouldn't be visible to readers. For example,
    ```@meta
    +# TODO: Mention also X, Y, and Z.
    +```

Docstrings

  • Every exported object needs a docstring
  • All examples in docstrings should be jldoctests
  • Always use complete English sentences with proper punctuation
  • Do not terminate lists with punctuation (for example, as in this doc)

Here is an example:

"""
+    signature(args; kwargs...)
+
+Short sentence describing the function.
+
+Optional: add a slightly longer paragraph describing the function.
+
+## Notes
+
+ - List any notes that the user should be aware of
+
+## Examples
+
+```jldoctest
+julia> 1 + 1
+2
+```
+"""

Testing

Use a module to encapsulate tests, and structure all tests as functions. This avoids leaking local variables between tests.

Here is a basic skeleton:

module TestPkg
+
+using Test
+
+function runtests()
+    for name in names(@__MODULE__; all = true)
+        if startswith("$(name)", "test_")
+            @testset "$(name)" begin
+                getfield(@__MODULE__, name)()
+            end
+        end
+    end
+    return
+end
+
+_helper_function() = 2
+
+function test_addition()
+    @test 1 + 1 == _helper_function()
+    return
+end
+
+end # module TestPkg
+
+TestPkg.runtests()

Break the tests into multiple files, with one module per file, so that subsets of the codebase can be tested by calling include with the relevant file.

diff --git a/previews/PR3536/extensions/DimensionalData/index.html b/previews/PR3536/extensions/DimensionalData/index.html new file mode 100644 index 00000000000..c710b53c5f3 --- /dev/null +++ b/previews/PR3536/extensions/DimensionalData/index.html @@ -0,0 +1,42 @@ + +rafaqz/DimensionalData.jl · JuMP

DimensionalData.jl

DimensionalData.jl provides tools and abstractions for working with rectangular arrays that have named dimensions.

Compat

Using the DimensionalData extension with JuMP requires Julia v1.9 or later.

The DimensionalData extension in JuMP lets you construct a DimensionalData.DimArray as an alternative to Containers.DenseAxisArray in the JuMP macros.

License

DimensionalData.jl is licensed under the MIT license.

Installation

Install DimensionalData using Pkg.add:

import Pkg
+Pkg.add("DimensionalData")

Use with JuMP

Activate the extension by loading both JuMP and DimensionalData:

julia> using JuMP, DimensionalData

Then, pass container = DimensionalData.DimArray in the @variable, @constraint, or @expression macros:

julia> model = Model();
+
+julia> @variable(
+           model,
+           x[i = 2:4, j = ["a", "b"]] >= i,
+           container = DimensionalData.DimArray,
+       )
+3×2 DimArray{VariableRef,2} with dimensions:
+  Dim{:i} Sampled{Int64} 2:4 ForwardOrdered Regular Points,
+  Dim{:j} Categorical{String} String["a", "b"] ForwardOrdered
+    "a"     "b"
+ 2  x[2,a]  x[2,b]
+ 3  x[3,a]  x[3,b]
+ 4  x[4,a]  x[4,b]

Here x is a DimensionalData.Dim array object, so indexing uses the DimensionalData syntax:

julia> x[At(2), At("a")]
+x[2,a]
+
+julia> x[2, 2]
+x[3,b]

You can use container = DimensionalData.DimArray in the @expression macro:

julia> @expression(
+           model,
+           expr[j = ["a", "b"]],
+           sum(x[At(i), At(j)] for i in 2:4),
+           container = DimensionalData.DimArray,
+       )
+2-element DimArray{AffExpr,1} with dimensions:
+  Dim{:j} Categorical{String} String["a", "b"] ForwardOrdered
+ "a"  x[2,a] + x[3,a] + x[4,a]
+ "b"  x[2,b] + x[3,b] + x[4,b]

and in @constraint:

julia> @constraint(
+           model,
+           [j = ["a", "b"]],
+           expr[At(j)] <= 1,
+           container = DimensionalData.DimArray,
+       )
+2-element DimArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape},1} with dimensions:
+  Dim{:j} Categorical{String} String["a", "b"] ForwardOrdered
+ "a"  x[2,a] + x[3,a] + x[4,a] ≤ 1
+ "b"  x[2,b] + x[3,b] + x[4,b] ≤ 1

Documentation

See the DimensionalData.jl documentation for more details on the syntax and features of DimensionalData.DimArray.

diff --git a/previews/PR3536/extensions/introduction/index.html b/previews/PR3536/extensions/introduction/index.html new file mode 100644 index 00000000000..6ae231aa0a6 --- /dev/null +++ b/previews/PR3536/extensions/introduction/index.html @@ -0,0 +1,6 @@ + +Introduction · JuMP

Introduction

This section of the documentation contains brief documentation for some popular JuMP extensions. The list of extensions is not exhaustive, but instead is intended to help you discover popular JuMP extensions, and to give you an overview of the types of extensions that are possible to write with JuMP.

Affiliation

Packages beginning with jump-dev/ are developed and maintained by the JuMP developers.

Packages that do not begin with jump-dev/ are developed independently. The developers of these packages requested or consented to the inclusion of their README contents in the JuMP documentation for the benefit of users.

Adding new extensions

Written an extension? Add it to this section of the JuMP documentation by making a pull request to the docs/packages.toml file.

Weak dependencies

Some extensions listed in this section are implemented using the weak dependency feature added to Julia in v1.9. These extensions are activated if and only if you have JuMP and the other package loaded into your current scope with using or import.

Compat

Using a weak dependency requires Julia v1.9 or later.

diff --git a/previews/PR3536/index.html b/previews/PR3536/index.html new file mode 100644 index 00000000000..14571f37f5f --- /dev/null +++ b/previews/PR3536/index.html @@ -0,0 +1,13 @@ + +Introduction · JuMP
JuMP logo +JuMP logo

Introduction

Welcome to the documentation for JuMP.

Note

This documentation is also available in PDF format: JuMP.pdf.

What is JuMP?

JuMP is a domain-specific modeling language for mathematical optimization embedded in Julia. It currently supports a number of open-source and commercial solvers for a variety of problem classes, including linear, mixed-integer, second-order conic, semidefinite, and nonlinear programming.

Tip

If you aren't sure if you should use JuMP, read Should you use JuMP?.

Resources for getting started

There are few ways to get started with JuMP:

Tip

Need help? Join the community forum to search for answers to commonly asked questions.

Before asking a question, make sure to read the post make it easier to help you, which contains a number of tips on how to ask a good question.

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.

  • Tutorials contain worked examples of solving problems with JuMP. Start here if you are new to JuMP, or you have a particular problem class you want to model.

  • The Manual contains short code-snippets that explain how to achieve specific tasks in JuMP. Look here if you want to know how to achieve a particular task, such as how to Delete a variable or how to Modify an objective coefficient.

  • The API Reference contains a complete list of the functions you can use in JuMP. Look here if you want to know how to use a particular function.

  • The Background information section contains background reading material to provide context to JuMP. Look here if you want an understanding of what JuMP is and why we created it, rather than how to use it.

  • The Developer docs section contains information for people contributing to JuMP development or writing JuMP extensions. Don't worry about this section if you are using JuMP to formulate and solve problems as a user.

  • The MathOptInterface section is a self-contained copy of the documentation for MathOptInterface. Look here for functions and constants beginning with MOI., as well as for general information on how MathOptInterface works.

Citing JuMP

If you find JuMP useful in your work, we kindly request that you cite the following paper (preprint):

@article{Lubin2023,
+    author = {Miles Lubin and Oscar Dowson and Joaquim {Dias Garcia} and Joey Huchette and Beno{\^i}t Legat and Juan Pablo Vielma},
+    title = {{JuMP} 1.0: {R}ecent improvements to a modeling language for mathematical optimization},
+    journal = {Mathematical Programming Computation},
+    year = {2023},
+    doi = {10.1007/s12532-023-00239-3}
+}

NumFOCUS

NumFOCUS logo

JuMP is a Sponsored Project of NumFOCUS, a 501(c)(3) nonprofit charity in the United States. NumFOCUS provides JuMP with fiscal, legal, and administrative support to help ensure the health and sustainability of the project. Visit numfocus.org for more information.

You can support JuMP by donating.

Donations to JuMP are managed by NumFOCUS. For donors in the United States, your gift is tax-deductible to the extent provided by law. As with any donation, you should consult with your tax adviser about your particular tax situation.

JuMP's largest expense is the annual JuMP-dev workshop. Donations will help us provide travel support for JuMP-dev attendees and take advantage of other opportunities that arise to support JuMP development.

License

JuMP is licensed under the MPL-2.0 software license. Consult the license and the Mozilla FAQ for more information. In addition, JuMP is typically used in conjunction with solver packages and extensions which have their own licences. Consult their package repositories for the specific licenses that apply.

diff --git a/previews/PR3536/installation/index.html b/previews/PR3536/installation/index.html new file mode 100644 index 00000000000..bb407a96bc7 --- /dev/null +++ b/previews/PR3536/installation/index.html @@ -0,0 +1,24 @@ + +Installation Guide · JuMP

Installation Guide

This guide explains how to install Julia and JuMP. If you have installation troubles, read the Common installation issues section below.

Install Julia

JuMP is a package for Julia. To use JuMP, first download and install Julia.

Tip

If you are new to Julia, read our Getting started with Julia tutorial.

Choosing a version

You can install the "Current stable release" or the "Long-term support (LTS) release."

  • The "Current stable release" is the latest release of Julia. It has access to newer features, and is likely faster.
  • The "Long-term support release" is an older version of Julia that has continued to receive bug and security fixes. However, it may not have the latest features or performance improvements.

For most users, you should install the "Current stable release," and whenever Julia releases a new version of the current stable release, you should update your version of Julia. Note that any code you write on one version of the current stable release will continue to work on all subsequent releases.

For users in restricted software environments (for example, your enterprise IT controls what software you can install), you may be better off installing the long-term support release because you will not have to update Julia as frequently.

Install JuMP

From Julia, JuMP is installed using the built-in package manager:

import Pkg
+Pkg.add("JuMP")
Tip

We recommend you create a Pkg environment for each project you use JuMP for, instead of adding lots of packages to the global environment. The Pkg manager documentation has more information on this topic.

When we release a new version of JuMP, you can update with:

import Pkg
+Pkg.update("JuMP")

Install a solver

JuMP depends on solvers to solve optimization problems. Therefore, you will need to install one before you can solve problems with JuMP.

Install a solver using the Julia package manager, replacing "Clp" by the Julia package name as appropriate.

import Pkg
+Pkg.add("Clp")

Once installed, you can use Clp as a solver with JuMP as follows, using set_attribute to set solver-specific options:

using JuMP
+using Clp
+model = Model(Clp.Optimizer)
+set_attribute(model, "LogLevel" => 1)
+set_attribute(model, "PrimalTolerance" => 1e-7)
Note

Most packages follow the ModuleName.Optimizer naming convention, but exceptions may exist. See the README of the Julia package's GitHub repository for more details on how to use a particular solver, including any solver-specific options.

Supported solvers

Most solvers are not written in Julia, and some require commercial licenses to use, so installation is often more complex.

  • If a solver has Manual in the Installation column, the solver requires a manual installation step, such as downloading and installing a binary, or obtaining a commercial license. Consult the README of the relevant Julia package for more information.
  • If the solver has Manualᴹ in the Installation column, the solver requires an installation of MATLAB.
  • If the Installation column is missing an entry, installing the Julia package will download and install any relevant solver binaries automatically, and you shouldn't need to do anything other than Pkg.add.

Solvers with a missing entry in the Julia Package column are written in Julia. The link in the Solver column is the corresponding Julia package.

SolverJulia PackageInstallationLicenseSupports
Alpine.jlTriad NS(MI)NLP
Artelys KnitroKNITRO.jlManualComm.(MI)LP, (MI)SOCP, (MI)NLP
BARONBARON.jlManualComm.(MI)NLP
BonminAmplNLWriter.jlEPL(MI)NLP
CbcCbc.jlEPL(MI)LP
CDCSCDCS.jlManualᴹGPLLP, SOCP, SDP
CDDCDDLib.jlGPLLP
Clarabel.jlApacheLP, QP, SOCP, SDP
ClpClp.jlEPLLP
COPTCOPT.jlComm.(MI)LP, SOCP, SDP
COSMO.jlApacheLP, QP, SOCP, SDP
CouenneAmplNLWriter.jlEPL(MI)NLP
CPLEXCPLEX.jlManualComm.(MI)LP, (MI)SOCP
CSDPCSDP.jlEPLLP, SDP
DAQPDAQP.jlMIT(Mixed-binary) QP
EAGO.jlMITNLP
ECOSECOS.jlGPLLP, SOCP
FICO XpressXpress.jlManualComm.(MI)LP, (MI)SOCP
GLPKGLPK.jlGPL(MI)LP
GurobiGurobi.jlManualComm.(MI)LP, (MI)SOCP
HiGHSHiGHS.jlMIT(MI)LP, QP
Hypatia.jlMITLP, SOCP, SDP
IpoptIpopt.jlEPLLP, QP, NLP
Juniper.jlMIT(MI)SOCP, (MI)NLP
Loraine.jlMITLP, SDP
MadNLP.jlMITLP, QP, NLP
MiniZincMiniZinc.jlManualMPL-2CP-SAT
MOSEKMosekTools.jlManualComm.(MI)LP, (MI)SOCP, SDP
NLoptNLopt.jlGPLLP, QP, NLP
OcteractAmplNLWriter.jlComm.(MI)NLP
OSQPOSQP.jlApacheLP, QP
PATHPATHSolver.jlMITMCP
Pajarito.jlMPL-2(MI)NLP, (MI)SOCP, (MI)SDP
Pavito.jlMPL-2(MI)NLP
PenbmiPenopt.jlComm.Bilinear SDP
ProxSDP.jlMITLP, SOCP, SDP
RAPOSaAmplNLWriter.jlManualRAPOSa(MI)NLP
SCIPSCIP.jlApache(MI)LP, (MI)NLP
SCSSCS.jlMITLP, QP, SOCP, SDP
SDPASDPA.jl, SDPAFamily.jlGPLLP, SDP
SDPNALSDPNAL.jlManualᴹCC BY-SALP, SDP
SDPT3SDPT3.jlManualᴹGPLLP, SOCP, SDP
SeDuMiSeDuMi.jlManualᴹGPLLP, SOCP, SDP
StatusSwitchingQP.jlMITLP, QP
Tulip.jlMPL-2LP

Where:

  • LP = Linear programming
  • QP = Quadratic programming
  • SOCP = Second-order conic programming (including problems with convex quadratic constraints or objective)
  • MCP = Mixed-complementarity programming
  • NLP = Nonlinear programming
  • SDP = Semidefinite programming
  • (MI)XXX = Mixed-integer equivalent of problem type XXX
  • CP-SAT = Constraint programming and Boolean satisfiability
Note

Developed a solver or solver wrapper? This table is open for new contributions. Edit the installation.md file, and use the checklist Adding a new solver to the documentation when opening the pull request.

Note

Developing a solver or solver wrapper? See Models and the MathOptInterface docs for more details on how JuMP interacts with solvers. Please get in touch via the Developer Chatroom with any questions about connecting new solvers with JuMP.

AMPL-based solvers

Use AmplNLWriter to access solvers that support the NL format.

Some solvers, such as Bonmin and Couenne can be installed via the Julia package manager. Others need to be manually installed.

Consult the AMPL documentation for a complete list of supported solvers.

GAMS-based solvers

Use GAMS.jl to access solvers available through GAMS. Such solvers include: AlphaECP, Antigone, BARON, CONOPT, Couenne, LocalSolver, PATHNLP, SHOT, SNOPT, SoPlex. See a complete list here.

Note

GAMS.jl requires an installation of the commercial software GAMS for which a free community license exists.

NEOS-based solvers

Use NEOSServer.jl to access solvers available through the NEOS Server.

Common installation issues

Tip

When in doubt, run import Pkg; Pkg.update() to see if updating your packages fixes the issue. Remember you will need to exit Julia and start a new session for the changes to take effect.

Check the version of your packages

Each package is versioned with a three-part number of the form vX.Y.Z. You can check which versions you have installed with import Pkg; Pkg.status().

This should almost always be the most-recent release. You can check the releases of a package by going to the relevant GitHub page, and navigating to the "releases" page. For example, the list of JuMP releases is available at: https://github.com/jump-dev/JuMP.jl/releases.

If you post on the community forum, please include the output of Pkg.status().

Unsatisfiable requirements detected

Did you get an error like Unsatisfiable requirements detected for package JuMP? The Pkg documentation has a section on how to understand and manage these conflicts.

Installing new packages can make JuMP downgrade to an earlier version

Another common complaint is that after adding a new package, code that previously worked no longer works.

This usually happens because the new package is not compatible with the latest version of JuMP. Therefore, the package manager rolls-back JuMP to an earlier version. Here's an example.

First, we add JuMP:

(jump_example) pkg> add JuMP
+  Resolving package versions...
+Updating `~/jump_example/Project.toml`
+  [4076af6c] + JuMP v0.21.5
+Updating `~/jump_example/Manifest.toml`
+  ... lines omitted ...

The + JuMP v0.21.5 line indicates that JuMP has been added at version 0.21.5. However, watch what happens when we add JuMPeR:

(jump_example) pkg> add JuMPeR
+  Resolving package versions...
+Updating `~/jump_example/Project.toml`
+  [4076af6c] ↓ JuMP v0.21.5 ⇒ v0.18.6
+  [707a9f91] + JuMPeR v0.6.0
+Updating `~/jump_example/Manifest.toml`
+  ... lines omitted ...

JuMPeR gets added at version 0.6.0 (+ JuMPeR v0.6.0), but JuMP gets downgraded from 0.21.5 to 0.18.6 (↓ JuMP v0.21.5 ⇒ v0.18.6)! The reason for this is that JuMPeR doesn't support a version of JuMP newer than 0.18.6.

Tip

Pay careful attention to the output of the package manager when adding new packages, especially when you see a package being downgraded.

diff --git a/previews/PR3536/manual/callbacks/index.html b/previews/PR3536/manual/callbacks/index.html new file mode 100644 index 00000000000..ca40331eb12 --- /dev/null +++ b/previews/PR3536/manual/callbacks/index.html @@ -0,0 +1,87 @@ + +Solver-independent Callbacks · JuMP

Solver-independent Callbacks

Many mixed-integer (linear, conic, and nonlinear) programming solvers offer the ability to modify the solve process. Examples include changing branching decisions in branch-and-bound, adding custom cutting planes, providing custom heuristics to find feasible solutions, or implementing on-demand separators to add new constraints only when they are violated by the current solution (also known as lazy constraints).

While historically this functionality has been limited to solver-specific interfaces, JuMP provides solver-independent support for three types of callbacks:

  1. lazy constraints
  2. user-cuts
  3. heuristic solutions

Available solvers

Solver-independent callback support is limited to a few solvers. This includes CPLEX, GLPK, Gurobi, Xpress, and SCIP.

Warning

While JuMP provides a solver-independent way of accessing callbacks, you should not assume that you will see identical behavior when running the same code on different solvers. For example, some solvers may ignore user-cuts for various reasons, while other solvers may add every user-cut. Read the underlying solver's callback documentation to understand details specific to each solver.

Tip

This page discusses solver-independent callbacks. However, each solver listed above also provides a solver-dependent callback to provide access to the full range of solver-specific features. Consult the solver's README for an example of how to use the solver-dependent callback. This will require you to understand the C interface of the solver.

Things you can and cannot do during solver-independent callbacks

There is a limited range of things you can do during a callback. Only use the functions and macros explicitly stated in this page of the documentation, or in the Callbacks tutorial.

Using any other part of the JuMP API (for example, adding a constraint with @constraint or modifying a variable bound with set_lower_bound) is undefined behavior, and your solver may throw an error, return an incorrect solution, or result in a segfault that aborts Julia.

In each of the three solver-independent callbacks, there are two things you may query:

If you need to query any other information, use a solver-dependent callback instead. Each solver supporting a solver-dependent callback has information on how to use it in the README of their GitHub repository.

If you want to modify the problem in a callback, you must use a lazy constraint.

Warning

You can only set each callback once. Calling set twice will over-write the earlier callback. In addition, if you use a solver-independent callback, you cannot set a solver-dependent callback.

Lazy constraints

Lazy constraints are useful when the full set of constraints is too large to explicitly include in the initial formulation. When a MIP solver reaches a new solution, for example with a heuristic or by solving a problem at a node in the branch-and-bound tree, it will give the user the chance to provide constraints that would make the current solution infeasible. For some more information about lazy constraints, see this blog post by Paul Rubin.

A lazy constraint callback can be set using the following syntax:

julia> import GLPK
+
+julia> model = Model(GLPK.Optimizer);
+
+julia> @variable(model, x <= 10, Int)
+x
+
+julia> @objective(model, Max, x)
+x
+
+julia> function my_callback_function(cb_data)
+           status = callback_node_status(cb_data, model)
+           if status == MOI.CALLBACK_NODE_STATUS_FRACTIONAL
+               # `callback_value(cb_data, x)` is not integer (to some tolerance).
+               # If, for example, your lazy constraint generator requires an
+               # integer-feasible primal solution, you can add a `return` here.
+               return
+           elseif status == MOI.CALLBACK_NODE_STATUS_INTEGER
+               # `callback_value(cb_data, x)` is integer (to some tolerance).
+           else
+               @assert status == MOI.CALLBACK_NODE_STATUS_UNKNOWN
+               # `callback_value(cb_data, x)` might be fractional or integer.
+           end
+           x_val = callback_value(cb_data, x)
+           if x_val > 2 + 1e-6
+               con = @build_constraint(x <= 2)
+               MOI.submit(model, MOI.LazyConstraint(cb_data), con)
+           end
+       end
+my_callback_function (generic function with 1 method)
+
+julia> set_attribute(model, MOI.LazyConstraintCallback(), my_callback_function)
Info

The lazy constraint callback may be called at fractional or integer nodes in the branch-and-bound tree. There is no guarantee that the callback is called at every primal solution.

Warning

Only add a lazy constraint if your primal solution violates the constraint. Adding the lazy constraint irrespective of feasibility may result in the solver returning an incorrect solution, or lead to many constraints being added, slowing down the solution process.

model = Model(GLPK.Optimizer)
+@variable(model, x <= 10, Int)
+@objective(model, Max, x)
+function bad_callback_function(cb_data)
+    # Don't do this!
+    con = @build_constraint(x <= 2)
+    MOI.submit(model, MOI.LazyConstraint(cb_data), con)
+end
+function good_callback_function(cb_data)
+    if callback_value(x) > 2
+        con = @build_constraint(x <= 2)
+        MOI.submit(model, MOI.LazyConstraint(cb_data), con)
+    end
+end
+set_attribute(model, MOI.LazyConstraintCallback(), good_callback_function)
Warning

During the solve, a solver may visit a point that was cut off by a previous lazy constraint, for example, because the earlier lazy constraint was removed during presolve. If this happens, you must re-add the lazy constraint.

User cuts

User cuts, or simply cuts, provide a way for the user to tighten the LP relaxation using problem-specific knowledge that the solver cannot or is unable to infer from the model. Just like with lazy constraints, when a MIP solver reaches a new node in the branch-and-bound tree, it will give the user the chance to provide cuts to make the current relaxed (fractional) solution infeasible in the hopes of obtaining an integer solution. For more details about the difference between user cuts and lazy constraints see the aforementioned blog post.

A user-cut callback can be set using the following syntax:

julia> import GLPK
+
+julia> model = Model(GLPK.Optimizer);
+
+julia> @variable(model, x <= 10.5, Int)
+x
+
+julia> @objective(model, Max, x)
+x
+
+julia> function my_callback_function(cb_data)
+           x_val = callback_value(cb_data, x)
+           con = @build_constraint(x <= floor(x_val))
+           MOI.submit(model, MOI.UserCut(cb_data), con)
+       end
+my_callback_function (generic function with 1 method)
+
+julia> set_attribute(model, MOI.UserCutCallback(), my_callback_function)
Warning

User cuts must not change the set of integer feasible solutions. Equivalently, user cuts can only remove fractional solutions. If you add a cut that removes an integer solution (even one that is not optimal), the solver may return an incorrect solution.

Info

The user-cut callback may be called at fractional nodes in the branch-and-bound tree. There is no guarantee that the callback is called at every fractional primal solution.

Heuristic solutions

Integer programming solvers frequently include heuristics that run at the nodes of the branch-and-bound tree. They aim to find integer solutions quicker than plain branch-and-bound would to tighten the bound, allowing us to fathom nodes quicker and to tighten the integrality gap.

Some heuristics take integer solutions and explore their "local neighborhood" (for example, flipping binary variables, fix some variables and solve a smaller MILP) and others take fractional solutions and attempt to round them in an intelligent way.

You may want to add a heuristic of your own if you have some special insight into the problem structure that the solver is not aware of, for example, you can consistently take fractional solutions and intelligently guess integer solutions from them.

A heuristic solution callback can be set using the following syntax:

julia> import GLPK
+
+julia> model = Model(GLPK.Optimizer);
+
+julia> @variable(model, x <= 10.5, Int)
+x
+
+julia> @objective(model, Max, x)
+x
+
+julia> function my_callback_function(cb_data)
+           x_val = callback_value(cb_data, x)
+           status = MOI.submit(
+               model, MOI.HeuristicSolution(cb_data), [x], [floor(Int, x_val)]
+           )
+           println("I submitted a heuristic solution, and the status was: ", status)
+       end
+my_callback_function (generic function with 1 method)
+
+julia> set_attribute(model, MOI.HeuristicCallback(), my_callback_function)

The third argument to submit is a vector of JuMP variables, and the fourth argument is a vector of values corresponding to each variable.

MOI.submit returns an enum that depends on whether the solver accepted the solution. The possible return codes are:

  • MOI.HEURISTIC_SOLUTION_ACCEPTED
  • MOI.HEURISTIC_SOLUTION_REJECTED
  • MOI.HEURISTIC_SOLUTION_UNKNOWN
Warning

Some solvers may accept partial solutions. Others require a feasible integer solution for every variable. If in doubt, provide a complete solution.

Info

The heuristic solution callback may be called at fractional nodes in the branch-and-bound tree. There is no guarantee that the callback is called at every fractional primal solution.

diff --git a/previews/PR3536/manual/complex/index.html b/previews/PR3536/manual/complex/index.html new file mode 100644 index 00000000000..7a1cba54395 --- /dev/null +++ b/previews/PR3536/manual/complex/index.html @@ -0,0 +1,151 @@ + +Complex number support · JuMP

Complex number support

This page explains the complex-valued variables and constraints that JuMP supports. For a worked-example using these features, read the Quantum state discrimination tutorial.

Complex-valued variables

Create a complex-valued variable using ComplexPlane:

julia> model = Model();
+
+julia> @variable(model, x in ComplexPlane())
+real(x) + imag(x) im

Note that x is not a VariableRef; instead, it is an affine expression with Complex{Float64}-valued coefficients:

julia> typeof(x)
+GenericAffExpr{ComplexF64, VariableRef}

Behind the scenes, JuMP has created two real-valued variables, with names "real(x)" and "imag(x)":

julia> all_variables(model)
+2-element Vector{VariableRef}:
+ real(x)
+ imag(x)
+
+julia> name.(all_variables(model))
+2-element Vector{String}:
+ "real(x)"
+ "imag(x)"

Use the real and imag functions on x to return a real-valued affine expression representing each variable:

julia> typeof(real(x))
+AffExpr (alias for GenericAffExpr{Float64, GenericVariableRef{Float64}})
+
+julia> typeof(imag(x))
+AffExpr (alias for GenericAffExpr{Float64, GenericVariableRef{Float64}})

To create an anonymous variable, use the set keyword argument:

julia> model = Model();
+
+julia> x = @variable(model, set = ComplexPlane())
+_[1] + _[2] im

Complex-valued variable bounds

Because complex-valued variables lack a total ordering, the definition of a variable bound for a complex-valued variable is ambiguous. If you pass a real- or complex-valued argument to keywords such as lower_bound, upper_bound, and start_value, JuMP will apply the real and imaginary parts to the associated real-valued variables.

julia> model = Model();
+
+julia> @variable(
+           model,
+           x in ComplexPlane(),
+           lower_bound = 1.0,
+           upper_bound = 2.0 + 3.0im,
+           start = 4im,
+       )
+real(x) + imag(x) im
+
+julia> vars = all_variables(model)
+2-element Vector{VariableRef}:
+ real(x)
+ imag(x)
+
+julia> lower_bound.(vars)
+2-element Vector{Float64}:
+ 1.0
+ 0.0
+
+julia> upper_bound.(vars)
+2-element Vector{Float64}:
+ 2.0
+ 3.0
+
+julia> start_value.(vars)
+2-element Vector{Float64}:
+ 0.0
+ 4.0

Complex-valued equality constraints

JuMP reformulates complex-valued equality constraints into two real-valued constraints: one representing the real part, and one representing the imaginary part. Thus, complex-valued equality constraints can be solved any solver that supports the real-valued constraint type.

For example:

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x[1:2]);
+
+julia> @constraint(model, (1 + 2im) * x[1] + 3 * x[2] == 4 + 5im)
+(1 + 2im) x[1] + 3 x[2] = (4 + 5im)
+
+julia> optimize!(model)
+
+julia> value.(x)
+2-element Vector{Float64}:
+ 2.5
+ 0.5

is equivalent to

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x[1:2]);
+
+julia> @constraint(model, 1 * x[1] + 3 * x[2] == 4)  # real component
+x[1] + 3 x[2] = 4
+
+julia> @constraint(model, 2 * x[1] == 5)  # imag component
+2 x[1] = 5
+
+julia> optimize!(model)
+
+julia> value.(x)
+2-element Vector{Float64}:
+ 2.5
+ 0.5

This also applies if the variables are complex-valued:

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x in ComplexPlane());
+
+julia> @constraint(model, (1 + 2im) * x + 3 * x == 4 + 5im)
+(4 + 2im) real(x) + (-2 + 4im) imag(x) = (4 + 5im)
+
+julia> optimize!(model)
+
+julia> value(x)
+1.3 + 0.6000000000000001im

which is equivalent to

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x_real);
+
+julia> @variable(model, x_imag);
+
+julia> @constraint(model, x_real - 2 * x_imag + 3 * x_real == 4)
+4 x_real - 2 x_imag = 4
+
+julia> @constraint(model, x_imag + 2 * x_real + 3 * x_imag == 5)
+2 x_real + 4 x_imag = 5
+
+julia> optimize!(model)
+
+julia> value(x_real) + value(x_imag) * im
+1.3 + 0.6000000000000001im

Hermitian PSD Cones

JuMP supports creating matrices where are Hermitian.

julia> model = Model();
+
+julia> @variable(model, H[1:3, 1:3] in HermitianPSDCone())
+3×3 LinearAlgebra.Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
+ real(H[1,1])                    …  real(H[1,3]) + imag(H[1,3]) im
+ real(H[1,2]) - imag(H[1,2]) im     real(H[2,3]) + imag(H[2,3]) im
+ real(H[1,3]) - imag(H[1,3]) im     real(H[3,3])

Behind the scenes, JuMP has created nine real-valued decision variables:

julia> all_variables(model)
+9-element Vector{VariableRef}:
+ real(H[1,1])
+ real(H[1,2])
+ real(H[2,2])
+ real(H[1,3])
+ real(H[2,3])
+ real(H[3,3])
+ imag(H[1,2])
+ imag(H[1,3])
+ imag(H[2,3])

and a Vector{VariableRef}-in-MOI.HermitianPositiveSemidefiniteConeTriangle constraint:

julia> num_constraints(model, Vector{VariableRef}, MOI.HermitianPositiveSemidefiniteConeTriangle)
+1

The MOI.HermitianPositiveSemidefiniteConeTriangle set can be efficiently bridged to MOI.PositiveSemidefiniteConeTriangle, so it can be solved by any solver that supports PSD constraints.

Each element of H is an affine expression with Complex{Float64}-valued coefficients:

julia> typeof(H[1, 1])
+GenericAffExpr{ComplexF64, VariableRef}
+
+julia> typeof(H[2, 1])
+GenericAffExpr{ComplexF64, VariableRef}

Hermitian PSD constraints

The HermitianPSDCone can also be used in the @constraint macro:

julia> model = Model();
+
+julia> @variable(model, x[1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> import LinearAlgebra
+
+julia> H = LinearAlgebra.Hermitian([x[1] 1im; -1im -x[2]])
+2×2 LinearAlgebra.Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
+ x[1]  im
+ -im   -x[2]
+
+julia> @constraint(model, H in HermitianPSDCone())
+[x[1]  im;
+ -im   -x[2]] ∈ HermitianPSDCone()
Note

The matrix H in H in HermitianPSDCone() must be a LinearAlgebra.Hermitian matrix type. A build_constraint error will be thrown if the matrix is a different matrix type.

diff --git a/previews/PR3536/manual/constraints/index.html b/previews/PR3536/manual/constraints/index.html new file mode 100644 index 00000000000..dfca20892bc --- /dev/null +++ b/previews/PR3536/manual/constraints/index.html @@ -0,0 +1,737 @@ + +Constraints · JuMP

Constraints

JuMP is based on the MathOptInterface (MOI) API. Because of this, JuMP uses the following standard form to represent problems:

\[\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}\]

Each constraint, $f_i(x) \in \mathcal{S}_i$, is composed of a function and a set. For example, instead of calling $a^\top x \le b$ a less-than-or-equal-to constraint, we say that it is a scalar-affine-in-less-than constraint, where the function $a^\top x$ belongs to the less-than set $(-\infty, b]$. We use the shorthand function-in-set to refer to constraints composed of different types of functions and sets.

This page explains how to write various types of constraints in JuMP. For nonlinear constraints, see Nonlinear Modeling instead.

Add a constraint

Add a constraint to a JuMP model using the @constraint macro. The syntax to use depends on the type of constraint you wish to add.

Add a linear constraint

Create linear constraints using the @constraint macro:

julia> model = Model();
+
+julia> @variable(model, x[1:3]);
+
+julia> @constraint(model, c1, sum(x) <= 1)
+c1 : x[1] + x[2] + x[3] ≤ 1
+
+julia> @constraint(model, c2, x[1] + 2 * x[3] >= 2)
+c2 : x[1] + 2 x[3] ≥ 2
+
+julia> @constraint(model, c3, sum(i * x[i] for i in 1:3) == 3)
+c3 : x[1] + 2 x[2] + 3 x[3] = 3
+
+julia> @constraint(model, c4, 4 <= 2 * x[2] <= 5)
+c4 : 2 x[2] ∈ [4, 5]

Normalization

JuMP normalizes constraints by moving all of the terms containing variables to the left-hand side and all of the constant terms to the right-hand side. Thus, we get:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, c, 2x + 1 <= 4x + 4)
+c : -2 x ≤ 3

Add a quadratic constraint

In addition to affine functions, JuMP also supports constraints with quadratic terms. For example:

julia> model = Model();
+
+julia> @variable(model, x[i=1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> @variable(model, t >= 0)
+t
+
+julia> @constraint(model, my_q, x[1]^2 + x[2]^2 <= t^2)
+my_q : x[1]² + x[2]² - t² ≤ 0
Tip

Because solvers can take advantage of the knowledge that a constraint is quadratic, prefer adding quadratic constraints using @constraint, rather than @NLconstraint.

Vectorized constraints

You can also add constraints to JuMP using vectorized linear algebra. For example:

julia> model = Model();
+
+julia> @variable(model, x[i=1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> A = [1 2; 3 4]
+2×2 Matrix{Int64}:
+ 1  2
+ 3  4
+
+julia> b = [5, 6]
+2-element Vector{Int64}:
+ 5
+ 6
+
+julia> @constraint(model, con_vector, A * x == b)
+con_vector : [x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Zeros(2)
+
+julia> @constraint(model, con_scalar, A * x .== b)
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:
+ con_scalar : x[1] + 2 x[2] = 5
+ con_scalar : 3 x[1] + 4 x[2] = 6

The two constraints, == and .== are similar, but subtly different. The first creates a single constraint that is a MOI.VectorAffineFunction in MOI.Zeros constraint. The second creates a vector of MOI.ScalarAffineFunction in MOI.EqualTo constraints.

Which formulation to choose depends on the solver, and what you want to do with the constraint object con_vector or con_scalar.

  • If you are using a conic solver, expect the dual of con_vector to be a Vector{Float64}, and do not intend to delete a row in the constraint, choose the == formulation.
  • If you are using a solver that expects a list of scalar constraints, for example HiGHS, or you wish to delete part of the constraint or access a single row of the constraint, for example, dual(con_scalar[2]), then use the broadcast .==.

JuMP reformulates both constraints into the other form if needed by the solver, but choosing the right format for a particular solver is more efficient.

You can also use <=, .<= , >=, and .>= as comparison operators in the constraint.

julia> @constraint(model, A * x <= b)
+[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonpositives(2)
+
+julia> @constraint(model, A * x .<= b)
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ x[1] + 2 x[2] ≤ 5
+ 3 x[1] + 4 x[2] ≤ 6
+
+julia> @constraint(model, A * x >= b)
+[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonnegatives(2)
+
+julia> @constraint(model, A * x .>= b)
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.GreaterThan{Float64}}, ScalarShape}}:
+ x[1] + 2 x[2] ≥ 5
+ 3 x[1] + 4 x[2] ≥ 6

Vectorized matrix constraints

In most cases, you cannot use the non-broadcasting syntax for general matrices. For example:

julia> model = Model();
+
+julia> @variable(model, X[1:2, 1:2])
+2×2 Matrix{VariableRef}:
+ X[1,1]  X[1,2]
+ X[2,1]  X[2,2]
+
+julia> @constraint(model, X >= 0)
+ERROR: At none:1: `@constraint(model, X >= 0)`: Unsupported matrix in vector-valued set. Did you mean to use the broadcasting syntax `.>=` instead? Alternatively, perhaps you are missing a set argument like `@constraint(model, X >= 0, PSDCone())` or `@constraint(model, X >= 0, HermmitianPSDCone())`.
+Stacktrace:
+[...]

Instead, to represent matrix inequalities you must always use the element-wise broadcasting .==, .>=, or .<=, or use the Set inequality syntax.

There are two exceptions: if the result of the left-hand side minus the right-hand side is a LinearAlgebra.Symmetric matrix or a LinearAlgebra.Hermitian matrix, you may use the non-broadcasting equality syntax:

julia> using LinearAlgebra
+
+julia> model = Model();
+
+julia> @variable(model, X[1:2, 1:2], Symmetric)
+2×2 Symmetric{VariableRef, Matrix{VariableRef}}:
+ X[1,1]  X[1,2]
+ X[1,2]  X[2,2]
+
+julia> @constraint(model, X == LinearAlgebra.I)
+[X[1,1] - 1  X[1,2];
+ X[1,2]      X[2,2] - 1] ∈ Zeros()

Despite the model showing the matrix in Zeros, this will add only three rows to the constraint matrix because the symmetric constraints are redundant. In contrast, the broadcasting syntax adds four linear constraints:

julia> @constraint(model, X .== LinearAlgebra.I)
+2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:
+ X[1,1] = 1  X[1,2] = 0
+ X[1,2] = 0  X[2,2] = 1

The same holds for LinearAlgebra.Hermitian matrices:

julia> using LinearAlgebra
+
+julia> model = Model();
+
+julia> @variable(model, X[1:2, 1:2] in HermitianPSDCone())
+2×2 Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
+ real(X[1,1])                    real(X[1,2]) + imag(X[1,2]) im
+ real(X[1,2]) - imag(X[1,2]) im  real(X[2,2])
+
+julia> @constraint(model, X == LinearAlgebra.I)
+[real(X[1,1]) - 1                real(X[1,2]) + imag(X[1,2]) im;
+ real(X[1,2]) - imag(X[1,2]) im  real(X[2,2]) - 1] ∈ Zeros()
+
+julia> @constraint(model, X .== LinearAlgebra.I)
+2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{ComplexF64}, MathOptInterface.EqualTo{ComplexF64}}, ScalarShape}}:
+ real(X[1,1]) = 1                    real(X[1,2]) + imag(X[1,2]) im = 0
+ real(X[1,2]) - imag(X[1,2]) im = 0  real(X[2,2]) = 1

Containers of constraints

The @constraint macro supports creating collections of constraints. We'll cover some brief syntax here; read the Constraint containers section for more details:

Create arrays of constraints:

julia> model = Model();
+
+julia> @variable(model, x[1:3]);
+
+julia> @constraint(model, c[i=1:3], x[i] <= i^2)
+3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ c[1] : x[1] ≤ 1
+ c[2] : x[2] ≤ 4
+ c[3] : x[3] ≤ 9
+
+julia> c[2]
+c[2] : x[2] ≤ 4

Sets can be any Julia type that supports iteration:

julia> model = Model();
+
+julia> @variable(model, x[1:3]);
+
+julia> @constraint(model, c[i=2:3, ["red", "blue"]], x[i] <= i^2)
+2-dimensional DenseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape},2,...} with index sets:
+    Dimension 1, 2:3
+    Dimension 2, ["red", "blue"]
+And data, a 2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ c[2,red] : x[2] ≤ 4  c[2,blue] : x[2] ≤ 4
+ c[3,red] : x[3] ≤ 9  c[3,blue] : x[3] ≤ 9
+
+julia> c[2, "red"]
+c[2,red] : x[2] ≤ 4

Sets can depend upon previous indices:

julia> model = Model();
+
+julia> @variable(model, x[1:3]);
+
+julia> @constraint(model, c[i=1:3, j=i:3], x[i] <= j)
+JuMP.Containers.SparseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}, 2, Tuple{Int64, Int64}} with 6 entries:
+  [1, 1]  =  c[1,1] : x[1] ≤ 1
+  [1, 2]  =  c[1,2] : x[1] ≤ 2
+  [1, 3]  =  c[1,3] : x[1] ≤ 3
+  [2, 2]  =  c[2,2] : x[2] ≤ 2
+  [2, 3]  =  c[2,3] : x[2] ≤ 3
+  [3, 3]  =  c[3,3] : x[3] ≤ 3

and you can filter elements in the sets using the ; syntax:

julia> model = Model();
+
+julia> @variable(model, x[1:9]);
+
+julia> @constraint(model, c[i=1:9; mod(i, 3) == 0], x[i] <= i)
+JuMP.Containers.SparseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}, 1, Tuple{Int64}} with 3 entries:
+  [3]  =  c[3] : x[3] ≤ 3
+  [6]  =  c[6] : x[6] ≤ 6
+  [9]  =  c[9] : x[9] ≤ 9

Registered constraints

When you create constraints, JuMP registers them inside the model using their corresponding symbol. Get a registered name using model[:key]:

julia> model = Model()
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, my_c, 2x <= 1)
+my_c : 2 x ≤ 1
+
+julia> model
+A JuMP Model
+Feasibility problem with:
+Variable: 1
+`AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 1 constraint
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+Names registered in the model: my_c, x
+
+julia> model[:my_c] === my_c
+true

Anonymous constraints

To reduce the likelihood of accidental bugs, and because JuMP registers constraints inside a model, creating two constraints with the same name is an error:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, c, 2x <= 1)
+c : 2 x ≤ 1
+
+julia> @constraint(model, c, 2x <= 1)
+ERROR: An object of name c is already attached to this model. If this
+    is intended, consider using the anonymous construction syntax, e.g.,
+    `x = @variable(model, [1:N], ...)` where the name of the object does
+    not appear inside the macro.
+
+    Alternatively, use `unregister(model, :c)` to first unregister
+    the existing name from the model. Note that this will not delete the
+    object; it will just remove the reference at `model[:c]`.
+[...]

A common reason for encountering this error is adding constraints in a loop.

As a work-around, JuMP provides anonymous constraints. Create an anonymous constraint by omitting the name argument:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> c = @constraint(model, 2x <= 1)
+2 x ≤ 1

Create a container of anonymous constraints by dropping the name in front of the [:

julia> model = Model();
+
+julia> @variable(model, x[1:3]);
+
+julia> c = @constraint(model, [i = 1:3], x[i] <= i)
+3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ x[1] ≤ 1
+ x[2] ≤ 2
+ x[3] ≤ 3

Constraint names

In addition to the symbol that constraints are registered with, constraints have a String name that is used for printing and writing to file formats.

Get and set the name of a constraint using name(::JuMP.ConstraintRef) and set_name(::JuMP.ConstraintRef, ::String):

julia> model = Model(); @variable(model, x);
+
+julia> @constraint(model, con, x <= 1)
+con : x ≤ 1
+
+julia> name(con)
+"con"
+
+julia> set_name(con, "my_con_name")
+
+julia> con
+my_con_name : x ≤ 1

Override the default choice of name using the base_name keyword:

julia> model = Model(); @variable(model, x);
+
+julia> con = @constraint(model, [i=1:2], x <= i, base_name = "my_con")
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ my_con[1] : x ≤ 1
+ my_con[2] : x ≤ 2

Note that names apply to each element of the container, not to the container of constraints:

julia> name(con[1])
+"my_con[1]"
+
+julia> set_name(con[1], "c")
+
+julia> con
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ c : x ≤ 1
+ my_con[2] : x ≤ 2
Tip

For some models, setting the string name of each constraint can take a non-trivial portion of the total time required to build the model. Turn off String names by passing set_string_name = false to @constraint:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con, x <= 2, set_string_name = false)
+x ≤ 2

See Disable string names for more information.

Retrieve a constraint by name

Retrieve a constraint from a model using constraint_by_name:

julia> constraint_by_name(model, "c")
+c : x ≤ 1

If the name is not present, nothing will be returned:

julia> constraint_by_name(model, "bad_name")

You can only look up individual constraints using constraint_by_name. Something like this will not work:

julia> model = Model(); @variable(model, x);
+
+julia> con = @constraint(model, [i=1:2], x <= i, base_name = "my_con")
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ my_con[1] : x ≤ 1
+ my_con[2] : x ≤ 2
+
+julia> constraint_by_name(model, "my_con")

To look up a collection of constraints, do not use constraint_by_name. Instead, register them using the model[:key] = value syntax:

julia> model = Model(); @variable(model, x);
+
+julia> model[:con] = @constraint(model, [i=1:2], x <= i, base_name = "my_con")
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ my_con[1] : x ≤ 1
+ my_con[2] : x ≤ 2
+
+julia> model[:con]
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ my_con[1] : x ≤ 1
+ my_con[2] : x ≤ 2

String names, symbolic names, and bindings

It's common for new users to experience confusion relating to constraints. Part of the problem is the difference between the name that a constraint is registered under and the String name used for printing.

Here's a summary of the differences:

  • Constraints are created using @constraint.
  • Constraints can be named or anonymous.
  • Named constraints have the form @constraint(model, c, expr). For named constraints:
    • The String name of the constraint is set to "c".
    • A Julia variable c is created that binds c to the JuMP constraint.
    • The name :c is registered as a key in the model with the value c.
  • Anonymous constraints have the form c = @constraint(model, expr). For anonymous constraints:
    • The String name of the constraint is set to "".
    • You control the name of the Julia variable used as the binding.
    • No name is registered as a key in the model.
  • The base_name keyword can override the String name of the constraint.
  • You can manually register names in the model via model[:key] = value.

Here's an example of the differences:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> c_binding = @constraint(model, 2x <= 1, base_name = "c")
+c : 2 x ≤ 1
+
+julia> model
+A JuMP Model
+Feasibility problem with:
+Variable: 1
+`AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 1 constraint
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+Names registered in the model: x
+
+julia> c
+ERROR: UndefVarError: `c` not defined
+
+julia> c_binding
+c : 2 x ≤ 1
+
+julia> name(c_binding)
+"c"
+
+julia> model[:c_register] = c_binding
+c : 2 x ≤ 1
+
+julia> model
+A JuMP Model
+Feasibility problem with:
+Variable: 1
+`AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 1 constraint
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+Names registered in the model: c_register, x
+
+julia> model[:c_register]
+c : 2 x ≤ 1
+
+julia> model[:c_register] === c_binding
+true
+
+julia> c
+ERROR: UndefVarError: `c` not defined

The @constraints macro

If you have many @constraint calls, use the @constraints macro to improve readability:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraints(model, begin
+           2x <= 1
+           c, x >= -1
+       end)
+(2 x ≤ 1, c : x ≥ -1)
+
+julia> print(model)
+Feasibility
+Subject to
+ c : x ≥ -1
+ 2 x ≤ 1

The @constraints macro returns a tuple of the constraints that were defined.

Duality

JuMP adopts the notion of conic duality from MathOptInterface. For linear programs, a feasible dual on a >= constraint is nonnegative and a feasible dual on a <= constraint is nonpositive. If the constraint is an equality constraint, it depends on which direction is binding.

Warning

JuMP's definition of duality is independent of the objective sense. That is, the sign of feasible duals associated with a constraint depends on the direction of the constraint and not whether the problem is maximization or minimization. This is a different convention from linear programming duality in some common textbooks. If you have a linear program, and you want the textbook definition, you probably want to use shadow_price and reduced_cost instead.

The dual value associated with a constraint in the most recent solution can be accessed using the dual function. Use has_duals to check if the model has a dual solution available to query. For example:

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, con, x <= 1)
+con : x ≤ 1
+
+julia> @objective(model, Min, -2x)
+-2 x
+
+julia> has_duals(model)
+false
+
+julia> optimize!(model)
+
+julia> has_duals(model)
+true
+
+julia> dual(con)
+-2.0
+
+julia> @objective(model, Max, 2x)
+2 x
+
+julia> optimize!(model)
+
+julia> dual(con)
+-2.0

To help users who may be less familiar with conic duality, JuMP provides shadow_price, which returns a value that can be interpreted as the improvement in the objective in response to an infinitesimal relaxation (on the scale of one unit) in the right-hand side of the constraint. shadow_price can be used only on linear constraints with a <=, >=, or == comparison operator.

In the example above, dual(con) returned -2.0 regardless of the optimization sense. However, in the second case when the optimization sense is Max, shadow_price returns:

julia> shadow_price(con)
+2.0

Duals of variable bounds

To query the dual variables associated with a variable bound, first obtain a constraint reference using one of UpperBoundRef, LowerBoundRef, or FixRef, and then call dual on the returned constraint reference. The reduced_cost function may simplify this process as it returns the shadow price of an active bound of a variable (or zero, if no active bound exists).

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x <= 1)
+x
+
+julia> @objective(model, Min, -2x)
+-2 x
+
+julia> optimize!(model)
+
+julia> dual(UpperBoundRef(x))
+-2.0
+
+julia> reduced_cost(x)
+-2.0

Modify a constant term

This section explains how to modify the constant term in a constraint. There are multiple ways to achieve this goal; we explain three options.

Option 1: change the right-hand side

Use set_normalized_rhs to modify the right-hand side (constant) term of a linear or quadratic constraint. Use normalized_rhs to query the right-hand side term.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con, 2x <= 1)
+con : 2 x ≤ 1
+
+julia> set_normalized_rhs(con, 3)
+
+julia> con
+con : 2 x ≤ 3
+
+julia> normalized_rhs(con)
+3.0
Warning

set_normalized_rhs sets the right-hand side term of the normalized constraint. See Normalization for more details.

Option 2: use fixed variables

If constraints are complicated, for example, they are composed of a number of components, each of which has a constant term, then it may be difficult to calculate what the right-hand side term is in the standard form.

For this situation, JuMP includes the ability to fix variables to a value using the fix function. Fixing a variable sets its lower and upper bound to the same value. Thus, changes in a constant term can be simulated by adding a new variable and fixing it to different values. Here is an example:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @variable(model, const_term)
+const_term
+
+julia> @constraint(model, con, 2x <= const_term + 1)
+con : 2 x - const_term ≤ 1
+
+julia> fix(const_term, 1.0)

The constraint con is now equivalent to 2x <= 2.

Warning

Fixed variables are not replaced with constants when communicating the problem to a solver. Therefore, even though const_term is fixed, it is still a decision variable, and so const_term * x is bilinear.

Option 3: modify the function's constant term

The third option is to use add_to_function_constant. The constant given is added to the function of a func-in-set constraint. In the following example, adding 2 to the function has the effect of removing 2 to the right-hand side:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con, 2x <= 1)
+con : 2 x ≤ 1
+
+julia> add_to_function_constant(con, 2)
+
+julia> con
+con : 2 x ≤ -1
+
+julia> normalized_rhs(con)
+-1.0

In the case of interval constraints, the constant is removed from each bound:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con, 0 <= 2x + 1 <= 2)
+con : 2 x ∈ [-1, 1]
+
+julia> add_to_function_constant(con, 3)
+
+julia> con
+con : 2 x ∈ [-4, -2]

Modify a variable coefficient

Scalar constraints

To modify the coefficients for a linear term (modifying the coefficient of a quadratic term is not supported) in a constraint, use set_normalized_coefficient. To query the current coefficient, use normalized_coefficient.

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @constraint(model, con, 2x[1] + x[2] <= 1)
+con : 2 x[1] + x[2] ≤ 1
+
+julia> set_normalized_coefficient(con, x[2], 0)
+
+julia> con
+con : 2 x[1] ≤ 1
+
+julia> normalized_coefficient(con, x[2])
+0.0
Warning

set_normalized_coefficient sets the coefficient of the normalized constraint. See Normalization for more details.

Vector constraints

To modify the coefficients of a vector-valued constraint, use set_normalized_coefficients.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, con, [2x + 3x, 4x] in MOI.Nonnegatives(2))
+con : [5 x, 4 x] ∈ MathOptInterface.Nonnegatives(2)
+
+julia> set_normalized_coefficients(con, x, [(1, 3.0)])
+
+julia> con
+con : [3 x, 4 x] ∈ MathOptInterface.Nonnegatives(2)
+
+julia> set_normalized_coefficients(con, x, [(1, 2.0), (2, 5.0)])
+
+julia> con
+con : [2 x, 5 x] ∈ MathOptInterface.Nonnegatives(2)

Delete a constraint

Use delete to delete a constraint from a model. Use is_valid to check if a constraint belongs to a model and has not been deleted.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con, 2x <= 1)
+con : 2 x ≤ 1
+
+julia> is_valid(model, con)
+true
+
+julia> delete(model, con)
+
+julia> is_valid(model, con)
+false

Deleting a constraint does not unregister the symbolic reference from the model. Therefore, creating a new constraint of the same name will throw an error:

julia> @constraint(model, con, 2x <= 1)
+ERROR: An object of name con is already attached to this model. If this
+    is intended, consider using the anonymous construction syntax, e.g.,
+    `x = @variable(model, [1:N], ...)` where the name of the object does
+    not appear inside the macro.
+
+    Alternatively, use `unregister(model, :con)` to first unregister
+    the existing name from the model. Note that this will not delete the
+    object; it will just remove the reference at `model[:con]`.
+[...]

After calling delete, call unregister to remove the symbolic reference:

julia> unregister(model, :con)
+
+julia> @constraint(model, con, 2x <= 1)
+con : 2 x ≤ 1
Info

delete does not automatically unregister because we do not distinguish between names that are automatically registered by JuMP macros, and names that are manually registered by the user by setting values in object_dictionary. In addition, deleting a constraint and then adding a new constraint of the same name is an easy way to introduce bugs into your code.

Start values

Provide a starting value (also called warmstart) for a constraint's primal and dual solutions using set_start_value and set_dual_start_value.

Query the starting value for a constraint's primal and dual solution using start_value and dual_start_value. If no start value has been set, the methods will return nothing.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, con, x >= 10)
+con : x ≥ 10
+
+julia> start_value(con)
+
+julia> set_start_value(con, 10.0)
+
+julia> start_value(con)
+10.0
+
+julia> dual_start_value(con)
+
+julia> set_dual_start_value(con, 2)
+
+julia> dual_start_value(con)
+2.0

Vector-valued constraints require a vector:

julia> model = Model();
+
+julia> @variable(model, x[1:3])
+3-element Vector{VariableRef}:
+ x[1]
+ x[2]
+ x[3]
+
+julia> @constraint(model, con, x in SecondOrderCone())
+con : [x[1], x[2], x[3]] in MathOptInterface.SecondOrderCone(3)
+
+julia> dual_start_value(con)
+
+julia> set_dual_start_value(con, [1.0, 2.0, 3.0])
+
+julia> dual_start_value(con)
+3-element Vector{Float64}:
+ 1.0
+ 2.0
+ 3.0
Tip

To simplify setting start values for all variables and constraints in a model, see set_start_values. The Primal and dual warm-starts tutorial also gives a detailed description of how to iterate over constraints in the model to set custom start values.

Constraint containers

Like Variable containers, JuMP provides a mechanism for building groups of constraints compactly. References to these groups of constraints are returned in containers. Three types of constraint containers are supported: Arrays, DenseAxisArrays, and SparseAxisArrays. We explain each of these in the following.

Tip

You can read more about containers in the Containers section.

Arrays

One way of adding a group of constraints compactly is the following:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con[i = 1:3], i * x <= i + 1)
+3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ con[1] : x ≤ 2
+ con[2] : 2 x ≤ 3
+ con[3] : 3 x ≤ 4

JuMP returns references to the three constraints in an Array that is bound to the Julia variable con. This array can be accessed and sliced as you would with any Julia array:

julia> con[1]
+con[1] : x ≤ 2
+
+julia> con[2:3]
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ con[2] : 2 x ≤ 3
+ con[3] : 3 x ≤ 4

Anonymous containers can also be constructed by dropping the name (for example, con) before the square brackets:

julia> con = @constraint(model, [i = 1:2], i * x <= i + 1)
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ x ≤ 2
+ 2 x ≤ 3

Just like @variable, JuMP will form an Array of constraints when it can determine at parse time that the indices are one-based integer ranges. Therefore con[1:b] will create an Array, but con[a:b] will not. A special case is con[Base.OneTo(n)] which will produce an Array. If JuMP cannot determine that the indices are one-based integer ranges (for example, in the case of con[a:b]), JuMP will create a DenseAxisArray instead.

DenseAxisArrays

The syntax for constructing a DenseAxisArray of constraints is very similar to the syntax for constructing a DenseAxisArray of variables.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con[i = 1:2, j = 2:3], i * x <= j + 1)
+2-dimensional DenseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape},2,...} with index sets:
+    Dimension 1, Base.OneTo(2)
+    Dimension 2, 2:3
+And data, a 2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ con[1,2] : x ≤ 3    con[1,3] : x ≤ 4
+ con[2,2] : 2 x ≤ 3  con[2,3] : 2 x ≤ 4

SparseAxisArrays

The syntax for constructing a SparseAxisArray of constraints is very similar to the syntax for constructing a SparseAxisArray of variables.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, con[i = 1:2, j = 1:2; i != j], i * x <= j + 1)
+JuMP.Containers.SparseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}, 2, Tuple{Int64, Int64}} with 2 entries:
+  [1, 2]  =  con[1,2] : x ≤ 3
+  [2, 1]  =  con[2,1] : 2 x ≤ 2
Warning

If you have many index dimensions and a large amount of sparsity, read Performance considerations.

Forcing the container type

When creating a container of constraints, JuMP will attempt to choose the tightest container type that can store the constraints. However, because this happens at parse time, it does not always make the best choice. Just like in @variable, you can force the type of container using the container keyword. For syntax and the reason behind this, take a look at the variable docs.

Constraints with similar indices

Containers are often used to create constraints over a set of indices. However, you'll often have cases in which you are repeating the indices:

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @variable(model, y[1:2]);
+
+julia> @constraints(model, begin
+           [i=1:2, j=1:2, k=1:2], i * x[j] <= k
+           [i=1:2, j=1:2, k=1:2], i * y[j] <= k
+       end);

This is hard to read and leads to a lot of copy-paste. A more readable way is to use a for-loop:

julia> for i=1:2, j=1:2, k=1:2
+           @constraints(model, begin
+               i * x[j] <= k
+               i * y[j] <= k
+           end)
+       end

Accessing constraints from a model

Query the types of function-in-set constraints in a model using list_of_constraint_types:

julia> model = Model();
+
+julia> @variable(model, x[i=1:2] >= i, Int);
+
+julia> @constraint(model, x[1] + x[2] <= 1);
+
+julia> list_of_constraint_types(model)
+3-element Vector{Tuple{Type, Type}}:
+ (AffExpr, MathOptInterface.LessThan{Float64})
+ (VariableRef, MathOptInterface.GreaterThan{Float64})
+ (VariableRef, MathOptInterface.Integer)

For a given combination of function and set type, use num_constraints to access the number of constraints and all_constraints to access a list of their references:

julia> num_constraints(model, VariableRef, MOI.Integer)
+2
+
+julia> cons = all_constraints(model, VariableRef, MOI.Integer)
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}, ScalarShape}}:
+ x[1] integer
+ x[2] integer

You can also count the total number of constraints in the model, but you must explicitly choose whether to count VariableRef constraints such as bound and integrality constraints:

julia> num_constraints(model; count_variable_in_set_constraints = true)
+5
+
+julia> num_constraints(model; count_variable_in_set_constraints = false)
+1

The same also applies for all_constraints:

julia> all_constraints(model; include_variable_in_set_constraints = true)
+5-element Vector{ConstraintRef}:
+ x[1] + x[2] ≤ 1
+ x[1] ≥ 1
+ x[2] ≥ 2
+ x[1] integer
+ x[2] integer
+
+julia> all_constraints(model; include_variable_in_set_constraints = false)
+1-element Vector{ConstraintRef}:
+ x[1] + x[2] ≤ 1

If you need finer-grained control on which constraints to include, use a variant of:

julia> sum(
+           num_constraints(model, F, S) for
+           (F, S) in list_of_constraint_types(model) if F != VariableRef
+       )
+1

Use constraint_object to get an instance of an AbstractConstraint object that stores the constraint data:

julia> con = constraint_object(cons[1])
+ScalarConstraint{VariableRef, MathOptInterface.Integer}(x[1], MathOptInterface.Integer())
+
+julia> con.func
+x[1]
+
+julia> con.set
+MathOptInterface.Integer()

MathOptInterface constraints

Because JuMP is based on MathOptInterface, you can add any constraints supported by MathOptInterface using the function-in-set syntax. For a list of supported functions and sets, read Standard form problem.

Note

We use MOI as an alias for the MathOptInterface module. This alias is defined by using JuMP. You may also define it in your code as follows:

import MathOptInterface as MOI

For example, the following two constraints are equivalent:

julia> model = Model();
+
+julia> @variable(model, x[1:3]);
+
+julia> @constraint(model, 2 * x[1] <= 1)
+2 x[1] ≤ 1
+
+julia> @constraint(model, 2 * x[1] in MOI.LessThan(1.0))
+2 x[1] ≤ 1

You can also use any set defined by MathOptInterface:

julia> @constraint(model, x - [1; 2; 3] in MOI.Nonnegatives(3))
+[x[1] - 1, x[2] - 2, x[3] - 3] ∈ MathOptInterface.Nonnegatives(3)
+
+julia> @constraint(model, x in MOI.ExponentialCone())
+[x[1], x[2], x[3]] ∈ MathOptInterface.ExponentialCone()
Info

Similar to how JuMP defines the <= and >= syntax as a convenience way to specify MOI.LessThan and MOI.GreaterThan constraints, the remaining sections in this page describe functions and syntax that have been added for the convenience of common modeling situations.

Set inequality syntax

For modeling convenience, the syntax @constraint(model, x >= y, Set()) is short-hand for @constraint(model, x - y in Set()). Therefore, the following calls are equivalent:

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> y = [0.5, 0.75];
+
+julia> @constraint(model, x >= y, MOI.Nonnegatives(2))
+[x[1] - 0.5, x[2] - 0.75] ∈ MathOptInterface.Nonnegatives(2)
+
+julia> @constraint(model, y <= x, MOI.Nonnegatives(2))
+[x[1] - 0.5, x[2] - 0.75] ∈ MathOptInterface.Nonnegatives(2)
+
+julia> @constraint(model, x - y in MOI.Nonnegatives(2))
+[x[1] - 0.5, x[2] - 0.75] ∈ MathOptInterface.Nonnegatives(2)

Non-zero constants are not supported in this syntax:

julia> @constraint(model, x >= 1, MOI.Nonnegatives(2))
+ERROR: Operation `sub_mul` between `Vector{VariableRef}` and `Int64` is not allowed. This most often happens when you write a constraint like `x >= y` where `x` is an array and `y` is a constant. Use the broadcast syntax `x .- y >= 0` instead.
+Stacktrace:
+[...]

Use instead:

julia> @constraint(model, x .- 1 >= 0, MOI.Nonnegatives(2))
+[x[1] - 1, x[2] - 1] ∈ MathOptInterface.Nonnegatives(2)

Second-order cone constraints

A SecondOrderCone constrains the variables t and x to the set:

\[||x||_2 \le t,\]

and $t \ge 0$. It can be added as follows:

julia> model = Model();
+
+julia> @variable(model, t)
+t
+
+julia> @variable(model, x[1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> @constraint(model, [t; x] in SecondOrderCone())
+[t, x[1], x[2]] ∈ MathOptInterface.SecondOrderCone(3)

Rotated second-order cone constraints

A RotatedSecondOrderCone constrains the variables t, u, and x to the set:

\[||x||_2^2 \le 2 t \cdot u\]

and $t, u \ge 0$. It can be added as follows:

julia> model = Model();
+
+julia> @variable(model, t)
+t
+
+julia> @variable(model, u)
+u
+
+julia> @variable(model, x[1:2])
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> @constraint(model, [t; u; x] in RotatedSecondOrderCone())
+[t, u, x[1], x[2]] ∈ MathOptInterface.RotatedSecondOrderCone(4)

Semi-integer and semi-continuous variables

Semi-continuous variables are constrained to the set $x \in \{0\} \cup [l, u]$.

Create a semi-continuous variable using the Semicontinuous set:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, x in Semicontinuous(1.5, 3.5))
+x in MathOptInterface.Semicontinuous{Float64}(1.5, 3.5)

Semi-integer variables are constrained to the set $x \in \{0\} \cup \{l, l+1, \dots, u\}$.

Create a semi-integer variable using the Semiinteger set:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @constraint(model, x in Semiinteger(1.0, 3.0))
+x in MathOptInterface.Semiinteger{Float64}(1.0, 3.0)

Special Ordered Sets of Type 1

In a Special Ordered Set of Type 1 (often denoted SOS-I or SOS1), at most one element can take a non-zero value.

Construct SOS-I constraints using the SOS1 set:

julia> model = Model();
+
+julia> @variable(model, x[1:3])
+3-element Vector{VariableRef}:
+ x[1]
+ x[2]
+ x[3]
+
+julia> @constraint(model, x in SOS1())
+[x[1], x[2], x[3]] in MathOptInterface.SOS1{Float64}([1.0, 2.0, 3.0])

Although not required for feasibility, solvers can benefit from an ordering of the variables (for example, the variables represent different factories to build, at most one factory can be built, and the factories can be ordered according to cost). To induce an ordering, a vector of weights can be provided, and the variables are ordered according to their corresponding weight.

For example, in the constraint:

julia> @constraint(model, x in SOS1([3.1, 1.2, 2.3]))
+[x[1], x[2], x[3]] in MathOptInterface.SOS1{Float64}([3.1, 1.2, 2.3])

the variables x have precedence x[2], x[3], x[1].

Special Ordered Sets of Type 2

In a Special Ordered Set of Type 2 (SOS-II), at most two elements can be non-zero, and if there are two non-zeros, they must be consecutive according to the ordering induced by a weight vector.

Construct SOS-II constraints using the SOS2 set:

julia> @constraint(model, x in SOS2([3.0, 1.0, 2.0]))
+[x[1], x[2], x[3]] in MathOptInterface.SOS2{Float64}([3.0, 1.0, 2.0])

The possible non-zero pairs are (x[1], x[3]) and (x[2], x[3]):

If the weight vector is omitted, JuMP induces an ordering from 1:length(x):

julia> @constraint(model, x in SOS2())
+[x[1], x[2], x[3]] in MathOptInterface.SOS2{Float64}([1.0, 2.0, 3.0])

Indicator constraints

Indicator constraints consist of a binary variable and a linear constraint. The constraint holds when the binary variable takes the value 1. The constraint may or may not hold when the binary variable takes the value 0.

To enforce the constraint x + y <= 1 when the binary variable a is 1, use:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> @variable(model, a, Bin)
+a
+
+julia> @constraint(model, a => {x + y <= 1})
+a => {x + y ≤ 1}

If the constraint must hold when a is zero, add ! or ¬ before the binary variable;

julia> @constraint(model, !a => {x + y <= 1})
+!a => {x + y ≤ 1}

Semidefinite constraints

To constrain a matrix to be positive semidefinite (PSD), use PSDCone:

julia> model = Model();
+
+julia> @variable(model, X[1:2, 1:2])
+2×2 Matrix{VariableRef}:
+ X[1,1]  X[1,2]
+ X[2,1]  X[2,2]
+
+julia> @constraint(model, X >= 0, PSDCone())
+[X[1,1]  X[1,2];
+ X[2,1]  X[2,2]] ∈ PSDCone()
Tip

Where possible, prefer constructing a matrix of Semidefinite variables using the @variable macro, rather than adding a constraint like @constraint(model, X >= 0, PSDCone()). In some solvers, adding the constraint via @constraint is less efficient, and can result in additional intermediate variables and constraints being added to the model.

The inequality X >= Y between two square matrices X and Y is understood as constraining X - Y to be positive semidefinite.

julia> Y = [1 2; 2 1]
+2×2 Matrix{Int64}:
+ 1  2
+ 2  1
+
+julia> @constraint(model, X >= Y, PSDCone())
+[X[1,1] - 1  X[1,2] - 2;
+ X[2,1] - 2  X[2,2] - 1] ∈ PSDCone()

Symmetry

Solvers supporting PSD constraints usually expect to be given a matrix that is symbolically symmetric, that is, for which the expression in corresponding off-diagonal entries are the same. In our example, the expressions of entries (1, 2) and (2, 1) are respectively X[1,2] - 2 and X[2,1] - 2 which are different.

To bridge the gap between the constraint modeled and what the solver expects, solvers may add an equality constraint X[1,2] - 2 == X[2,1] - 2 to force symmetry. Use LinearAlgebra.Symmetric to explicitly tell the solver that the matrix is symmetric:

julia> import LinearAlgebra
+
+julia> Z = [X[1, 1] X[1, 2]; X[1, 2] X[2, 2]]
+2×2 Matrix{VariableRef}:
+ X[1,1]  X[1,2]
+ X[1,2]  X[2,2]
+
+julia> @constraint(model, LinearAlgebra.Symmetric(Z) >= 0, PSDCone())
+[X[1,1]  X[1,2];
+ X[1,2]  X[2,2]] ∈ PSDCone()

Note that the lower triangular entries are ignored even if they are different so use it with caution:

julia> @constraint(model, LinearAlgebra.Symmetric(X) >= 0, PSDCone())
+[X[1,1]  X[1,2];
+ X[1,2]  X[2,2]] ∈ PSDCone()

(Note the (2, 1) element of the constraint is X[1,2], not X[2,1].)

Complementarity constraints

A mixed complementarity constraint F(x) ⟂ x consists of finding x in the interval [lb, ub], such that the following holds:

  • F(x) == 0 if lb < x < ub
  • F(x) >= 0 if lb == x
  • F(x) <= 0 if x == ub

JuMP supports mixed complementarity constraints via complements(F(x), x) or F(x) ⟂ x in the @constraint macro. The interval set [lb, ub] is obtained from the variable bounds on x.

For example, to define the problem 2x - 1 ⟂ x with x ∈ [0, ∞), do:

julia> model = Model();
+
+julia> @variable(model, x >= 0)
+x
+
+julia> @constraint(model, 2x - 1 ⟂ x)
+[2 x - 1, x] ∈ MathOptInterface.Complements(2)

This problem has a unique solution at x = 0.5.

The perp operator can be entered in most editors (and the Julia REPL) by typing \perp<tab>.

An alternative approach that does not require the symbol uses the complements function as follows:

julia> @constraint(model, complements(2x - 1, x))
+[2 x - 1, x] ∈ MathOptInterface.Complements(2)

In both cases, the mapping F(x) is supplied as the first argument, and the matching variable x is supplied as the second.

Vector-valued complementarity constraints are also supported:

julia> @variable(model, -2 <= y[1:2] <= 2)
+2-element Vector{VariableRef}:
+ y[1]
+ y[2]
+
+julia> M = [1 2; 3 4]
+2×2 Matrix{Int64}:
+ 1  2
+ 3  4
+
+julia> q = [5, 6]
+2-element Vector{Int64}:
+ 5
+ 6
+
+julia> @constraint(model, M * y + q ⟂ y)
+[y[1] + 2 y[2] + 5, 3 y[1] + 4 y[2] + 6, y[1], y[2]] ∈ MathOptInterface.Complements(4)
diff --git a/previews/PR3536/manual/containers/index.html b/previews/PR3536/manual/containers/index.html new file mode 100644 index 00000000000..f10d55ea35b --- /dev/null +++ b/previews/PR3536/manual/containers/index.html @@ -0,0 +1,227 @@ + +Containers · JuMP

Containers

JuMP provides specialized containers similar to AxisArrays that enable multi-dimensional arrays with non-integer indices.

These containers are created automatically by JuMP's macros. Each macro has the same basic syntax:

@macroname(model, name[key1=index1, index2; optional_condition], other stuff)

The containers are generated by the name[key1=index1, index2; optional_condition] syntax. Everything else is specific to the particular macro.

Containers can be named, for example, name[key=index], or unnamed, for example, [key=index]. We call unnamed containers anonymous.

We call the bits inside the square brackets and before the ; the index sets. The index sets can be named, for example, [i = 1:4], or they can be unnamed, for example, [1:4].

We call the bit inside the square brackets and after the ; the condition. Conditions are optional.

In addition to the standard JuMP macros like @variable and @constraint, which construct containers of variables and constraints respectively, you can use Containers.@container to construct containers with arbitrary elements.

We will use this macro to explain the three types of containers that are natively supported by JuMP: Array, Containers.DenseAxisArray, and Containers.SparseAxisArray.

Array

An Array is created when the index sets are rectangular and the index sets are of the form 1:n.

julia> Containers.@container(x[i = 1:2, j = 1:3], (i, j))
+2×3 Matrix{Tuple{Int64, Int64}}:
+ (1, 1)  (1, 2)  (1, 3)
+ (2, 1)  (2, 2)  (2, 3)

The result is a normal Julia Array, so you can do all the usual things.

Slicing

Arrays can be sliced

julia> x[:, 1]
+2-element Vector{Tuple{Int64, Int64}}:
+ (1, 1)
+ (2, 1)
+
+julia> x[2, :]
+3-element Vector{Tuple{Int64, Int64}}:
+ (2, 1)
+ (2, 2)
+ (2, 3)

Looping

Use eachindex to loop over the elements:

julia> for key in eachindex(x)
+           println(x[key])
+       end
+(1, 1)
+(2, 1)
+(1, 2)
+(2, 2)
+(1, 3)
+(2, 3)

Get the index sets

Use axes to obtain the index sets:

julia> axes(x)
+(Base.OneTo(2), Base.OneTo(3))

Broadcasting

Broadcasting over an Array returns an Array

julia> swap(x::Tuple) = (last(x), first(x))
+swap (generic function with 1 method)
+
+julia> swap.(x)
+2×3 Matrix{Tuple{Int64, Int64}}:
+ (1, 1)  (2, 1)  (3, 1)
+ (1, 2)  (2, 2)  (3, 2)

Tables

Use Containers.rowtable to convert the Array into a Tables.jl compatible Vector{<:NamedTuple}:

julia> table = Containers.rowtable(x; header = [:I, :J, :value])
+6-element Vector{NamedTuple{(:I, :J, :value), Tuple{Int64, Int64, Tuple{Int64, Int64}}}}:
+ (I = 1, J = 1, value = (1, 1))
+ (I = 2, J = 1, value = (2, 1))
+ (I = 1, J = 2, value = (1, 2))
+ (I = 2, J = 2, value = (2, 2))
+ (I = 1, J = 3, value = (1, 3))
+ (I = 2, J = 3, value = (2, 3))

Because it supports the Tables.jl interface, you can pass it to any function which accepts a table as input:

julia> import DataFrames;
+
+julia> DataFrames.DataFrame(table)
+6×3 DataFrame
+ Row │ I      J      value
+     │ Int64  Int64  Tuple…
+─────┼──────────────────────
+   1 │     1      1  (1, 1)
+   2 │     2      1  (2, 1)
+   3 │     1      2  (1, 2)
+   4 │     2      2  (2, 2)
+   5 │     1      3  (1, 3)
+   6 │     2      3  (2, 3)

DenseAxisArray

A Containers.DenseAxisArray is created when the index sets are rectangular, but not of the form 1:n. The index sets can be of any type.

julia> x = Containers.@container([i = 1:2, j = [:A, :B]], (i, j))
+2-dimensional DenseAxisArray{Tuple{Int64, Symbol},2,...} with index sets:
+    Dimension 1, Base.OneTo(2)
+    Dimension 2, [:A, :B]
+And data, a 2×2 Matrix{Tuple{Int64, Symbol}}:
+ (1, :A)  (1, :B)
+ (2, :A)  (2, :B)

Slicing

DenseAxisArrays can be sliced

julia> x[:, :A]
+1-dimensional DenseAxisArray{Tuple{Int64, Symbol},1,...} with index sets:
+    Dimension 1, Base.OneTo(2)
+And data, a 2-element Vector{Tuple{Int64, Symbol}}:
+ (1, :A)
+ (2, :A)
+
+julia> x[1, :]
+1-dimensional DenseAxisArray{Tuple{Int64, Symbol},1,...} with index sets:
+    Dimension 1, [:A, :B]
+And data, a 2-element Vector{Tuple{Int64, Symbol}}:
+ (1, :A)
+ (1, :B)

Looping

Use eachindex to loop over the elements:

julia> for key in eachindex(x)
+           println(x[key])
+       end
+(1, :A)
+(2, :A)
+(1, :B)
+(2, :B)

Get the index sets

Use axes to obtain the index sets:

julia> axes(x)
+(Base.OneTo(2), [:A, :B])

Broadcasting

Broadcasting over a DenseAxisArray returns a DenseAxisArray

julia> swap(x::Tuple) = (last(x), first(x))
+swap (generic function with 1 method)
+
+julia> swap.(x)
+2-dimensional DenseAxisArray{Tuple{Symbol, Int64},2,...} with index sets:
+    Dimension 1, Base.OneTo(2)
+    Dimension 2, [:A, :B]
+And data, a 2×2 Matrix{Tuple{Symbol, Int64}}:
+ (:A, 1)  (:B, 1)
+ (:A, 2)  (:B, 2)

Access internal data

Use Array(x) to copy the internal data array into a new Array:

julia> Array(x)
+2×2 Matrix{Tuple{Int64, Symbol}}:
+ (1, :A)  (1, :B)
+ (2, :A)  (2, :B)

To access the internal data without a copy, use x.data.

julia> x.data
+2×2 Matrix{Tuple{Int64, Symbol}}:
+ (1, :A)  (1, :B)
+ (2, :A)  (2, :B)

Tables

Use Containers.rowtable to convert the DenseAxisArray into a Tables.jl compatible Vector{<:NamedTuple}:

julia> table = Containers.rowtable(x; header = [:I, :J, :value])
+4-element Vector{NamedTuple{(:I, :J, :value), Tuple{Int64, Symbol, Tuple{Int64, Symbol}}}}:
+ (I = 1, J = :A, value = (1, :A))
+ (I = 2, J = :A, value = (2, :A))
+ (I = 1, J = :B, value = (1, :B))
+ (I = 2, J = :B, value = (2, :B))

Because it supports the Tables.jl interface, you can pass it to any function which accepts a table as input:

julia> import DataFrames;
+
+julia> DataFrames.DataFrame(table)
+4×3 DataFrame
+ Row │ I      J       value
+     │ Int64  Symbol  Tuple…
+─────┼────────────────────────
+   1 │     1  A       (1, :A)
+   2 │     2  A       (2, :A)
+   3 │     1  B       (1, :B)
+   4 │     2  B       (2, :B)

Keyword indexing

If all axes are named, you can use keyword indexing:

julia> x[i = 2, j = :A]
+(2, :A)
+
+julia> x[i = :, j = :B]
+1-dimensional DenseAxisArray{Tuple{Int64, Symbol},1,...} with index sets:
+    Dimension 1, Base.OneTo(2)
+And data, a 2-element Vector{Tuple{Int64, Symbol}}:
+ (1, :B)
+ (2, :B)

SparseAxisArray

A Containers.SparseAxisArray is created when the index sets are non-rectangular. This occurs in two circumstances:

An index depends on a prior index:

julia> Containers.@container([i = 1:2, j = i:2], (i, j))
+JuMP.Containers.SparseAxisArray{Tuple{Int64, Int64}, 2, Tuple{Int64, Int64}} with 3 entries:
+  [1, 1]  =  (1, 1)
+  [1, 2]  =  (1, 2)
+  [2, 2]  =  (2, 2)

The [indices; condition] syntax is used:

julia> x = Containers.@container([i = 1:3, j = [:A, :B]; i > 1], (i, j))
+JuMP.Containers.SparseAxisArray{Tuple{Int64, Symbol}, 2, Tuple{Int64, Symbol}} with 4 entries:
+  [2, A]  =  (2, :A)
+  [2, B]  =  (2, :B)
+  [3, A]  =  (3, :A)
+  [3, B]  =  (3, :B)

Here we have the index sets i = 1:3, j = [:A, :B], followed by ;, and then a condition, which evaluates to true or false: i > 1.

Slicing

Slicing is supported:

julia> y = x[:, :B]
+JuMP.Containers.SparseAxisArray{Tuple{Int64, Symbol}, 1, Tuple{Int64}} with 2 entries:
+  [2]  =  (2, :B)
+  [3]  =  (3, :B)

Looping

Use eachindex to loop over the elements:

julia> for key in eachindex(y)
+           println(y[key])
+       end
+(2, :B)
+(3, :B)

Broadcasting

Broadcasting over a SparseAxisArray returns a SparseAxisArray

julia> swap(x::Tuple) = (last(x), first(x))
+swap (generic function with 1 method)
+
+julia> swap.(y)
+JuMP.Containers.SparseAxisArray{Tuple{Symbol, Int64}, 1, Tuple{Int64}} with 2 entries:
+  [2]  =  (:B, 2)
+  [3]  =  (:B, 3)

Tables

Use Containers.rowtable to convert the SparseAxisArray into a Tables.jl compatible Vector{<:NamedTuple}:

julia> table = Containers.rowtable(x; header = [:I, :J, :value])
+4-element Vector{NamedTuple{(:I, :J, :value), Tuple{Int64, Symbol, Tuple{Int64, Symbol}}}}:
+ (I = 2, J = :A, value = (2, :A))
+ (I = 2, J = :B, value = (2, :B))
+ (I = 3, J = :A, value = (3, :A))
+ (I = 3, J = :B, value = (3, :B))

Because it supports the Tables.jl interface, you can pass it to any function which accepts a table as input:

julia> import DataFrames;
+
+julia> DataFrames.DataFrame(table)
+4×3 DataFrame
+ Row │ I      J       value
+     │ Int64  Symbol  Tuple…
+─────┼────────────────────────
+   1 │     2  A       (2, :A)
+   2 │     2  B       (2, :B)
+   3 │     3  A       (3, :A)
+   4 │     3  B       (3, :B)

Keyword indexing

If all axes are named, you can use keyword indexing:

julia> x[i = 2, j = :A]
+(2, :A)
+
+julia> x[i = :, j = :B]
+JuMP.Containers.SparseAxisArray{Tuple{Int64, Symbol}, 1, Tuple{Int64}} with 2 entries:
+  [2]  =  (2, :B)
+  [3]  =  (3, :B)

Forcing the container type

Pass container = T to use T as the container. For example:

julia> Containers.@container([i = 1:2, j = 1:2], i + j, container = Array)
+2×2 Matrix{Int64}:
+ 2  3
+ 3  4
+
+julia> Containers.@container([i = 1:2, j = 1:2], i + j, container = Dict)
+Dict{Tuple{Int64, Int64}, Int64} with 4 entries:
+  (1, 2) => 3
+  (1, 1) => 2
+  (2, 2) => 4
+  (2, 1) => 3

You can also pass DenseAxisArray or SparseAxisArray.

How different container types are chosen

If the compiler can prove at compile time that the index sets are rectangular, and indexed by a compact set of integers that start at 1, Containers.@container will return an array. This is the case if your index sets are visible to the macro as 1:n:

julia> Containers.@container([i=1:3, j=1:5], i + j)
+3×5 Matrix{Int64}:
+ 2  3  4  5  6
+ 3  4  5  6  7
+ 4  5  6  7  8

or an instance of Base.OneTo:

julia> set = Base.OneTo(3)
+Base.OneTo(3)
+
+julia> Containers.@container([i=set, j=1:5], i + j)
+3×5 Matrix{Int64}:
+ 2  3  4  5  6
+ 3  4  5  6  7
+ 4  5  6  7  8

If the compiler can prove that the index set is rectangular, but not necessarily of the form 1:n at compile time, then a Containers.DenseAxisArray will be constructed instead:

julia> set = 1:3
+1:3
+
+julia> Containers.@container([i=set, j=1:5], i + j)
+2-dimensional DenseAxisArray{Int64,2,...} with index sets:
+    Dimension 1, 1:3
+    Dimension 2, Base.OneTo(5)
+And data, a 3×5 Matrix{Int64}:
+ 2  3  4  5  6
+ 3  4  5  6  7
+ 4  5  6  7  8
Info

What happened here? Although we know that set contains 1:3, at compile time the typeof(set) is a UnitRange{Int}. Therefore, Julia can't prove that the range starts at 1 (it only finds this out at runtime), and it defaults to a DenseAxisArray. The case where we explicitly wrote i = 1:3 worked because the macro can "see" the 1 at compile time.

However, if you know that the indices do form an Array, you can force the container type with container = Array:

julia> set = 1:3
+1:3
+
+julia> Containers.@container([i=set, j=1:5], i + j, container = Array)
+3×5 Matrix{Int64}:
+ 2  3  4  5  6
+ 3  4  5  6  7
+ 4  5  6  7  8

Here's another example with something similar:

julia> a = 1
+1
+
+julia> Containers.@container([i=a:3, j=1:5], i + j)
+2-dimensional DenseAxisArray{Int64,2,...} with index sets:
+    Dimension 1, 1:3
+    Dimension 2, Base.OneTo(5)
+And data, a 3×5 Matrix{Int64}:
+ 2  3  4  5  6
+ 3  4  5  6  7
+ 4  5  6  7  8
+
+julia> Containers.@container([i=1:a, j=1:5], i + j)
+1×5 Matrix{Int64}:
+ 2  3  4  5  6

Finally, if the compiler cannot prove that the index set is rectangular, a Containers.SparseAxisArray will be created.

This occurs when some indices depend on a previous one:

julia> Containers.@container([i=1:3, j=1:i], i + j)
+JuMP.Containers.SparseAxisArray{Int64, 2, Tuple{Int64, Int64}} with 6 entries:
+  [1, 1]  =  2
+  [2, 1]  =  3
+  [2, 2]  =  4
+  [3, 1]  =  4
+  [3, 2]  =  5
+  [3, 3]  =  6

or if there is a condition on the index sets:

julia> Containers.@container([i = 1:5; isodd(i)], i^2)
+JuMP.Containers.SparseAxisArray{Int64, 1, Tuple{Int64}} with 3 entries:
+  [1]  =  1
+  [3]  =  9
+  [5]  =  25

The condition can depend on multiple indices, the only requirement is that it is an expression that returns true or false:

julia> condition(i, j) = isodd(i) && iseven(j)
+condition (generic function with 1 method)
+
+julia> Containers.@container([i = 1:2, j = 1:4; condition(i, j)], i + j)
+JuMP.Containers.SparseAxisArray{Int64, 2, Tuple{Int64, Int64}} with 2 entries:
+  [1, 2]  =  3
+  [1, 4]  =  5
diff --git a/previews/PR3536/manual/expressions/index.html b/previews/PR3536/manual/expressions/index.html new file mode 100644 index 00000000000..21fc5a6ab55 --- /dev/null +++ b/previews/PR3536/manual/expressions/index.html @@ -0,0 +1,230 @@ + +Expressions · JuMP

Expressions

JuMP has three types of expressions: affine, quadratic, and nonlinear. These expressions can be inserted into constraints or into the objective. This is particularly useful if an expression is used in multiple places in the model.

Affine expressions

There are four ways of constructing an affine expression in JuMP: with the @expression macro, with operator overloading, with the AffExpr constructor, and with add_to_expression!.

Macros

The recommended way to create an affine expression is via the @expression macro.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> ex = @expression(model, 2x + y - 1)
+2 x + y - 1

This expression can be used in the objective or added to a constraint. For example:

julia> @objective(model, Min, 2 * ex - 1)
+4 x + 2 y - 3
+
+julia> objective_function(model)
+4 x + 2 y - 3

Just like variables and constraints, named expressions can also be created. For example

julia> model = Model();
+
+julia> @variable(model, x[i = 1:3]);
+
+julia> @expression(model, expr[i = 1:3], i * sum(x[j] for j in i:3));
+
+julia> expr
+3-element Vector{AffExpr}:
+ x[1] + x[2] + x[3]
+ 2 x[2] + 2 x[3]
+ 3 x[3]
Tip

You can read more about containers in the Containers section.

Operator overloading

Expressions can also be created without macros. However, note that in some cases, this can be much slower that constructing an expression using macros.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> ex = 2x + y - 1
+2 x + y - 1

Constructors

A third way to create an affine expression is by the AffExpr constructor. The first argument is the constant term, and the remaining arguments are variable-coefficient pairs.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> ex = AffExpr(-1.0, x => 2.0, y => 1.0)
+2 x + y - 1

add_to_expression!

The fourth way to create an affine expression is by using add_to_expression!. Compared to the operator overloading method, this approach is faster because it avoids constructing temporary objects. The @expression macro uses add_to_expression! behind-the-scenes.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> ex = AffExpr(-1.0)
+-1
+
+julia> add_to_expression!(ex, 2.0, x)
+2 x - 1
+
+julia> add_to_expression!(ex, 1.0, y)
+2 x + y - 1
Warning

Read the section Initializing arrays for some cases to be careful about when using add_to_expression!.

Removing zero terms

Use drop_zeros! to remove terms from an affine expression with a 0 coefficient.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @expression(model, ex, x + 1 - x)
+0 x + 1
+
+julia> drop_zeros!(ex)
+
+julia> ex
+1

Coefficients

Use coefficient to return the coefficient associated with a variable in an affine expression.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> @expression(model, ex, 2x + 1)
+2 x + 1
+
+julia> coefficient(ex, x)
+2.0
+
+julia> coefficient(ex, y)
+0.0

Quadratic expressions

Like affine expressions, there are four ways of constructing a quadratic expression in JuMP: macros, operator overloading, constructors, and add_to_expression!.

Macros

The @expression macro can be used to create quadratic expressions by including quadratic terms.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> ex = @expression(model, x^2 + 2 * x * y + y^2 + x + y - 1)
+x² + 2 x*y + y² + x + y - 1

Operator overloading

Operator overloading can also be used to create quadratic expressions. The same performance warning (discussed in the affine expression section) applies.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> ex = x^2 + 2 * x * y + y^2 + x + y - 1
+x² + 2 x*y + y² + x + y - 1

Constructors

Quadratic expressions can also be created using the QuadExpr constructor. The first argument is an affine expression, and the remaining arguments are pairs, where the first term is a JuMP.UnorderedPair and the second term is the coefficient.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> aff_expr = AffExpr(-1.0, x => 1.0, y => 1.0)
+x + y - 1
+
+julia> quad_expr = QuadExpr(
+           aff_expr,
+           UnorderedPair(x, x) => 1.0,
+           UnorderedPair(x, y) => 2.0,
+           UnorderedPair(y, y) => 1.0,
+       )
+x² + 2 x*y + y² + x + y - 1

add_to_expression!

Finally, add_to_expression! can also be used to add quadratic terms.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> ex = QuadExpr(x + y - 1.0)
+x + y - 1
+
+julia> add_to_expression!(ex, 1.0, x, x)
+x² + x + y - 1
+
+julia> add_to_expression!(ex, 2.0, x, y)
+x² + 2 x*y + x + y - 1
+
+julia> add_to_expression!(ex, 1.0, y, y)
+x² + 2 x*y + y² + x + y - 1
Warning

Read the section Initializing arrays for some cases to be careful about when using add_to_expression!.

Removing zero terms

Use drop_zeros! to remove terms from a quadratic expression with a 0 coefficient.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @expression(model, ex, x^2 + x + 1 - x^2)
+0 x² + x + 1
+
+julia> drop_zeros!(ex)
+
+julia> ex
+x + 1

Coefficients

Use coefficient to return the coefficient associated with a pair of variables in a quadratic expression.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> @expression(model, ex, 2*x*y + 3*x)
+2 x*y + 3 x
+
+julia> coefficient(ex, x, y)
+2.0
+
+julia> coefficient(ex, x, x)
+0.0
+
+julia> coefficient(ex, y, x)
+2.0
+
+julia> coefficient(ex, x)
+3.0

Nonlinear expressions

Nonlinear expressions in JuMP are represented by a NonlinearExpr object. See Nonlinear expressions in detail for more details.

Initializing arrays

JuMP implements zero(AffExpr) and one(AffExpr) to support various functions in LinearAlgebra (for example, accessing the off-diagonal of a Diagonal matrix).

julia> zero(AffExpr)
+0
+
+julia> one(AffExpr)
+1

However, this can result in a subtle bug if you call add_to_expression! or the MutableArithmetics API on an element created by zeros or ones:

julia> x = zeros(AffExpr, 2)
+2-element Vector{AffExpr}:
+ 0
+ 0
+
+julia> add_to_expression!(x[1], 1.1)
+1.1
+
+julia> x
+2-element Vector{AffExpr}:
+ 1.1
+ 1.1

Notice how we modified x[1], but we also changed x[2]!

This happened because zeros(AffExpr, 2) calls zero(AffExpr) once to obtain a zero element, and then creates an appropriately sized array filled with the same element.

This also happens with broadcasting calls containing a conversion of 0 or 1:

julia> x = Vector{AffExpr}(undef, 2)
+2-element Vector{AffExpr}:
+ #undef
+ #undef
+
+julia> x .= 0
+2-element Vector{AffExpr}:
+ 0
+ 0
+
+julia> add_to_expression!(x[1], 1.1)
+1.1
+
+julia> x
+2-element Vector{AffExpr}:
+ 1.1
+ 1.1

The recommended way to create an array of empty expressions is as follows:

julia> x = Vector{AffExpr}(undef, 2)
+2-element Vector{AffExpr}:
+ #undef
+ #undef
+
+julia> for i in eachindex(x)
+           x[i] = AffExpr(0.0)
+       end
+
+julia> add_to_expression!(x[1], 1.1)
+1.1
+
+julia> x
+2-element Vector{AffExpr}:
+ 1.1
+ 0

Alternatively, use non-mutating operation to avoid updating x[1] in-place:

julia> x = zeros(AffExpr, 2)
+2-element Vector{AffExpr}:
+ 0
+ 0
+
+julia> x[1] += 1.1
+1.1
+
+julia> x
+2-element Vector{AffExpr}:
+ 1.1
+ 0

Note that for large expressions this will be slower due to the allocation of additional temporary objects.

diff --git a/previews/PR3536/manual/models/index.html b/previews/PR3536/manual/models/index.html new file mode 100644 index 00000000000..ccd43720ef3 --- /dev/null +++ b/previews/PR3536/manual/models/index.html @@ -0,0 +1,237 @@ + +Models · JuMP

Models

JuMP models are the fundamental building block that we use to construct optimization problems. They hold things like the variables and constraints, as well as which solver to use and even solution information.

Info

JuMP uses "optimizer" as a synonym for "solver." Our convention is to use "solver" to refer to the underlying software, and use "optimizer" to refer to the Julia object that wraps the solver. For example, HiGHS is a solver, and HiGHS.Optimizer is an optimizer.

Tip

See Supported solvers for a list of available solvers.

Create a model

Create a model by passing an optimizer to Model:

julia> model = Model(HiGHS.Optimizer)
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: EMPTY_OPTIMIZER
+Solver name: HiGHS

If you don't know which optimizer you will be using at creation time, create a model without an optimizer, and then call set_optimizer at any time prior to optimize!:

julia> model = Model()
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+
+julia> set_optimizer(model, HiGHS.Optimizer)
Tip

Don't know what the fields Model mode and CachingOptimizer state mean? Read the Backends section.

What is the difference?

For most models, there is no difference between passing the optimizer to Model, and calling set_optimizer.

However, if an optimizer does not support a constraint in the model, the timing of when an error will be thrown can differ:

  • If you pass an optimizer, an error will be thrown when you try to add the constraint.
  • If you call set_optimizer, an error will be thrown when you try to solve the model via optimize!.

Therefore, most users should pass an optimizer to Model because it provides the earliest warning that your solver is not suitable for the model you are trying to build. However, if you are modifying a problem by adding and deleting different constraint types, you may need to use set_optimizer. See Switching optimizer for the relaxed problem for an example of when this is useful.

Reducing time-to-first-solve latency

By default, JuMP uses bridges to reformulate the model you are building into an equivalent model supported by the solver.

However, if your model is already supported by the solver, bridges add latency (read The "time-to-first-solve" issue). This is particularly noticeable for small models.

To reduce the "time-to-first-solve,s" try passing add_bridges = false.

julia> model = Model(HiGHS.Optimizer; add_bridges = false);

or

julia> model = Model();
+
+julia> set_optimizer(model, HiGHS.Optimizer; add_bridges = false)

However, be wary. If your model and solver combination needs bridges, an error will be thrown:

julia> model = Model(SCS.Optimizer; add_bridges = false);
+
+
+julia> @variable(model, x)
+x
+
+julia> @constraint(model, 2x <= 1)
+ERROR: Constraints of type MathOptInterface.ScalarAffineFunction{Float64}-in-MathOptInterface.LessThan{Float64} are not supported by the solver.
+
+If you expected the solver to support your problem, you may have an error in your formulation. Otherwise, consider using a different solver.
+
+The list of available solvers, along with the problem types they support, is available at https://jump.dev/JuMP.jl/stable/installation/#Supported-solvers.
+[...]

Solvers which expect environments

Some solvers accept (or require) positional arguments such as a license environment or a path to a binary executable. For these solvers, you can pass a function to Model which takes zero arguments and returns an instance of the optimizer.

A common use-case for this is passing an environment or sub-solver to the optimizer:

julia> import HiGHS
+
+julia> import MultiObjectiveAlgorithms as MOA
+
+julia> model = Model(() -> MOA.Optimizer(HiGHS.Optimizer))
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: EMPTY_OPTIMIZER
+Solver name: MOA[algorithm=MultiObjectiveAlgorithms.Lexicographic, optimizer=HiGHS]

Solver options

JuMP uses "attribute" as a synonym for "option." Use optimizer_with_attributes to create an optimizer with some attributes initialized:

julia> model = Model(
+           optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false),
+       )
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: EMPTY_OPTIMIZER
+Solver name: HiGHS

Alternatively, use set_attribute to set an attribute after the model has been created:

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_attribute(model, "output_flag", false)
+
+julia> get_attribute(model, "output_flag")
+false

You can also modify attributes within an optimizer_with_attributes object:

julia> solver = optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => true);
+
+julia> get_attribute(solver, "output_flag")
+true
+
+julia> set_attribute(solver, "output_flag", false)
+
+julia> get_attribute(solver, "output_flag")
+false
+
+julia> model = Model(solver);

Changing the number types

By default, the coefficients of affine and quadratic expressions are numbers of type either Float64 or Complex{Float64} (see Complex number support).

The type Float64 can be changed using the GenericModel constructor:

julia> model = GenericModel{Rational{BigInt}}();
+
+julia> @variable(model, x)
+x
+
+julia> @expression(model, expr, 1 // 3 * x)
+1//3 x
+
+julia> typeof(expr)
+GenericAffExpr{Rational{BigInt}, GenericVariableRef{Rational{BigInt}}}

Using a value_type other than Float64 is an advanced operation and should be used only if the underlying solver actually solves the problem using the provided value type.

Warning

Nonlinear Modeling is currently restricted to the Float64 number type.

By default, show(model) will print a summary of the problem:

julia> model = Model(); @variable(model, x >= 0); @objective(model, Max, x);
+
+julia> model
+A JuMP Model
+Maximization problem with:
+Variable: 1
+Objective function type: VariableRef
+`VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 1 constraint
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+Names registered in the model: x

Use print to print the formulation of the model (in IJulia, this will render as LaTeX.

julia> print(model)
+Max x
+Subject to
+ x ≥ 0
Warning

This format is specific to JuMP and may change in any future release. It is not intended to be an instance format. To write the model to a file, use write_to_file instead.

Use latex_formulation to display the model in LaTeX form.

julia> latex_formulation(model)
+$$ \begin{aligned}
+\max\quad & x\\
+\text{Subject to} \quad & x \geq 0\\
+\end{aligned} $$

In IJulia (and Documenter), ending a cell in with latex_formulation will render the model in LaTeX:

latex_formulation(model)

\[ \begin{aligned} +\max\quad & x\\ +\text{Subject to} \quad & x \geq 0\\ +\end{aligned} \]

Turn off output

Use set_silent and unset_silent to disable or enable printing output from the solver.

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> unset_silent(model)
Tip

Most solvers will also have a solver-specific option to provide finer-grained control over the output. Consult their README's for details.

Set a time limit

Use set_time_limit_sec, unset_time_limit_sec, and time_limit_sec to manage time limits.

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_time_limit_sec(model, 60.0)
+
+
+julia> time_limit_sec(model)
+60.0
+
+julia> unset_time_limit_sec(model)
+
+julia> limit = time_limit_sec(model)
+
+julia> limit === nothing
+true

If your time limit is encoded as a Dates.Period object, use the following code to convert it to Float64 for set_time_limit_sec:

julia> import Dates
+
+julia> seconds(x::Dates.Period) = 1e-3 * Dates.value(round(x, Dates.Millisecond))
+seconds (generic function with 1 method)
+
+julia> set_time_limit_sec(model, seconds(Dates.Hour(1)))
+
+julia> time_limit_sec(model)
+3600.0
Info

Some solvers do not support time limits. In these cases, an error will be thrown.

Write a model to file

JuMP can write models to a variety of file-formats using write_to_file and Base.write.

For most common file formats, the file type will be detected from the extension.

For example, here is how to write an MPS file:

julia> model = Model();
+
+julia> write_to_file(model, "model.mps")

Other supported file formats include:

To write to a specific io::IO, use Base.write. Specify the file type by passing a MOI.FileFormats.FileFormat enum.

julia> model = Model();
+
+julia> io = IOBuffer();
+
+julia> write(io, model; format = MOI.FileFormats.FORMAT_MPS)

Read a model from file

JuMP models can be created from file formats using read_from_file and Base.read.

julia> model = read_from_file("model.mps")
+A JuMP Model
+Minimization problem with:
+Variables: 0
+Objective function type: AffExpr
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+
+julia> seekstart(io);
+
+julia> model2 = read(io, Model; format = MOI.FileFormats.FORMAT_MPS)
+A JuMP Model
+Minimization problem with:
+Variables: 0
+Objective function type: AffExpr
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
Note

Because file formats do not serialize the containers of JuMP variables and constraints, the names in the model will not be registered. Therefore, you cannot access named variables and constraints via model[:x]. Instead, use variable_by_name or constraint_by_name to access specific variables or constraints.

Relax integrality

Use relax_integrality to remove any integrality constraints from the model, such as integer and binary restrictions on variables. relax_integrality returns a function that can be later called with zero arguments to re-add the removed constraints:

julia> model = Model();
+
+julia> @variable(model, x, Int)
+x
+
+julia> num_constraints(model, VariableRef, MOI.Integer)
+1
+
+julia> undo = relax_integrality(model);
+
+julia> num_constraints(model, VariableRef, MOI.Integer)
+0
+
+julia> undo()
+
+julia> num_constraints(model, VariableRef, MOI.Integer)
+1

Switching optimizer for the relaxed problem

A common reason for relaxing integrality is to compute dual variables of the relaxed problem. However, some mixed-integer linear solvers (for example, Cbc) do not return dual solutions, even if the problem does not have integrality restrictions.

Therefore, after relax_integrality you should call set_optimizer with a solver that does support dual solutions, such as Clp.

For example, instead of:

using JuMP, Cbc
+model = Model(Cbc.Optimizer)
+@variable(model, x, Int)
+undo = relax_integrality(model)
+optimize!(model)
+reduced_cost(x)  # Errors

do:

using JuMP, Cbc, Clp
+model = Model(Cbc.Optimizer)
+@variable(model, x, Int)
+undo = relax_integrality(model)
+set_optimizer(model, Clp.Optimizer)
+optimize!(model)
+reduced_cost(x)  # Works

Backends

Info

This section discusses advanced features of JuMP. For new users, you may want to skip this section. You don't need to know how JuMP manages problems behind the scenes to create and solve JuMP models.

A JuMP Model is a thin layer around a backend of type MOI.ModelLike that stores the optimization problem and acts as the optimization solver.

However, if you construct a model like Model(HiGHS.Optimizer), the backend is not a HiGHS.Optimizer, but a more complicated object.

From JuMP, the MOI backend can be accessed using the backend function. Let's see what the backend of a JuMP Model is:

julia> model = Model(HiGHS.Optimizer);
+
+julia> b = backend(model)
+MOIU.CachingOptimizer{MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}, MOIU.UniversalFallback{MOIU.Model{Float64}}}
+in state EMPTY_OPTIMIZER
+in mode AUTOMATIC
+with model cache MOIU.UniversalFallback{MOIU.Model{Float64}}
+  fallback for MOIU.Model{Float64}
+with optimizer MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}
+  with 0 variable bridges
+  with 0 constraint bridges
+  with 0 objective bridges
+  with inner model A HiGHS model with 0 columns and 0 rows.

Uh oh. Even though we passed a HiGHS.Optimizer, the backend is a much more complicated object.

CachingOptimizer

A MOIU.CachingOptimizer is a layer that abstracts the difference between solvers that support incremental modification (for example, they support adding variables one-by-one), and solvers that require the entire problem in a single API call (for example, they only accept the A, b and c matrices of a linear program).

It has two parts:

  1. A cache, where the model can be built and modified incrementally
    julia> b.model_cache
    +MOIU.UniversalFallback{MOIU.Model{Float64}}
    +fallback for MOIU.Model{Float64}
  2. An optimizer, which is used to solve the problem
    julia> b.optimizer
    +MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}
    +with 0 variable bridges
    +with 0 constraint bridges
    +with 0 objective bridges
    +with inner model A HiGHS model with 0 columns and 0 rows.
Info

The LazyBridgeOptimizer section explains what a LazyBridgeOptimizer is.

The CachingOptimizer has logic to decide when to copy the problem from the cache to the optimizer, and when it can efficiently update the optimizer in-place.

A CachingOptimizer may be in one of three possible states:

  • NO_OPTIMIZER: The CachingOptimizer does not have any optimizer.
  • EMPTY_OPTIMIZER: The CachingOptimizer has an empty optimizer, and it is not synchronized with the cached model.
  • ATTACHED_OPTIMIZER: The CachingOptimizer has an optimizer, and it is synchronized with the cached model.

A CachingOptimizer has two modes of operation:

  • AUTOMATIC: The CachingOptimizer changes its state when necessary. For example, optimize! will automatically call attach_optimizer (an optimizer must have been previously set). Attempting to add a constraint or perform a modification not supported by the optimizer results in a drop to EMPTY_OPTIMIZER mode.
  • MANUAL: The user must change the state of the CachingOptimizer using MOIU.reset_optimizer(::JuMP.Model), MOIU.drop_optimizer(::JuMP.Model), and MOIU.attach_optimizer(::JuMP.Model). Attempting to perform an operation in the incorrect state results in an error.

By default Model will create a CachingOptimizer in AUTOMATIC mode.

LazyBridgeOptimizer

The second layer that JuMP applies automatically is a MOI.Bridges.LazyBridgeOptimizer. A MOI.Bridges.LazyBridgeOptimizer is an MOI layer that attempts to transform the problem from the formulation provided by the user into an equivalent problem supported by the solver. This may involve adding new variables and constraints to the optimizer. The transformations are selected from a set of known recipes called bridges.

A common example of a bridge is one that splits an interval constraint like @constraint(model, 1 <= x + y <= 2) into two constraints, @constraint(model, x + y >= 1) and @constraint(model, x + y <= 2).

Use the add_bridges = false keyword to remove the bridging layer:

julia> model = Model(HiGHS.Optimizer; add_bridges = false)
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: EMPTY_OPTIMIZER
+Solver name: HiGHS
+
+julia> backend(model)
+MOIU.CachingOptimizer{HiGHS.Optimizer, MOIU.UniversalFallback{MOIU.Model{Float64}}}
+in state EMPTY_OPTIMIZER
+in mode AUTOMATIC
+with model cache MOIU.UniversalFallback{MOIU.Model{Float64}}
+  fallback for MOIU.Model{Float64}
+with optimizer A HiGHS model with 0 columns and 0 rows.

Bridges can be added and removed from a MOI.Bridges.LazyBridgeOptimizer using add_bridge and remove_bridge. Use print_active_bridges to see which bridges are used to reformulate the model. Read the Ellipsoid approximation tutorial for more details.

Unsafe backend

In some advanced use-cases, it is necessary to work with the inner optimization model directly. To access this model, use unsafe_backend:

julia> backend(model)
+MOIU.CachingOptimizer{MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}, MOIU.UniversalFallback{MOIU.Model{Float64}}}
+in state EMPTY_OPTIMIZER
+in mode AUTOMATIC
+with model cache MOIU.UniversalFallback{MOIU.Model{Float64}}
+  fallback for MOIU.Model{Float64}
+with optimizer MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}
+  with 0 variable bridges
+  with 0 constraint bridges
+  with 0 objective bridges
+  with inner model A HiGHS model with 0 columns and 0 rows.
+
+julia> unsafe_backend(model)
+A HiGHS model with 0 columns and 0 rows.
Warning

backend and unsafe_backend are advanced routines. Read their docstrings to understand the caveats of their usage, and only call them if you wish to access low-level solver-specific functions.

Direct mode

Using a CachingOptimizer results in an additional copy of the model being stored by JuMP in the .model_cache field. To avoid this overhead, create a JuMP model using direct_model:

julia> model = direct_model(HiGHS.Optimizer())
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: DIRECT
+Solver name: HiGHS
Warning

Solvers that do not support incremental modification do not support direct_model. An error will be thrown, telling you to use a CachingOptimizer instead.

The benefit of using direct_model is that there are no extra layers (for example, Cachingoptimizer or LazyBridgeOptimizer) between model and the provided optimizer:

julia> backend(model)
+A HiGHS model with 0 columns and 0 rows.

A downside of direct mode is that there is no bridging layer. Therefore, only constraints which are natively supported by the solver are supported. For example, HiGHS.jl does not implement quadratic constraints:

julia> model = direct_model(HiGHS.Optimizer());
+
+julia> set_silent(model)
+
+julia> @variable(model, x[1:2]);
+
+julia> @constraint(model, x[1]^2 + x[2]^2 <= 2)
+ERROR: Constraints of type MathOptInterface.ScalarQuadraticFunction{Float64}-in-MathOptInterface.LessThan{Float64} are not supported by the solver.
+
+If you expected the solver to support your problem, you may have an error in your formulation. Otherwise, consider using a different solver.
+
+The list of available solvers, along with the problem types they support, is available at https://jump.dev/JuMP.jl/stable/installation/#Supported-solvers.
+Stacktrace:
Warning

Another downside of direct mode is that the behavior of querying solution information after modifying the problem is solver-specific. This can lead to errors, or the solver silently returning an incorrect value. See OptimizeNotCalled errors for more information.

diff --git a/previews/PR3536/manual/nlp/index.html b/previews/PR3536/manual/nlp/index.html new file mode 100644 index 00000000000..de6a76619d1 --- /dev/null +++ b/previews/PR3536/manual/nlp/index.html @@ -0,0 +1,347 @@ + +Nonlinear Modeling (Legacy) · JuMP

Nonlinear Modeling (Legacy)

Warning

This page describes the legacy nonlinear interface to JuMP. It has a number of quirks and limitations that prompted the development of a new nonlinear interface. The new interface is documented at Nonlinear Modeling. This legacy interface will remain for all future v1.X releases of JuMP. The two nonlinear interfaces cannot be combined.

JuMP has support for general smooth nonlinear (convex and nonconvex) optimization problems. JuMP is able to provide exact, sparse second-order derivatives to solvers. This information can improve solver accuracy and performance.

There are three main changes to solve nonlinear programs in JuMP.

Info

There are some restrictions on what syntax you can use in the @NLxxx macros. Make sure to read the Syntax notes.

Set a nonlinear objective

Use @NLobjective to set a nonlinear objective.

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @NLobjective(model, Min, exp(x[1]) - sqrt(x[2]))

To modify a nonlinear objective, call @NLobjective again.

Add a nonlinear constraint

Use @NLconstraint to add a nonlinear constraint.

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @NLconstraint(model, exp(x[1]) <= 1)
+exp(x[1]) - 1.0 ≤ 0
+
+julia> @NLconstraint(model, [i = 1:2], x[i]^i >= i)
+2-element Vector{NonlinearConstraintRef{ScalarShape}}:
+ x[1] ^ 1.0 - 1.0 ≥ 0
+ x[2] ^ 2.0 - 2.0 ≥ 0
+
+julia> @NLconstraint(model, con[i = 1:2], prod(x[j] for j = 1:i) == i)
+2-element Vector{NonlinearConstraintRef{ScalarShape}}:
+ (*)(x[1]) - 1.0 = 0
+ x[1] * x[2] - 2.0 = 0
Info

You can only create nonlinear constraints with <=, >=, and ==. More general Nonlinear-in-Set constraints are not supported.

Delete a nonlinear constraint using delete:

julia> delete(model, con[1])

Create a nonlinear expression

Use @NLexpression to create nonlinear expression objects. The syntax is identical to @expression, except that the expression can contain nonlinear terms.

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> expr = @NLexpression(model, exp(x[1]) + sqrt(x[2]))
+subexpression[1]: exp(x[1]) + sqrt(x[2])
+
+julia> my_anon_expr = @NLexpression(model, [i = 1:2], sin(x[i]))
+2-element Vector{NonlinearExpression}:
+ subexpression[2]: sin(x[1])
+ subexpression[3]: sin(x[2])
+
+julia> @NLexpression(model, my_expr[i = 1:2], sin(x[i]))
+2-element Vector{NonlinearExpression}:
+ subexpression[4]: sin(x[1])
+ subexpression[5]: sin(x[2])

Nonlinear expression can be used in @NLobjective, @NLconstraint, and even nested in other @NLexpressions.

julia> @NLobjective(model, Min, expr^2 + 1)
+
+julia> @NLconstraint(model, [i = 1:2], my_expr[i] <= i)
+2-element Vector{NonlinearConstraintRef{ScalarShape}}:
+ subexpression[4] - 1.0 ≤ 0
+ subexpression[5] - 2.0 ≤ 0
+
+julia> @NLexpression(model, nested[i = 1:2], sin(my_expr[i]))
+2-element Vector{NonlinearExpression}:
+ subexpression[6]: sin(subexpression[4])
+ subexpression[7]: sin(subexpression[5])

Use value to query the value of a nonlinear expression:

julia> set_start_value(x[1], 1.0)
+
+julia> value(start_value, nested[1])
+0.7456241416655579
+
+julia> sin(sin(1.0))
+0.7456241416655579

Create a nonlinear parameter

For nonlinear models only, JuMP offers a syntax for explicit "parameter" objects, which are constants in the model that can be efficiently updated between solves.

Nonlinear parameters are declared by using the @NLparameter macro and may be indexed by arbitrary sets analogously to JuMP variables and expressions.

The initial value of the parameter must be provided on the right-hand side of the == sign.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @NLparameter(model, p[i = 1:2] == i)
+2-element Vector{NonlinearParameter}:
+ parameter[1] == 1.0
+ parameter[2] == 2.0

Create anonymous parameters using the value keyword:

julia> anon_parameter = @NLparameter(model, value = 1)
+parameter[3] == 1.0
Info

A parameter is not an optimization variable. It must be fixed to a value with ==. If you want a parameter that is <= or >=, create a variable instead using @variable.

Use value and set_value to query or update the value of a parameter.

julia> value.(p)
+2-element Vector{Float64}:
+ 1.0
+ 2.0
+
+julia> set_value(p[2], 3.0)
+3.0
+
+julia> value.(p)
+2-element Vector{Float64}:
+ 1.0
+ 3.0

Nonlinear parameters must be used within nonlinear macros only.

When to use a parameter

Nonlinear parameters are useful when solving nonlinear models in a sequence:

using JuMP, Ipopt
+model = Model(Ipopt.Optimizer)
+set_silent(model)
+@variable(model, z)
+@NLparameter(model, x == 1.0)
+@NLobjective(model, Min, (z - x)^2)
+optimize!(model)
+@show value(z) # Equals 1.0.
+
+# Now, update the value of x to solve a different problem.
+set_value(x, 5.0)
+optimize!(model)
+@show value(z) # Equals 5.0
value(z) = 1.0
+value(z) = 5.0
Info

Using nonlinear parameters can be faster than creating a new model from scratch with updated data because JuMP is able to avoid repeating a number of steps in processing the model before handing it off to the solver.

Syntax notes

The syntax accepted in nonlinear macros is more restricted than the syntax for linear and quadratic macros. We note some important points below.

Scalar operations only

Except for the splatting syntax discussed below, all expressions must be simple scalar operations. You cannot use dot, matrix-vector products, vector slices, etc.

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @variable(model, y);
+
+julia> c = [1, 2];
+
+julia> @NLobjective(model, Min, c' * x + 3y)
+ERROR: Unexpected array [1 2] in nonlinear expression. Nonlinear expressions may contain only scalar expressions.
+[...]

Translate vector operations into explicit sum() operations:

julia> @NLobjective(model, Min, sum(c[i] * x[i] for i = 1:2) + 3y)

Or use an @expression:

julia> @expression(model, expr, c' * x)
+x[1] + 2 x[2]
+
+julia> @NLobjective(model, Min, expr + 3y)
+

Splatting

The splatting operator ... is recognized in a very restricted setting for expanding function arguments. The expression splatted can be only a symbol. More complex expressions are not recognized.

julia> model = Model();
+
+julia> @variable(model, x[1:3]);
+
+julia> @NLconstraint(model, *(x...) <= 1.0)
+x[1] * x[2] * x[3] - 1.0 ≤ 0
+
+julia> @NLconstraint(model, *((x / 2)...) <= 0.0)
+ERROR: Unsupported use of the splatting operator. JuMP supports splatting only symbols. For example, `x...` is ok, but `(x + 1)...`, `[x; y]...` and `g(f(y)...)` are not.

User-defined Functions

JuMP natively supports the set of univariate and multivariate functions recognized by the MOI.Nonlinear submodule. In addition to this list of functions, it is possible to register custom user-defined nonlinear functions. User-defined functions can be used anywhere in @NLobjective, @NLconstraint, and @NLexpression.

JuMP will attempt to automatically register functions it detects in your nonlinear expressions, which usually means manually registering a function is not needed. Two exceptions are if you want to provide custom derivatives, or if the function is not available in the scope of the nonlinear expression.

Warning

User-defined functions must return a scalar output. For a work-around, see User-defined operators with vector outputs.

Automatic differentiation

JuMP does not support black-box optimization, so all user-defined functions must provide derivatives in some form. Fortunately, JuMP supports automatic differentiation of user-defined functions, a feature to our knowledge not available in any comparable modeling systems.

Info

Automatic differentiation is not finite differencing. JuMP's automatically computed derivatives are not subject to approximation error.

JuMP uses ForwardDiff.jl to perform automatic differentiation; see the ForwardDiff.jl documentation for a description of how to write a function suitable for automatic differentiation.

Common mistakes when writing a user-defined function

Warning

Get an error like No method matching Float64(::ForwardDiff.Dual)? Read this section, and see the guidelines at ForwardDiff.jl.

The most common error is that your user-defined function is not generic with respect to the number type, that is, don't assume that the input to the function is Float64.

f(x::Float64) = 2 * x  # This will not work.
+f(x::Real)    = 2 * x  # This is good.
+f(x)          = 2 * x  # This is also good.

Another reason you may encounter this error is if you create arrays inside your function which are Float64.

function bad_f(x...)
+    y = zeros(length(x))  # This constructs an array of `Float64`!
+    for i = 1:length(x)
+        y[i] = x[i]^i
+    end
+    return sum(y)
+end
+
+function good_f(x::T...) where {T<:Real}
+    y = zeros(T, length(x))  # Construct an array of type `T` instead!
+    for i = 1:length(x)
+        y[i] = x[i]^i
+    end
+    return sum(y)
+end

Register a function

To register a user-defined function with derivatives computed by automatic differentiation, use the register method as in the following example:

square(x) = x^2
+f(x, y) = (x - 1)^2 + (y - 2)^2
+
+model = Model()
+
+register(model, :square, 1, square; autodiff = true)
+register(model, :my_f, 2, f; autodiff = true)
+
+@variable(model, x[1:2] >= 0.5)
+@NLobjective(model, Min, my_f(x[1], square(x[2])))

The above code creates a JuMP model with the objective function (x[1] - 1)^2 + (x[2]^2 - 2)^2. The arguments to register are:

  1. The model for which the functions are registered.
  2. A Julia symbol object which serves as the name of the user-defined function in JuMP expressions.
  3. The number of input arguments that the function takes.
  4. The Julia method which computes the function
  5. A flag to instruct JuMP to compute exact gradients automatically.
Tip

The symbol :my_f doesn't have to match the name of the function f. However, it's more readable if it does. Make sure you use my_f and not f in the macros.

Warning

User-defined functions cannot be re-registered and will not update if you modify the underlying Julia function. If you want to change a user-defined function between solves, rebuild the model or use a different name. To use a different name programmatically, see Raw expression input.

Register a function and gradient

Forward-mode automatic differentiation as implemented by ForwardDiff.jl has a computational cost that scales linearly with the number of input dimensions. As such, it is not the most efficient way to compute gradients of user-defined functions if the number of input arguments is large. In this case, users may want to provide their own routines for evaluating gradients.

Univariate functions

For univariate functions, the gradient function ∇f returns a number that represents the first-order derivative:

f(x) = x^2
+∇f(x) = 2x
+model = Model()
+register(model, :my_square, 1, f, ∇f; autodiff = true)
+@variable(model, x >= 0)
+@NLobjective(model, Min, my_square(x))

If autodiff = true, JuMP will use automatic differentiation to compute the hessian.

Multivariate functions

For multivariate functions, the gradient function ∇f must take a gradient vector as the first argument that is filled in-place:

f(x, y) = (x - 1)^2 + (y - 2)^2
+function ∇f(g::AbstractVector{T}, x::T, y::T) where {T}
+    g[1] = 2 * (x - 1)
+    g[2] = 2 * (y - 2)
+    return
+end
+
+model = Model()
+register(model, :my_square, 2, f, ∇f)
+@variable(model, x[1:2] >= 0)
+@NLobjective(model, Min, my_square(x[1], x[2]))
Warning

Make sure the first argument to ∇f supports an AbstractVector, and do not assume the input is Float64.

Register a function, gradient, and hessian

You can also register a function with the second-order derivative information, which is a scalar for univariate functions, and a symmetric matrix for multivariate functions.

Univariate functions

Pass a function which returns a number representing the second-order derivative:

f(x) = x^2
+∇f(x) = 2x
+∇²f(x) = 2
+model = Model()
+register(model, :my_square, 1, f, ∇f, ∇²f)
+@variable(model, x >= 0)
+@NLobjective(model, Min, my_square(x))

Multivariate functions

For multivariate functions, the hessian function ∇²f must take an AbstractMatrix as the first argument, the lower-triangular of which is filled in-place:

f(x...) = (1 - x[1])^2 + 100 * (x[2] - x[1]^2)^2
+function ∇f(g, x...)
+    g[1] = 400 * x[1]^3 - 400 * x[1] * x[2] + 2 * x[1] - 2
+    g[2] = 200 * (x[2] - x[1]^2)
+    return
+end
+function ∇²f(H, x...)
+    H[1, 1] = 1200 * x[1]^2 - 400 * x[2] + 2
+    # H[1, 2] = -400 * x[1]  <-- Not needed. Fill the lower-triangular only.
+    H[2, 1] = -400 * x[1]
+    H[2, 2] = 200.0
+    return
+end
+
+model = Model()
+register(model, :rosenbrock, 2, f, ∇f, ∇²f)
+@variable(model, x[1:2])
+@NLobjective(model, Min, rosenbrock(x[1], x[2]))
Warning

You may assume the Hessian matrix H is initialized with zeros, and because H is symmetric, you need only to fill in the non-zero of the lower-triangular terms. The matrix type passed in as H depends on the automatic differentiation system, so make sure the first argument to the Hessian function supports an AbstractMatrix (it may be something other than Matrix{Float64}). However, you may assume only that H supports size(H) and setindex!. Finally, the matrix is treated as dense, so the performance will be poor on functions with high-dimensional input.

User-defined functions with vector inputs

User-defined functions which take vectors as input arguments (for example, f(x::Vector)) are not supported. Instead, use Julia's splatting syntax to create a function with scalar arguments. For example, instead of

f(x::Vector) = sum(x[i]^i for i in 1:length(x))

define:

f(x...) = sum(x[i]^i for i in 1:length(x))

This function f can be used in a JuMP model as follows:

model = Model()
+@variable(model, x[1:5] >= 0)
+f(x...) = sum(x[i]^i for i in 1:length(x))
+register(model, :f, 5, f; autodiff = true)
+@NLobjective(model, Min, f(x...))
Tip

Make sure to read the syntax restrictions of Splatting.

Factors affecting solution time

The execution time when solving a nonlinear programming problem can be divided into two parts, the time spent in the optimization algorithm (the solver) and the time spent evaluating the nonlinear functions and corresponding derivatives. Ipopt explicitly displays these two timings in its output, for example:

Total CPU secs in IPOPT (w/o function evaluations)   =      7.412
+Total CPU secs in NLP function evaluations           =      2.083

For Ipopt in particular, one can improve the performance by installing advanced sparse linear algebra packages, see Installation Guide. For other solvers, see their respective documentation for performance tips.

The function evaluation time, on the other hand, is the responsibility of the modeling language. JuMP computes derivatives by using reverse-mode automatic differentiation with graph coloring methods for exploiting sparsity of the Hessian matrix. As a conservative bound, JuMP's performance here currently may be expected to be within a factor of 5 of AMPL's. Our paper in SIAM Review has more details.

Querying derivatives from a JuMP model

For some advanced use cases, one may want to directly query the derivatives of a JuMP model instead of handing the problem off to a solver. Internally, JuMP implements the MOI.AbstractNLPEvaluator interface. To obtain an NLP evaluator object from a JuMP model, use NLPEvaluator. index returns the MOI.VariableIndex corresponding to a JuMP variable. MOI.VariableIndex itself is a type-safe wrapper for Int64 (stored in the .value field.)

For example:

julia> raw_index(v::MOI.VariableIndex) = v.value
+raw_index (generic function with 1 method)
+
+julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> @NLobjective(model, Min, sin(x) + sin(y))
+
+julia> values = zeros(2)
+2-element Vector{Float64}:
+ 0.0
+ 0.0
+
+julia> x_index = raw_index(JuMP.index(x))
+1
+
+julia> y_index = raw_index(JuMP.index(y))
+2
+
+julia> values[x_index] = 2.0
+2.0
+
+julia> values[y_index] = 3.0
+3.0
+
+julia> d = NLPEvaluator(model)
+Nonlinear.Evaluator with available features:
+  * :Grad
+  * :Jac
+  * :JacVec
+  * :Hess
+  * :HessVec
+  * :ExprGraph
+
+julia> MOI.initialize(d, [:Grad])
+
+julia> MOI.eval_objective(d, values)
+1.0504174348855488
+
+julia> sin(2.0) + sin(3.0)
+1.0504174348855488
+
+julia> ∇f = zeros(2)
+2-element Vector{Float64}:
+ 0.0
+ 0.0
+
+julia> MOI.eval_objective_gradient(d, ∇f, values)
+
+julia> ∇f[x_index], ∇f[y_index]
+(-0.4161468365471424, -0.9899924966004454)
+
+julia> cos(2.0), cos(3.0)
+(-0.4161468365471424, -0.9899924966004454)

Only nonlinear constraints (those added with @NLconstraint), and nonlinear objectives (added with @NLobjective) exist in the scope of the NLPEvaluator.

The NLPEvaluator does not evaluate derivatives of linear or quadratic constraints or objectives.

The index method applied to a nonlinear constraint reference object returns its index as a MOI.Nonlinear.ConstraintIndex. For example:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @NLconstraint(model, cons1, sin(x) <= 1);
+
+julia> @NLconstraint(model, cons2, x + 5 == 10);
+
+julia> typeof(cons1)
+NonlinearConstraintRef{ScalarShape} (alias for ConstraintRef{GenericModel{Float64}, MathOptInterface.Nonlinear.ConstraintIndex, ScalarShape})
+
+julia> index(cons1)
+MathOptInterface.Nonlinear.ConstraintIndex(1)
+
+julia> index(cons2)
+MathOptInterface.Nonlinear.ConstraintIndex(2)

Note that for one-sided nonlinear constraints, JuMP subtracts any values on the right-hand side when computing expressions. In other words, one-sided nonlinear constraints are always transformed to have a right-hand side of zero.

This method of querying derivatives directly from a JuMP model is convenient for interacting with the model in a structured way, for example, for accessing derivatives of specific variables. For example, in statistical maximum likelihood estimation problems, one is often interested in the Hessian matrix at the optimal solution, which can be queried using the NLPEvaluator.

Raw expression input

Warning

This section requires advanced knowledge of Julia's Expr. You should read the Expressions and evaluation section of the Julia documentation first.

In addition to the @NLexpression, @NLobjective and @NLconstraint macros, it is also possible to provide Julia Expr objects directly by using add_nonlinear_expression, set_nonlinear_objective and add_nonlinear_constraint.

This input form may be useful if the expressions are generated programmatically, or if you experience compilation issues with the macro input (see Known performance issues for more information).

Add a nonlinear expression

Use add_nonlinear_expression to add a nonlinear expression to the model.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> expr = :($(x) + sin($(x)^2))
+:(x + sin(x ^ 2))
+
+julia> expr_ref = add_nonlinear_expression(model, expr)
+subexpression[1]: x + sin(x ^ 2.0)

This is equivalent to

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> expr_ref = @NLexpression(model, x + sin(x^2))
+subexpression[1]: x + sin(x ^ 2.0)
Note

You must interpolate the variables directly into the expression expr.

Set the objective function

Use set_nonlinear_objective to set a nonlinear objective.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> expr = :($(x) + $(x)^2)
+:(x + x ^ 2)
+
+julia> set_nonlinear_objective(model, MIN_SENSE, expr)

This is equivalent to

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @NLobjective(model, Min, x + x^2)
Note

You must use MIN_SENSE or MAX_SENSE instead of Min and Max.

Add a constraint

Use add_nonlinear_constraint to add a nonlinear constraint.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> expr = :($(x) + $(x)^2)
+:(x + x ^ 2)
+
+julia> add_nonlinear_constraint(model, :($(expr) <= 1))
+(x + x ^ 2.0) - 1.0 ≤ 0

This is equivalent to

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @NLconstraint(model, Min, x + x^2 <= 1)
+(x + x ^ 2.0) - 1.0 ≤ 0

More complicated examples

Raw expression input is most useful when the expressions are generated programmatically, often in conjunction with user-defined functions.

As an example, we construct a model with the nonlinear constraints f(x) <= 1, where f(x) = x^2 and f(x) = sin(x)^2:

julia> function main(functions::Vector{Function})
+           model = Model()
+           @variable(model, x)
+           for (i, f) in enumerate(functions)
+               f_sym = Symbol("f_$(i)")
+               register(model, f_sym, 1, f; autodiff = true)
+               add_nonlinear_constraint(model, :($(f_sym)($(x)) <= 1))
+           end
+           print(model)
+           return
+       end
+main (generic function with 1 method)
+
+julia> main([x -> x^2, x -> sin(x)^2])
+Feasibility
+Subject to
+ f_1(x) - 1.0 ≤ 0
+ f_2(x) - 1.0 ≤ 0

As another example, we construct a model with the constraint x^2 + sin(x)^2 <= 1:

julia> function main(functions::Vector{Function})
+           model = Model()
+           @variable(model, x)
+           expr = Expr(:call, :+)
+           for (i, f) in enumerate(functions)
+               f_sym = Symbol("f_$(i)")
+               register(model, f_sym, 1, f; autodiff = true)
+               push!(expr.args, :($(f_sym)($(x))))
+           end
+           add_nonlinear_constraint(model, :($(expr) <= 1))
+           print(model)
+           return
+       end
+main (generic function with 1 method)
+
+julia> main([x -> x^2, x -> sin(x)^2])
+Feasibility
+Subject to
+ (f_1(x) + f_2(x)) - 1.0 ≤ 0

Registered functions with a variable number of arguments

User defined functions require a fixed number of input arguments. However, sometimes you will want to use a registered function like:

julia> f(x...) = sum(exp(x[i]^2) for i in 1:length(x));

with different numbers of arguments.

The solution is to register the same function f for each unique number of input arguments, making sure to use a unique name each time. For example:

julia> A = [[1], [1, 2], [2, 3, 4], [1, 3, 4, 5]];
+
+julia> model = Model();
+
+julia> @variable(model, x[1:5]);
+
+julia> funcs = Set{Symbol}();
+
+julia> for a in A
+           key = Symbol("f$(length(a))")
+           if !(key in funcs)
+               push!(funcs, key)
+               register(model, key, length(a), f; autodiff = true)
+           end
+           add_nonlinear_constraint(model, :($key($(x[a]...)) <= 1))
+       end
+
+julia> print(model)
+Feasibility
+Subject to
+ f1(x[1]) - 1.0 ≤ 0
+ f2(x[1], x[2]) - 1.0 ≤ 0
+ f3(x[2], x[3], x[4]) - 1.0 ≤ 0
+ f4(x[1], x[3], x[4], x[5]) - 1.0 ≤ 0

Known performance issues

The macro-based input to JuMP's nonlinear interface can cause a performance issue if you:

  1. write a macro with a large number (hundreds) of terms
  2. call that macro from within a function instead of from the top-level in global scope.

The first issue does not depend on the number of resulting terms in the mathematical expression, but rather the number of terms in the Julia Expr representation of that expression. For example, the expression sum(x[i] for i in 1:1_000_000) contains one million mathematical terms, but the Expr representation is just a single sum.

The most common cause, other than a lot of tedious typing, is if you write a program that automatically writes a JuMP model as a text file, which you later execute. One example is MINLPlib.jl which automatically transpiled models in the GAMS scalar format into JuMP examples.

As a rule of thumb, if you are writing programs to automatically generate expressions for the JuMP macros, you should target the Raw expression input instead. For more information, read MathOptInterface Issue#1997.

diff --git a/previews/PR3536/manual/nonlinear/index.html b/previews/PR3536/manual/nonlinear/index.html new file mode 100644 index 00000000000..5dcbd91249c --- /dev/null +++ b/previews/PR3536/manual/nonlinear/index.html @@ -0,0 +1,199 @@ + +Nonlinear Modeling · JuMP

Nonlinear Modeling

Warning

This page describes a new nonlinear interface to JuMP. It replaces the legacy @NL interface, which is documented at Nonlinear Modeling (Legacy). The API described below is stable, and it will not break with future 1.X releases of JuMP. However, solver support may be limited, and there may be gaps in functionality compared with the legacy interface. To report a bug, or request a missing feature, please open an issue.

JuMP has support for nonlinear (convex and nonconvex) optimization problems. JuMP is able to automatically provide exact, sparse second-order derivatives to solvers. This information can improve solver accuracy and performance.

Set a nonlinear objective

Use @objective to set a nonlinear objective.

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @objective(model, Min, exp(x[1]) - sqrt(x[2]))
+exp(x[1]) - sqrt(x[2])

To modify a nonlinear objective, call @objective again.

Add a nonlinear constraint

Use @constraint to add a nonlinear constraint.

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @constraint(model, exp(x[1]) <= 1)
+exp(x[1]) - 1.0 ≤ 0
+
+julia> @constraint(model, con[i = 1:2], 2^x[i] >= i)
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarNonlinearFunction, MathOptInterface.GreaterThan{Float64}}, ScalarShape}}:
+ con[1] : (2.0 ^ x[1]) - 1.0 ≥ 0
+ con[2] : (2.0 ^ x[2]) - 2.0 ≥ 0

Delete a nonlinear constraint using delete:

julia> delete(model, con[1])

Create a nonlinear expression

Use @expression to create nonlinear expression objects:

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> expr = @expression(model, exp(x[1]) + sqrt(x[2]))
+exp(x[1]) + sqrt(x[2])
+
+julia> my_anon_expr = @expression(model, [i = 1:2], sin(x[i]))
+2-element Vector{NonlinearExpr}:
+ sin(x[1])
+ sin(x[2])
+
+julia> @expression(model, my_expr[i = 1:2], sin(x[i]))
+2-element Vector{NonlinearExpr}:
+ sin(x[1])
+ sin(x[2])

A NonlinearExpr can be used in @objective, @constraint, and even nested in other @expressions.

julia> @objective(model, Min, expr^2 + 1)
+((exp(x[1]) + sqrt(x[2])) ^ 2.0) + 1.0
+
+julia> @constraint(model, [i = 1:2], my_expr[i] <= i)
+2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarNonlinearFunction, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
+ sin(x[1]) - 1.0 ≤ 0
+ sin(x[2]) - 2.0 ≤ 0
+
+julia> @expression(model, nested[i = 1:2], sin(my_expr[i]))
+2-element Vector{NonlinearExpr}:
+ sin(sin(x[1]))
+ sin(sin(x[2]))

Use value to query the value of a nonlinear expression:

julia> set_start_value(x[1], 1.0)
+
+julia> value(start_value, nested[1])
+0.7456241416655579
+
+julia> sin(sin(1.0))
+0.7456241416655579

Nonlinear expressions in detail

Nonlinear expressions in JuMP are represented by a NonlinearExpr object.

Constructors

Nonlinear expressions can be created using the NonlinearExpr constructors:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> expr = NonlinearExpr(:sin, Any[x])
+sin(x)

or via operator overloading:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> expr = sin(x)
+sin(x)

Supported arguments

Nonlinear expressions can contain a mix of numbers, AffExpr, QuadExpr, and other NonlinearExpr:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> aff = x + 1;
+
+julia> quad = x^2 + x;
+
+julia> expr = cos(x) * sin(quad) + aff
+(cos(x) * sin(x² + x)) + (x + 1)

Supported operators

The list of supported operators may vary between solvers. Given an optimizer, query the list of supported operators using MOI.ListOfSupportedNonlinearOperators:

julia> import Ipopt
+
+julia> import MathOptInterface as MOI
+
+julia> MOI.get(Ipopt.Optimizer(), MOI.ListOfSupportedNonlinearOperators())
+85-element Vector{Symbol}:
+ :+
+ :-
+ :abs
+ :sqrt
+ :cbrt
+ :abs2
+ :inv
+ :log
+ :log10
+ :log2
+ ⋮
+ :min
+ :max
+ :&&
+ :||
+ :<=
+ :(==)
+ :>=
+ :<
+ :>

In some univariate cases, the operator is defined in SpecialFunctions.jl. To use these functions, you must explicitly import SpecialFunctions.jl

julia> import Ipopt
+
+julia> op = MOI.get(Ipopt.Optimizer(), MOI.ListOfSupportedNonlinearOperators());
+
+julia> :erfcx in op
+true
+
+julia> :dawson in op
+true
+
+julia> import SpecialFunctions
+
+julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @expression(model, SpecialFunctions.erfcx(x))
+erfcx(x)
+
+julia> @expression(model, SpecialFunctions.dawson(x))
+dawson(x)

Limitations

Some nonlinear expressions cannot be created via operator overloading. For example, to minimize the likelihood of bugs in user-code, we have not overloaded comparisons such as < and >= between JuMP objects:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> x < 1
+ERROR: Cannot evaluate `<` between a variable and a number.
+[...]

Instead, wrap the expression in the @expression macro:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> expr = @expression(model, x < 1)
+x < 1

For technical reasons, other operators that are not overloaded include ||, &&, and ifelse.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> expr = @expression(model, ifelse(x < -1 || x >= 1, x^2, 0.0))
+ifelse((x < -1) || (x >= 1), x², 0.0)

As an alternative, use the JuMP.op_ functions, which fallback to the various comparison and logical operators:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> expr = op_ifelse(
+           op_or(op_strictly_less_than(x, -1), op_greater_than_or_equal_to(x, 1)),
+           x^2,
+           0.0,
+       )
+ifelse((x < -1) || (x >= 1), x², 0.0)

The available functions are:

JuMP functionJulia function
op_ifelseifelse
op_and&&
op_or||
op_greater_than_or_equal_to>=
op_less_than_or_equal_to<=
op_equal_to==
op_strictly_greater_than>
op_strictly_less_than<

Fields

Each NonlinearExpr has two fields.

The .head field is a Symbol that represents the operator being called:

julia> expr.head
+:sin

The .args field is a Vector{Any} containing the arguments to the operator:

julia> expr.args
+1-element Vector{Any}:
+ x

User-defined operators

In addition to a standard list of univariate and multivariate operators recognized by the MOI.Nonlinear submodule, JuMP supports user-defined Julia operators.

Warning

User-defined operators must return a scalar output. For a work-around, see User-defined operators with vector outputs.

Add an operator

Add a user-defined operator using the @operator macro:

julia> using JuMP
julia> square(x) = x^2square (generic function with 1 method)
julia> f(x, y) = (x - 1)^2 + (y - 2)^2f (generic function with 1 method)
julia> model = Model();
julia> @operator(model, op_square, 1, square)NonlinearOperator(square, :op_square)
julia> @operator(model, op_f, 2, f)NonlinearOperator(f, :op_f)
julia> @variable(model, x[1:2]);
julia> @objective(model, Min, op_f(x[1], op_square(x[2])))op_f(x[1], op_square(x[2]))

The arguments to @operator are:

  1. The model to which the operator is added.
  2. A Julia symbol object which serves as the name of the user-defined operator in JuMP expressions. This name must not be the same as that of the function.
  3. The number of scalar input arguments that the function takes.
  4. A Julia method which computes the function.
Warning

User-defined operators cannot be deleted.

You can obtain a reference to the operator using the model[:key] syntax:

julia> using JuMP
julia> square(x) = x^2square (generic function with 1 method)
julia> model = Model();
julia> @operator(model, op_square, 1, square)NonlinearOperator(square, :op_square)
julia> op_square_2 = model[:op_square]NonlinearOperator(square, :op_square)

Add an operator without macros

The @operator macro is syntactic sugar for add_nonlinear_operator. Thus, the non-macro version of the preceding example is:

julia> using JuMP
julia> square(x) = x^2square (generic function with 1 method)
julia> f(x, y) = (x - 1)^2 + (y - 2)^2f (generic function with 1 method)
julia> model = Model();
julia> op_square = add_nonlinear_operator(model, 1, square; name = :op_square)NonlinearOperator(square, :op_square)
julia> model[:op_square] = op_squareNonlinearOperator(square, :op_square)
julia> op_f = add_nonlinear_operator(model, 2, f; name = :op_f)NonlinearOperator(f, :op_f)
julia> model[:op_f] = op_fNonlinearOperator(f, :op_f)
julia> @variable(model, x[1:2]);
julia> @objective(model, Min, op_f(x[1], op_square(x[2])))op_f(x[1], op_square(x[2]))

Operators with the same name as an existing function

A common error encountered is the following:

julia> using JuMP
+
+julia> model = Model();
+
+julia> f(x) = x^2
+f (generic function with 1 method)
+
+julia> @operator(model, f, 1, f)
+ERROR: Unable to add the nonlinear operator `:f` with the same name as
+an existing function.
+[...]

This error occurs because @operator(model, f, 1, f) is equivalent to:

julia> f = add_nonlinear_operator(model, 1, f; name = :f)

but f already exists as a Julia function.

If you evaluate the function without adding it as an operator, JuMP will trace the function using operator overloading:

julia> @variable(model, x);
+
+julia> f(x)
+x²

To force JuMP to treat f as a user-defined operator and not trace it, add the operator using add_nonlinear_operator and define a new method which manually creates a NonlinearExpr:

julia> _ = add_nonlinear_operator(model, 1, f; name = :f)
+NonlinearOperator(f, :f)
+
+julia> f(x::AbstractJuMPScalar) = NonlinearExpr(:f, Any[x])
+f (generic function with 2 methods)
+
+julia> @expression(model, log(f(x)))
+log(f(x))

Gradients and Hessians

By default, JuMP will use automatic differentiation to compute the gradient and Hessian of user-defined operators. If your function is not amenable to automatic differentiation, or you can compute analytic derivatives, you may pass additional arguments to @operator to compute the first- and second-derivatives.

Univariate functions

For univariate functions, a gradient function ∇f returns a number that represents the first-order derivative. You may, in addition, pass a third function which returns a number representing the second-order derivative:

julia> using JuMP
julia> f(x) = x^2f (generic function with 1 method)
julia> ∇f(x) = 2x∇f (generic function with 1 method)
julia> ∇²f(x) = 2∇²f (generic function with 1 method)
julia> model = Model();
julia> @operator(model, op_f, 1, f, ∇f, ∇²f) # Providing ∇²f is optionalNonlinearOperator(f, :op_f)
julia> @variable(model, x)x
julia> @objective(model, Min, op_f(x))op_f(x)

Multivariate functions

For multivariate functions, the gradient function ∇f must take an AbstractVector as the first argument that is filled in-place. The Hessian function, ∇²f, must take an AbstractMatrix as the first argument, the lower-triangular of which is filled in-place:

julia> using JuMP
julia> f(x...) = (1 - x[1])^2 + 100 * (x[2] - x[1]^2)^2f (generic function with 1 method)
julia> function ∇f(g::AbstractVector{T}, x::T...) where {T} + g[1] = 400 * x[1]^3 - 400 * x[1] * x[2] + 2 * x[1] - 2 + g[2] = 200 * (x[2] - x[1]^2) + return + end∇f (generic function with 1 method)
julia> function ∇²f(H::AbstractMatrix{T}, x::T...) where {T} + H[1, 1] = 1200 * x[1]^2 - 400 * x[2] + 2 + # H[1, 2] = -400 * x[1] <-- Not needed. Fill the lower-triangular only. + H[2, 1] = -400 * x[1] + H[2, 2] = 200.0 + return + end∇²f (generic function with 1 method)
julia> model = Model();
julia> @operator(model, rosenbrock, 2, f, ∇f, ∇²f) # Providing ∇²f is optionalNonlinearOperator(f, :rosenbrock)
julia> @variable(model, x[1:2])2-element Vector{VariableRef}: + x[1] + x[2]
julia> @objective(model, Min, rosenbrock(x[1], x[2]))rosenbrock(x[1], x[2])

You may assume the Hessian matrix H is initialized with zeros, and because H is symmetric, you need only to fill in the non-zero lower-triangular terms. The matrix type passed in as H depends on the automatic differentiation system, so make sure the first argument to the Hessian function supports an AbstractMatrix (it may be something other than Matrix{Float64}). Moreover, you may assume only that H supports size(H) and setindex!. Finally, the matrix is treated as dense, so the performance will be poor on functions with high-dimensional input.

User-defined operators with vector inputs

User-defined operators which take vectors as input arguments (for example, f(x::Vector)) are not supported. Instead, use Julia's splatting syntax to create a function with scalar arguments. For example, instead of:

f(x::Vector) = sum(x[i]^i for i in 1:length(x))

define:

f(x...) = sum(x[i]^i for i in 1:length(x))

Another approach is to define the splatted function as an anonymous function:

julia> using JuMP
julia> model = Model();
julia> @variable(model, x[1:5])5-element Vector{VariableRef}: + x[1] + x[2] + x[3] + x[4] + x[5]
julia> f(x::Vector) = sum(x[i]^i for i in 1:length(x))f (generic function with 1 method)
julia> @operator(model, op_f, 5, (x...) -> f(collect(x)))NonlinearOperator(#6, :op_f)
julia> @objective(model, Min, op_f(x...))op_f(x[1], x[2], x[3], x[4], x[5])

Automatic differentiation

JuMP does not support black-box optimization, so all user-defined operators must provide derivatives in some form. Fortunately, JuMP supports automatic differentiation of user-defined operators.

Info

Automatic differentiation is not finite differencing. JuMP's automatically computed derivatives are not subject to approximation error.

JuMP uses ForwardDiff.jl to perform automatic differentiation of user-defined operators; see the ForwardDiff.jl documentation for a description of how to write a function suitable for automatic differentiation.

Common mistakes when writing a user-defined operator

Warning

Get an error like No method matching Float64(::ForwardDiff.Dual)? Read this section, and see the guidelines at ForwardDiff.jl.

The most common error is that your user-defined operator is not generic with respect to the number type, that is, don't assume that the input to the function is Float64.

f(x::Float64) = 2 * x  # This will not work.
+f(x::Real)    = 2 * x  # This is good.
+f(x)          = 2 * x  # This is also good.

Another reason you may encounter this error is if you create arrays inside your function which are Float64.

function bad_f(x...)
+    y = zeros(length(x))  # This constructs an array of `Float64`!
+    for i in 1:length(x)
+        y[i] = x[i]^i
+    end
+    return sum(y)
+end
+
+function good_f(x::T...) where {T<:Real}
+    y = zeros(T, length(x))  # Construct an array of type `T` instead!
+    for i in 1:length(x)
+        y[i] = x[i]^i
+    end
+    return sum(y)
+end
diff --git a/previews/PR3536/manual/objective/index.html b/previews/PR3536/manual/objective/index.html new file mode 100644 index 00000000000..09acacc458a --- /dev/null +++ b/previews/PR3536/manual/objective/index.html @@ -0,0 +1,168 @@ + +Objectives · JuMP

Objectives

This page describes macros and functions related to linear and quadratic objective functions only, unless otherwise indicated. For nonlinear objective functions, see Nonlinear Modeling.

Set a linear objective

Use the @objective macro to set a linear objective function.

Use Min to create a minimization objective:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Min, 2x + 1)
+2 x + 1

Use Max to create a maximization objective:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Max, 2x + 1)
+2 x + 1

Set a quadratic objective

Use the @objective macro to set a quadratic objective function.

Use ^2 to have a variable squared:

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Min, x^2 + 2x + 1)
+x² + 2 x + 1

You can also have bilinear terms between variables:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, y)
+y
+
+julia> @objective(model, Max, x * y + x + y)
+x*y + x + y

Set a nonlinear objective

Use the @objective macro to set a nonlinear objective function:

julia> model = Model();
+
+julia> @variable(model, x <= 1);
+
+julia> @objective(model, Max, log(x))
+log(x)

Query the objective function

Use objective_function to return the current objective function.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Min, 2x + 1)
+2 x + 1
+
+julia> objective_function(model)
+2 x + 1

Evaluate the objective function at a point

Use value to evaluate an objective function at a point specifying values for variables.

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @objective(model, Min, 2x[1]^2 + x[1] + 0.5*x[2])
+2 x[1]² + x[1] + 0.5 x[2]
+
+julia> f = objective_function(model)
+2 x[1]² + x[1] + 0.5 x[2]
+
+julia> point = Dict(x[1] => 2.0, x[2] => 1.0);
+
+julia> value(z -> point[z], f)
+10.5

Query the objective sense

Use objective_sense to return the current objective sense.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Min, 2x + 1)
+2 x + 1
+
+julia> objective_sense(model)
+MIN_SENSE::OptimizationSense = 0

Modify an objective

To modify an objective, call @objective with the new objective function.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Min, 2x)
+2 x
+
+julia> @objective(model, Max, -2x)
+-2 x

Alternatively, use set_objective_function.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Min, 2x)
+2 x
+
+julia> new_objective = @expression(model, -2 * x)
+-2 x
+
+julia> set_objective_function(model, new_objective)

Modify an objective coefficient

Use set_objective_coefficient to modify an objective coefficient.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Min, 2x)
+2 x
+
+julia> set_objective_coefficient(model, x, 3)
+
+julia> objective_function(model)
+3 x
Info

There is no way to modify the coefficient of a quadratic term. Set a new objective instead.

Modify the objective sense

Use set_objective_sense to modify the objective sense.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Min, 2x)
+2 x
+
+julia> objective_sense(model)
+MIN_SENSE::OptimizationSense = 0
+
+julia> set_objective_sense(model, MAX_SENSE);
+
+julia> objective_sense(model)
+MAX_SENSE::OptimizationSense = 1

Alternatively, call @objective and pass the existing objective function.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @objective(model, Min, 2x)
+2 x
+
+julia> @objective(model, Max, objective_function(model))
+2 x

Set a vector-valued objective

Define a multi-objective optimization problem by passing a vector of objectives:

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @objective(model, Min, [1 + x[1], 2 * x[2]])
+2-element Vector{AffExpr}:
+ x[1] + 1
+ 2 x[2]
+
+julia> f = objective_function(model)
+2-element Vector{AffExpr}:
+ x[1] + 1
+ 2 x[2]
Tip

The Multi-objective knapsack tutorial provides an example of solving a multi-objective integer program.

In most cases, multi-objective optimization solvers will return multiple solutions, corresponding to points on the Pareto frontier. See Multiple solutions for information on how to query and work with multiple solutions.

Note that you must set a single objective sense, that is, you cannot have both minimization and maximization objectives. Work around this limitation by choosing Min and negating any objectives you want to maximize:

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @expression(model, obj1, 1 + x[1])
+x[1] + 1
+
+julia> @expression(model, obj2, 2 * x[1])
+2 x[1]
+
+julia> @objective(model, Min, [obj1, -obj2])
+2-element Vector{AffExpr}:
+ x[1] + 1
+ -2 x[1]

Defining your objectives as expressions allows flexibility in how you can solve variations of the same problem, with some objectives removed and constrained to be no worse that a fixed value.

julia> model = Model();
+
+julia> @variable(model, x[1:2]);
+
+julia> @expression(model, obj1, 1 + x[1])
+x[1] + 1
+
+julia> @expression(model, obj2, 2 * x[1])
+2 x[1]
+
+julia> @expression(model, obj3, x[1] + x[2])
+x[1] + x[2]
+
+julia> @objective(model, Min, [obj1, obj2, obj3])  # Three-objective problem
+3-element Vector{AffExpr}:
+ x[1] + 1
+ 2 x[1]
+ x[1] + x[2]
+
+julia> # optimize!(model), look at the solution, talk to stakeholders, then
+       # decide you want to solve a new problem where the third objective is
+       # removed and constrained to be better than 2.0.
+       nothing
+
+julia> @objective(model, Min, [obj1, obj2])   # Two-objective problem
+2-element Vector{AffExpr}:
+ x[1] + 1
+ 2 x[1]
+
+julia> @constraint(model, obj3 <= 2.0)
+x[1] + x[2] ≤ 2
diff --git a/previews/PR3536/manual/solutions/index.html b/previews/PR3536/manual/solutions/index.html new file mode 100644 index 00000000000..d506b0fa8f4 --- /dev/null +++ b/previews/PR3536/manual/solutions/index.html @@ -0,0 +1,374 @@ + +Solutions · JuMP

Solutions

This section of the manual describes how to access a solved solution to a problem. It uses the following model as an example:

julia> begin
+           model = Model(HiGHS.Optimizer)
+           set_silent(model)
+           @variable(model, x >= 0)
+           @variable(model, y[[:a, :b]] <= 1)
+           @objective(model, Max, -12x - 20y[:a])
+           @expression(model, my_expr, 6x + 8y[:a])
+           @constraint(model, my_expr >= 100)
+           @constraint(model, c1, 7x + 12y[:a] >= 120)
+           optimize!(model)
+           print(model)
+       end
+Max -12 x - 20 y[a]
+Subject to
+ 6 x + 8 y[a] ≥ 100
+ c1 : 7 x + 12 y[a] ≥ 120
+ x ≥ 0
+ y[a] ≤ 1
+ y[b] ≤ 1

Solutions summary

solution_summary can be used for checking the summary of the optimization solutions.

julia> solution_summary(model)
+* Solver : HiGHS
+
+* Status
+  Result count       : 1
+  Termination status : OPTIMAL
+  Message from the solver:
+  "kHighsModelStatusOptimal"
+
+* Candidate solution (result #1)
+  Primal status      : FEASIBLE_POINT
+  Dual status        : FEASIBLE_POINT
+  Objective value    : -2.05143e+02
+  Objective bound    : -0.00000e+00
+  Relative gap       : Inf
+  Dual objective value : -2.05143e+02
+
+* Work counters
+  Solve time (sec)   : 6.70987e-04
+  Simplex iterations : 2
+  Barrier iterations : 0
+  Node count         : -1
+
+julia> solution_summary(model; verbose = true)
+* Solver : HiGHS
+
+* Status
+  Result count       : 1
+  Termination status : OPTIMAL
+  Message from the solver:
+  "kHighsModelStatusOptimal"
+
+* Candidate solution (result #1)
+  Primal status      : FEASIBLE_POINT
+  Dual status        : FEASIBLE_POINT
+  Objective value    : -2.05143e+02
+  Objective bound    : -0.00000e+00
+  Relative gap       : Inf
+  Dual objective value : -2.05143e+02
+  Primal solution :
+    x : 1.54286e+01
+    y[a] : 1.00000e+00
+    y[b] : 1.00000e+00
+  Dual solution :
+    c1 : 1.71429e+00
+
+* Work counters
+  Solve time (sec)   : 6.70987e-04
+  Simplex iterations : 2
+  Barrier iterations : 0
+  Node count         : -1

Why did the solver stop?

Usetermination_status to understand why the solver stopped.

julia> termination_status(model)
+OPTIMAL::TerminationStatusCode = 1

The MOI.TerminationStatusCode enum describes the full list of statuses that could be returned.

Common return values include OPTIMAL, LOCALLY_SOLVED, INFEASIBLE, DUAL_INFEASIBLE, and TIME_LIMIT.

Info

A return status of OPTIMAL means the solver found (and proved) a globally optimal solution. A return status of LOCALLY_SOLVED means the solver found a locally optimal solution (which may also be globally optimal, but it could not prove so).

Warning

A return status of DUAL_INFEASIBLE does not guarantee that the primal is unbounded. When the dual is infeasible, the primal is unbounded if there exists a feasible primal solution.

Use raw_status to get a solver-specific string explaining why the optimization stopped:

julia> raw_status(model)
+"kHighsModelStatusOptimal"

Primal solutions

Primal solution status

Use primal_status to return an MOI.ResultStatusCode enum describing the status of the primal solution.

julia> primal_status(model)
+FEASIBLE_POINT::ResultStatusCode = 1

Other common returns are NO_SOLUTION, and INFEASIBILITY_CERTIFICATE. The first means that the solver doesn't have a solution to return, and the second means that the primal solution is a certificate of dual infeasibility (a primal unbounded ray).

You can also use has_values, which returns true if there is a solution that can be queried, and false otherwise.

julia> has_values(model)
+true

Objective values

The objective value of a solved problem can be obtained via objective_value. The best known bound on the optimal objective value can be obtained via objective_bound. If the solver supports it, the value of the dual objective can be obtained via dual_objective_value.

julia> objective_value(model)
+-205.14285714285714
+
+julia> objective_bound(model)  # HiGHS only implements objective bound for MIPs
+-0.0
+
+julia> dual_objective_value(model)
+-205.1428571428571

Primal solution values

If the solver has a primal solution to return, use value to access it:

julia> value(x)
+15.428571428571429

Broadcast value over containers:

julia> value.(y)
+1-dimensional DenseAxisArray{Float64,1,...} with index sets:
+    Dimension 1, [:a, :b]
+And data, a 2-element Vector{Float64}:
+ 1.0
+ 1.0

value also works on expressions:

julia> value(my_expr)
+100.57142857142857

and constraints:

julia> value(c1)
+120.0
Info

Calling value on a constraint returns the constraint function evaluated at the solution.

Dual solutions

Dual solution status

Use dual_status to return an MOI.ResultStatusCode enum describing the status of the dual solution.

julia> dual_status(model)
+FEASIBLE_POINT::ResultStatusCode = 1

Other common returns are NO_SOLUTION, and INFEASIBILITY_CERTIFICATE. The first means that the solver doesn't have a solution to return, and the second means that the dual solution is a certificate of primal infeasibility (a dual unbounded ray).

You can also use has_duals, which returns true if there is a solution that can be queried, and false otherwise.

julia> has_duals(model)
+true

Dual solution values

If the solver has a dual solution to return, use dual to access it:

julia> dual(c1)
+1.7142857142857142

Query the duals of variable bounds using LowerBoundRef, UpperBoundRef, and FixRef:

julia> dual(LowerBoundRef(x))
+0.0
+
+julia> dual.(UpperBoundRef.(y))
+1-dimensional DenseAxisArray{Float64,1,...} with index sets:
+    Dimension 1, [:a, :b]
+And data, a 2-element Vector{Float64}:
+ -0.5714285714285694
+  0.0
Warning

JuMP's definition of duality is independent of the objective sense. That is, the sign of feasible duals associated with a constraint depends on the direction of the constraint and not whether the problem is maximization or minimization. This is a different convention from linear programming duality in some common textbooks. If you have a linear program, and you want the textbook definition, you probably want to use shadow_price and reduced_cost instead.

julia> shadow_price(c1)
+1.7142857142857142
+
+julia> reduced_cost(x)
+-0.0
+
+julia> reduced_cost.(y)
+1-dimensional DenseAxisArray{Float64,1,...} with index sets:
+    Dimension 1, [:a, :b]
+And data, a 2-element Vector{Float64}:
+  0.5714285714285694
+ -0.0

The recommended workflow for solving a model and querying the solution is something like the following:

julia> begin
+           if termination_status(model) == OPTIMAL
+               println("Solution is optimal")
+           elseif termination_status(model) == TIME_LIMIT && has_values(model)
+               println("Solution is suboptimal due to a time limit, but a primal solution is available")
+           else
+               error("The model was not solved correctly.")
+           end
+           println("  objective value = ", objective_value(model))
+           if primal_status(model) == FEASIBLE_POINT
+               println("  primal solution: x = ", value(x))
+           end
+           if dual_status(model) == FEASIBLE_POINT
+               println("  dual solution: c1 = ", dual(c1))
+           end
+       end
+Solution is optimal
+  objective value = -205.14285714285714
+  primal solution: x = 15.428571428571429
+  dual solution: c1 = 1.7142857142857142

OptimizeNotCalled errors

Due to differences in how solvers cache solutions internally, modifying a model after calling optimize! will reset the model into the MOI.OPTIMIZE_NOT_CALLED state. If you then attempt to query solution information, an OptimizeNotCalled error will be thrown.

If you are iteratively querying solution information and modifying a model, query all the results first, then modify the problem.

For example, instead of:

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x >= 0);
+
+julia> optimize!(model)
+
+julia> termination_status(model)
+OPTIMAL::TerminationStatusCode = 1
+
+julia> set_upper_bound(x, 1)
+
+julia> x_val = value(x)
+┌ Warning: The model has been modified since the last call to `optimize!` (or `optimize!` has not been called yet). If you are iteratively querying solution information and modifying a model, query all the results first, then modify the model.
+└ @ JuMP ~/work/JuMP.jl/JuMP.jl/src/optimizer_interface.jl:712
+ERROR: OptimizeNotCalled()
+Stacktrace:
+[...]
+
+julia> termination_status(model)
+OPTIMIZE_NOT_CALLED::TerminationStatusCode = 0

do

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x >= 0);
+
+julia> optimize!(model);
+
+julia> x_val = value(x)
+0.0
+
+julia> termination_status(model)
+OPTIMAL::TerminationStatusCode = 1
+
+julia> set_upper_bound(x, 1)
+
+julia> set_lower_bound(x, x_val)
+
+julia> termination_status(model)
+OPTIMIZE_NOT_CALLED::TerminationStatusCode = 0

If you know that your particular solver supports querying solution information after modifications, you can use direct_model to bypass the MOI.OPTIMIZE_NOT_CALLED state:

julia> model = direct_model(HiGHS.Optimizer());
+
+julia> set_silent(model)
+
+julia> @variable(model, x >= 0);
+
+julia> optimize!(model)
+
+julia> termination_status(model)
+OPTIMAL::TerminationStatusCode = 1
+
+julia> set_upper_bound(x, 1)
+
+julia> x_val = value(x)
+0.0
+
+julia> set_lower_bound(x, x_val)
+
+julia> termination_status(model)
+OPTIMAL::TerminationStatusCode = 1
Warning

Be careful doing this. If your particular solver does not support querying solution information after modification, it may silently return incorrect solutions or throw an error.

Accessing attributes

MathOptInterface defines many model attributes that can be queried. Some attributes can be directly accessed by getter functions. These include:

Sensitivity analysis for LP

Given an LP problem and an optimal solution corresponding to a basis, we can question how much an objective coefficient or standard form right-hand side coefficient (c.f., normalized_rhs) can change without violating primal or dual feasibility of the basic solution.

Note that not all solvers compute the basis, and for sensitivity analysis, the solver interface must implement MOI.ConstraintBasisStatus.

Tip

Read the Sensitivity analysis of a linear program for more information on sensitivity analysis.

To give a simple example, we could analyze the sensitivity of the optimal solution to the following (non-degenerate) LP problem:

julia> begin
+           model = Model(HiGHS.Optimizer)
+           set_silent(model)
+           @variable(model, x[1:2])
+           set_lower_bound(x[2], -0.5)
+           set_upper_bound(x[2], 0.5)
+           @constraint(model, c1, x[1] + x[2] <= 1)
+           @constraint(model, c2, x[1] - x[2] <= 1)
+           @objective(model, Max, x[1])
+           print(model)
+       end
+Max x[1]
+Subject to
+ c1 : x[1] + x[2] ≤ 1
+ c2 : x[1] - x[2] ≤ 1
+ x[2] ≥ -0.5
+ x[2] ≤ 0.5

To analyze the sensitivity of the problem we could check the allowed perturbation ranges of, for example, the cost coefficients and the right-hand side coefficient of the constraint c1 as follows:

julia> optimize!(model)
+
+julia> value.(x)
+2-element Vector{Float64}:
+  1.0
+ -0.0
+
+julia> report = lp_sensitivity_report(model);
+
+julia> x1_lo, x1_hi = report[x[1]]
+(-1.0, Inf)
+
+julia> println("The objective coefficient of x[1] could decrease by $(x1_lo) or increase by $(x1_hi).")
+The objective coefficient of x[1] could decrease by -1.0 or increase by Inf.
+
+julia> x2_lo, x2_hi = report[x[2]]
+(-1.0, 1.0)
+
+julia> println("The objective coefficient of x[2] could decrease by $(x2_lo) or increase by $(x2_hi).")
+The objective coefficient of x[2] could decrease by -1.0 or increase by 1.0.
+
+julia> c_lo, c_hi = report[c1]
+(-1.0, 1.0)
+
+julia> println("The RHS of c1 could decrease by $(c_lo) or increase by $(c_hi).")
+The RHS of c1 could decrease by -1.0 or increase by 1.0.

The range associated with a variable is the range of the allowed perturbation of the corresponding objective coefficient. Note that the current primal solution remains optimal within this range; however the corresponding dual solution might change since a cost coefficient is perturbed. Similarly, the range associated with a constraint is the range of the allowed perturbation of the corresponding right-hand side coefficient. In this range the current dual solution remains optimal, but the optimal primal solution might change.

If the problem is degenerate, there are multiple optimal bases and hence these ranges might not be as intuitive and seem too narrow, for example, a larger cost coefficient perturbation might not invalidate the optimality of the current primal solution. Moreover, if a problem is degenerate, due to finite precision, it can happen that, for example, a perturbation seems to invalidate a basis even though it doesn't (again providing too narrow ranges). To prevent this, increase the atol keyword argument to lp_sensitivity_report. Note that this might make the ranges too wide for numerically challenging instances. Thus, do not blindly trust these ranges, especially not for highly degenerate or numerically unstable instances.

Conflicts

When the model you input is infeasible, some solvers can help you find the cause of this infeasibility by offering a conflict, that is, a subset of the constraints that create this infeasibility. Depending on the solver, this can also be called an IIS (irreducible inconsistent subsystem).

If supported by the solver, use compute_conflict! to trigger the computation of a conflict. Once this process is finished, query the MOI.ConflictStatus attribute to check if a conflict was found.

If found, copy the IIS to a new model using copy_conflict, which you can then print or write to a file for easier debugging:

julia> using JuMP
+
+julia> import Gurobi
+
+julia> model = Model(Gurobi.Optimizer)
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: EMPTY_OPTIMIZER
+Solver name: Gurobi
+
+julia> set_silent(model)
+
+julia> @variable(model, x >= 0)
+x
+
+julia> @constraint(model, c1, x >= 2)
+c1 : x ≥ 2.0
+
+julia> @constraint(model, c2, x <= 1)
+c2 : x ≤ 1.0
+
+julia> optimize!(model)
+
+julia> compute_conflict!(model)
+
+julia> if get_attribute(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND
+           iis_model, _ = copy_conflict(model)
+           print(iis_model)
+       end
+Feasibility
+Subject to
+ c1 : x ≥ 2.0
+ c2 : x ≤ 1.0

If you need more control over the list of constraints that appear in the conflict, iterate over the list of constraints and query the MOI.ConstraintConflictStatus attribute:

julia> list_of_conflicting_constraints = ConstraintRef[]
+ConstraintRef[]
+
+julia> for (F, S) in list_of_constraint_types(model)
+           for con in all_constraints(model, F, S)
+               if get_attribute(con, MOI.ConstraintConflictStatus()) == MOI.IN_CONFLICT
+                   push!(list_of_conflicting_constraints, con)
+               end
+           end
+       end
+
+julia> list_of_conflicting_constraints
+2-element Vector{ConstraintRef}:
+ c1 : x ≥ 2.0
+ c2 : x ≤ 1.0

Multiple solutions

Some solvers support returning multiple solutions. You can check how many solutions are available to query using result_count.

Functions for querying the solutions, for example, primal_status, dual_status, value, dual, and solution_summary all take an additional keyword argument result which can be used to specify which result to return.

Warning

Even if termination_status is OPTIMAL, some of the returned solutions may be suboptimal. However, if the solver found at least one optimal solution, then result = 1 will always return an optimal solution. Use objective_value to assess the quality of the remaining solutions.

julia> using JuMP
+
+julia> import MultiObjectiveAlgorithms as MOA
+
+julia> import HiGHS
+
+julia> model = Model(() -> MOA.Optimizer(HiGHS.Optimizer));
+
+julia> set_attribute(model, MOA.Algorithm(), MOA.Dichotomy())
+
+julia> set_silent(model)
+
+julia> @variable(model, x1 >= 0)
+x1
+
+julia> @variable(model, 0 <= x2 <= 3)
+x2
+
+julia> @objective(model, Min, [3x1 + x2, -x1 - 2x2])
+2-element Vector{AffExpr}:
+ 3 x1 + x2
+ -x1 - 2 x2
+
+julia> @constraint(model, 3x1 - x2 <= 6)
+3 x1 - x2 ≤ 6
+
+julia> optimize!(model)
+
+julia> solution_summary(model; result = 1)
+* Solver : MOA[algorithm=MultiObjectiveAlgorithms.Dichotomy, optimizer=HiGHS]
+
+* Status
+  Result count       : 3
+  Termination status : OPTIMAL
+  Message from the solver:
+  "Solve complete. Found 3 solution(s)"
+
+* Candidate solution (result #1)
+  Primal status      : FEASIBLE_POINT
+  Dual status        : NO_SOLUTION
+  Objective value    : [0.00000e+00,0.00000e+00]
+  Objective bound    : [0.00000e+00,-9.00000e+00]
+  Relative gap       : Inf
+  Dual objective value : -9.00000e+00
+
+* Work counters
+  Solve time (sec)   : 1.82720e-03
+  Simplex iterations : 1
+  Barrier iterations : 0
+  Node count         : -1
+
+julia> for i in 1:result_count(model)
+           println("Solution $i")
+           println("   x = ", value.([x1, x2]; result = i))
+           println(" obj = ", objective_value(model; result = i))
+       end
+Solution 1
+   x = [0.0, 0.0]
+ obj = [0.0, 0.0]
+Solution 2
+   x = [0.0, 3.0]
+ obj = [3.0, -6.0]
+Solution 3
+   x = [3.0, 3.0]
+ obj = [12.0, -9.0]
Tip

The Multi-objective knapsack tutorial provides more examples of querying multiple solutions.

Checking feasibility of solutions

To check the feasibility of a primal solution, use primal_feasibility_report, which takes a model, a dictionary mapping each variable to a primal solution value (defaults to the last solved solution), and a tolerance atol (defaults to 0.0).

The function returns a dictionary which maps the infeasible constraint references to the distance between the primal value of the constraint and the nearest point in the corresponding set. A point is classed as infeasible if the distance is greater than the supplied tolerance atol.

julia> model = Model(HiGHS.Optimizer);
+
+julia> set_silent(model)
+
+julia> @variable(model, x >= 1, Int);
+
+julia> @variable(model, y);
+
+julia> @constraint(model, c1, x + y <= 1.95);
+
+julia> point = Dict(x => 1.9, y => 0.06);
+
+julia> primal_feasibility_report(model, point)
+Dict{Any, Float64} with 2 entries:
+  x integer         => 0.1
+  c1 : x + y ≤ 1.95 => 0.01
+
+julia> primal_feasibility_report(model, point; atol = 0.02)
+Dict{Any, Float64} with 1 entry:
+  x integer => 0.1

If the point is feasible, an empty dictionary is returned:

julia> primal_feasibility_report(model, Dict(x => 1.0, y => 0.0))
+Dict{Any, Float64}()

To use the primal solution from a solve, omit the point argument:

julia> optimize!(model)
+
+julia> primal_feasibility_report(model; atol = 0.0)
+Dict{Any, Float64}()

Calling primal_feasibility_report without the point argument is useful when primal_status is FEASIBLE_POINT or NEARLY_FEASIBLE_POINT, and you want to assess the solution quality.

Warning

To apply primal_feasibility_report to infeasible models, you must also provide a candidate point (solvers generally do not provide one). To diagnose the source of infeasibility, see Conflicts.

Pass skip_mising = true to skip constraints which contain variables that are not in point:

julia> primal_feasibility_report(model, Dict(x => 2.1); skip_missing = true)
+Dict{Any, Float64} with 1 entry:
+  x integer => 0.1

You can also use the functional form, where the first argument is a function that maps variables to their primal values:

julia> optimize!(model)
+
+julia> primal_feasibility_report(v -> value(v), model)
+Dict{Any, Float64}()
diff --git a/previews/PR3536/manual/variables/index.html b/previews/PR3536/manual/variables/index.html new file mode 100644 index 00000000000..c9cba155c6a --- /dev/null +++ b/previews/PR3536/manual/variables/index.html @@ -0,0 +1,637 @@ + +Variables · JuMP

Variables

The term variable in mathematical optimization has many meanings. For example, optimization variables (also called decision variables) are the unknowns $x$ that we are solving for in the problem:

\[\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}\]

To complicate things, Julia uses variable to mean a binding between a name and a value. For example, in the statement:

julia> x = 1
+1

x is a variable that stores the value 1.

JuMP uses variable in a third way, to mean an instance of the VariableRef struct. JuMP variables are the link between Julia and the optimization variables inside a JuMP model.

This page explains how to create and manage JuMP variables in a variety of contexts.

Create a variable

Create variables using the @variable macro:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> typeof(x)
+VariableRef (alias for GenericVariableRef{Float64})
+
+julia> num_variables(model)
+1

Here x is a Julia variable that is bound to a VariableRef object, and we have added 1 decision variable to our model.

To make the binding more explicit, we could have written:

julia> model = Model();
+
+julia> x = @variable(model, x)
+x

but there is no need to in general; the macro does it for us.

When creating a variable, you can also specify variable bounds:

julia> model = Model();
+
+julia> @variable(model, x_free)
+x_free
+
+julia> @variable(model, x_lower >= 0)
+x_lower
+
+julia> @variable(model, x_upper <= 1)
+x_upper
+
+julia> @variable(model, 2 <= x_interval <= 3)
+x_interval
+
+julia> @variable(model, x_fixed == 4)
+x_fixed
+
+julia> print(model)
+Feasibility
+Subject to
+ x_fixed = 4
+ x_lower ≥ 0
+ x_interval ≥ 2
+ x_upper ≤ 1
+ x_interval ≤ 3
Warning

When creating a variable with a single lower- or upper-bound, and the value of the bound is not a numeric literal (for example, 1 or 1.0), the name of the variable must appear on the left-hand side. Putting the name on the right-hand side is an error. For example, to create a variable x:

a = 1
+@variable(model, x >= 1)      # ✓ Okay
+@variable(model, 1.0 <= x)    # ✓ Okay
+@variable(model, x >= a)      # ✓ Okay
+@variable(model, a <= x)      # × Not okay
+@variable(model, x >= 1 / 2)  # ✓ Okay
+@variable(model, 1 / 2 <= x)  # × Not okay

Containers of variables

The @variable macro also supports creating collections of JuMP variables. We'll cover some brief syntax here; read the Variable containers section for more details.

You can create arrays of JuMP variables:

julia> model = Model();
+
+julia> @variable(model, x[1:2, 1:2])
+2×2 Matrix{VariableRef}:
+ x[1,1]  x[1,2]
+ x[2,1]  x[2,2]
+
+julia> x[1, 2]
+x[1,2]

Index sets can be named, and bounds can depend on those names:

julia> model = Model();
+
+julia> @variable(model, sqrt(i) <= x[i = 1:3] <= i^2)
+3-element Vector{VariableRef}:
+ x[1]
+ x[2]
+ x[3]
+
+julia> x[2]
+x[2]

Sets can be any Julia type that supports iteration:

julia> model = Model();
+
+julia> @variable(model, x[i = 2:3, j = 1:2:3, ["red", "blue"]] >= 0)
+3-dimensional DenseAxisArray{VariableRef,3,...} with index sets:
+    Dimension 1, 2:3
+    Dimension 2, 1:2:3
+    Dimension 3, ["red", "blue"]
+And data, a 2×2×2 Array{VariableRef, 3}:
+[:, :, "red"] =
+ x[2,1,red]  x[2,3,red]
+ x[3,1,red]  x[3,3,red]
+
+[:, :, "blue"] =
+ x[2,1,blue]  x[2,3,blue]
+ x[3,1,blue]  x[3,3,blue]
+
+julia> x[2, 1, "red"]
+x[2,1,red]

Sets can depend upon previous indices:

julia> model = Model();
+
+julia> @variable(model, u[i = 1:2, j = i:3])
+JuMP.Containers.SparseAxisArray{VariableRef, 2, Tuple{Int64, Int64}} with 5 entries:
+  [1, 1]  =  u[1,1]
+  [1, 2]  =  u[1,2]
+  [1, 3]  =  u[1,3]
+  [2, 2]  =  u[2,2]
+  [2, 3]  =  u[2,3]

and we can filter elements in the sets using the ; syntax:

julia> model = Model();
+
+julia> @variable(model, v[i = 1:9; mod(i, 3) == 0])
+JuMP.Containers.SparseAxisArray{VariableRef, 1, Tuple{Int64}} with 3 entries:
+  [3]  =  v[3]
+  [6]  =  v[6]
+  [9]  =  v[9]

Registered variables

When you create variables, JuMP registers them inside the model using their corresponding symbol. Get a registered name using model[:key]:

julia> model = Model()
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+
+julia> @variable(model, x)
+x
+
+julia> model
+A JuMP Model
+Feasibility problem with:
+Variable: 1
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+Names registered in the model: x
+
+julia> model[:x] === x
+true

Registered names are most useful when you start to write larger models and want to break up the model construction into functions:

julia> function set_objective(model::Model)
+           @objective(model, Min, 2 * model[:my_x] + 1)
+           return
+       end
+set_objective (generic function with 1 method)
+
+julia> model = Model();
+
+julia> @variable(model, my_x);
+
+julia> set_objective(model)
+
+julia> print(model)
+Min 2 my_x + 1
+Subject to

Anonymous variables

To reduce the likelihood of accidental bugs, and because JuMP registers variables inside a model, creating two variables with the same name is an error:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> @variable(model, x)
+ERROR: An object of name x is already attached to this model. If this
+    is intended, consider using the anonymous construction syntax, e.g.,
+    `x = @variable(model, [1:N], ...)` where the name of the object does
+    not appear inside the macro.
+
+    Alternatively, use `unregister(model, :x)` to first unregister
+    the existing name from the model. Note that this will not delete the
+    object; it will just remove the reference at `model[:x]`.
+[...]

A common reason for encountering this error is adding variables in a loop.

As a work-around, JuMP provides anonymous variables. Create a scalar valued anonymous variable by omitting the name argument:

julia> model = Model();
+
+julia> x = @variable(model)
+_[1]

Anonymous variables get printed as an underscore followed by a unique index of the variable.

Warning

The index of the variable may not correspond to the column of the variable in the solver.

Create a container of anonymous JuMP variables by dropping the name in front of the [:

julia> model = Model();
+
+julia> y = @variable(model, [1:2])
+2-element Vector{VariableRef}:
+ _[1]
+ _[2]

The <= and >= short-hand cannot be used to set bounds on scalar-valued anonymous JuMP variables. Instead, use the lower_bound and upper_bound keywords:

julia> model = Model();
+
+julia> x_lower = @variable(model, lower_bound = 1.0)
+_[1]
+
+julia> x_upper = @variable(model, upper_bound = 2.0)
+_[2]
+
+julia> x_interval = @variable(model, lower_bound = 3.0, upper_bound = 4.0)
+_[3]

Variable names

In addition to the symbol that variables are registered with, JuMP variables have a String name that is used for printing and writing to file formats.

Get and set the name of a variable using name and set_name:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> name(x)
+"x"
+
+julia> set_name(x, "my_x_name")
+
+julia> x
+my_x_name

Override the default choice of name using the base_name keyword:

julia> model = Model();
+
+julia> @variable(model, x[i=1:2], base_name = "my_var")
+2-element Vector{VariableRef}:
+ my_var[1]
+ my_var[2]

Note that names apply to each element of the container, not to the container of variables:

julia> name(x[1])
+"my_var[1]"
+
+julia> set_name(x[1], "my_x")
+
+julia> x
+2-element Vector{VariableRef}:
+ my_x
+ my_var[2]
Tip

For some models, setting the string name of each variable can take a non-trivial portion of the total time required to build the model. Turn off String names by passing set_string_name = false to @variable:

julia> model = Model();
+
+julia> @variable(model, x, set_string_name = false)
+_[1]

See Disable string names for more information.

Retrieve a variable by name

Retrieve a variable from a model using variable_by_name:

julia> variable_by_name(model, "my_x")
+my_x

If the name is not present, nothing will be returned:

julia> variable_by_name(model, "bad_name")

You can only look up individual variables using variable_by_name. Something like this will not work:

julia> model = Model();
+
+julia> @variable(model, [i = 1:2], base_name = "my_var")
+2-element Vector{VariableRef}:
+ my_var[1]
+ my_var[2]
+
+julia> variable_by_name(model, "my_var")

To look up a collection of variables, do not use variable_by_name. Instead, register them using the model[:key] = value syntax:

julia> model = Model();
+
+julia> model[:x] = @variable(model, [i = 1:2], base_name = "my_var")
+2-element Vector{VariableRef}:
+ my_var[1]
+ my_var[2]
+
+julia> model[:x]
+2-element Vector{VariableRef}:
+ my_var[1]
+ my_var[2]

String names, symbolic names, and bindings

It's common for new users to experience confusion relating to JuMP variables. Part of the problem is the overloaded use of "variable" in mathematical optimization, along with the difference between the name that a variable is registered under and the String name used for printing.

Here's a summary of the differences:

  • JuMP variables are created using @variable.
  • JuMP variables can be named or anonymous.
  • Named JuMP variables have the form @variable(model, x). For named variables:
    • The String name of the variable is set to "x".
    • A Julia variable x is created that binds x to the JuMP variable.
    • The name :x is registered as a key in the model with the value x.
  • Anonymous JuMP variables have the form x = @variable(model). For anonymous variables:
    • The String name of the variable is set to "". When printed, this is replaced with "_[i]" where i is the index of the variable.
    • You control the name of the Julia variable used as the binding.
    • No name is registered as a key in the model.
  • The base_name keyword can override the String name of the variable.
  • You can manually register names in the model via model[:key] = value

Here's an example that should make things clearer:

julia> model = Model();
+
+julia> x_binding = @variable(model, base_name = "x")
+x
+
+julia> model
+A JuMP Model
+Feasibility problem with:
+Variable: 1
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+
+julia> x
+ERROR: UndefVarError: `x` not defined
+
+julia> x_binding
+x
+
+julia> name(x_binding)
+"x"
+
+julia> model[:x_register] = x_binding
+x
+
+julia> model
+A JuMP Model
+Feasibility problem with:
+Variable: 1
+Model mode: AUTOMATIC
+CachingOptimizer state: NO_OPTIMIZER
+Solver name: No optimizer attached.
+Names registered in the model: x_register
+
+julia> model[:x_register]
+x
+
+julia> model[:x_register] === x_binding
+true
+
+julia> x
+ERROR: UndefVarError: `x` not defined

Create, delete, and modify variable bounds

Query whether a variable has a bound using has_lower_bound, has_upper_bound, and is_fixed:

julia> has_lower_bound(x_free)
+false
+
+julia> has_upper_bound(x_upper)
+true
+
+julia> is_fixed(x_fixed)
+true

If a variable has a particular bound, query the value of it using lower_bound, upper_bound, and fix_value:

julia> lower_bound(x_interval)
+2.0
+
+julia> upper_bound(x_interval)
+3.0
+
+julia> fix_value(x_fixed)
+4.0

Querying the value of a bound that does not exist will result in an error.

Delete variable bounds using delete_lower_bound, delete_upper_bound, and unfix:

julia> delete_lower_bound(x_lower)
+
+julia> has_lower_bound(x_lower)
+false
+
+julia> delete_upper_bound(x_upper)
+
+julia> has_upper_bound(x_upper)
+false
+
+julia> unfix(x_fixed)
+
+julia> is_fixed(x_fixed)
+false

Set or update variable bounds using set_lower_bound, set_upper_bound, and fix:

julia> set_lower_bound(x_lower, 1.1)
+
+julia> set_upper_bound(x_upper, 2.1)
+
+julia> fix(x_fixed, 4.1)

Fixing a variable with existing bounds will throw an error. To delete the bounds prior to fixing, use fix(variable, value; force = true).

julia> model = Model();
+
+julia> @variable(model, x >= 1)
+x
+
+julia> fix(x, 2)
+ERROR: Unable to fix x to 2 because it has existing variable bounds. Consider calling `JuMP.fix(variable, value; force=true)` which will delete existing bounds before fixing the variable.
+
+julia> fix(x, 2; force = true)
+
+
+julia> fix_value(x)
+2.0
Tip

Use fix instead of @constraint(model, x == 2). The former modifies variable bounds, while the latter adds a new linear constraint to the problem.

Binary variables

Binary variables are constrained to the set $x \in \{0, 1\}$.

Create a binary variable by passing Bin as an optional positional argument:

julia> model = Model();
+
+julia> @variable(model, x, Bin)
+x

Check if a variable is binary using is_binary:

julia> is_binary(x)
+true

Delete a binary constraint using unset_binary:

julia> unset_binary(x)
+
+julia> is_binary(x)
+false

Binary variables can also be created by setting the binary keyword to true:

julia> model = Model();
+
+julia> @variable(model, x, binary=true)
+x

or by using set_binary:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> set_binary(x)

Integer variables

Integer variables are constrained to the set $x \in \mathbb{Z}$.

Create an integer variable by passing Int as an optional positional argument:

julia> model = Model();
+
+julia> @variable(model, x, Int)
+x

Check if a variable is integer using is_integer:

julia> is_integer(x)
+true

Delete an integer constraint using unset_integer.

julia> unset_integer(x)
+
+julia> is_integer(x)
+false

Integer variables can also be created by setting the integer keyword to true:

julia> model = Model();
+
+julia> @variable(model, x, integer=true)
+x

or by using set_integer:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> set_integer(x)
Tip

The relax_integrality function relaxes all integrality constraints in the model, returning a function that can be called to undo the operation later on.

Start values

There are two ways to provide a primal starting solution (also called MIP-start or a warmstart) for each variable:

The starting value of a variable can be queried using start_value. If no start value has been set, start_value will return nothing.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> start_value(x)
+
+julia> @variable(model, y, start = 1)
+y
+
+julia> start_value(y)
+1.0
+
+julia> set_start_value(y, 2)
+
+julia> start_value(y)
+2.0

The start keyword argument can depend on the indices of a variable container:

julia> model = Model();
+
+julia> @variable(model, z[i = 1:2], start = i^2)
+2-element Vector{VariableRef}:
+ z[1]
+ z[2]
+
+julia> start_value.(z)
+2-element Vector{Float64}:
+ 1.0
+ 4.0
Warning

Some solvers do not support start values. If a solver does not support start values, an MathOptInterface.UnsupportedAttribute{MathOptInterface.VariablePrimalStart} error will be thrown.

Tip

To set the optimal solution from a previous solve as a new starting value, use all_variables to get a vector of all the variables in the model, then run:

x = all_variables(model)
+x_solution = value.(x)
+set_start_value.(x, x_solution)

Alternatively, use set_start_values.

Delete a variable

Use delete to delete a variable from a model. Use is_valid to check if a variable belongs to a model and has not been deleted.

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> is_valid(model, x)
+true
+
+julia> delete(model, x)
+
+julia> is_valid(model, x)
+false

Deleting a variable does not unregister the corresponding name from the model. Therefore, creating a new variable of the same name will throw an error:

julia> @variable(model, x)
+ERROR: An object of name x is already attached to this model. If this
+    is intended, consider using the anonymous construction syntax, e.g.,
+    `x = @variable(model, [1:N], ...)` where the name of the object does
+    not appear inside the macro.
+
+    Alternatively, use `unregister(model, :x)` to first unregister
+    the existing name from the model. Note that this will not delete the
+    object; it will just remove the reference at `model[:x]`.
+[...]

After calling delete, call unregister to remove the symbolic reference:

julia> unregister(model, :x)
+
+julia> @variable(model, x)
+x
Info

delete does not automatically unregister because we do not distinguish between names that are automatically registered by JuMP macros and names that are manually registered by the user by setting values in object_dictionary. In addition, deleting a variable and then adding a new variable of the same name is an easy way to introduce bugs into your code.

Variable containers

JuMP provides a mechanism for creating collections of variables in three types of data structures, which we refer to as containers.

The three types are Arrays, DenseAxisArrays, and SparseAxisArrays. We explain each of these in the following.

Tip

You can read more about containers in the Containers section.

Arrays

We have already seen the creation of an array of JuMP variables with the x[1:2] syntax. This can be extended to create multi-dimensional arrays of JuMP variables. For example:

julia> model = Model();
+
+julia> @variable(model, x[1:2, 1:2])
+2×2 Matrix{VariableRef}:
+ x[1,1]  x[1,2]
+ x[2,1]  x[2,2]

Arrays of JuMP variables can be indexed and sliced as follows:

julia> x[1, 2]
+x[1,2]
+
+julia> x[2, :]
+2-element Vector{VariableRef}:
+ x[2,1]
+ x[2,2]

Variable bounds can depend upon the indices:

julia> model = Model();
+
+julia> @variable(model, x[i=1:2, j=1:2] >= 2i + j)
+2×2 Matrix{VariableRef}:
+ x[1,1]  x[1,2]
+ x[2,1]  x[2,2]
+
+julia> lower_bound.(x)
+2×2 Matrix{Float64}:
+ 3.0  4.0
+ 5.0  6.0

JuMP will form an Array of JuMP variables when it can determine at compile time that the indices are one-based integer ranges. Therefore x[1:b] will create an Array of JuMP variables, but x[a:b] will not. If JuMP cannot determine that the indices are one-based integer ranges (for example, in the case of x[a:b]), JuMP will create a DenseAxisArray instead.

DenseAxisArrays

We often want to create arrays where the indices are not one-based integer ranges. For example, we may want to create a variable indexed by the name of a product or a location. The syntax is the same as that above, except with an arbitrary vector as an index as opposed to a one-based range. The biggest difference is that instead of returning an Array of JuMP variables, JuMP will return a DenseAxisArray. For example:

julia> model = Model();
+
+julia> @variable(model, x[1:2, [:A,:B]])
+2-dimensional DenseAxisArray{VariableRef,2,...} with index sets:
+    Dimension 1, Base.OneTo(2)
+    Dimension 2, [:A, :B]
+And data, a 2×2 Matrix{VariableRef}:
+ x[1,A]  x[1,B]
+ x[2,A]  x[2,B]

DenseAxisArrays can be indexed and sliced as follows:

julia> x[1, :A]
+x[1,A]
+
+julia> x[2, :]
+1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
+    Dimension 1, [:A, :B]
+And data, a 2-element Vector{VariableRef}:
+ x[2,A]
+ x[2,B]

Bounds can depend upon indices:

julia> model = Model();
+
+julia> @variable(model, x[i=2:3, j=1:2:3] >= 0.5i + j)
+2-dimensional DenseAxisArray{VariableRef,2,...} with index sets:
+    Dimension 1, 2:3
+    Dimension 2, 1:2:3
+And data, a 2×2 Matrix{VariableRef}:
+ x[2,1]  x[2,3]
+ x[3,1]  x[3,3]
+
+julia> lower_bound.(x)
+2-dimensional DenseAxisArray{Float64,2,...} with index sets:
+    Dimension 1, 2:3
+    Dimension 2, 1:2:3
+And data, a 2×2 Matrix{Float64}:
+ 2.0  4.0
+ 2.5  4.5

SparseAxisArrays

The third container type that JuMP natively supports is SparseAxisArray. These arrays are created when the indices do not form a rectangular set. For example, this applies when indices have a dependence upon previous indices (called triangular indexing). JuMP supports this as follows:

julia> model = Model();
+
+julia> @variable(model, x[i=1:2, j=i:2])
+JuMP.Containers.SparseAxisArray{VariableRef, 2, Tuple{Int64, Int64}} with 3 entries:
+  [1, 1]  =  x[1,1]
+  [1, 2]  =  x[1,2]
+  [2, 2]  =  x[2,2]

We can also conditionally create variables via a JuMP-specific syntax. This syntax appends a comparison check that depends upon the named indices and is separated from the indices by a semi-colon (;). For example:

julia> model = Model();
+
+julia> @variable(model, x[i=1:4; mod(i, 2)==0])
+JuMP.Containers.SparseAxisArray{VariableRef, 1, Tuple{Int64}} with 2 entries:
+  [2]  =  x[2]
+  [4]  =  x[4]

Performance considerations

When using the semi-colon as a filter, JuMP iterates over all indices and evaluates the conditional for each combination. If there are many index dimensions and a large amount of sparsity, this can be inefficient.

For example:

julia> model = Model();
+
+julia> N = 10
+10
+
+julia> S = [(1, 1, 1), (N, N, N)]
+2-element Vector{Tuple{Int64, Int64, Int64}}:
+ (1, 1, 1)
+ (10, 10, 10)
+
+julia> @time @variable(model, x1[i=1:N, j=1:N, k=1:N; (i, j, k) in S])
+  0.203861 seconds (392.22 k allocations: 23.977 MiB, 99.10% compilation time)
+JuMP.Containers.SparseAxisArray{VariableRef, 3, Tuple{Int64, Int64, Int64}} with 2 entries:
+  [1, 1, 1   ]  =  x1[1,1,1]
+  [10, 10, 10]  =  x1[10,10,10]
+
+julia> @time @variable(model, x2[S])
+  0.045407 seconds (65.24 k allocations: 3.771 MiB, 99.15% compilation time)
+1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
+    Dimension 1, [(1, 1, 1), (10, 10, 10)]
+And data, a 2-element Vector{VariableRef}:
+ x2[(1, 1, 1)]
+ x2[(10, 10, 10)]

The first option is slower because it is equivalent to:

julia> model = Model();
+
+julia> x1 = Dict{NTuple{3,Int},VariableRef}()
+Dict{Tuple{Int64, Int64, Int64}, VariableRef}()
+
+julia> for i in 1:N
+           for j in 1:N
+               for k in 1:N
+                   if (i, j, k) in S
+                       x1[i, j, k] = @variable(model, base_name = "x1[$i,$j,$k]")
+                   end
+               end
+           end
+       end
+
+julia> x1
+Dict{Tuple{Int64, Int64, Int64}, VariableRef} with 2 entries:
+  (1, 1, 1)    => x1[1,1,1]
+  (10, 10, 10) => x1[10,10,10]

If performance is a concern, explicitly construct the set of indices instead of using the filtering syntax.

Forcing the container type

When creating a container of JuMP variables, JuMP will attempt to choose the tightest container type that can store the JuMP variables. Thus, it will prefer to create an Array before a DenseAxisArray and a DenseAxisArray before a SparseAxisArray. However, because this happens at compile time, JuMP does not always make the best choice. To illustrate this, consider the following example:

julia> model = Model();
+
+julia> A = 1:2
+1:2
+
+julia> @variable(model, x[A])
+1-dimensional DenseAxisArray{VariableRef,1,...} with index sets:
+    Dimension 1, 1:2
+And data, a 2-element Vector{VariableRef}:
+ x[1]
+ x[2]

Since the value (and type) of A is unknown at parsing time, JuMP is unable to infer that A is a one-based integer range. Therefore, JuMP creates a DenseAxisArray, even though it could store these two variables in a standard one-dimensional Array.

We can share our knowledge that it is possible to store these JuMP variables as an array by setting the container keyword:

julia> @variable(model, y[A], container=Array)
+2-element Vector{VariableRef}:
+ y[1]
+ y[2]

JuMP now creates a vector of JuMP variables instead of a DenseAxisArray. Choosing an invalid container type will throw an error.

User-defined containers

In addition to the built-in container types, you can create your own collections of JuMP variables.

Tip

This is a point that users often overlook: you are not restricted to the built-in container types in JuMP.

For example, the following code creates a dictionary with symmetric matrices as the values:

julia> model = Model();
+
+julia> variables = Dict{Symbol,Array{VariableRef,2}}(
+           key => @variable(model, [1:2, 1:2], Symmetric, base_name = "$(key)")
+           for key in [:A, :B]
+       )
+Dict{Symbol, Matrix{VariableRef}} with 2 entries:
+  :A => [A[1,1] A[1,2]; A[1,2] A[2,2]]
+  :B => [B[1,1] B[1,2]; B[1,2] B[2,2]]

Another common scenario is a request to add variables to existing containers, for example:

using JuMP
+model = Model()
+@variable(model, x[1:2] >= 0)
+# Later I want to add
+@variable(model, x[3:4] >= 0)

This is not possible with the built-in JuMP container types. However, you can use regular Julia types instead:

julia> model = Model();
+
+julia> x = model[:x] = @variable(model, [1:2], lower_bound = 0, base_name = "x")
+2-element Vector{VariableRef}:
+ x[1]
+ x[2]
+
+julia> append!(x, @variable(model, [1:2], lower_bound = 0, base_name = "y"));
+
+julia> model[:x]
+4-element Vector{VariableRef}:
+ x[1]
+ x[2]
+ y[1]
+ y[2]

Semidefinite variables

Declare a square matrix of JuMP variables to be positive semidefinite by passing PSD as an optional positional argument:

julia> model = Model();
+
+julia> @variable(model, x[1:2, 1:2], PSD)
+2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
+ x[1,1]  x[1,2]
+ x[1,2]  x[2,2]

This will ensure that x is symmetric, and that all of its eigenvalues are nonnegative.

Note

x must be a square 2-dimensional Array of JuMP variables; it cannot be a DenseAxisArray or a SparseAxisArray.

Symmetric variables

Declare a square matrix of JuMP variables to be symmetric (but not necessarily positive semidefinite) by passing Symmetric as an optional positional argument:

julia> model = Model();
+
+julia> @variable(model, x[1:2, 1:2], Symmetric)
+2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
+ x[1,1]  x[1,2]
+ x[1,2]  x[2,2]

The @variables macro

If you have many @variable calls, JuMP provides the macro @variables that can improve readability:

julia> model = Model();
+
+julia> @variables(model, begin
+           x
+           y[i=1:2] >= i, (start = i, base_name = "Y_$i")
+           z, Bin
+       end)
+(x, VariableRef[Y_1[1], Y_2[2]], z)
+
+julia> print(model)
+Feasibility
+Subject to
+ Y_1[1] ≥ 1
+ Y_2[2] ≥ 2
+ z binary

The @variables macro returns a tuple of the variables that were defined.

Note

Keyword arguments must be contained within parentheses.

Variables constrained on creation

All uses of the @variable macro documented so far translate into separate calls for variable creation and the adding of any bound or integrality constraints.

For example, @variable(model, x >= 0, Int), is equivalent to:

julia> model = Model();
+
+julia> @variable(model, x)
+x
+
+julia> set_lower_bound(x, 0.0)
+
+julia> set_integer(x)

Importantly, the bound and integrality constraints are added after the variable has been created.

However, some solvers require a set specifying the variable domain to be given when the variable is first created. We say that these variables are constrained on creation.

Use in within @variable to access the special syntax for constraining variables on creation.

For example, the following creates a vector of variables that belong to the SecondOrderCone:

julia> model = Model();
+
+julia> @variable(model, y[1:3] in SecondOrderCone())
+3-element Vector{VariableRef}:
+ y[1]
+ y[2]
+ y[3]

For contrast, the standard syntax is as follows:

julia> @variable(model, x[1:3])
+3-element Vector{VariableRef}:
+ x[1]
+ x[2]
+ x[3]
+
+julia> @constraint(model, x in SecondOrderCone())
+[x[1], x[2], x[3]] ∈ MathOptInterface.SecondOrderCone(3)

An alternate syntax to x in Set is to use the set keyword of @variable. This is most useful when creating anonymous variables:

julia> model = Model();
+
+julia> x = @variable(model, [1:3], set = SecondOrderCone())
+3-element Vector{VariableRef}:
+ _[1]
+ _[2]
+ _[3]
Note

You cannot delete the constraint associated with a variable constrained on creation.

Example: positive semidefinite variables

An alternative to the syntax in Semidefinite variables, declare a matrix of JuMP variables to be positive semidefinite using PSDCone:

julia> model = Model();
+
+julia> @variable(model, x[1:2, 1:2] in PSDCone())
+2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
+ x[1,1]  x[1,2]
+ x[1,2]  x[2,2]

Example: symmetric variables

As an alternative to the syntax in Symmetric variables, declare a matrix of JuMP variables to be symmetric using SymmetricMatrixSpace:

julia> model = Model();
+
+julia> @variable(model, x[1:2, 1:2] in SymmetricMatrixSpace())
+2×2 LinearAlgebra.Symmetric{VariableRef, Matrix{VariableRef}}:
+ x[1,1]  x[1,2]
+ x[1,2]  x[2,2]

Example: skew-symmetric variables

Declare a matrix of JuMP variables to be skew-symmetric using SkewSymmetricMatrixSpace:

julia> model = Model();
+
+julia> @variable(model, x[1:2, 1:2] in SkewSymmetricMatrixSpace())
+2×2 Matrix{AffExpr}:
+ 0        x[1,2]
+ -x[1,2]  0
Note

Even though x is a 2 by 2 matrix, only one decision variable is added to model; the remaining elements in x are linear transformations of the single variable.

Because the returned matrix x is Matrix{AffExpr}, you cannot use variable-related functions on its elements:

julia> set_lower_bound(x[1, 2], 0.0)
+ERROR: MethodError: no method matching set_lower_bound(::AffExpr, ::Float64)
+[...]

Instead, you can convert an upper-triangular elements to a variable as follows:

julia> to_variable(x::AffExpr) = first(keys(x.terms))
+to_variable (generic function with 1 method)
+
+julia> to_variable(x[1, 2])
+x[1,2]
+
+julia> set_lower_bound(to_variable(x[1, 2]), 0.0)

Example: Hermitian positive semidefinite variables

Declare a matrix of JuMP variables to be Hermitian positive semidefinite using HermitianPSDCone:

julia> model = Model();
+
+julia> @variable(model, H[1:2, 1:2] in HermitianPSDCone())
+2×2 LinearAlgebra.Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
+ real(H[1,1])                    real(H[1,2]) + imag(H[1,2]) im
+ real(H[1,2]) - imag(H[1,2]) im  real(H[2,2])

This adds 4 real variables in the MOI.HermitianPositiveSemidefiniteConeTriangle:

julia> first(all_constraints(model, Vector{VariableRef}, MOI.HermitianPositiveSemidefiniteConeTriangle))
+[real(H[1,1]), real(H[1,2]), real(H[2,2]), imag(H[1,2])] ∈ MathOptInterface.HermitianPositiveSemidefiniteConeTriangle(2)

Example: Hermitian variables

Declare a matrix of JuMP variables to be Hermitian using the Hermitian tag:

julia> model = Model();
+
+julia> @variable(model, x[1:2, 1:2], Hermitian)
+2×2 LinearAlgebra.Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
+ real(x[1,1])                    real(x[1,2]) + imag(x[1,2]) im
+ real(x[1,2]) - imag(x[1,2]) im  real(x[2,2])

This is equivalent to declaring the variable in HermitianMatrixSpace:

julia> model = Model();
+
+julia> @variable(model, x[1:2, 1:2] in HermitianMatrixSpace())
+2×2 LinearAlgebra.Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:
+ real(x[1,1])                    real(x[1,2]) + imag(x[1,2]) im
+ real(x[1,2]) - imag(x[1,2]) im  real(x[2,2])

Why use variables constrained on creation?

For most users, it does not matter if you use the constrained on creation syntax. Therefore, use whatever syntax you find most convenient.

However, if you use direct_model, you may be forced to use the constrained on creation syntax.

The technical difference between variables constrained on creation and the standard JuMP syntax is that variables constrained on creation calls MOI.add_constrained_variables, while the standard JuMP syntax calls MOI.add_variables and then MOI.add_constraint.

Consult the implementation of solver package you are using to see if your solver requires MOI.add_constrained_variables.

Parameters

Some solvers have explicit support for parameters, which are constants in the model that can be efficiently updated between solves.

JuMP implements parameters by a decision variable constrained on creation to the Parameter set.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @variable(model, p[i = 1:2] in Parameter(i))
+2-element Vector{VariableRef}:
+ p[1]
+ p[2]

Create anonymous parameters using the set keyword:

julia> anon_parameter = @variable(model, set = Parameter(1.0))
+_[4]

Use parameter_value and set_parameter_value to query or update the value of a parameter.

julia> parameter_value.(p)
+2-element Vector{Float64}:
+ 1.0
+ 2.0
+
+julia> set_parameter_value(p[2], 3.0)
+
+julia> parameter_value.(p)
+2-element Vector{Float64}:
+ 1.0
+ 3.0

Limitations

Parameters are implemented as decision variables belonging to the Parameter set. If the solver supports the MOI.Parameter set, it may decide to replace all instances of the parameter variable by the associated constant. If the solver does not support parameters, it will add the parameter as a decision variable with fixed bounds.

The most important implication of this design is that JuMP treats a parameter multiplied by a decision variable as a quadratic expression, even though it is equivalent to a linear expression.

julia> model = Model();
+
+julia> @variable(model, x);
+
+julia> @variable(model, p in Parameter(2));
+
+julia> px = @expression(model, p * x)
+p*x
+
+julia> typeof(px)
+QuadExpr (alias for GenericQuadExpr{Float64, GenericVariableRef{Float64}})

When to use a parameter

Parameters are most useful when solving nonlinear models in a sequence:

julia> using JuMP, Ipopt
julia> model = Model(Ipopt.Optimizer);
julia> set_silent(model)
julia> @variable(model, x)x
julia> @variable(model, p in Parameter(1.0))p
julia> @objective(model, Min, (x - p)^2)x² - 2 p*x + p²
julia> optimize!(model)
julia> value(x)1.0
julia> set_parameter_value(p, 5.0)
julia> optimize!(model)
julia> value(x)5.0

Using parameters can be faster than creating a new model from scratch with updated data because JuMP is able to avoid repeating a number of steps in processing the model before handing it off to the solver.

diff --git a/previews/PR3536/moi/assets/latency.png b/previews/PR3536/moi/assets/latency.png new file mode 100755 index 00000000000..9b51f3b25fc Binary files /dev/null and b/previews/PR3536/moi/assets/latency.png differ diff --git a/previews/PR3536/moi/background/duality/index.html b/previews/PR3536/moi/background/duality/index.html new file mode 100644 index 00000000000..c4fd237401f --- /dev/null +++ b/previews/PR3536/moi/background/duality/index.html @@ -0,0 +1,84 @@ + +Duality · JuMP

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 closed convex cones, the model may be called a conic optimization problem.

For a minimization problem in geometric conic form, the primal is:

\[\begin{align} +& \min_{x \in \mathbb{R}^n} & a_0^T x + b_0 +\\ +& \;\;\text{s.t.} & A_i x + b_i & \in \mathcal{C}_i & i = 1 \ldots m +\end{align}\]

and the dual is a maximization problem in standard conic form:

\[\begin{align} +& \max_{y_1, \ldots, y_m} & -\sum_{i=1}^m b_i^T y_i + b_0 +\\ +& \;\;\text{s.t.} & a_0 - \sum_{i=1}^m A_i^T y_i & = 0 +\\ +& & y_i & \in \mathcal{C}_i^* & i = 1 \ldots m +\end{align}\]

where each $\mathcal{C}_i$ is a closed convex cone and $\mathcal{C}_i^*$ is its dual cone.

For a maximization problem in geometric conic form, the primal is:

\[\begin{align} +& \max_{x \in \mathbb{R}^n} & a_0^T x + b_0 +\\ +& \;\;\text{s.t.} & A_i x + b_i & \in \mathcal{C}_i & i = 1 \ldots m +\end{align}\]

and the dual is a minimization problem in standard conic form:

\[\begin{align} +& \min_{y_1, \ldots, y_m} & \sum_{i=1}^m b_i^T y_i + b_0 +\\ +& \;\;\text{s.t.} & a_0 + \sum_{i=1}^m A_i^T y_i & = 0 +\\ +& & y_i & \in \mathcal{C}_i^* & i = 1 \ldots m +\end{align}\]

A linear inequality constraint $a^T x + b \ge c$ is equivalent to $a^T x + b - c \in \mathbb{R}_+$, and $a^T x + b \le c$ is equivalent to $a^T x + b - c \in \mathbb{R}_-$. Variable-wise constraints are affine constraints with the appropriate identity mapping in place of $A_i$.

For the special case of minimization LPs, the MOI primal form can be stated as:

\[\begin{align} +& \min_{x \in \mathbb{R}^n} & a_0^T x &+ b_0 +\\ +& \;\;\text{s.t.} +&A_1 x & \ge b_1\\ +&& A_2 x & \le b_2\\ +&& A_3 x & = b_3 +\end{align}\]

By applying the stated transformations to conic form, taking the dual, and transforming back into linear inequality form, one obtains the following dual:

\[\begin{align} +& \max_{y_1,y_2,y_3} & b_1^Ty_1 + b_2^Ty_2 + b_3^Ty_3 &+ b_0 +\\ +& \;\;\text{s.t.} +&A_1^Ty_1 + A_2^Ty_2 + A_3^Ty_3 & = a_0\\ +&& y_1 &\ge 0\\ +&& y_2 &\le 0 +\end{align}\]

For maximization LPs, the MOI primal form can be stated as:

\[\begin{align} +& \max_{x \in \mathbb{R}^n} & a_0^T x &+ b_0 +\\ +& \;\;\text{s.t.} +&A_1 x & \ge b_1\\ +&& A_2 x & \le b_2\\ +&& A_3 x & = b_3 +\end{align}\]

and similarly, the dual is:

\[\begin{align} +& \min_{y_1,y_2,y_3} & -b_1^Ty_1 - b_2^Ty_2 - b_3^Ty_3 &+ b_0 +\\ +& \;\;\text{s.t.} +&A_1^Ty_1 + A_2^Ty_2 + A_3^Ty_3 & = -a_0\\ +&& y_1 &\ge 0\\ +&& y_2 &\le 0 +\end{align}\]

Warning

For the LP case, the signs of the feasible dual variables depend only on the sense of the corresponding primal inequality and not on the objective sense.

Duality and scalar product

The scalar product is different from the canonical one for the sets PositiveSemidefiniteConeTriangle, LogDetConeTriangle, RootDetConeTriangle.

If the set $C_i$ of the section Duality is one of these three cones, then the rows of the matrix $A_i$ corresponding to off-diagonal entries are twice the value of the coefficients field in the VectorAffineFunction for the corresponding rows. See PositiveSemidefiniteConeTriangle for details.

Dual for problems with quadratic functions

Quadratic Programs (QPs)

For quadratic programs with only affine conic constraints,

\[\begin{align*} +& \min_{x \in \mathbb{R}^n} & \frac{1}{2}x^TQ_0x + a_0^T x + b_0 +\\ +& \;\;\text{s.t.} & A_i x + b_i & \in \mathcal{C}_i & i = 1 \ldots m. +\end{align*}\]

with cones $\mathcal{C}_i \subseteq \mathbb{R}^{m_i}$ for $i = 1, \ldots, m$, consider the Lagrangian function

\[L(x, y) = \frac{1}{2}x^TQ_0x + a_0^T x + b_0 - \sum_{i = 1}^m y_i^T (A_i x + b_i).\]

Let $z(y)$ denote $\sum_{i = 1}^m A_i^T y_i - a_0$, the Lagrangian can be rewritten as

\[L(x, y) = \frac{1}{2}{x}^TQ_0x - z(y)^T x + b_0 - \sum_{i = 1}^m y_i^T b_i.\]

The condition $\nabla_x L(x, y) = 0$ gives

\[0 = \nabla_x L(x, y) = Q_0x + a_0 - \sum_{i = 1}^m y_i^T b_i\]

which gives $Q_0x = z(y)$. This allows to obtain that

\[\min_{x \in \mathbb{R}^n} L(x, y) = -\frac{1}{2}x^TQ_0x + b_0 - \sum_{i = 1}^m y_i^T b_i\]

so the dual problem is

\[\max_{y_i \in \mathcal{C}_i^*} \min_{x \in \mathbb{R}^n} -\frac{1}{2}x^TQ_0x + b_0 - \sum_{i = 1}^m y_i^T b_i.\]

If $Q_0$ is invertible, we have $x = Q_0^{-1}z(y)$ hence

\[\min_{x \in \mathbb{R}^n} L(x, y) = -\frac{1}{2}z(y)^TQ_0^{-1}z(y) + b_0 - \sum_{i = 1}^m y_i^T b_i\]

so the dual problem is

\[\max_{y_i \in \mathcal{C}_i^*} -\frac{1}{2}z(y)^TQ_0^{-1}z(y) + b_0 - \sum_{i = 1}^m y_i^T b_i.\]

Quadratically Constrained Quadratic Programs (QCQPs)

Given a problem with both quadratic function and quadratic objectives:

\[\begin{align*} +& \min_{x \in \mathbb{R}^n} & \frac{1}{2}x^TQ_0x + a_0^T x + b_0 +\\ +& \;\;\text{s.t.} & \frac{1}{2}x^TQ_ix + a_i^T x + b_i & \in \mathcal{C}_i & i = 1 \ldots m. +\end{align*}\]

with cones $\mathcal{C}_i \subseteq \mathbb{R}$ for $i = 1 \ldots m$, consider the Lagrangian function

\[L(x, y) = \frac{1}{2}x^TQ_0x + a_0^T x + b_0 - \sum_{i = 1}^m y_i (\frac{1}{2}x^TQ_ix + a_i^T x + b_i)\]

A pair of primal-dual variables $(x^\star, y^\star)$ is optimal if

  • $x^\star$ is a minimizer of

    \[\min_{x \in \mathbb{R}^n} L(x, y^\star).\]

    That is,

    \[0 = \nabla_x L(x, y^\star) = Q_0x + a_0 - \sum_{i = 1}^m y_i^\star (Q_ix + a_i).\]

  • and $y^\star$ is a maximizer of

    \[\max_{y_i \in \mathcal{C}_i^*} L(x^\star, y).\]

    That is, for all $i = 1, \ldots, m$, $\frac{1}{2}x^TQ_ix + a_i^T x + b_i$ is either zero or in the normal cone of $\mathcal{C}_i^*$ at $y^\star$. For instance, if $\mathcal{C}_i$ is $\{ z \in \mathbb{R} : z \le 0 \}$, this means that if $\frac{1}{2}x^TQ_ix + a_i^T x + b_i$ is nonzero at $x^\star$ then $y_i^\star = 0$. This is the classical complementary slackness condition.

If $\mathcal{C}_i$ is a vector set, the discussion remains valid with $y_i(\frac{1}{2}x^TQ_ix + a_i^T x + b_i)$ replaced with the scalar product between $y_i$ and the vector of scalar-valued quadratic functions.

Dual for square semidefinite matrices

The set PositiveSemidefiniteConeTriangle is a self-dual. That is, querying ConstraintDual of a PositiveSemidefiniteConeTriangle constraint returns a vector that is itself a member of PositiveSemidefiniteConeTriangle.

However, the dual of PositiveSemidefiniteConeSquare is not so straight forward. This section explains the duality convention we use, and how it is derived.

Info

If you have a PositiveSemidefiniteConeSquare constraint, the result matrix $A$ from ConstraintDual is not positive semidefinite. However, $A + A^\top$ is positive semidefinite.

Let $\mathcal{S}_+$ be the cone of symmetric semidefinite matrices in the $\frac{n(n+1)}{2}$ dimensional space of symmetric $\mathbb{R}^{n \times n}$ matrices. That is, $\mathcal{S}_+$ is the set PositiveSemidefiniteConeTriangle. It is well known that $\mathcal{S}_+$ is a self-dual proper cone.

Let $\mathcal{P}_+$ be the cone of symmetric semidefinite matrices in the $n^2$ dimensional space of $\mathbb{R}^{n \times n}$ matrices. That is $\mathcal{P}_+$ is the set PositiveSemidefiniteConeSquare.

In addition, let $\mathcal{D}_+$ be the cone of matrices $A$ such that $A+A^\top \in \mathcal{P}_+$.

$\mathcal{P}_+$ is not proper because it is not solid (it is not $n^2$ dimensional), so it is not necessarily true that $\mathcal{P}_+^{**} = \mathcal{P}_+$.

However, this is the case, because we will show that $\mathcal{P}_+^{*} = \mathcal{D}_+$ and $\mathcal{D}_+^{*} = \mathcal{P}_+$.

First, let us see why $\mathcal{P}_+^{*} = \mathcal{D}_+$.

If $B$ is symmetric, then

\[\langle A,B \rangle = \langle A^\top, B^\top \rangle = \langle A^\top, B\rangle\]

so

\[2\langle A, B \rangle = \langle A, B \rangle + \langle A^\top, B \rangle = \langle A + A^\top , B \rangle.\]

Therefore, $\langle A,B\rangle \ge 0$ for all $B \in \mathcal{P}_+$ if and only if $\langle A+A^\top,B\rangle \ge 0$ for all $B \in \mathcal{P}_+$. Since $A+A^\top$ is symmetric, and we know that $\mathcal{S}_+$ is self-dual, we have shown that $\mathcal{P}_+^{*}$ is the set of matrices $A$ such that $A+A^\top \in \mathcal{P}_+$.

Second, let us see why $\mathcal{D}_+^{*} = \mathcal{P}_+$.

Since $A \in \mathcal{D}_+$ implies that $A^\top \in \mathcal{D}_+$, $B \in \mathcal{D}_+^{*}$ means that $\langle A+A^\top,B\rangle \ge 0$ for all $A \in \mathcal{D}_+$, and hence $B \in \\mathcal{P}_+$.

To see why it should be symmetric, simply notice that if $B_{i,j} < B_{j,i}$, then $\langle A,B\rangle$ can be made arbitrarily small by setting $A_{i,j} = A_{i,j} + s$ and $A_{j,i} = A_{j,i} - s$, with $s$ arbitrarily large, and $A$ stays in $\mathcal{D}_+$ because $A+A^\top$ does not change.

Typically, the primal/dual pair for semidefinite programs is presented as:

\[\begin{align} + \min & \langle C, X \rangle \\ +\text{s.t.} \;\; & \langle A_k, X\rangle = b_k \forall k \\ + & X \in \mathcal{S}_+ +\end{align}\]

with the dual

\[\begin{align} + \max & \sum_k b_k y_k \\ +\text{s.t.} \;\; & C - \sum A_k y_k \in \mathcal{S}_+ +\end{align}\]

If we allow $A_k$ to be non-symmetric, we should instead use:

\[\begin{align} + \min & \langle C, X \rangle \\ +\text{s.t.} \;\; & \langle A_k, X\rangle = b_k \forall k \\ + & X \in \mathcal{D}_+ +\end{align}\]

with the dual

\[\begin{align} + \max & \sum b_k y_k \\ +\text{s.t.} \;\; & C - \sum A_k y_k \in \mathcal{P}_+ +\end{align}\]

This is implemented as:

\[\begin{align} + \min & \langle C, Z \rangle + \langle C - C^\top, S \rangle \\ +\text{s.t.} \;\; & \langle A_k, Z \rangle + \langle A_k - A_k^\top, S \rangle = b_k \forall k \\ + & Z \in \mathcal{S}_+ +\end{align}\]

with the dual

\[\begin{align} + \max & \sum b_k y_k \\ +\text{s.t.} \;\; & C+C^\top - \sum (A_k+A_k^\top) y_k \in \mathcal{S}_+ \\ + & C-C^\top - \sum(A_k-A_k^\top) y_k = 0 +\end{align}\]

and we recover $Z = X + X^\top$.

diff --git a/previews/PR3536/moi/background/infeasibility_certificates/index.html b/previews/PR3536/moi/background/infeasibility_certificates/index.html new file mode 100644 index 00000000000..2b520640673 --- /dev/null +++ b/previews/PR3536/moi/background/infeasibility_certificates/index.html @@ -0,0 +1,32 @@ + +Infeasibility certificates · JuMP

Infeasibility certificates

When given a conic problem that is infeasible or unbounded, some solvers can produce a certificate of infeasibility. This page explains what a certificate of infeasibility is, and the related conventions that MathOptInterface adopts.

Conic duality

MathOptInterface uses conic duality to define infeasibility certificates. A full explanation is given in the section Duality, but here is a brief overview.

Minimization problems

For a minimization problem in geometric conic form, the primal is:

\[\begin{align} +& \min_{x \in \mathbb{R}^n} & a_0^\top x + b_0 +\\ +& \;\;\text{s.t.} & A_i x + b_i & \in \mathcal{C}_i & i = 1 \ldots m, +\end{align}\]

and the dual is a maximization problem in standard conic form:

\[\begin{align} +& \max_{y_1, \ldots, y_m} & -\sum_{i=1}^m b_i^\top y_i + b_0 +\\ +& \;\;\text{s.t.} & a_0 - \sum_{i=1}^m A_i^\top y_i & = 0 +\\ +& & y_i & \in \mathcal{C}_i^* & i = 1 \ldots m, +\end{align}\]

where each $\mathcal{C}_i$ is a closed convex cone and $\mathcal{C}_i^*$ is its dual cone.

Maximization problems

For a maximization problem in geometric conic form, the primal is:

\[\begin{align} +& \max_{x \in \mathbb{R}^n} & a_0^\top x + b_0 +\\ +& \;\;\text{s.t.} & A_i x + b_i & \in \mathcal{C}_i & i = 1 \ldots m, +\end{align}\]

and the dual is a minimization problem in standard conic form:

\[\begin{align} +& \min_{y_1, \ldots, y_m} & \sum_{i=1}^m b_i^\top y_i + b_0 +\\ +& \;\;\text{s.t.} & a_0 + \sum_{i=1}^m A_i^\top y_i & = 0 +\\ +& & y_i & \in \mathcal{C}_i^* & i = 1 \ldots m. +\end{align}\]

Unbounded problems

A problem is unbounded if and only if:

  1. there exists a feasible primal solution
  2. the dual is infeasible.

A feasible primal solution—if one exists—can be obtained by setting ObjectiveSense to FEASIBILITY_SENSE before optimizing. Therefore, most solvers stop after they prove the dual is infeasible via a certificate of dual infeasibility, but before they have found a feasible primal solution. This is also the reason that MathOptInterface defines the DUAL_INFEASIBLE status instead of UNBOUNDED.

A certificate of dual infeasibility is an improving ray of the primal problem. That is, there exists some vector $d$ such that for all $\eta > 0$:

\[A_i (x + \eta d) + b_i \in \mathcal{C}_i,\ \ i = 1 \ldots m,\]

and (for minimization problems):

\[a_0^\top (x + \eta d) + b_0 < a_0^\top x + b_0,\]

for any feasible point $x$. The latter simplifies to $a_0^\top d < 0$. For maximization problems, the inequality is reversed, so that $a_0^\top d > 0$.

If the solver has found a certificate of dual infeasibility:

Note

The choice of whether to scale the ray $d$ to have magnitude 1 is left to the solver.

Infeasible problems

A certificate of primal infeasibility is an improving ray of the dual problem. However, because infeasibility is independent of the objective function, we first homogenize the primal problem by removing its objective.

For a minimization problem, a dual improving ray is some vector $d$ such that for all $\eta > 0$:

\[\begin{align} +-\sum_{i=1}^m A_i^\top (y_i + \eta d_i) & = 0 \\ +(y_i + \eta d_i) & \in \mathcal{C}_i^* & i = 1 \ldots m, +\end{align}\]

and:

\[-\sum_{i=1}^m b_i^\top (y_i + \eta d_i) > -\sum_{i=1}^m b_i^\top y_i,\]

for any feasible dual solution $y$. The latter simplifies to $-\sum_{i=1}^m b_i^\top d_i > 0$. For a maximization problem, the inequality is $\sum_{i=1}^m b_i^\top d_i < 0$. (Note that these are the same inequality, modulo a - sign.)

If the solver has found a certificate of primal infeasibility:

Note

The choice of whether to scale the ray $d$ to have magnitude 1 is left to the solver.

Infeasibility certificates of variable bounds

Many linear solvers (for example, Gurobi) do not provide explicit access to the primal infeasibility certificate of a variable bound. However, given a set of linear constraints:

\[\begin{align} +l_A \le A x \le u_A \\ +l_x \le x \le u_x, +\end{align}\]

the primal certificate of the variable bounds can be computed using the primal certificate associated with the affine constraints, $d$. (Note that $d$ will have one element for each row of the $A$ matrix, and that some or all of the elements in the vectors $l_A$ and $u_A$ may be $\pm \infty$. If both $l_A$ and $u_A$ are finite for some row, the corresponding element in `d must be 0.)

Given $d$, compute $\bar{d} = d^\top A$. If the bound is finite, a certificate for the lower variable bound of $x_i$ is $\max\{\bar{d}_i, 0\}$, and a certificate for the upper variable bound is $\min\{\bar{d}_i, 0\}$.

diff --git a/previews/PR3536/moi/background/motivation/index.html b/previews/PR3536/moi/background/motivation/index.html new file mode 100644 index 00000000000..1e4dd5ed30f --- /dev/null +++ b/previews/PR3536/moi/background/motivation/index.html @@ -0,0 +1,6 @@ + +Motivation · JuMP

Motivation

MathOptInterface (MOI) is a replacement for MathProgBase, the first-generation abstraction layer for mathematical optimization previously used by JuMP and Convex.jl.

To address a number of limitations of MathProgBase, MOI is designed to:

  • Be simple and extensible
    • unifying linear, quadratic, and conic optimization,
    • seamlessly facilitating extensions to essentially arbitrary constraints and functions (for example, 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)
    • by using multiple dispatch and avoiding requiring containers of non-concrete types
  • Allow a solver to return multiple results (for example, a pool of solutions)
  • Allow a solver to return extra arbitrary information via attributes (for example, 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/previews/PR3536/moi/background/naming_conventions/index.html b/previews/PR3536/moi/background/naming_conventions/index.html new file mode 100644 index 00000000000..20910abbb84 --- /dev/null +++ b/previews/PR3536/moi/background/naming_conventions/index.html @@ -0,0 +1,6 @@ + +Naming conventions · JuMP

Naming conventions

MOI follows several conventions for naming functions and structures. These should also be followed by packages extending MOI.

Sets

Sets encode the structure of constraints. Their names should follow the following conventions:

  • Abstract types in the set hierarchy should begin with Abstract and end in Set, for example, AbstractScalarSet, AbstractVectorSet.
  • Vector-valued conic sets should end with Cone, for example, NormInfinityCone, SecondOrderCone.
  • Vector-valued Cartesian products should be plural and not end in Cone, for example, Nonnegatives, not NonnegativeCone.
  • Matrix-valued conic sets should provide two representations: ConeSquare and ConeTriangle, for example, RootDetConeTriangle and RootDetConeSquare. See Matrix cones for more details.
  • Scalar sets should be singular, not plural, for example, Integer, not Integers.
  • As much as possible, the names should follow established conventions in the domain where this set is used: for instance, convex sets should have names close to those of CVX, and constraint-programming sets should follow MiniZinc's constraints.
diff --git a/previews/PR3536/moi/changelog/index.html b/previews/PR3536/moi/changelog/index.html new file mode 100644 index 00000000000..6a0de9ea048 --- /dev/null +++ b/previews/PR3536/moi/changelog/index.html @@ -0,0 +1,34 @@ + +Release notes · JuMP

Release notes

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

v1.20.1 (September 24, 2023)

Fixed

Other

  • Added MathOptSetDistances to solver-tests.yml (#2265)
  • Updated Documenter (#2266)
  • Fixed various JET errors (#2267) (#2269) (#2270) (#2271) (#2276) (#2277) (#2289)
  • Various style improvements
    • Replaced using Package with import Package where possible (#2274)
    • Removed Utilities.EMPTYSTRING (#2283)
    • Removed unnecessary const acronyms in Utilities (#2280) (#2281)
    • Removed invalid and unused method (#2286)
  • Refactored src/Utilities/model.jl (#2287)

v1.20.0 (September 7, 2023)

Added

Other

  • Updated dependencies (#2258)
  • Improved performance of ScalarNonlinearFunction utilities (#2259)
  • Fixed docstrings (#2261)

v1.19.0 (August 15, 2023)

Added

Fixed

Other

  • Added extensions to solver-tests.yml (#2229)
  • Refactored test/Benchmarks (#2234)
  • Fixed warnings in tests (#2241) (#2243)
  • Small refactoring of bridges for upcoming VectorNonlinearFunction (#2244) (#2245)
  • Fixed various typos (#2251) (#2255)
  • Partitioned how we run the tests on GitHub actions (#2252) (#2253)

v1.18.0 (June 23, 2023)

Added

Fixed

  • Fixed a missing @require in MOI.Test (#2195) (#2196)
  • Fixed incorrect usage of Utilities.operate! in bridges (#2207) (#2216)
  • Fixed splatting nonlinear expression with univariate operator (#2221)

Other

  • Removed unused argument names (#2199)
  • Reduced memory requirement for tests (#2204)
  • Refactored Utilities.promote_operation (#2206)
  • Improved code style in Utilities/mutable_arithmetics.jl (#2209)
  • Refactored various methods in Utilities/functions.jl (#2208) (#2212) (#2213) (#2214) (#2215)

v1.17.1 (June 6, 2023)

Fixed

Other

  • Added documentation for enum instances (#2186)
  • Updated chatroom links in documentation (#2188)
  • Changed the documentation to build on Julia v1.9 (#2191)

v1.17.0 (June 1, 2023)

Added

Fixed

  • Fixed support for external sets in Utilities.loadfromstring! (#2177)
  • Fixed promote_operation for ScalarNonlinearFunction (#2179)
  • Fixed two issues in FileFormats.LP when reading files with quadratic functions (#2182) (#2184)

v1.16.0 (May 16, 2023)

Added

Fixed

  • Fixed support for Julia v1.9 to work around a bug in the upstream Julia compiler (#2161) (#2163)
  • Fixed a correctness bug in Bridges.Constraint.HermitianToSymmetricPSDBridge (#2171)
  • Fixed convert(::VariableIndex, ::ScalarAffineFunction) when the function has terms with 0coefficients (#2173)

Other

  • Fixed solver-tests.yml (#2157)
  • Updated documentation links to developer chatroom (#2160)
  • Added various tests for bridges (#2156)
  • Added checklists to the developer documentation (#2167) (#2168)

v1.15.1 (April 25, 2023)

Fixed

  • Fixed deleting a variable in a bridged objective (#2150)

v1.15.0 (April 19, 2023)

Added

Fixed

Other

  • Add a test for variables in one-sided open Interval sets (#2133)
  • Minor style fixes in the source code (#2148)

v1.14.1 (April 6, 2023)

Fixed

Other

  • Added a warning when an ambiguous string is passed to exclude in Test.runtests (#2136)

v1.14.0 (April 4, 2023)

Added

Fixed

  • Fixed ResultCount when parsing .sol files in FileFormats.NL (#2130)

v1.13.2 (March 21, 2023)

Fixed

Other

  • Fixed typos in the documentation (#2114)
  • Functions now print to the REPL in algebraic form. This is potentially breaking if you have tests which rely on a specific String form of MOI functions. (#2112) (#2126)

v1.13.1 (March 3, 2023)

Other

  • Added the Google style guide to the documentation linter Vale, and fixed the resulting warnings (#2110)
  • Improved the docstrings in src/functions.jl (#2108)

v1.13.0 (February 28, 2023)

Added

Fixed

Other

  • Added tests for vector-valued objective functions in FileFormats.MOF (#2093)
  • Used and documented preference for import MathOptInterface as MOI (#2096)
  • Fix and test links in the documentation with linkcheck = true (#2098)
  • Improved docstrings of sets in src/sets.jl (#2099)
  • Skip checking flakey links in documentation with linkcheck_ignore (#2103)

v1.12.0 (February 10, 2023)

Added

Fixed

  • Fixed a number of constraint bridges so that Bridges.final_touch can be called multiple times without forcing a rebuild of the reformulation (#2089)

Other

v1.11.5 (January 24, 2023)

Fixed

  • Fixed a bug writing .lp files with an off-diagonal quadratic objective (#2082)

Other

  • Added SnoopPrecompile directives for reduced time-to-first-X in Julia v1.9 (#2080)

v1.11.4 (January 12, 2023)

Fixed

  • Fixed a bug reading .lp files with an Integer section (#2078)

v1.11.3 (January 12, 2023)

Fixed

  • Fixed a performance bug when deleting a vector of constraints (#2072)
  • Fixed a bug reading .lp files with terms like x -1 y (#2076)

Other

  • Documented the two-argument method of optimize! (#2074)

v1.11.2 (January 2, 2023)

Fixed

  • Fixed a bug reading .mof.json files with ConstraintName set for VariableIndex constraints (#2066)
  • Fixed a bug reading .mof.json files with nonlinear objectives and no constraints (#2068)

v1.11.1 (December 22, 2022)

Fixed

  • Fixed a bug reading .mof.json files with integer coefficients for affine and quadratic functions (#2063)

v1.11.0 (December 2, 2022)

Added

Other

  • Tidied these release notes (#2055)

v1.10.0 (November 22, 2022)

Added

Fixed

  • Fixed Bridges.Objective.SlackBridge when the objective function is complex-valued (#2036) (#2038)
  • Fixed docstring of Test.runtests to clarify the warn_unsupported argument (#2037)
  • Fixed reading of free variables in FileFormats.LP (#2044)
  • Fixed numerous edge cases reading files from QPLIB using FileFormats.LP (#2042) (#2044)
  • Fixed situations in which x^y returns a complex value in Nonlinear (#2050)

Other

  • Improved the error message thrown when a user-defined nonlinear function does not accept splatted input (#2032)
  • Removed specialized iterators for keys and values in Utilities.CleverDicts (#2051)

v1.9.0 (October 29, 2022)

Added

  • Added default fallback for getting ListOfConstraintIndices and NumberOfConstraints when the constraint type is unsupported by the model (#2021)
  • Added support for min and max in nonlinear expressions (#2023)
  • Added support for Indicator{EqualTo{T}} constraints in FileFormats.MPS (#2022)
  • Added default fallback for write_to_file and read_from_file (#2029)

Fixed

  • Fixed Constraint.ZeroOneBridge by adding new bounds as affine constraints instead of variable bounds (#1879)
  • Fixed reading free rows in FileFormats.MPS files (#2009)
  • Fixed parsing of OBJSENSE blocks in FileFormats.MPS files (#2016) (#2019)
  • Fixed the parsing of deeply nested nonlinear expressions by removing the use of recursion (#2020)
  • Fixed the requirements check in Test.test_constrainnt_get_ConstraintIndex (#2024)

v1.8.2 (September 20, 2022)

Documentation

  • Added vale as a documentation linter (#2002)
  • Improved styling of code blocks in the PDF (#1999) (#2000)
  • Fixed a number of typos in the documentation (#2001) (#2003)

v1.8.1 (September 12, 2022)

Fixed

  • Fixed a bug in supports(::AbstractBridgeOptimizer for constraint attributes (#1991) (#1992)

v1.8.0 (September 1, 2022)

Added

Fixed

  • Lazily construct expressions in Nonlinear so that expressions are updated when Nonlinear.Parameter values are updated (#1984)
  • Allow NORM_LIMIT as a TerminationStatus for unbounded problems in Test (#1990)

v1.7.0 (August 16, 2022)

Added

Fixed

  • Fixed some missing promotion rules

Other

  • Improved the performance of Jacobian products in Nonlinear
  • Removed an un-needed copy in Utilities.modify_function
  • Various clean-ups in Bridges/bridge_optimizer.jl

v1.6.1 (July 23, 2022)

Fixed

  • Added support for ExponentialCone in MatrixOfConstraints
  • Fix PSDSquare_3 test to reflect a previously fixed bug getting the ConstraintDual of a PositiveSemidefiniteConeSquare constraint

v1.6.0 (July 2, 2022)

Added

  • Added Bridges.needs_final_touch and Bridges.final_touch
  • Added new bridges from constraint programming sets to mixed-integer linear programs:
    • AllDifferentToCountDistinctBridge
    • CountAtLeastToCountBelongsBridge
    • CountBelongsToMILPBridge
    • CountDistinctToMILPBridge
    • CountGreaterThanToMILPBridge
    • CircuitToMILPBridge

Fixed

  • Relax an instance of ::Vector to ::AbstractVector in MOI.Nonlinear
  • Fix BinPackingToMILPBridge to respect variable bounds
  • Fix SemiToBinaryBridge to throw error if other bounds are set

v1.5.0 (June 27, 2022)

Added

  • Added GetAttributeNotAllowed for solvers to indicate when getting an attribute encounters an error
  • Added Utilities.get_fallback support for ObjectiveValue and DualObjectiveValue
  • Added new bridges:
    • RootDetConeSquare to RootDetConeTriangle
    • LogDetConeSquare to LogDetConeTriangle
    • BinPacking to a mixed-integer linear program
    • Table to a mixed-integer linear program
  • Added Bridges.print_active_bridges to display the current optimal hyper-path in a Bridges.LazyBridgeOptimizer

Fixed

  • Fixed ZeroOne tests with lower and upper bounds
  • Fixed error in FileFormats.LP when reading a malformed file
  • Fixed reading of nonlinear programs in FileFormats.MOF
  • Fixed bug in ConstraintDual when using SquareBridge

Other

  • Improved documentation of nonlinear API
  • Documented duality convention for PositiveSemidefiniteConeSquare sets
  • Fixed typo in Bridges.Constraint.QuadToSOCBridge docstring

v1.4.0 (June 9, 2022)

Added

  • Added a number of sets for constraint programming:
    • AllDifferent
    • BinPacking
    • Circuit
    • CountAtLeast
    • CountBelongs
    • CountDistinct
    • CountGreaterThan
    • Cumulative
    • Path
    • Table
  • Added support for user-defined hessians in Nonlinear
  • Added Bridges.runtests to simplify the testing of bridge implementations

Fixed

  • Fixed a bug in FileFormats.NL when writing univariate *

Other

  • Began a large refactoring of the Bridges submodule, with greatly improved documentation.

v1.3.0 (May 27, 2022)

Added

  • Add MOI.Nonlinear submodule. This is a large new submodule that has been refactored from code that was in JuMP. For now, it should be considered experimental.
  • Add FileFormats.NL.SolFileResults(::IO, ::Model)
  • Add FileFormats.NL.read!(::IO, ::Model)
  • Add MOI.modify that accepts a vector of modifications

Fixed

  • Fixed a bug in Test which attempted to include non-.jl files
  • Fixed a bug in FileFormats for models with open interval constraints

Other

  • Fixed a performance issue in Utilities.DoubleDict
  • Various minor improvements to the documentation

v1.2.0 (April 25, 2022)

Added

  • Add support for the FORMAT_REW/.rew file format in FileFormats.

Fixed

  • Fix bug handling of default variable bounds in FileFormats.LP
  • Fix FileFormats.MPS to not write OBJSENSE by default since this is only supported by some readers.

v1.1.2 (March 31, 2022)

Fixed

  • Fix a range of bugs in FileFormats.LP
  • Fix reading of problem dimensions in FileFormats.SDPA

v1.1.1 (March 23, 2022)

Fixed

  • Fix bug in test_model_UpperBoundAlreadySet
  • Fix bug in test_infeasible_ tests
  • Fix bug in test_objective_ObjectiveFunction_blank
  • Relax restriction of MOI.AbstractOptimizer to MOI.ModelLike in Utilities.CachingOptimizer and instantiate.

New tests

  • Add test_conic_empty_matrix that checks conic solvers support problems with no variables.

v1.1.0 (March 2, 2022)

Added

  • Added MOI.Utilities.throw_unsupported(::UniversalFallback) for simplifying solver wrappers which copy from a UniversalFallback.

v1.0.2 (March 1, 2022)

Fixed

  • Fixed a bug in the test_model_ScalarFunctionConstantNotZero test
  • Fixed the error type when an AbstractFunctionConversionBridge cannot get or set an attribute
  • Identified a correctness bug in RSOCtoPSDBridge. We now thrown an error instead of returning an incorrect result.

v1.0.1 (February 25, 2022)

Fixed

  • Fixed a bug in which OptimizerAttributes were not copied in CachingOptimizer
  • Fixed a bug in which shift_constant did not promote mixed types of coefficients
  • Fixed a bug in which deleting a constraint of a bridged variable threw ErrorException instead of MOI.DeleteNotAllowed
  • Fixed a bug in which add_constraint in MatrixOfConstraints did not canonicalize the function
  • Fixed a bug when modifying scalar constants of a function containing a bridged variable
  • Fixed a bug in which final_touch was not always called with a CachingOptimizer

v1.0.0 (February 17, 2022)

Although tagged as a breaking release, v1.0.0 is v0.10.9 with deprecations removed, similar to how Julia 1.0 was Julia 0.7 with deprecations removed.

Breaking

  • Julia 1.6 is now the minimum supported version
  • All deprecations have been removed

Troubleshooting problems when updating

If you experience problems when updating, you are likely using previously deprecated features. (By default, Julia does not warn when you use deprecated features.)

To find the deprecated features you are using, start Julia with --depwarn=yes:

$ julia --depwarn=yes

Then install MathOptInterface v0.10.9:

julia> using Pkg
+julia> pkg"add MathOptInterface@0.10"

And then run your code. Apply any suggestions, or search the release notes below for advice on updating a specific deprecated feature.

v0.10.9 (February 16, 2022)

Added

  • Added MOI.Utilities.FreeVariables as a new VariablesConstrainer for conic solvers
  • Added MOI.default_cache for specifying the model used in CachingOptimizer

Fixed

  • Fixed LaTeX printing of MOI.Interval sets

Other

  • Added Aqua.jl as a CI check, and fixed suggested issues
  • The constructors of GeoMeanBridge, StructOfConstraints, and CachingOptimizer were changed from outer to inner constructors. This change is technically breaking, but does not impact users who followed the documented API.

v0.10.8 (February 3, 2022)

Added

  • Added a Base.read! for FileFormats.LP.

Fixed

  • Fixed a bug in MutableSparseMatrix
  • Fixed a bug when calling operate!(vcat, ...) with Number arguments
  • Removed unintended export of deprecated symbols
  • Fixed a bug with PowerCone and DualPowerCone in MatrixOfConstraints.

v0.10.7 (January 5, 2022)

Added

  • Added test for modifying the constant vector in a VectorAffineFunction-in-Zeros constraint.

Fixed

  • Fixed the order in which sets are added to a LazyBridgeOptimizer. Compared to v0.10.6, this may result in bridged models being created with a different number (and order) of variables and constraints. However, it was necessary to fix cases which were previously rejected as unsupported, even though there was a valid bridge transformation.
  • Fixed an error message in FileFormats.CBF
  • Fixed comparison in test_linear_integration_Interval
  • Fixed errors for ConstraintPrimal in a CachingOptimizer
  • Fixed printing of models with non-Float64 coefficients.

Other

  • Various improvements to reduce time-to-first-solve latency
  • Improved error message when an optimizer does not support compute_conflict!

v0.10.6 (November 30, 2021)

Added

  • Added new documentation and tests for infeasibility certificates
  • Added a version control system for the tests in MOI.Test.runtests. Pass exclude_tests_after = v"0.10.5" to run tests added in v0.10.5 and earlier.
  • MOI.Test.runtests now supports generic number types. To specify the number type T, pass MOI.Test.Config(T).
  • Added infeasible_status to MOI.Test.Config for solvers which return LOCALLY_INFEASIBLE
  • CachingOptimizers now use a fallback for ConstraintPrimal. This should enable solvers using a CachingOptimizer to pass tests requiring ConstraintPrimal.

Fixed

  • Fixed a StackOverflow bug in copy_to
  • Fixed error thrown when nonconvex quadratic constraints cannot be bridged
  • Fixed a bug in copy_to for FileFormats.NL.Model
  • Fixed a bug in FileFormats.NL when printing large integers
  • Remove a common test failure for LowerBoundAlreadySet tests
  • Utilities.num_rows is now exported
  • Remove parts of failing test_model_copy_to_xxx tests due to bridges

v0.10.5 (November 7, 2021)

Fixed

  • Fixed getter in UniversalFallback
  • Fixed test_solve_conflict_zeroone_ii

Other

  • Make normalize_and_add_constraint more flexible
  • Update paper BibTeX

v0.10.4 (October 26, 2021)

Added

  • Add SolverVersion attribute
  • Add new tests:
    • test_solve_conflict_zeroone_ii
    • test_nonlinear_objective
  • Utilities.VariablesContainer now supports ConstraintFunction and ConstraintSet
  • The documentation is now available as a PDF

Other

  • Update to MutableArithmetics 0.3
  • Various improvements to the documentation

v0.10.3 (September 18, 2021)

Fixed

  • Fixed bug which prevented callbacks from working through a CachingOptimizer
  • Fixed bug in Test submodule

v0.10.2 (September 16, 2021)

  • Updated MathOptFormat to v1.0
  • Updated JSONSchema to v1.0
  • Added Utilities.set_with_dimension
  • Added two-argument optimize!(::AbstractOptimizer, ::ModelLike)
  • The experimental feature copy_to_and_optimize! has been removed
  • Det bridges now support getting ConstraintFunction and ConstraintSet
  • Various minor bug fixes identified by improved testing

v0.10.1 (September 8, 2021)

  • Various fixes to MOI.Test

v0.10.0 (September 6, 2021)

MOI v0.10 is a significant breaking release. There are a large number of user-visible breaking changes and code refactors, as well as a substantial number of new features.

Breaking in MOI

  • SingleVariable has been removed; use VariableIndex instead
  • SingleVariableConstraintNameError has been renamed to VariableIndexConstraintNameError
  • SettingSingleVariableFunctionNotAllowed has been renamed to SettingVariableIndexFunctionNotAllowed
  • VariableIndex constraints should not support ConstraintName
  • VariableIndex constraints should not support ConstraintBasisStatus; implement VariableBasisStatus instead
  • ListOfConstraints has been renamed to ListOfConstraintTypesPresent
  • ListOfConstraintTypesPresent should now return Tuple{Type,Type} instead of Tuple{DataType,DataType}
  • SolveTime has been renamed to SolveTimeSec
  • IndicatorSet has been renamed to Indicator
  • RawParameter has been renamed to RawOptimizerAttribute and now takes String instead of Any as the only argument
  • The .N field in result attributes has been renamed to .result_index
  • The .variable_index field in ScalarAffineTerm has been renamed to .variable
  • The .variable_index_1 field in ScalarQuadraticTerm has been renamed to .variable_1
  • The .variable_index_2 field in ScalarQuadraticTerm has been renamed to .variable_2
  • The order of affine_terms and quadratic_terms in ScalarQuadraticFunction and VectorQuadraticFunction have been reversed. Both functions now accept quadratic, affine, and constant terms in that order.
  • The index_value function has been removed. Use .value instead.
  • isapprox has been removed for SOS1 and SOS2.
  • The dimension argument to Complements(dimension::Int) should now be the length of the corresponding function, instead of half the length. An ArgumentError is thrown if dimension is not even.
  • copy_to no longer takes keyword arguments:
    • copy_names: now copy names if they are supported by the destination solver
    • filter_constraints: use Utilities.ModelFilter instead
    • warn_attributes: never warn about optimizer attributes

Breaking in Bridges

  • Constraint.RSOCBridge has been renamed to Constraint.RSOCtoSOCBridge
  • Constraint.SOCRBridge has been renamed to Constraint.SOCtoRSOCBridge
  • Bridges now return vectors that can be modified by the user. Previously, some bridges returned views instead of copies.
  • Bridges.IndexInVector has been unified into a single type. Previously, there was a different type for each submodule within Bridges
  • The signature of indicator bridges has been fixed. Use MOI.Bridges.Constraint.IndicatortoSOS1{Float64}(model).

Breaking in FileFormats

  • FileFormats.MOF.Model no longer accepts validate argument. Use the JSONSchema package to validate the MOF file. See the documentation for more information.

Breaking in Utilities

  • The datastructure of Utilities.Model (and models created with Utilities.@model) has been significantly refactored in a breaking way. This includes the way that objective functions and variable-related information is stored.
  • Utilities.supports_default_copy has been renamed to supports_incremental_interface
  • Utilities.automatic_copy_to has been renamed to Utilities.default_copy_to
  • The allocate-load API has been removed
  • CachingOptimizers are now initialized as EMPTY_OPTIMIZER instead of ATTACHED_OPTIMIZER. If your code relies on the optimizer being attached, call MOIU.attach_optimizer(model) after creation.
  • The field names of Utilities.IndexMap have been renamed to var_map and con_map. Accessing these fields directly is considered a private detail that may change. Use the public getindex and setindex! API instead.
  • The size argument to Utilities.CleverDicts.CleverDict(::Integer) has been removed.
  • The size argument to Utilities.IndexMap(::Integer) has been removed.
  • Utilities.DoubleDicts have been significantly refactored. Consult the source code for details.
  • Utilities.test_models_equal has been moved to MOI.Test

Breaking in Test

  • MOI.Test has been renamed to MOI.DeprecatedTest
  • An entirely new MOI.Test submodule has been written. See the documentation for details. The new MOI.Test submodule may find many bugs in the implementations of existing solvers that were previously untested.

Other changes:

  • attribute_value_type has been added
  • copy_to_and_optimize! has been added
  • VariableBasisStatus has been added
  • print(model) now prints a human-readable description of the model
  • Various improvements to the FileFormats submodule
    • FileFormats.CBF was refactored and received bugfixes
    • Support for MathOptFormat v0.6 was added in FileFormats.MOF
    • FileFormats.MPS has had bugfixes and support for more features such as OBJSENSE and objective constants.
    • FileFormats.NL has been added to support nonlinear files
  • Improved type inference throughout to reduce latency

Updating

A helpful script when updating is:

for (root, dirs, files) in walkdir(".")
+    for file in files
+        if !endswith(file, ".jl")
+            continue
+        end
+        path = joinpath(root, file)
+        s = read(path, String)
+        for pair in [
+            ".variable_index" => ".variable",
+            "RawParameter" => "RawOptimizerAttribute",
+            "ListOfConstraints" => "ListOfConstraintTypesPresent",
+            "TestConfig" => "Config",
+            "attr.N" => "attr.result_index",
+            "SolveTime" => "SolveTimeSec",
+            "DataType" => "Type",
+            "Utilities.supports_default_copy_to" =>
+                "supports_incremental_interface",
+            "SingleVariableConstraintNameError" =>
+                "VariableIndexConstraintNameError",
+            "SettingSingleVariableFunctionNotAllowed" =>
+                "SettingVariableIndexFunctionNotAllowed",
+            "automatic_copy_to" => "default_copy_to",
+        ]
+            s = replace(s, pair)
+        end
+        write(path, s)
+    end
+end

v0.9.22 (May 22, 2021)

This release contains backports from the ongoing development of the v0.10 release.

  • Improved type inference in Utilities, Bridges and FileFormats submodules to reduce latency.
  • Improved performance of Utilities.is_canonical.
  • Fixed Utilities.pass_nonvariable_constraints with bridged variables.
  • Fixed performance regression of Utilities.Model.
  • Fixed ordering of objective setting in parser.

v0.9.21 (April 23, 2021)

  • Added supports_shift_constant.
  • Improve performance of bridging quadratic constraints.
  • Add precompilation statements.
  • Large improvements to the documentation.
  • Fix a variety of inference issues, benefiting precompilation and reducing initial latency.
  • RawParameters are now ignored when resetting a CachingOptimizer. Previously, changing the underlying optimizer after RawParameters were set would throw an error.
  • Utilities.AbstractModel is being refactored. This may break users interacting with private fields of a model generated using @model.

v0.9.20 (February 20, 2021)

  • Improved performance of Utilities.ScalarFunctionIterator
  • Added support for compute_conflict to MOI layers
  • Added test with zero off-diagonal quadratic term in objective
  • Fixed double deletion of nested bridged SingleVariable/VectorOfVariables constraints
  • Fixed modification of un-set objective
  • Fixed function modification with duplicate terms
  • Made unit tests abort without failing if the problem class is not supported
  • Formatted code with JuliaFormatter
  • Clarified BasisStatusCode's docstring

v0.9.19 (December 1, 2020)

  • Added CallbackNodeStatus attribute
  • Added bridge from GreaterThan or LessThan to Interval
  • Added tests for infeasibility certificates and double optimize
  • Fixed support for Julia v1.6
  • Re-organized MOI docs and added documentation for adding a test

v0.9.18 (November 3, 2020)

  • Various improvements for working with complex numbers
  • Added GeoMeantoRelEntrBridge to bridge a GeometricMeanCone constraint to a relative entropy constraint

v0.9.17 (September 21, 2020)

  • Fixed CleverDict with variable of negative index value
  • Implement supports_add_constrained_variable for MockOptimizer

v0.9.16 (September 17, 2020)

  • Various fixes:
    • 32-bit support
    • CleverDict with abstract value type
    • Checks in test suite

v0.9.15 (September 14, 2020)

  • Bridges improvements:
    • (R)SOCtoNonConvexQuad bridge
    • ZeroOne bridge
    • Use supports_add_constrained_variable in LazyBridgeOptimizer
    • Exposed VariableBridgeCost and ConstraintBridgeCost attributes
    • Prioritize constraining variables on creation according to these costs
    • Refactor bridge debugging
  • Large performance improvements across all submodules
  • Lots of documentation improvements
  • FileFormats improvements:
    • Update MathOptFormat to v0.5
    • Fix supported objectives in FileFormats
  • Testing improvements:
    • Add name option for basic_constraint_test
  • Bug fixes and missing methods
    • Add length for iterators
    • Fix bug with duplicate terms
    • Fix order of LinearOfConstraintIndices

v0.9.14 (May 30, 2020)

  • Add a solver-independent interface for accessing the set of conflicting constraints an Irreducible Inconsistent Subsystem (#1056).
  • Bump JSONSchema dependency from v0.2 to v0.3 (#1090).
  • Documentation improvements:
    • Fix typos (#1054, #1060, #1061, #1064, #1069, #1070).
    • Remove the outdated recommendation for a package implementing MOI for a solver XXX to be called MathOptInterfaceXXX (#1087).
  • Utilities improvements:
    • Fix is_canonical for quadratic functions (#1081, #1089).
    • Implement add_constrained_variable[s] for CachingOptimizer so that it is added as constrained variables to the underlying optimizer (#1084).
    • Add support for custom objective functions for UniversalFallback (#1086).
    • Deterministic ordering of constraints in UniversalFallback (#1088).
  • Testing improvements:
    • Add NormOneCone/NormInfinityCone tests (#1045).
  • Bridges improvements:
    • Add bridges from Semiinteger and Semicontinuous (#1059).
    • Implement getting ConstraintSet for Variable.FlipSignBridge (#1066).
    • Fix setting ConstraintFunction for Constraint.ScalarizeBridge (#1093).
    • Fix NormOne/NormInf bridges with nonzero constants (#1045).
    • Fix StackOverflow in debug (#1063).
  • FileFormats improvements:
    • [SDPA] Implement the extension for integer variables (#1079).
    • [SDPA] Ignore comments after m and nblocks and detect dat-s extension (#1077).
    • [SDPA] No scaling of off-diagonal coefficient (#1076).
    • [SDPA] Add missing negation of constant (#1075).

v0.9.13 (March 24, 2020)

  • Added tests for Semicontinuous and Semiinteger variables (#1033).
  • Added tests for using ExprGraphs from NLP evaluators (#1043).
  • Update version compatibilities of dependencies (#1034, #1051, #1052).
  • Fixed typos in documentation (#1044).

v0.9.12 (February 28, 2020)

  • Fixed writing NLPBlock in MathOptFormat (#1037).
  • Fixed MockOptimizer for result attributes with non-one result index (#1039).
  • Updated test template with instantiate (#1032).

v0.9.11 (February 21, 2020)

  • Add an option for the model created by Utilities.@model to be a subtype of AbstractOptimizer (#1031).
  • Described dual cone in docstrings of GeoMeanCone and RelativeEntropyCone (#1018, #1028).
  • Fixed typos in documentation (#1022, #1024).
  • Fixed warning of unsupported attribute (#1027).
  • Added more rootdet/logdet conic tests (#1026).
  • Implemented ConstraintDual for Constraint.GeoMeanBridge, Constraint.RootDetBridge and Constraint.LogDetBridge and test duals in tests with GeoMeanCone and RootDetConeTriangle and LogDetConeTriangle cones (#1025, #1026).

v0.9.10 (January 31, 2020)

  • Added OptimizerWithAttributes grouping an optimizer constructor and a list of optimizer attributes (#1008).
  • Added RelativeEntropyCone with corresponding bridge into exponential cone constraints (#993).
  • Added NormSpectralCone and NormNuclearCone with corresponding bridges into positive semidefinite constraints (#976).
  • Added supports_constrained_variable(s) (#1004).
  • Added dual_set_type (#1002).
  • Added tests for vector specialized version of delete (#989, #1011).
  • Added PSD3 test (#1007).
  • Clarified dual solution of Tests.pow1v and Tests.pow1f (#1013).
  • Added support for EqualTo and Zero in Bridges.Constraint.SplitIntervalBridge (#1005).
  • Fixed Utilities.vectorize for empty vector (#1003).
  • Fixed free variables in LP writer (#1006).

v0.9.9 (December 29, 2019)

  • Incorporated MathOptFormat.jl as the FileFormats submodule. FileFormats provides readers and writers for a number of standard file formats and MOF, a file format specialized for MOI (#969).
  • Improved performance of deletion of vector of variables in MOI.Utilities.Model (#983).
  • Updated to MutableArithmetics v0.2 (#981).
  • Added MutableArithmetics.promote_operation allocation tests (#975).
  • Fixed inference issue on Julia v1.1 (#982).

v0.9.8 (December 19, 2019)

  • Implemented MutableArithmetics API (#924).
  • Fixed callbacks with CachingOptimizer (#959).
  • Fixed MOI.dimension for MOI.Complements (#948).
  • Added fallback for add_variables (#972).
  • Added is_diagonal_vectorized_index utility (#965).
  • Improved linear constraints display in manual (#963, #964).
  • Bridges improvements:
    • Added IndicatorSet to SOS1 bridge (#877).
    • Added support for starting values for Variable.VectorizeBridge (#944).
    • Fixed MOI.add_constraints with non-bridged variable constraint on bridged variable (#951).
    • Fixed corner cases and docstring of GeoMeanBridge (#961, #962, #966).
    • Fixed choice between variable or constraint bridges for constrained variables (#973).
    • Improve performance of bridge shortest path (#945, #946, #956).
    • Added docstring for test_delete_bridge (#954).
    • Added Variable bridge tests (#952).

v0.9.7 (October 30, 2019)

  • Implemented _result_index_field for NLPBlockDual (#934).
  • Fixed copy of model with starting values for vector constraints (#941).
  • Bridges improvements:
    • Improved performance of add_bridge and added has_bridge (#935).
    • Added AbstractSetMapBridge for bridges between sets S1, S2 such that there is a linear map A such that A*S1 = S2 (#933).
    • Added support for starting values for FlipSignBridge, VectorizeBridge, ScalarizeBridge, SlackBridge, SplitIntervalBridge, RSOCBridge, SOCRBridge NormInfinityBridge, SOCtoPSDBridge and RSOCtoPSDBridge (#933, #936, #937, #938, #939).

v0.9.6 (October 25, 2019)

  • Added complementarity constraints (#913).
  • Allowed ModelLike objects as value of attributes (#928).
  • Testing improvements:
    • Added dual_objective_value option to MOI.Test.TestConfig (#922).
    • Added InvalidIndex tests in basic_constraint_tests (#921).
    • Added tests for the constant term in indicator constraint (#929).
  • Bridges improvements:
    • Added support for starting values for Functionize bridges (#923).
    • Added variable indices context to variable bridges (#920).
    • Fixed a typo in printing o debug_supports (#927).

v0.9.5 (October 9, 2019)

  • Clarified PrimalStatus/DualStatus to be NO_SOLUTION if result_index is out of bounds (#912).
  • Added tolerance for checks and use ResultCount + 1 for the result_index in MOI.Test.solve_result_status (#910, #917).
  • Use 0.5 instead of 2.0 for power in PowerCone in basic_constraint_test (#916).
  • Bridges improvements:
    • Added debug utilities for unsupported variable/constraint/objective (#861).
    • Fixed deletion of variables in bridged VectorOfVariables constraints (#909).
    • Fixed result_index with objective bridges (#911).

v0.9.4 (October 2, 2019)

  • Added solver-independent MIP callbacks (#782).
  • Implements submit for Utilities.CachingOptimizer and Bridges.AbstractBridgeOptimizer (#906).
  • Added tests for result count of solution attributes (#901, #904).
  • Added NumberOfThreads attribute (#892).
  • Added Utilities.get_bounds to get the bounds on a variable (#890).
  • Added a note on duplicate coefficients in documentation (#581).
  • Added result index in ConstraintBasisStatus (#898).
  • Added extension dictionary to Utilities.Model (#884, #895).
  • Fixed deletion of constrained variables for CachingOptimizer (#905).
  • Implemented Utilities.shift_constraint for Test.UnknownScalarSet (#896).
  • Bridges improvements:
    • Added Variable.RSOCtoSOCBridge (#907).
    • Implemented MOI.get for ConstraintFunction/ConstraintSet for Bridges.Constraint.SquareBridge (#899).

v0.9.3 (September 20, 2019)

  • Fixed ambiguity detected in Julia v1.3 (#891, #893).
  • Fixed missing sets from ListOfSupportedConstraints (#880).
  • Fixed copy of VectorOfVariables constraints with duplicate indices (#886).
  • Added extension dictionary to MOIU.Model (#884).
  • Implemented MOI.get for function and set for GeoMeanBridge (#888).
  • Updated documentation for SingleVariable indices and bridges (#885).
  • Testing improvements:
    • Added more comprehensive tests for names (#882).
    • Added tests for SingleVariable duals (#883).
    • Added tests for DualExponentialCone and DualPowerCone (#873).
  • Improvements for arbitrary coefficient type:
    • Fixed == for sets with mutable fields (#887).
    • Removed some Float64 assumptions in bridges (#878).
    • Automatic selection of Constraint.[Scalar|Vector]FunctionizeBridge (#889).

v0.9.2 (September 5, 2019)

  • Implemented model printing for MOI.ModelLike and specialized it for models defined in MOI (864).
  • Generalized contlinear tests for arbitrary coefficient type (#855).
  • Fixed supports_constraint for Semiinteger and Semicontinuous and supports for ObjectiveFunction (#859).
  • Fixed Allocate-Load copy for single variable constraints (#856).
  • Bridges improvements:
    • Add objective bridges (#789).
    • Fixed Variable.RSOCtoPSDBridge for dimension 2 (#869).
    • Added Variable.SOCtoRSOCBridge (#865).
    • Added Constraint.SOCRBridge and disable MOI.Bridges.Constraint.SOCtoPSDBridge (#751).
    • Fixed added_constraint_types for Contraint.LogDetBridge and Constraint.RootDetBridge (#870).

v0.9.1 (August 22, 2019)

  • Fix support for Julia v1.2 (#834).
  • L1 and L∞ norm epigraph cones and corresponding bridges to LP were added (#818).
  • Added tests to MOI.Test.nametest (#833).
  • Fix MOI.Test.soc3test for solvers not supporting infeasibility certificates (#839).
  • Implements operate for operators * and / between vector function and constant (#837).
  • Implements show for MOI.Utilities.IndexMap (#847).
  • Fix corner cases for mapping of variables in MOI.Utilities.CachingOptimizer and substitution of variables in MOI.Bridges.AbstractBridgeOptimizer (#848).
  • Fix transformation of constant terms for MOI.Bridges.Constraint.SOCtoPSDBridge and MOI.Bridges.Constraint.RSOCtoPSDBridge (#840).

v0.9.0 (August 13, 2019)

  • Support for Julia v0.6 and v0.7 was dropped (#714, #717).
  • A MOI.Utilities.Model implementation of ModelLike, this should replace most use cases of MOI.Utilities.@model (#781).
  • add_constrained_variable and add_constrained_variables were added (#759).
  • Support for indicator constraints was added (#709, #712).
  • DualObjectiveValue attribute was added (#473).
  • RawParameter attribute was added (#733).
  • A dual_set function was added (#804).
  • A Benchmarks submodule was added to facilitate solver benchmarking (#769).
  • A submit function was added, this may for instance allow the user to submit solutions or cuts to the solver from a callback (#775).
  • The field of ObjectiveValue was renamed to result_index (#729).
  • The _constant and Utilities.getconstant function were renamed to constant
  • REDUCTION_CERTIFICATE result status was added (#734).
  • Abstract matrix sets were added (#731).
  • Testing improvements:
    • The testing guideline was updated (#728).
    • Quadratic tests were added (#697).
    • Unit tests for RawStatusString, SolveTime, Silent and SolverName were added (#726, #741).
    • A rotated second-order cone test was added (#759).
    • A power cone test was added (#768).
    • Tests for ZeroOne variables with variable bounds were added (#772).
    • An unbounded test was added (#773).
    • Existing tests had a few updates (#702, #703, #763).
  • Documentation improvements:
    • Added a section on CachingOptimizer (#777).
    • Added a section on UniversalFallback, Model and @model (#762).
    • Transition the knapsack example to a doctest with MockOptimizer (#786).
  • Utilities improvements:
    • A CleverDict utility was added for a vector that automatically transform into a dictionary once a first index is removed (#767).
    • The Utilities.constant function was renamed to Utilities.constant_vector (#740).
    • Implement optimizer attributes for CachingOptimizer (#745).
    • Rename Utilities.add_scalar_constraint to Utilities.normalize_and_add_constraint (#801).
    • operate with vcat, SingleVariable and VectorOfVariables now returns a VectorOfVariables (#616).
    • Fix a type piracy of operate (#784).
    • The load_constraint fallback signature was fixed (#760).
    • The set_dot function was extended to work with sparse arrays (#805).
  • Bridges improvements:
    • The bridges no longer store the constraint function and set before it is bridged, the bridges now have to implement ConstraintFunction and ConstraintSet if the user wants to recover them. As a consequence, the @bridge macro was removed (#722).
    • Bridge are now instantiated with a bridge_constraint function instead of using a constructor (#730).
    • Fix constraint attributes for bridges (#699).
    • Constraint bridges were moved to the Bridges/Constraint submodule so they should now inherit from MOI.Bridges.Constraint.Abstract and should implement MOI.Bridges.Constraint.concrete_bridge_type instead of MOI.Bridges.concrete_bridge_type (#756).
    • Variable bridges were added in (#759).
    • Various improvements (#746, #747).

v0.8.4 (March 13, 2019)

  • Performance improvement in default_copy_to and bridge optimizer (#696).
  • Add Silent and implement setting optimizer attributes in caching and mock optimizers (#695).
  • Add Functionize bridges (SingleVariable and VectorOfVariables) (#659).
  • Minor typo fixes (#694).

v0.8.3 (March 6, 2019)

  • Use zero constant in scalar constraint function of MOI.Test.copytest (#691).
  • Fix variable deletion with SingleVariable objective function (#690).
  • Fix LazyBridgeOptimizer with bridges that add no constraints (#689).
  • Error message improvements (#673, #685, #686, #688).
  • Documentation improvements (#682, #683, #687).
  • Basis status:
    • Remove VariableBasisStatus (#679).
    • Test ConstraintBasisStatus and implement it in bridges (#678).
  • Fix inference of NumberOfVariables and NumberOfConstraints (#677).
  • Implement division between a quadratic function and a number (#675).

v0.8.2 (February 7, 2019)

  • Add RawStatusString attribute (#629).
  • Do not set names to the optimizer but only to the cache in CachingOptimizer (#638).
  • Make scalar MOI functions act as scalars in broadcast (#646).
  • Add function utilities:
    • Implement Base.zero (#634), Base.iszero (#643), add missing arithmetic operations (#644, #645) and fix division (#648).
    • Add a vectorize function that turns a vector of ScalarAffineFunction into a VectorAffineFunction (#642).
  • Improve support for starting values:
    • Show a warning in copy when starting values are not supported instead of throwing an error (#630).
    • Fix UniversalFallback for getting an variable or constraint attribute set to no indices (#623).
    • Add a test in contlineartest with partially set VariablePrimalStart.
  • Bridges improvements:
    • Fix StackOverFlow in LazyBridgeOptimizer when there is a cycle in the graph of bridges.
    • Add Slack bridges (#610, #650).
    • Add FlipSign bridges (#658).
  • Add tests with duplicate coefficients in ScalarAffineFunction and VectorAffineFunction (#639).
  • Use tolerance to compare VariablePrimal in rotatedsoc1 test (#632).
  • Use a zero constant in ScalarAffineFunction of constraints in psdt2 (#622).

v0.8.1 (January 7, 2019)

  • Adding an NLP objective now overrides any objective set using the ObjectiveFunction attribute (#619).
  • Rename fullbridgeoptimizer into full_bridge_optimizer (#621).
  • Allow custom constraint types with full_bridge_optimizer (#617).
  • Add Vectorize bridge which transforms scalar linear constraints into vector linear constraints (#615).

v0.8.0 (December 18, 2018)

  • Rename all enum values to follow the JuMP naming guidelines for constants, for example, Optimal becomes OPTIMAL, and DualInfeasible becomes DUAL_INFEASIBLE.
  • Rename CachingOptimizer methods for style compliance.
  • Add an MOI.TerminationStatusCode called ALMOST_DUAL_INFEASIBLE.

v0.7.0 (December 13, 2018)

  • Test that MOI.TerminationStatus is MOI.OptimizeNotCalled before MOI.optimize! is called.
  • Check supports_default_copy_to in tests (#594).
  • Key pieces of information like optimality, infeasibility, etc., are now reported through TerminationStatusCode. It is typically no longer necessary to check the result statuses in addition to the termination status.
  • Add perspective dimension to log-det cone (#593).

v0.6.4 (November 27, 2018)

  • Add OptimizeNotCalled termination status (#577) and improve documentation of other statuses (#575).
  • Add a solver naming guideline (#578).
  • Make FeasibilitySense the default ObjectiveSense (#579).
  • Fix Utilities.@model and Bridges.@bridge macros for functions and sets defined outside MOI (#582).
  • Document solver-specific attributes (#580) and implement them in Utilities.CachingOptimizer (#565).

v0.6.3 (November 16, 2018)

  • Variables and constraints are now allowed to have duplicate names. An error is thrown only on lookup. This change breaks some existing tests. (#549)
  • Attributes may now be partially set (some values could be nothing). (#563)
  • Performance improvements in Utilities.Model (#549, #567, #568)
  • Fix bug in QuadtoSOC (#558).
  • New supports_default_copy_to method that optimizers should implement to control caching behavior.
  • Documentation improvements.

v0.6.2 (October 26, 2018)

  • Improve hygiene of @model macro (#544).
  • Fix bug in copy tests (#543).
  • Fix bug in UniversalFallback attribute getter (#540).
  • Allow all correct solutions for solve_blank_obj unit test (#537).
  • Add errors for Allocate-Load and bad constraints (#534).
  • [performance] Add specialized implementation of hash for VariableIndex (#533).
  • [performance] Construct the name to object dictionaries lazily in model (#535).
  • Add the QuadtoSOC bridge which transforms ScalarQuadraticFunction constraints into RotatedSecondOrderCone (#483).

v0.6.1 (September 22, 2018)

  • Enable PositiveSemidefiniteConeSquare set and quadratic functions in MOIB.fullbridgeoptimizer (#524).
  • Add warning in the bridge between PositiveSemidefiniteConeSquare and PositiveSemidefiniteConeTriangle when the matrix is almost symmetric (#522).
  • Modify MOIT.copytest to not add multiples constraints on the same variable (#521).
  • Add missing keyword argument in one of MOIU.add_scalar_constraint methods (#520).

v0.6.0 (August 30, 2018)

  • The MOIU.@model and MOIB.@bridge macros now support functions and sets defined in external modules. As a consequence, function and set names in the macro arguments need to be prefixed by module name.
  • Rename functions according to the JuMP style guide:
    • copy! with keyword arguments copynames and warnattributes -> copy_to with keyword arguments copy_names and warn_attributes;
    • set! -> set;
    • addvariable[s]! -> add_variable[s];
    • supportsconstraint -> supports_constraint;
    • addconstraint[s]! -> add_constraint[s];
    • isvalid -> is_valid;
    • isempty -> is_empty;
    • Base.delete! -> delete;
    • modify! -> modify;
    • transform! -> transform;
    • initialize! -> initialize;
    • write -> write_to_file; and
    • read! -> read_from_file.
  • Remove free! (use Base.finalize instead).
  • Add the SquarePSD bridge which transforms PositiveSemidefiniteConeTriangle constraints into PositiveSemidefiniteConeTriangle.
  • Add result fallback for ConstraintDual of variable-wise constraint, ConstraintPrimal and ObjectiveValue.
  • Add tests for ObjectiveBound.
  • Add test for empty rows in vector linear constraint.
  • Rework errors: CannotError has been renamed NotAllowedError and the distinction between UnsupportedError and NotAllowedError is now about whether the element is not supported (for example, it cannot be copied a model containing this element) or the operation is not allowed (either because it is not implemented, because it cannot be performed in the current state of the model, or because it cannot be performed for a specific index)
  • canget is removed. NoSolution is added as a result status to indicate that the solver does not have either a primal or dual solution available (See #479).

v0.5.0 (August 5, 2018)

  • Fix names with CachingOptimizer.
  • Cleanup thanks to @mohamed82008.
  • Added a universal fallback for constraints.
  • Fast utilities for function canonicalization thanks to @rdeits.
  • Renamed dimension field to side_dimension in the context of matrix-like sets.
  • New and improved tests for cases like duplicate terms and ObjectiveBound.
  • Removed cantransform, canaddconstraint, canaddvariable, canset, canmodify, and candelete functions from the API. They are replaced by a new set of errors that are thrown: Subtypes of UnsupportedError indicate unsupported operations, while subtypes of CannotError indicate operations that cannot be performed in the current state.
  • The API for copy! is updated to remove the CopyResult type.
  • Updates for the new JuMP style guide.

v0.4.1 (June 28, 2018)

  • Fixes vector function modification on 32 bits.
  • Fixes Bellman-Ford algorithm for bridges.
  • Added an NLP test with FeasibilitySense.
  • Update modification documentation.

v0.4.0 (June 23, 2018)

  • Helper constructors for VectorAffineTerm and VectorQuadraticTerm.
  • Added modify_lhs to TestConfig.
  • Additional unit tests for optimizers.
  • Added a type parameter to CachingOptimizer for the optimizer field.
  • New API for problem modification (#388)
  • Tests pass without deprecation warnings on Julia 0.7.
  • Small fixes and documentation updates.

v0.3.0 (May 25, 2018)

  • Functions have been redefined to use arrays-of-structs instead of structs-of-arrays.
  • Improvements to MockOptimizer.
  • Significant changes to Bridges.
  • New and improved unit tests.
  • Fixes for Julia 0.7.

v0.2.0 (April 24, 2018)

  • Improvements to and better coverage of Tests.
  • Documentation fixes.
  • SolverName attribute.
  • Changes to the NLP interface (new definition of variable order and arrays of structs for bound pairs and sparsity patterns).
  • Addition of NLP tests.
  • Introduction of UniversalFallback.
  • copynames keyword argument to MOI.copy!.
  • Add Bridges submodule.

v0.1.0 (February 28, 2018)

  • Initial public release.
  • The framework for MOI was developed at the JuMP-dev workshop at MIT in June 2017 as a sorely needed replacement for MathProgBase.
diff --git a/previews/PR3536/moi/developer/checklists/index.html b/previews/PR3536/moi/developer/checklists/index.html new file mode 100644 index 00000000000..5e58742f134 --- /dev/null +++ b/previews/PR3536/moi/developer/checklists/index.html @@ -0,0 +1,116 @@ + +Checklists · JuMP

Checklists

The purpose of this page is to collate a series of checklists for commonly performed changes to the source code of MathOptInterface.

In each case, copy the checklist into the description of the pull request.

Making a release

Use this checklist when making a release of the MathOptInterface repository.

## Basic
+
+ - [ ] `version` field of `Project.toml` has been updated
+       - If a breaking change, increment the MAJOR field and reset others to 0
+       - If adding new features, increment the MINOR field and reset PATCH to 0
+       - If adding bug fixes or documentation changes, increment the PATCH field
+
+## Documentation
+
+ - [ ] Add a new entry to `docs/src/changelog.md`, following existing style
+
+## Tests
+
+ - [ ] The `solver-tests.yml` GitHub action does not have unexpected failures.
+       To run the action, go to:
+       https://github.com/jump-dev/MathOptInterface.jl/actions/workflows/solver-tests.yml
+       and click "Run workflow"

Adding a new set

Use this checklist when adding a new set to the MathOptInterface repository.

## Basic
+
+ - [ ] Add a new `AbstractScalarSet` or `AbstractVectorSet` to `src/sets.jl`
+ - [ ] If `isbitstype(S) == false`, implement `Base.copy(set::S)`
+ - [ ] If `isbitstype(S) == false`, implement `Base.:(==)(x::S, y::S)`
+ - [ ] If an `AbstractVectorSet`, implement `dimension(set::S)`, unless the
+       dimension is given by `set.dimension`.
+
+## Utilities
+
+ - [ ] If an `AbstractVectorSet`, implement `Utilities.set_dot`,
+       unless the dot product between two vectors in the set is equivalent to
+       `LinearAlgebra.dot`
+ - [ ] If an `AbstractVectorSet`, implement `Utilities.set_with_dimension` in
+       `src/Utilities/matrix_of_constraints.jl`
+ - [ ] Add the set to the `@model` macro at the bottom of `src/Utilities.model.jl`
+
+## Documentation
+
+ - [ ] Add a docstring, which gives the mathematical definition of the set,
+       along with an `## Example` block containing a `jldoctest`
+ - [ ] Add the docstring to `docs/src/reference/standard_form.md`
+ - [ ] Add the set to the relevant table in `docs/src/manual/standard_form.md`
+
+## Tests
+
+ - [ ] Define a new `_set(::Type{S})` method in `src/Test/test_basic_constraint.jl`
+       and add the name of the set to the list at the bottom of that files
+ - [ ] If the set has any checks in its constructor, add tests to `test/sets.jl`
+
+## MathOptFormat
+
+ - [ ] Open an issue at `https://github.com/jump-dev/MathOptFormat` to add
+       support for the new set {{ replace with link to the issue }}
+
+## Optional
+
+ - [ ] Implement `dual_set(::S)` and `dual_set_type(::Type{S})`
+ - [ ] Add new tests to the `Test` submodule exercising your new set
+ - [ ] Add new bridges to convert your set into more commonly used sets

Adding a new bridge

Use this checklist when adding a new bridge to the MathOptInterface repository.

The steps are mostly the same, but locations depend on whether the bridge is a Constraint, Objective, or Variable bridge. In each case below, replace XXX with the appropriate type of bridge.

## Basic
+
+ - [ ] Create a new file in `src/Bridges/XXX/bridges`
+ - [ ] Define the bridge, following existing examples. The name of the bridge
+       struct must end in `Bridge`
+ - [ ] Check if your bridge can be a subtype of [`MOI.Bridges.Constraint.SetMapBridge`](@ref)
+ - [ ] Define a new `const` that is a `SingleBridgeOptimizer` wrapping the
+       new bridge. The name of the const must be the name of the bridge, less
+       the `Bridge` suffix
+ - [ ] `include` the file in `src/Bridges/XXX/bridges/XXX.jl`
+ - [ ] If the bridge should be enabled by default, add the bridge to
+       `add_all_bridges` at the bottom of `src/Bridges/XXX/XXX.jl`
+
+## Tests
+
+ - [ ] Create a new file in the appropriate subdirectory of `tests/Bridges/XXX`
+ - [ ] Use `MOI.Bridges.runtests` to test various inputs and outputs of the
+       bridge
+ - [ ] If, after opening the pull request to add the bridge, some lines are not
+       covered by the tests, add additional bridge-specific tests to cover the
+       untested lines.
+
+## Documentation
+
+ - [ ] Add a docstring which uses the same template as existing bridges.
+ - [ ] Add the docstring to `docs/src/submodules/Bridges/list_of_bridges.md`
+
+## Final touch
+
+If the bridge depends on run-time values of other variables and constraints in
+the model:
+
+ - [ ] Implement `MOI.Utilities.needs_final_touch(::Bridge)`
+ - [ ] Implement `MOI.Utilities.final_touch(::Bridge, ::MOI.ModelLike)`
+ - [ ] Ensure that `final_touch` can be called multiple times in a row

Updating MathOptFormat

Use this checklist when updating the version of MathOptFormat.

## Basic
+
+ - [ ] The file at `src/FileFormats/MOF/mof.X.Y.schema.json` is updated
+ - [ ] The constants `SCHEMA_PATH`, `VERSION`, and `SUPPORTED_VERSIONS` are
+       updated in `src/FileFormats/MOF/MOF.jl`
+
+## New sets
+
+ - [ ] New sets are added to the `@model` in `src/FileFormats/MOF/MOF.jl`
+ - [ ] New sets are added to the `@enum` in `src/FileFormats/MOF/read.jl`
+ - [ ] `set_to_moi` is defined for each set in `src/FileFormats/MOF/read.jl`
+ - [ ] `head_name` is defined for each set in `src/FileFormats/MOF/write.jl`
+ - [ ] A new unit test calling `_test_model_equality` is aded to
+       `test/FileFormats/MOF/MOF.jl`
+
+## Tests
+
+ - [ ] The version field in `test/FileFormats/MOF/nlp.mof.json` is updated
+
+## Documentation
+
+ - [ ] The version fields are updated in `docs/src/submodules/FileFormats/overview.md`
diff --git a/previews/PR3536/moi/index.html b/previews/PR3536/moi/index.html new file mode 100644 index 00000000000..0136692f309 --- /dev/null +++ b/previews/PR3536/moi/index.html @@ -0,0 +1,13 @@ + +Introduction · JuMP

Introduction

Warning

This documentation in this section is a copy of the official MathOptInterface documentation available at https://jump.dev/MathOptInterface.jl/v1.20.1. It is included here to make it easier to link concepts between JuMP and MathOptInterface.

What is MathOptInterface?

MathOptInterface.jl (MOI) is an abstraction layer designed to provide a unified interface to mathematical optimization solvers so that users do not need to understand multiple solver-specific APIs.

Tip

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 or 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 Tutorials section contains articles on how to use and implement the MathOptInteraface API. Look here if you want to write a model in MOI, or write an interface to a new solver.
  • The Manual contains short code-snippets that explain how to use the MOI API. Look here for more details on particular areas of MOI.
  • The Background section contains articles on the theory behind MathOptInterface. Look here if you want to understand why, rather than how.
  • The API Reference contains a complete list of functions and types that comprise the MOI API. Look here is you want to know how to use (or implement) a particular function.
  • The Submodules section contains stand-alone documentation for each of the submodules within MOI. These submodules are not required to interface a solver with MOI, but they make the job much easier.

Citing MathOptInterface

A paper describing the design and features of MathOptInterface is available on arXiv.

If you find MathOptInterface useful in your work, we kindly request that you cite the following paper:

@article{legat2021mathoptinterface,
+    title={{MathOptInterface}: a data structure for mathematical optimization problems},
+    author={Legat, Beno{\^\i}t and Dowson, Oscar and Garcia, Joaquim Dias and Lubin, Miles},
+    journal={INFORMS Journal on Computing},
+    year={2021},
+    doi={10.1287/ijoc.2021.1067},
+    publisher={INFORMS}
+}
diff --git a/previews/PR3536/moi/manual/constraints/index.html b/previews/PR3536/moi/manual/constraints/index.html new file mode 100644 index 00000000000..a5b842e1f58 --- /dev/null +++ b/previews/PR3536/moi/manual/constraints/index.html @@ -0,0 +1,26 @@ + +Constraints · JuMP

Constraints

Add a constraint

Use add_constraint to add a single constraint.

julia> c = MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Nonnegatives(2))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Nonnegatives}(1)

add_constraint returns a ConstraintIndex type, which is used to refer to the added constraint in other calls.

Check if a ConstraintIndex is valid using is_valid.

julia> MOI.is_valid(model, c)
+true

Use add_constraints to add a number of constraints of the same type.

julia> c = MOI.add_constraints(
+           model,
+           [x[1], x[2]],
+           [MOI.GreaterThan(0.0), MOI.GreaterThan(1.0)]
+       )
+2-element Vector{MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.GreaterThan{Float64}}}:
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.GreaterThan{Float64}}(1)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.GreaterThan{Float64}}(2)

This time, a vector of ConstraintIndex are returned.

Use supports_constraint to check if the model supports adding a constraint type.

julia> MOI.supports_constraint(
+           model,
+           MOI.VariableIndex,
+           MOI.GreaterThan{Float64},
+       )
+true

Delete a constraint

Use delete to delete a constraint.

julia> MOI.delete(model, c)
+
+julia> MOI.is_valid(model, c)
+false

Constraint attributes

The following attributes are available for constraints:

Get and set these attributes using get and set.

julia> MOI.set(model, MOI.ConstraintName(), c, "con_c")
+
+julia> MOI.get(model, MOI.ConstraintName(), c)
+"con_c"

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 non-negative (resp. non-positive) real numbers.

Linear constraints

Mathematical ConstraintMOI FunctionMOI Set
$a^Tx \le \beta$ScalarAffineFunctionLessThan
$a^Tx \ge \alpha$ScalarAffineFunctionGreaterThan
$a^Tx = \beta$ScalarAffineFunctionEqualTo
$\alpha \le a^Tx \le \beta$ScalarAffineFunctionInterval
$x_i \le \beta$VariableIndexLessThan
$x_i \ge \alpha$VariableIndexGreaterThan
$x_i = \beta$VariableIndexEqualTo
$\alpha \le x_i \le \beta$VariableIndexInterval
$Ax + b \in \mathbb{R}_+^n$VectorAffineFunctionNonnegatives
$Ax + b \in \mathbb{R}_-^n$VectorAffineFunctionNonpositives
$Ax + b = 0$VectorAffineFunctionZeros

By convention, solvers are not expected to support nonzero constant terms in the ScalarAffineFunctions the first four rows of the preceding table because they are redundant with the parameters of the sets. For example, encode $2x + 1 \le 2$ as $2x \le 1$.

Constraints with VariableIndex in LessThan, GreaterThan, EqualTo, or Interval 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 will throw respectively LowerBoundAlreadySet or UpperBoundAlreadySet.

Moreover, adding two VariableIndex 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.

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 constraint and by using separate LessThan and GreaterThan 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 ConstraintMOI FunctionMOI Set
$\lVert Ax + b\rVert_2 \le c^Tx + d$VectorAffineFunctionSecondOrderCone
$y \ge \lVert x \rVert_2$VectorOfVariablesSecondOrderCone
$2yz \ge \lVert x \rVert_2^2, y,z \ge 0$VectorOfVariablesRotatedSecondOrderCone
$(a_1^Tx + b_1,a_2^Tx + b_2,a_3^Tx + b_3) \in \mathcal{E}$VectorAffineFunctionExponentialCone
$A(x) \in \mathcal{S}_+$VectorAffineFunctionPositiveSemidefiniteConeTriangle
$B(x) \in \mathcal{S}_+$VectorAffineFunctionPositiveSemidefiniteConeSquare
$x \in \mathcal{S}_+$VectorOfVariablesPositiveSemidefiniteConeTriangle
$x \in \mathcal{S}_+$VectorOfVariablesPositiveSemidefiniteConeSquare

where $\mathcal{E}$ is the exponential cone (see ExponentialCone), $\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 ConstraintMOI FunctionMOI Set
$\frac{1}{2}x^TQx + a^Tx + b \ge 0$ScalarQuadraticFunctionGreaterThan
$\frac{1}{2}x^TQx + a^Tx + b \le 0$ScalarQuadraticFunctionLessThan
$\frac{1}{2}x^TQx + a^Tx + b = 0$ScalarQuadraticFunctionEqualTo
Bilinear matrix inequalityVectorQuadraticFunctionPositiveSemidefiniteCone...
Note

For more details on the internal format of the quadratic functions see ScalarQuadraticFunction or VectorQuadraticFunction.

Discrete and logical constraints

Mathematical ConstraintMOI FunctionMOI Set
$x_i \in \mathbb{Z}$VariableIndexInteger
$x_i \in \{0,1\}$VariableIndexZeroOne
$x_i \in \{0\} \cup [l,u]$VariableIndexSemicontinuous
$x_i \in \{0\} \cup \{l,l+1,\ldots,u-1,u\}$VariableIndexSemiinteger
At most one component of $x$ can be nonzeroVectorOfVariablesSOS1
At most two components of $x$ can be nonzero, and if so they must be adjacent componentsVectorOfVariablesSOS2
$y = 1 \implies a^T x \in S$VectorAffineFunctionIndicator

JuMP mapping

The following bullet points show examples of how JuMP constraints are translated into MOI function-set pairs:

  • @constraint(m, 2x + y <= 10) becomes ScalarAffineFunction-in-LessThan
  • @constraint(m, 2x + y >= 10) becomes ScalarAffineFunction-in-GreaterThan
  • @constraint(m, 2x + y == 10) becomes ScalarAffineFunction-in-EqualTo
  • @constraint(m, 0 <= 2x + y <= 10) becomes ScalarAffineFunction-in-Interval
  • @constraint(m, 2x + y in ArbitrarySet()) becomes ScalarAffineFunction-in-ArbitrarySet.

Variable bounds are handled in a similar fashion:

  • @variable(m, x <= 1) becomes VariableIndex-in-LessThan
  • @variable(m, x >= 1) becomes VariableIndex-in-GreaterThan

One notable difference is that a variable with an upper and lower bound is translated into two constraints, rather than an interval, that is:

  • @variable(m, 0 <= x <= 1) becomes VariableIndex-in-LessThan and VariableIndex-in-GreaterThan.
diff --git a/previews/PR3536/moi/manual/models/index.html b/previews/PR3536/moi/manual/models/index.html new file mode 100644 index 00000000000..d1e5b94dd75 --- /dev/null +++ b/previews/PR3536/moi/manual/models/index.html @@ -0,0 +1,6 @@ + +Models · JuMP

Models

The most significant part of MOI is the definition of the model API that is used to specify an instance of an optimization problem (for example, by adding variables and constraints). Objects that implement the model API must inherit from the ModelLike abstract type.

Notably missing from the model API is the method to solve an optimization problem. ModelLike objects may store an instance (for example, in memory or backed by a file format) without being linked to a particular solver. In addition to the model API, MOI defines AbstractOptimizer and provides methods to solve the model and interact with solutions. See the Solutions section for more details.

Info

Throughout the rest of the manual, model is used as a generic ModelLike, and optimizer is used as a generic AbstractOptimizer.

Tip

MOI does not export functions, but for brevity we often omit qualifying names with the MOI module. Best practice is to have

import MathOptInterface as MOI

and prefix all MOI methods with MOI. in user code. If a name is also available in base Julia, we always explicitly use the module prefix, for example, with MOI.get.

Attributes

Attributes are properties of the model that can be queried and modified. These include constants such as the number of variables in a model NumberOfVariables), and properties of variables and constraints such as the name of a variable (VariableName).

There are four types of attributes:

Some attributes are values that can be queried by the user but not modified, while other attributes can be modified by the user.

All interactions with attributes occur through the get and set functions.

Consult the docstrings of each attribute for information on what it represents.

ModelLike API

The following attributes are available:

AbstractOptimizer API

The following attributes are available:

diff --git a/previews/PR3536/moi/manual/modification/index.html b/previews/PR3536/moi/manual/modification/index.html new file mode 100644 index 00000000000..0a92cbb5f28 --- /dev/null +++ b/previews/PR3536/moi/manual/modification/index.html @@ -0,0 +1,129 @@ + +Problem modification · JuMP

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

Some ModelLike objects do not support problem modification.

Modify the set of a constraint

Use set and ConstraintSet to modify the set of a constraint by replacing it with a new instance of the same type.

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> 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 to GreaterThan). For these cases, MathOptInterface provides the transform method.

The transform function returns a new constraint index, and the old constraint index (that is, c) is no longer valid.

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 cannot be called with a set of the same type. Use set instead.

Modify the function of a constraint

Use set and ConstraintFunction to modify the function of a constraint by replacing it with a new instance of the same type.

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> MOI.set(model, MOI.ConstraintFunction(), c, x)
+ERROR: [...]

Modify constant term in a scalar function

Use modify and ScalarConstantChange to modify the constant term in a ScalarAffineFunction or ScalarQuadraticFunction.

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

ScalarConstantChange can also be used to modify the objective function by passing an instance of ObjectiveFunction:

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 and VectorConstantChange to modify the constant vector in a VectorAffineFunction or VectorQuadraticFunction.

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 and ScalarCoefficientChange to modify the affine coefficient of a ScalarAffineFunction or ScalarQuadraticFunction.

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

ScalarCoefficientChange can also be used to modify the objective function by passing an instance of ObjectiveFunction.

Modify affine coefficients in a vector function

Use modify and MultirowChange to modify a vector of affine coefficients in a VectorAffineFunction or a VectorQuadraticFunction.

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/previews/PR3536/moi/manual/solutions/index.html b/previews/PR3536/moi/manual/solutions/index.html new file mode 100644 index 00000000000..ad9993d07ee --- /dev/null +++ b/previews/PR3536/moi/manual/solutions/index.html @@ -0,0 +1,39 @@ + +Solutions · JuMP

Solutions

Solving and retrieving the results

Once an optimizer is loaded with the objective function and all of the constraints, we can ask the solver to solve the model by calling optimize!.

MOI.optimize!(optimizer)

Why did the solver stop?

The optimization procedure may stop for a number of reasons. The TerminationStatus attribute of the optimizer returns a TerminationStatusCode 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 attribute is as follows:

status = MOI.get(optimizer, TerminationStatus())
+if status == MOI.OPTIMAL
+    # Ok, we solved the problem!
+else
+    # Handle other cases.
+end

After checking the TerminationStatus, check ResultCount. 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 infeasibility, a solver may return no result or a proof of infeasibility. The ResultCount attribute distinguishes between these two cases.

Primal solutions

Use the PrimalStatus optimizer attribute to return a ResultStatusCode describing the status of the primal solution.

Common returns are described below in the Common status situations section.

Query the primal solution using the VariablePrimal and ConstraintPrimal attributes.

Query the objective function value using the ObjectiveValue attribute.

Dual solutions

Warning

See Duality for a discussion of the MOI conventions for primal-dual pairs and certificates.

Use the DualStatus optimizer attribute to return a ResultStatusCode describing the status of the dual solution.

Query the dual solution using the ConstraintDual attribute.

Query the dual objective function value using the DualObjectiveValue attribute.

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?TerminationStatusResultCountPrimalStatusDualStatus
Proved optimalityOPTIMAL1FEASIBLE_POINTFEASIBLE_POINT
Proved infeasibleINFEASIBLE1NO_SOLUTIONINFEASIBILITY_CERTIFICATE
Optimal within relaxed tolerancesALMOST_OPTIMAL1FEASIBLE_POINTFEASIBLE_POINT
Optimal within relaxed tolerancesALMOST_OPTIMAL1ALMOST_FEASIBLE_POINTALMOST_FEASIBLE_POINT
Detected an unbounded ray of the primalDUAL_INFEASIBLE1INFEASIBILITY_CERTIFICATENO_SOLUTION
StallSLOW_PROGRESS1**

Global branch-and-bound solvers

Mixed-integer programming solvers fall into this category.

What happened?TerminationStatusResultCountPrimalStatusDualStatus
Proved optimalityOPTIMAL1FEASIBLE_POINTNO_SOLUTION
Presolve detected infeasibility or unboundednessINFEASIBLE_OR_UNBOUNDED0NO_SOLUTIONNO_SOLUTION
Proved infeasibilityINFEASIBLE0NO_SOLUTIONNO_SOLUTION
Timed out (no solution)TIME_LIMIT0NO_SOLUTIONNO_SOLUTION
Timed out (with a solution)TIME_LIMIT1FEASIBLE_POINTNO_SOLUTION
CPXMIP_OPTIMAL_INFEASALMOST_OPTIMAL1INFEASIBLE_POINTNO_SOLUTION
Info

CPXMIP_OPTIMAL_INFEAS 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.

What happened?TerminationStatusResultCountPrimalStatusDualStatus
Converged to a stationary pointLOCALLY_SOLVED1FEASIBLE_POINTFEASIBLE_POINT
Completed a non-global tree search (with a solution)LOCALLY_SOLVED1FEASIBLE_POINTFEASIBLE_POINT
Converged to an infeasible pointLOCALLY_INFEASIBLE1INFEASIBLE_POINT*
Completed a non-global tree search (no solution found)LOCALLY_INFEASIBLE0NO_SOLUTIONNO_SOLUTION
Iteration limitITERATION_LIMIT1**
Diverging iteratesNORM_LIMIT or OBJECTIVE_LIMIT1**

Querying solution attributes

Some solvers will not implement every solution attribute. Therefore, a call like MOI.get(model, MOI.SolveTimeSec()) may throw an UnsupportedAttribute error.

If you need to write code that is agnostic to the solver (for example, you are writing a library that an end-user passes their choice of solver to), you can work-around this problem using a try-catch:

function get_solve_time(model)
+    try
+        return MOI.get(model, MOI.SolveTimeSec())
+    catch err
+        if err isa MOI.UnsupportedAttribute
+            return NaN  # Solver doesn't support. Return a placeholder value.
+        end
+        rethrow(err)  # Something else went wrong. Rethrow the error
+    end
+end

If, after careful profiling, you find that the try-catch is taking a significant portion of your runtime, you can improve performance by caching the result of the try-catch:

mutable struct CachedSolveTime{M}
+    model::M
+    supports_solve_time::Bool
+    CachedSolveTime(model::M) where {M} = new(model, true)
+end
+
+function get_solve_time(model::CachedSolveTime)
+    if !model.supports_solve_time
+        return NaN
+    end
+    try
+        return MOI.get(model, MOI.SolveTimeSec())
+    catch err
+        if err isa MOI.UnsupportedAttribute
+            model.supports_solve_time = false
+            return NaN
+        end
+        rethrow(err)  # Something else went wrong. Rethrow the error
+    end
+end
diff --git a/previews/PR3536/moi/manual/standard_form/index.html b/previews/PR3536/moi/manual/standard_form/index.html new file mode 100644 index 00000000000..0c245f30219 --- /dev/null +++ b/previews/PR3536/moi/manual/standard_form/index.html @@ -0,0 +1,10 @@ + +Standard form problem · JuMP

Standard form problem

MathOptInterface represents optimization problems in the standard form:

\[\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 objects
  • the sets $\mathcal{S}_1, \ldots, \mathcal{S}_m$ are specified by AbstractSet objects
Tip

For more information on this standard form, read our paper.

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:

FunctionDescription
VariableIndex$x_j$, the projection onto a single coordinate defined by a variable index $j$.
VectorOfVariablesThe projection onto multiple coordinates (that is, extracting a sub-vector).
ScalarAffineFunction$a^T x + b$, where $a$ is a vector and $b$ scalar.
ScalarNonlinearFunction$f(x)$, where $f$ is a nonlinear function.
VectorAffineFunction$A x + b$, where $A$ is a matrix and $b$ is a vector.
ScalarQuadraticFunction$\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.
VectorQuadraticFunctionA vector of scalar-valued quadratic functions.
VectorNonlinearFunction$f(x)$, where $f$ is a vector-valued nonlinear function.

Extensions for nonlinear programming are present but not yet well documented.

One-dimensional sets

The one-dimensional set types implemented in MathOptInterface.jl are:

SetDescription
LessThan(u)$(-\infty, u]$
GreaterThan(l)$[l, \infty)$
EqualTo(v)$\{v\}$
Interval(l, u)$[l, u]$
Integer()$\mathbb{Z}$
ZeroOne()$\{ 0, 1 \}$
Semicontinuous(l, u)$\{ 0\} \cup [l, u]$
Semiinteger(l, u)$\{ 0\} \cup \{l,l+1,\ldots,u-1,u\}$

Vector cones

The vector-valued set types implemented in MathOptInterface.jl are:

SetDescription
Reals(d)$\mathbb{R}^{d}$
Zeros(d)$0^{d}$
Nonnegatives(d)$\{ x \in \mathbb{R}^{d} : x \ge 0 \}$
Nonpositives(d)$\{ x \in \mathbb{R}^{d} : x \le 0 \}$
SecondOrderCone(d)$\{ (t,x) \in \mathbb{R}^{d} : t \ge \lVert x \rVert_2 \}$
RotatedSecondOrderCone(d)$\{ (t,u,x) \in \mathbb{R}^{d} : 2tu \ge \lVert x \rVert_2^2, t \ge 0,u \ge 0 \}$
ExponentialCone()$\{ (x,y,z) \in \mathbb{R}^3 : y \exp (x/y) \le z, y > 0 \}$
DualExponentialCone()$\{ (u,v,w) \in \mathbb{R}^3 : -u \exp (v/u) \le \exp(1) w, u < 0 \}$
GeometricMeanCone(d)$\{ (t,x) \in \mathbb{R}^{1+n} : x \ge 0, t \le \sqrt[n]{x_1 x_2 \cdots x_n} \}$ where $n$ is $d - 1$
PowerCone(α)$\{ (x,y,z) \in \mathbb{R}^3 : x^{\alpha} y^{1-\alpha} \ge |z|, x \ge 0,y \ge 0 \}$
DualPowerCone(α)$\{ (u,v,w) \in \mathbb{R}^3 : \left(\frac{u}{\alpha}\right(^{\alpha}\left(\frac{v}{1-\alpha}\right)^{1-\alpha} \ge |w|, u,v \ge 0 \}$
NormOneCone(d)$\{ (t,x) \in \mathbb{R}^{d} : t \ge \sum_i \lvert x_i \rvert \}$
NormInfinityCone(d)$\{ (t,x) \in \mathbb{R}^{d} : t \ge \max_i \lvert x_i \rvert \}$
RelativeEntropyCone(d)$\{ (u, v, w) \in \mathbb{R}^{d} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}$
HyperRectangle(l, u)$\{x \in \bar{\mathbb{R}}^d: x_i \in [l_i, u_i] \forall i=1,\ldots,d\}$
NormCone(p, d)``{ (t,x) \in \mathbb{R}^{d} : t \ge \left(\sum\limits_i

Matrix cones

The matrix-valued set types implemented in MathOptInterface.jl are:

SetDescription
RootDetConeTriangle(d)$\{ (t,X) \in \mathbb{R}^{1+d(1+d)/2} : t \le \det(X)^{1/d}, X \mbox{ is the upper triangle of a PSD matrix} \}$
RootDetConeSquare(d)$\{ (t,X) \in \mathbb{R}^{1+d^2} : t \le \det(X)^{1/d}, X \mbox{ is a PSD matrix} \}$
PositiveSemidefiniteConeTriangle(d)$\{ X \in \mathbb{R}^{d(d+1)/2} : X \mbox{ is the upper triangle of a PSD matrix} \}$
PositiveSemidefiniteConeSquare(d)$\{ X \in \mathbb{R}^{d^2} : X \mbox{ is a PSD matrix} \}$
LogDetConeTriangle(d)$\{ (t,u,X) \in \mathbb{R}^{2+d(1+d)/2} : t \le u\log(\det(X/u)), X \mbox{ is the upper triangle of a PSD matrix}, u > 0 \}$
LogDetConeSquare(d)$\{ (t,u,X) \in \mathbb{R}^{2+d^2} : t \le u \log(\det(X/u)), X \mbox{ is a PSD matrix}, u > 0 \}$
NormSpectralCone(r, c)$\{ (t, X) \in \mathbb{R}^{1 + r \times c} : t \ge \sigma_1(X), X \mbox{ is a } r\times c\mbox{ matrix} \}$
NormNuclearCone(r, c)$\{ (t, X) \in \mathbb{R}^{1 + r \times c} : t \ge \sum_i \sigma_i(X), X \mbox{ is a } r\times c\mbox{ matrix} \}$
HermitianPositiveSemidefiniteConeTriangle(d)The cone of Hermitian positive semidefinite matrices, with
side_dimension rows and columns.
Scaled(S)The set S scaled so that Utilities.set_dot corresponds to LinearAlgebra.dot

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

Other sets are vector-valued, with a particular combinatorial structure. Read their docstrings for more information on how to interpret them.

SetDescription
SOS1A Special Ordered Set (SOS) of Type I
SOS2A Special Ordered Set (SOS) of Type II
IndicatorA set to specify an indicator constraint
ComplementsA set to specify a mixed complementarity constraint
AllDifferentThe all_different global constraint
BinPackingThe bin_packing global constraint
CircuitThe circuit global constraint
CountAtLeastThe at_least global constraint
CountBelongsThe nvalue global constraint
CountDistinctThe distinct global constraint
CountGreaterThanThe count_gt global constraint
CumulativeThe cumulative global constraint
PathThe path global constraint
TableThe table global constraint
diff --git a/previews/PR3536/moi/manual/variables/index.html b/previews/PR3536/moi/manual/variables/index.html new file mode 100644 index 00000000000..d0bc13a6b0f --- /dev/null +++ b/previews/PR3536/moi/manual/variables/index.html @@ -0,0 +1,17 @@ + +Variables · JuMP

Variables

Add a variable

Use add_variable to add a single variable.

julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)

add_variable returns a VariableIndex type, which is used to refer to the added variable in other calls.

Check if a VariableIndex is valid using is_valid.

julia> MOI.is_valid(model, x)
+true

Use add_variables to add a number of variables.

julia> y = MOI.add_variables(model, 2)
+2-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(2)
+ MOI.VariableIndex(3)
Warning

The integer does not necessarily correspond to the column inside an optimizer.

Delete a variable

Delete a variable using delete.

julia> MOI.delete(model, x)
+
+julia> MOI.is_valid(model, x)
+false
Warning

Not all ModelLike models support deleting variables. A DeleteNotAllowed error is thrown if this is not supported.

Variable attributes

The following attributes are available for variables:

Get and set these attributes using get and set.

julia> MOI.set(model, MOI.VariableName(), x, "var_x")
+
+julia> MOI.get(model, MOI.VariableName(), x)
+"var_x"
diff --git a/previews/PR3536/moi/reference/callbacks/index.html b/previews/PR3536/moi/reference/callbacks/index.html new file mode 100644 index 00000000000..3bfd5b34da6 --- /dev/null +++ b/previews/PR3536/moi/reference/callbacks/index.html @@ -0,0 +1,36 @@ + +Callbacks · JuMP

Callbacks

MathOptInterface.AbstractCallbackType
abstract type AbstractCallback <: AbstractModelAttribute end

Abstract type for a model attribute representing a callback function. The value set to subtypes of AbstractCallback is a function that may be called during optimize!. As optimize! is in progress, the result attributes (i.e, the attributes attr such that is_set_by_optimize(attr)) may not be accessible from the callback, hence trying to get result attributes might throw a OptimizeInProgress error.

At most one callback of each type can be registered. If an optimizer already has a function for a callback type, and the user registers a new function, then the old one is replaced.

The value of the attribute should be a function taking only one argument, commonly called callback_data, that can be used for instance in LazyConstraintCallback, HeuristicCallback and UserCutCallback.

source
MathOptInterface.submitFunction
submit(
+    optimizer::AbstractOptimizer,
+    sub::AbstractSubmittable,
+    values...,
+)::Nothing

Submit values to the submittable sub of the optimizer optimizer.

An UnsupportedSubmittable error is thrown if model does not support the attribute attr (see supports) and a SubmitNotAllowed error is thrown if it supports the submittable sub but it cannot be submitted.

source

Attributes

MathOptInterface.CallbackNodeStatusCodeType
CallbackNodeStatusCode

An Enum of possible return values from calling get with CallbackNodeStatus.

Values

Possible values are:

source

Lazy constraints

MathOptInterface.LazyConstraintCallbackType
LazyConstraintCallback() <: AbstractCallback

The callback can be used to reduce the feasible set given the current primal solution by submitting a LazyConstraint. For instance, it may be called at an incumbent of a mixed-integer problem. Note that there is no guarantee that the callback is called at every feasible primal solution.

The current primal solution is accessed through CallbackVariablePrimal. Trying to access other result attributes will throw OptimizeInProgress as discussed in AbstractCallback.

Examples

x = MOI.add_variables(optimizer, 8)
+MOI.set(optimizer, MOI.LazyConstraintCallback(), callback_data -> begin
+    sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x)
+    if # should add a lazy constraint
+        func = # computes function
+        set = # computes set
+        MOI.submit(optimizer, MOI.LazyConstraint(callback_data), func, set)
+    end
+end)
source
MathOptInterface.LazyConstraintType
LazyConstraint(callback_data)

Lazy constraint func-in-set submitted as func, set. The optimal solution returned by VariablePrimal will satisfy all lazy constraints that have been submitted.

This can be submitted only from the LazyConstraintCallback. The field callback_data is a solver-specific callback type that is passed as the argument to the feasible solution callback.

Examples

Suppose x and y are VariableIndexs of optimizer. To add a LazyConstraint for 2x + 3y <= 1, write

func = 2.0x + 3.0y
+set = MOI.LessThan(1.0)
+MOI.submit(optimizer, MOI.LazyConstraint(callback_data), func, set)

inside a LazyConstraintCallback of data callback_data.

source

User cuts

MathOptInterface.UserCutCallbackType
UserCutCallback() <: AbstractCallback

The callback can be used to submit UserCut given the current primal solution. For instance, it may be called at fractional (i.e., non-integer) nodes in the branch and bound tree of a mixed-integer problem. Note that there is not guarantee that the callback is called everytime the solver has an infeasible solution.

The infeasible solution is accessed through CallbackVariablePrimal. Trying to access other result attributes will throw OptimizeInProgress as discussed in AbstractCallback.

Examples

x = MOI.add_variables(optimizer, 8)
+MOI.set(optimizer, MOI.UserCutCallback(), callback_data -> begin
+    sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x)
+    if # can find a user cut
+        func = # computes function
+        set = # computes set
+        MOI.submit(optimizer, MOI.UserCut(callback_data), func, set)
+    end
+end
source
MathOptInterface.UserCutType
UserCut(callback_data)

Constraint func-to-set suggested to help the solver detect the solution given by CallbackVariablePrimal as infeasible. The cut is submitted as func, set. Typically CallbackVariablePrimal will violate integrality constraints, and a cut would be of the form ScalarAffineFunction-in-LessThan or ScalarAffineFunction-in-GreaterThan. Note that, as opposed to LazyConstraint, the provided constraint cannot modify the feasible set, the constraint should be redundant, e.g., it may be a consequence of affine and integrality constraints.

This can be submitted only from the UserCutCallback. The field callback_data is a solver-specific callback type that is passed as the argument to the infeasible solution callback.

Note that the solver may silently ignore the provided constraint.

source

Heuristic solutions

MathOptInterface.HeuristicCallbackType
HeuristicCallback() <: AbstractCallback

The callback can be used to submit HeuristicSolution given the current primal solution. For example, it may be called at fractional (i.e., non-integer) nodes in the branch and bound tree of a mixed-integer problem. Note that there is no guarantee that the callback is called every time the solver has an infeasible solution.

The current primal solution is accessed through CallbackVariablePrimal. Trying to access other result attributes will throw OptimizeInProgress as discussed in AbstractCallback.

Examples

x = MOI.add_variables(optimizer, 8)
+MOI.set(optimizer, MOI.HeuristicCallback(), callback_data -> begin
+    sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x)
+    if # can find a heuristic solution
+        values = # computes heuristic solution
+        MOI.submit(optimizer, MOI.HeuristicSolution(callback_data), x,
+                   values)
+    end
+end
source
MathOptInterface.HeuristicSolutionType
HeuristicSolution(callback_data)

Heuristically obtained feasible solution. The solution is submitted as variables, values where values[i] gives the value of variables[i], similarly to set. The submit call returns a HeuristicSolutionStatus indicating whether the provided solution was accepted or rejected.

This can be submitted only from the HeuristicCallback. The field callback_data is a solver-specific callback type that is passed as the argument to the heuristic callback.

Some solvers require a complete solution, others only partial solutions.

source
diff --git a/previews/PR3536/moi/reference/constraints/index.html b/previews/PR3536/moi/reference/constraints/index.html new file mode 100644 index 00000000000..1f339f5d735 --- /dev/null +++ b/previews/PR3536/moi/reference/constraints/index.html @@ -0,0 +1,16 @@ + +Constraints · JuMP

Constraints

Types

MathOptInterface.ConstraintIndexType
ConstraintIndex{F, S}

A type-safe wrapper for Int64 for use in referencing F-in-S constraints in a model. The parameter F is the type of the function in the constraint, and the parameter S is the type of set in the constraint. To allow for deletion, indices need not be consecutive. Indices within a constraint type (i.e. F-in-S) must be unique, but non-unique indices across different constraint types are allowed. If F is VariableIndex then the index is equal to the index of the variable. That is for an index::ConstraintIndex{VariableIndex}, we always have

index.value == MOI.get(model, MOI.ConstraintFunction(), index).value
source

Functions

MathOptInterface.is_validMethod
is_valid(model::ModelLike, index::Index)::Bool

Return a Bool indicating whether this index refers to a valid object in the model model.

source
MathOptInterface.add_constraintFunction
add_constraint(model::ModelLike, func::F, set::S)::ConstraintIndex{F,S} where {F,S}

Add the constraint $f(x) \in \mathcal{S}$ where $f$ is defined by func, and $\mathcal{S}$ is defined by set.

add_constraint(model::ModelLike, v::VariableIndex, set::S)::ConstraintIndex{VariableIndex,S} where {S}
+add_constraint(model::ModelLike, vec::Vector{VariableIndex}, set::S)::ConstraintIndex{VectorOfVariables,S} where {S}

Add the constraint $v \in \mathcal{S}$ where $v$ is the variable (or vector of variables) referenced by v and $\mathcal{S}$ is defined by set.

source
MathOptInterface.add_constraintsFunction
add_constraints(model::ModelLike, funcs::Vector{F}, sets::Vector{S})::Vector{ConstraintIndex{F,S}} where {F,S}

Add the set of constraints specified by each function-set pair in funcs and sets. F and S should be concrete types. This call is equivalent to add_constraint.(model, funcs, sets) but may be more efficient.

source
MathOptInterface.transformFunction

Transform Constraint Set

transform(model::ModelLike, c::ConstraintIndex{F,S1}, newset::S2)::ConstraintIndex{F,S2}

Replace the set in constraint c with newset. The constraint index c will no longer be valid, and the function returns a new constraint index with the correct type.

Solvers may only support a subset of constraint transforms that they perform efficiently (for example, changing from a LessThan to GreaterThan set). In addition, set modification (where S1 = S2) should be performed via the modify function.

Typically, the user should delete the constraint and add a new one.

Examples

If c is a ConstraintIndex{ScalarAffineFunction{Float64},LessThan{Float64}},

c2 = transform(model, c, GreaterThan(0.0))
+transform(model, c, LessThan(0.0)) # errors
source
MathOptInterface.supports_constraintFunction
supports_constraint(
+    model::ModelLike,
+    ::Type{F},
+    ::Type{S},
+)::Bool where {F<:AbstractFunction,S<:AbstractSet}

Return a Bool indicating whether model supports F-in-S constraints, that is, copy_to(model, src) does not throw UnsupportedConstraint when src contains F-in-S constraints. If F-in-S constraints are only not supported in specific circumstances, e.g. F-in-S constraints cannot be combined with another type of constraint, it should still return true.

source
MOI.supports_constraint(
+    BT::Type{<:AbstractBridge},
+    F::Type{<:MOI.AbstractFunction},
+    S::Type{<:MOI.AbstractSet},
+)::Bool

Return a Bool indicating whether the bridges of type BT support bridging F-in-S constraints.

Implementation notes

  • This method depends only on the type of the inputs, not the runtime values.
  • There is a default fallback, so you need only implement this method for constraint types that the bridge implements.
source

Attributes

MathOptInterface.ConstraintNameType
ConstraintName()

A constraint attribute for a string identifying the constraint.

It is valid for constraints variables to have the same name; however, constraints with duplicate names cannot be looked up using get, regardless of whether they have the same F-in-S type.

ConstraintName has a default value of "" if not set.

Notes

You should not implement ConstraintName for VariableIndex constraints.

source
MathOptInterface.ConstraintPrimalType
ConstraintPrimal(result_index::Int = 1)

A constraint attribute for the assignment to some constraint's primal value(s) in result result_index.

If the constraint is f(x) in S, then in most cases the ConstraintPrimal is the value of f, evaluated at the correspondng VariablePrimal solution.

However, some conic solvers reformulate b - Ax in S to s = b - Ax, s in S. These solvers may return the value of s for ConstraintPrimal, rather than b - Ax. (Although these are constrained by an equality constraint, due to numerical tolerances they may not be identical.)

If the solver does not have a primal value for the constraint because the result_index is beyond the available solutions (whose number is indicated by the ResultCount attribute), getting this attribute must throw a ResultIndexBoundsError. Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check PrimalStatus before accessing the ConstraintPrimal attribute.

If result_index is omitted, it is 1 by default. See ResultCount for information on how the results are ordered.

source
MathOptInterface.ConstraintDualType
ConstraintDual(result_index::Int = 1)

A constraint attribute for the assignment to some constraint's dual value(s) in result result_index. If result_index is omitted, it is 1 by default.

If the solver does not have a dual value for the variable because the result_index is beyond the available solutions (whose number is indicated by the ResultCount attribute), getting this attribute must throw a ResultIndexBoundsError. Otherwise, if the result is unavailable for another reason (for instance, only a primal solution is available), the result is undefined. Users should first check DualStatus before accessing the ConstraintDual attribute.

See ResultCount for information on how the results are ordered.

source
MathOptInterface.ConstraintBasisStatusType
ConstraintBasisStatus(result_index::Int = 1)

A constraint attribute for the BasisStatusCode of some constraint in result result_index, with respect to an available optimal solution basis. If result_index is omitted, it is 1 by default.

If the solver does not have a basis statue for the constraint because the result_index is beyond the available solutions (whose number is indicated by the ResultCount attribute), getting this attribute must throw a ResultIndexBoundsError. Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check PrimalStatus before accessing the ConstraintBasisStatus attribute.

See ResultCount for information on how the results are ordered.

Notes

For the basis status of a variable, query VariableBasisStatus.

ConstraintBasisStatus does not apply to VariableIndex constraints. You can infer the basis status of a VariableIndex constraint by looking at the result of VariableBasisStatus.

source
MathOptInterface.ConstraintFunctionType
ConstraintFunction()

A constraint attribute for the AbstractFunction object used to define the constraint.

It is guaranteed to be equivalent but not necessarily identical to the function provided by the user.

source
MathOptInterface.CanonicalConstraintFunctionType
CanonicalConstraintFunction()

A constraint attribute for a canonical representation of the AbstractFunction object used to define the constraint.

Getting this attribute is guaranteed to return a function that is equivalent but not necessarily identical to the function provided by the user.

By default, MOI.get(model, MOI.CanonicalConstraintFunction(), ci) fallbacks to MOI.Utilities.canonical(MOI.get(model, MOI.ConstraintFunction(), ci)). However, if model knows that the constraint function is canonical then it can implement a specialized method that directly return the function without calling Utilities.canonical. Therefore, the value returned cannot be assumed to be a copy of the function stored in model. Moreover, Utilities.Model checks with Utilities.is_canonical whether the function stored internally is already canonical and if it's the case, then it returns the function stored internally instead of a copy.

source
MathOptInterface.BasisStatusCodeType
BasisStatusCode

An Enum of possible values for the ConstraintBasisStatus and VariableBasisStatus attributes, explaining the status of a given element with respect to an optimal solution basis.

Notes

  • NONBASIC_AT_LOWER and NONBASIC_AT_UPPER should be used only for

constraints with the Interval set. In this case, they are necessary to distinguish which side of the constraint is active. One-sided constraints (e.g., LessThan and GreaterThan) should use NONBASIC instead of the NONBASIC_AT_* values. This restriction does not apply to VariableBasisStatus, which should return NONBASIC_AT_* regardless of whether the alternative bound exists.

  • In linear programs, SUPER_BASIC occurs when a variable with no bounds is not

in the basis.

Values

Possible values are:

source
diff --git a/previews/PR3536/moi/reference/errors/index.html b/previews/PR3536/moi/reference/errors/index.html new file mode 100644 index 00000000000..293a34e1f16 --- /dev/null +++ b/previews/PR3536/moi/reference/errors/index.html @@ -0,0 +1,52 @@ + +Errors · JuMP

Errors

When an MOI call fails on a model, precise errors should be thrown when possible instead of simply calling error with a message. The docstrings for the respective methods describe the errors that the implementation should throw in certain situations. This error-reporting system allows code to distinguish between internal errors (that should be shown to the user) and unsupported operations which may have automatic workarounds.

When an invalid index is used in an MOI call, an InvalidIndex is thrown:

When an invalid result index is used to retrieve an attribute, a ResultIndexBoundsError is thrown:

MathOptInterface.ResultIndexBoundsErrorType
struct ResultIndexBoundsError{AttrType} <: Exception
+    attr::AttrType
+    result_count::Int
+end

An error indicating that the requested attribute attr could not be retrieved, because the solver returned too few results compared to what was requested. For instance, the user tries to retrieve VariablePrimal(2) when only one solution is available, or when the model is infeasible and has no solution.

See also: check_result_index_bounds.

source

As discussed in JuMP mapping, for scalar constraint with a nonzero function constant, a ScalarFunctionConstantNotZero exception may be thrown:

Some VariableIndex constraints cannot be combined on the same variable:

As discussed in AbstractCallback, trying to get attributes inside a callback may throw:

MathOptInterface.OptimizeInProgressType
struct OptimizeInProgress{AttrType<:AnyAttribute} <: Exception
+    attr::AttrType
+end

Error thrown from optimizer when MOI.get(optimizer, attr) is called inside an AbstractCallback while it is only defined once optimize! has completed. This can only happen when is_set_by_optimize(attr) is true.

source

Trying to submit the wrong type of AbstractSubmittable inside an AbstractCallback (for example, a UserCut inside a LazyConstraintCallback) will throw:

The rest of the errors defined in MOI fall in two categories represented by the following two abstract types:

MathOptInterface.NotAllowedErrorType
NotAllowedError <: Exception

Abstract type for error thrown when an operation is supported but cannot be applied in the current state of the model.

source

The different UnsupportedError and NotAllowedError are the following errors:

MathOptInterface.UnsupportedAttributeType
struct UnsupportedAttribute{AttrType} <: UnsupportedError
+    attr::AttrType
+    message::String
+end

An error indicating that the attribute attr is not supported by the model, i.e. that supports returns false.

source
MathOptInterface.SetAttributeNotAllowedType
struct SetAttributeNotAllowed{AttrType} <: NotAllowedError
+    attr::AttrType
+    message::String
+end

An error indicating that the attribute attr is supported (see supports) but cannot be set for some reason (see the error string).

source
MathOptInterface.AddVariableNotAllowedType
struct AddVariableNotAllowed <: NotAllowedError
+    message::String # Human-friendly explanation why the attribute cannot be set
+end

An error indicating that variables cannot be added to the model.

source
MathOptInterface.UnsupportedConstraintType
struct UnsupportedConstraint{F<:AbstractFunction, S<:AbstractSet} <: UnsupportedError
+    message::String # Human-friendly explanation why the attribute cannot be set
+end

An error indicating that constraints of type F-in-S are not supported by the model, i.e. that supports_constraint returns false.

source
MathOptInterface.AddConstraintNotAllowedType
struct AddConstraintNotAllowed{F<:AbstractFunction, S<:AbstractSet} <: NotAllowedError
+    message::String # Human-friendly explanation why the attribute cannot be set
+end

An error indicating that constraints of type F-in-S are supported (see supports_constraint) but cannot be added.

source
MathOptInterface.ModifyConstraintNotAllowedType
struct ModifyConstraintNotAllowed{F<:AbstractFunction, S<:AbstractSet,
+                                  C<:AbstractFunctionModification} <: NotAllowedError
+    constraint_index::ConstraintIndex{F, S}
+    change::C
+    message::String
+end

An error indicating that the constraint modification change cannot be applied to the constraint of index ci.

source
MathOptInterface.ModifyObjectiveNotAllowedType
struct ModifyObjectiveNotAllowed{C<:AbstractFunctionModification} <: NotAllowedError
+    change::C
+    message::String
+end

An error indicating that the objective modification change cannot be applied to the objective.

source
MathOptInterface.DeleteNotAllowedType
struct DeleteNotAllowed{IndexType <: Index} <: NotAllowedError
+    index::IndexType
+    message::String
+end

An error indicating that the index index cannot be deleted.

source
MathOptInterface.UnsupportedSubmittableType
struct UnsupportedSubmittable{SubmitType} <: UnsupportedError
+    sub::SubmitType
+    message::String
+end

An error indicating that the submittable sub is not supported by the model, i.e. that supports returns false.

source
MathOptInterface.SubmitNotAllowedType
struct SubmitNotAllowed{SubmitTyp<:AbstractSubmittable} <: NotAllowedError
+    sub::SubmitType
+    message::String
+end

An error indicating that the submittable sub is supported (see supports) but cannot be added for some reason (see the error string).

source
MathOptInterface.UnsupportedNonlinearOperatorType
UnsupportedNonlinearOperator(head::Symbol[, message::String]) <: UnsupportedError

An error thrown by optimizers if they do not support the operator head in a ScalarNonlinearFunction.

Example

julia> import MathOptInterface as MOI
+
+julia> throw(MOI.UnsupportedNonlinearOperator(:black_box))
+ERROR: MathOptInterface.UnsupportedNonlinearOperator: The nonlinear operator `:black_box` is not supported by the model.
+Stacktrace:
+[...]
source

Note that setting the ConstraintFunction of a VariableIndex constraint is not allowed:

diff --git a/previews/PR3536/moi/reference/models/index.html b/previews/PR3536/moi/reference/models/index.html new file mode 100644 index 00000000000..a18eee0d2c0 --- /dev/null +++ b/previews/PR3536/moi/reference/models/index.html @@ -0,0 +1,136 @@ + +Models · JuMP

Models

Attribute interface

MathOptInterface.is_set_by_optimizeFunction
is_set_by_optimize(::AnyAttribute)

Return a Bool indicating whether the value of the attribute is modified during an optimize! call, that is, the attribute is used to query the result of the optimization.

Important note when defining new attributes

This function returns false by default so it should be implemented for attributes that are modified by optimize!.

source
MathOptInterface.is_copyableFunction
is_copyable(::AnyAttribute)

Return a Bool indicating whether the value of the attribute may be copied during copy_to using set.

Important note when defining new attributes

By default is_copyable(attr) returns !is_set_by_optimize(attr). A specific method should be defined for attributes which are copied indirectly during copy_to. For instance, both is_copyable and is_set_by_optimize return false for the following attributes:

source
MathOptInterface.getFunction
get(model::GenericModel, attr::MathOptInterface.AbstractOptimizerAttribute)

Return the value of the attribute attr from the model's MOI backend.

source
get(model::GenericModel, attr::MathOptInterface.AbstractModelAttribute)

Return the value of the attribute attr from the model's MOI backend.

source
MOI.get(b::AbstractBridge, ::MOI.NumberOfVariables)::Int64

Return the number of variables created by the bridge b in the model.

See also MOI.NumberOfConstraints.

Implementation notes

  • There is a default fallback, so you need only implement this if the bridge adds new variables.
source
MOI.get(b::AbstractBridge, ::MOI.ListOfVariableIndices)

Return the list of variables created by the bridge b.

See also MOI.ListOfVariableIndices.

Implementation notes

  • There is a default fallback, so you need only implement this if the bridge adds new variables.
source
MOI.get(b::AbstractBridge, ::MOI.NumberOfConstraints{F,S})::Int64 where {F,S}

Return the number of constraints of the type F-in-S created by the bridge b.

See also MOI.NumberOfConstraints.

Implementation notes

  • There is a default fallback, so you need only implement this for the constraint types returned by added_constraint_types.
source
MOI.get(b::AbstractBridge, ::MOI.ListOfConstraintIndices{F,S}) where {F,S}

Return a Vector{ConstraintIndex{F,S}} with indices of all constraints of type F-in-S created by the bride b.

See also MOI.ListOfConstraintIndices.

Implementation notes

  • There is a default fallback, so you need only implement this for the constraint types returned by added_constraint_types.
source
function MOI.get(
+    model::MOI.ModelLike,
+    attr::MOI.AbstractConstraintAttribute,
+    bridge::AbstractBridge,
+)

Return the value of the attribute attr of the model model for the constraint bridged by bridge.

source
get(optimizer::AbstractOptimizer, attr::AbstractOptimizerAttribute)

Return an attribute attr of the optimizer optimizer.

get(model::ModelLike, attr::AbstractModelAttribute)

Return an attribute attr of the model model.

get(model::ModelLike, attr::AbstractVariableAttribute, v::VariableIndex)

If the attribute attr is set for the variable v in the model model, return its value, return nothing otherwise. If the attribute attr is not supported by model then an error should be thrown instead of returning nothing.

get(model::ModelLike, attr::AbstractVariableAttribute, v::Vector{VariableIndex})

Return a vector of attributes corresponding to each variable in the collection v in the model model.

get(model::ModelLike, attr::AbstractConstraintAttribute, c::ConstraintIndex)

If the attribute attr is set for the constraint c in the model model, return its value, return nothing otherwise. If the attribute attr is not supported by model then an error should be thrown instead of returning nothing.

get(
+    model::ModelLike,
+    attr::AbstractConstraintAttribute,
+    c::Vector{ConstraintIndex{F,S}},
+) where {F,S}

Return a vector of attributes corresponding to each constraint in the collection c in the model model.

get(model::ModelLike, ::Type{VariableIndex}, name::String)

If a variable with name name exists in the model model, return the corresponding index, otherwise return nothing. Errors if two variables have the same name.

get(
+    model::ModelLike,
+    ::Type{ConstraintIndex{F,S}},
+    name::String,
+) where {F,S}

If an F-in-S constraint with name name exists in the model model, return the corresponding index, otherwise return nothing. Errors if two constraints have the same name.

get(model::ModelLike, ::Type{ConstraintIndex}, name::String)

If any constraint with name name exists in the model model, return the corresponding index, otherwise return nothing. This version is available for convenience but may incur a performance penalty because it is not type stable. Errors if two constraints have the same name.

source
MathOptInterface.get!Function
get!(output, model::ModelLike, args...)

An in-place version of get.

The signature matches that of get except that the the result is placed in the vector output.

source
MathOptInterface.setFunction
function MOI.set(
+    model::MOI.ModelLike,
+    attr::MOI.AbstractConstraintAttribute,
+    bridge::AbstractBridge,
+    value,
+)

Set the value of the attribute attr of the model model for the constraint bridged by bridge.

source
set(optimizer::AbstractOptimizer, attr::AbstractOptimizerAttribute, value)

Assign value to the attribute attr of the optimizer optimizer.

set(model::ModelLike, attr::AbstractModelAttribute, value)

Assign value to the attribute attr of the model model.

set(model::ModelLike, attr::AbstractVariableAttribute, v::VariableIndex, value)

Assign value to the attribute attr of variable v in model model.

set(
+    model::ModelLike,
+    attr::AbstractVariableAttribute,
+    v::Vector{VariableIndex},
+    vector_of_values,
+)

Assign a value respectively to the attribute attr of each variable in the collection v in model model.

set(
+    model::ModelLike,
+    attr::AbstractConstraintAttribute,
+    c::ConstraintIndex,
+    value,
+)

Assign a value to the attribute attr of constraint c in model model.

set(
+    model::ModelLike,
+    attr::AbstractConstraintAttribute,
+    c::Vector{ConstraintIndex{F,S}},
+    vector_of_values,
+) where {F,S}

Assign a value respectively to the attribute attr of each constraint in the collection c in model model.

An UnsupportedAttribute error is thrown if model does not support the attribute attr (see supports) and a SetAttributeNotAllowed error is thrown if it supports the attribute attr but it cannot be set.

set(
+    model::ModelLike,
+    ::ConstraintSet,
+    c::ConstraintIndex{F,S},
+    set::S,
+) where {F,S}

Change the set of constraint c to the new set set which should be of the same type as the original set.

set(
+    model::ModelLike,
+    ::ConstraintFunction,
+    c::ConstraintIndex{F,S},
+    func::F,
+) where {F,S}

Replace the function in constraint c with func. F must match the original function type used to define the constraint.

Note

Setting the constraint function is not allowed if F is VariableIndex; a SettingVariableIndexNotAllowed error is thrown instead. This is because, it would require changing the index c since the index of VariableIndex constraints must be the same as the index of the variable.

source
MathOptInterface.supportsFunction
MOI.supports(
+    model::MOI.ModelLike,
+    attr::MOI.AbstractConstraintAttribute,
+    BT::Type{<:AbstractBridge},
+)

Return a Bool indicating whether BT supports setting attr to model.

source
supports(model::ModelLike, sub::AbstractSubmittable)::Bool

Return a Bool indicating whether model supports the submittable sub.

supports(model::ModelLike, attr::AbstractOptimizerAttribute)::Bool

Return a Bool indicating whether model supports the optimizer attribute attr. That is, it returns false if copy_to(model, src) shows a warning in case attr is in the ListOfOptimizerAttributesSet of src; see copy_to for more details on how unsupported optimizer attributes are handled in copy.

supports(model::ModelLike, attr::AbstractModelAttribute)::Bool

Return a Bool indicating whether model supports the model attribute attr. That is, it returns false if copy_to(model, src) cannot be performed in case attr is in the ListOfModelAttributesSet of src.

supports(
+    model::ModelLike,
+    attr::AbstractVariableAttribute,
+    ::Type{VariableIndex},
+)::Bool

Return a Bool indicating whether model supports the variable attribute attr. That is, it returns false if copy_to(model, src) cannot be performed in case attr is in the ListOfVariableAttributesSet of src.

supports(
+    model::ModelLike,
+    attr::AbstractConstraintAttribute,
+    ::Type{ConstraintIndex{F,S}},
+)::Bool where {F,S}

Return a Bool indicating whether model supports the constraint attribute attr applied to an F-in-S constraint. That is, it returns false if copy_to(model, src) cannot be performed in case attr is in the ListOfConstraintAttributesSet of src.

For all five methods, if the attribute is only not supported in specific circumstances, it should still return true.

Note that supports is only defined for attributes for which is_copyable returns true as other attributes do not appear in the list of attributes set obtained by ListOf...AttributesSet.

source
MathOptInterface.attribute_value_typeFunction
attribute_value_type(attr::AnyAttribute)

Given an attribute attr, return the type of value expected by get, or returned by set.

Notes

  • Only implement this if it make sense to do so. If un-implemented, the default is Any.
source

Model interface

MathOptInterface.is_emptyFunction
is_empty(model::ModelLike)

Returns false if the model has any model attribute set or has any variables or constraints.

Note that an empty model can have optimizer attributes set.

source
MathOptInterface.empty!Function
empty!(model::ModelLike)

Empty the model, that is, remove all variables, constraints and model attributes but not optimizer attributes.

source
MathOptInterface.write_to_fileFunction
write_to_file(model::ModelLike, filename::String)

Write the current model to the file at filename.

Supported file types depend on the model type.

source
MathOptInterface.read_from_fileFunction
read_from_file(model::ModelLike, filename::String)

Read the file filename into the model model. If model is non-empty, this may throw an error.

Supported file types depend on the model type.

Note

Once the contents of the file are loaded into the model, users can query the variables via get(model, ListOfVariableIndices()). However, some filetypes, such as LP files, do not maintain an explicit ordering of the variables. Therefore, the returned list may be in an arbitrary order.

To avoid depending on the order of the indices, look up each variable index by name using get(model, VariableIndex, "name").

source
MathOptInterface.copy_toFunction
copy_to(dest::ModelLike, src::ModelLike)::IndexMap

Copy the model from src into dest.

The target dest is emptied, and all previous indices to variables and constraints in dest are invalidated.

Returns an IndexMap object that translates variable and constraint indices from the src model to the corresponding indices in the dest model.

Notes

AbstractOptimizerAttributes are not copied to the dest model.

IndexMap

Implementations of copy_to must return an IndexMap. For technical reasons, this type is defined in the Utilities submodule as MOI.Utilities.IndexMap. However, since it is an integral part of the MOI API, we provide MOI.IndexMap as an alias.

Example

# Given empty `ModelLike` objects `src` and `dest`.
+
+x = add_variable(src)
+
+is_valid(src, x)   # true
+is_valid(dest, x)  # false (`dest` has no variables)
+
+index_map = copy_to(dest, src)
+is_valid(dest, x) # false (unless index_map[x] == x)
+is_valid(dest, index_map[x]) # true
source
MathOptInterface.IndexMapType
IndexMap()

The dictionary-like object returned by copy_to.

IndexMap

Implementations of copy_to must return an IndexMap. For technical reasons, the IndexMap type is defined in the Utilities submodule as MOI.Utilities.IndexMap. However, since it is an integral part of the MOI API, we provide this MOI.IndexMap as an alias.

source

Model attributes

MathOptInterface.NameType
Name()

A model attribute for the string identifying the model. It has a default value of "" if not set`.

source
MathOptInterface.ObjectiveFunctionType
ObjectiveFunction{F<:AbstractScalarFunction}()

A model attribute for the objective function which has a type F<:AbstractScalarFunction.

F should be guaranteed to be equivalent but not necessarily identical to the function type provided by the user.

Throws an InexactError if the objective function cannot be converted to F, e.g., the objective function is quadratic and F is ScalarAffineFunction{Float64} or it has non-integer coefficient and F is ScalarAffineFunction{Int}.

source
MathOptInterface.ObjectiveFunctionTypeType
ObjectiveFunctionType()

A model attribute for the type F of the objective function set using the ObjectiveFunction{F} attribute.

Examples

In the following code, attr should be equal to MOI.VariableIndex:

x = MOI.add_variable(model)
+MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x)
+attr = MOI.get(model, MOI.ObjectiveFunctionType())
source
MathOptInterface.ObjectiveSenseType
ObjectiveSense()

A model attribute for the objective sense of the objective function, which must be an OptimizationSense: MIN_SENSE, MAX_SENSE, or FEASIBILITY_SENSE. The default is FEASIBILITY_SENSE.

Interaction with ObjectiveFunction

Setting the sense to FEASIBILITY_SENSE unsets the ObjectiveFunction attribute. That is, if you first set ObjectiveFunction and then set ObjectiveSense to be FEASIBILITY_SENSE, no objective function will be passed to the solver.

In addition, some reformulations of ObjectiveFunction via bridges rely on the value of ObjectiveSense. Therefore, you should set ObjectiveSense before setting ObjectiveFunction.

source
MathOptInterface.ListOfModelAttributesSetType
ListOfModelAttributesSet()

A model attribute for the Vector{AbstractModelAttribute} of all model attributes attr such that:

  1. is_copyable(attr) returns true, and
  2. the attribute was set to the model
source
MathOptInterface.ListOfVariableAttributesSetType
ListOfVariableAttributesSet()

A model attribute for the Vector{AbstractVariableAttribute} of all variable attributes attr such that 1) is_copyable(attr) returns true and 2) the attribute was set to variables.

source
MathOptInterface.UserDefinedFunctionType
UserDefinedFunction(name::Symbol, arity::Int) <: AbstractModelAttribute

Set this attribute to register a user-defined function by the name of name with arity arguments.

Once registered, name will appear in ListOfSupportedNonlinearOperators.

You cannot register multiple UserDefinedFunctions with the same name but different arity.

Value type

The value to be set is a tuple containing one, two, or three functions to evaluate the function, the first-order derivative, and the second-order derivative respectively. Both derivatives are optional, but if you pass the second-order derivative you must also pass the first-order derivative.

For univariate functions with arity == 1, the functions in the tuple must have the form:

  • f(x::T)::T: returns the value of the function at x
  • ∇f(x::T)::T: returns the first-order derivative of f with respect to x
  • ∇²f(x::T)::T: returns the second-order derivative of f with respect to x.

For multivariate functions with arity > 1, the functions in the tuple must have the form:

  • f(x::T...)::T: returns the value of the function at x
  • ∇f(g::AbstractVector{T}, x::T...)::Nothing: fills the components of g, with g[i] being the first-order partial derivative of f with respect to x[i]
  • ∇²f(H::AbstractMatrix{T}, x::T...)::Nothing: fills the non-zero components of H, with H[i, j] being the second-order partial derivative of f with respect to x[i] and then x[j]. H is initialized to the zero matrix, so you do not need to set any zero elements.

Examples

julia> import MathOptInterface as MOI
+
+julia> f(x, y) = x^2 + y^2
+f (generic function with 1 method)
+
+julia> function ∇f(g, x, y)
+           g .= 2 * x, 2 * y
+           return
+       end
+∇f (generic function with 1 method)
+
+julia> function ∇²f(H, x...)
+           H[1, 1] = H[2, 2] = 2.0
+           return
+       end
+∇²f (generic function with 1 method)
+
+julia> model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
+MOIU.UniversalFallback{MOIU.Model{Float64}}
+fallback for MOIU.Model{Float64}
+
+julia> MOI.set(model, MOI.UserDefinedFunction(:f, 2), (f,))
+
+julia> MOI.set(model, MOI.UserDefinedFunction(:g, 2), (f, ∇f))
+
+julia> MOI.set(model, MOI.UserDefinedFunction(:h, 2), (f, ∇f, ∇²f))
+
+julia> x = MOI.add_variables(model, 2)
+2-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+ MOI.VariableIndex(2)
+
+julia> MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
+
+julia> obj_f = MOI.ScalarNonlinearFunction(:f, Any[x[1], x[2]])
+f(MOI.VariableIndex(1), MOI.VariableIndex(2))
+
+julia> MOI.set(model, MOI.ObjectiveFunction{typeof(obj_f)}(), obj_f)
+
+julia> print(model)
+Minimize ScalarNonlinearFunction:
+ f(v[1], v[2])
+
+Subject to:
+
source

Optimizer interface

MathOptInterface.AbstractOptimizerType
AbstractOptimizer <: ModelLike

Abstract supertype for objects representing an instance of an optimization problem tied to a particular solver. This is typically a solver's in-memory representation. In addition to ModelLike, AbstractOptimizer objects let you solve the model and query the solution.

source
MathOptInterface.OptimizerWithAttributesType
struct OptimizerWithAttributes
+    optimizer_constructor
+    params::Vector{Pair{AbstractOptimizerAttribute,<:Any}}
+end

Object grouping an optimizer constructor and a list of optimizer attributes. Instances are created with instantiate.

source
MathOptInterface.optimize!Method
optimize!(dest::AbstractOptimizer, src::ModelLike)::Tuple{IndexMap,Bool}

A "one-shot" call that copies the problem from src into dest and then uses dest to optimize the problem.

Returns a tuple of an IndexMap and a Bool copied.

  • The IndexMap object translates variable and constraint indices from the src model to the corresponding indices in the dest optimizer. See copy_to for details.
  • If copied == true, src was copied to dest and then cached, allowing incremental modification if supported by the solver.
  • If copied == false, a cache of the model was not kept in dest. Therefore, only the solution information (attributes for which is_set_by_optimize is true) is available to query.
Note

The main purpose of optimize! method with two arguments is for use in Utilities.CachingOptimizer.

Relationship to the single-argument optimize!

The default fallback of optimize!(dest::AbstractOptimizer, src::ModelLike) is

function optimize!(dest::AbstractOptimizer, src::ModelLike)
+    index_map = copy_to(dest, src)
+    optimize!(dest)
+    return index_map, true
+end

Therefore, subtypes of AbstractOptimizer should either implement this two-argument method, or implement both copy_to(::Optimizer, ::ModelLike) and optimize!(::Optimizer).

source
MathOptInterface.instantiateFunction
instantiate(
+    optimizer_constructor,
+    with_cache_type::Union{Nothing,Type} = nothing,
+    with_bridge_type::Union{Nothing,Type} = nothing,
+)

Create an instance of an optimizer by either:

  • calling optimizer_constructor.optimizer_constructor() and setting the parameters in optimizer_constructor.params if optimizer_constructor is a OptimizerWithAttributes
  • calling optimizer_constructor() if optimizer_constructor is callable.

withcachetype

If with_cache_type is not nothing, then the optimizer is wrapped in a Utilities.CachingOptimizer to store a cache of the model. This is most useful if the optimizer you are constructing does not support the incremental interface (see supports_incremental_interface).

withbridgetype

If with_bridge_type is not nothing, the optimizer is wrapped in a Bridges.full_bridge_optimizer, enabling all the bridges defined in the MOI.Bridges submodule with coefficient type with_bridge_type.

In addition, if the optimizer created by optimizer_constructor does not support the incremental interface (see supports_incremental_interface), then, irrespective of with_cache_type, the optimizer is wrapped in a Utilities.CachingOptimizer to store a cache of the bridged model.

If with_cache_type and with_bridge_type are both not nothing, then they must be the same type.

source
MathOptInterface.default_cacheFunction
default_cache(optimizer::ModelLike, ::Type{T}) where {T}

Return a new instance of the default model type to be used as cache for optimizer in a Utilities.CachingOptimizer for holding constraints of coefficient type T. By default, this returns Utilities.UniversalFallback(Utilities.Model{T}()). If copying from a instance of a given model type is faster for optimizer then a new method returning an instance of this model type should be defined.

source

Optimizer attributes

MathOptInterface.SolverVersionType
SolverVersion()

An optimizer attribute for the string identifying the version of the solver.

Note

For solvers supporting semantic versioning, the SolverVersion should be a string of the form "vMAJOR.MINOR.PATCH", so that it can be converted to a Julia VersionNumber (e.g., `VersionNumber("v1.2.3")).

We do not require Semantic Versioning because some solvers use alternate versioning systems. For example, CPLEX uses Calendar Versioning, so SolverVersion will return a string like "202001".

source
MathOptInterface.SilentType
Silent()

An optimizer attribute for silencing the output of an optimizer. When set to true, it takes precedence over any other attribute controlling verbosity and requires the solver to produce no output. The default value is false which has no effect. In this case the verbosity is controlled by other attributes.

Note

Every optimizer should have verbosity on by default. For instance, if a solver has a solver-specific log level attribute, the MOI implementation should set it to 1 by default. If the user sets Silent to true, then the log level should be set to 0, even if the user specifically sets a value of log level. If the value of Silent is false then the log level set to the solver is the value given by the user for this solver-specific parameter or 1 if none is given.

source
MathOptInterface.TimeLimitSecType
TimeLimitSec()

An optimizer attribute for setting a time limit (in seconnds) for an optimization. When set to nothing, it deactivates the solver time limit. The default value is nothing.

source
MathOptInterface.ObjectiveLimitType
ObjectiveLimit()

An optimizer attribute for setting a limit on the objective value.

The provided limit must be a Union{Real,Nothing}.

When set to nothing, the limit reverts to the solver's default.

The default value is nothing.

The solver may stop when the ObjectiveValue is better (lower for minimization, higher for maximization) than the ObjectiveLimit. If stopped, the TerminationStatus should be OBJECTIVE_LIMIT.

source
MathOptInterface.NumberOfThreadsType
NumberOfThreads()

An optimizer attribute for setting the number of threads used for an optimization. When set to nothing uses solver default. Values are positive integers. The default value is nothing.

source
MathOptInterface.AbsoluteGapToleranceType
AbsoluteGapTolerance()

An optimizer attribute for setting the absolute gap tolerance for an optimization. This is an optimizer attribute, and should be set before calling optimize!. When set to nothing (if supported), uses solver default.

To set a relative gap tolerance, see RelativeGapTolerance.

Warning

The mathematical definition of "absolute gap", and its treatment during the optimization, are solver-dependent. However, assuming no other limit nor issue is encountered during the optimization, most solvers that implement this attribute will stop once $|f - b| ≤ g_{abs}$, where $b$ is the best bound, $f$ is the best feasible objective value, and $g_{abs}$ is the absolute gap.

source
MathOptInterface.RelativeGapToleranceType
RelativeGapTolerance()

An optimizer attribute for setting the relative gap tolerance for an optimization. This is an optimizer attribute, and should be set before calling optimize!. When set to nothing (if supported), uses solver default.

If you are looking for the relative gap of the current best solution, see RelativeGap. If no limit nor issue is encountered during the optimization, the value of RelativeGap should be at most as large as RelativeGapTolerance.

# Before optimizing: set relative gap tolerance
+# set 0.1% relative gap tolerance
+MOI.set(model, MOI.RelativeGapTolerance(), 1e-3)
+MOI.optimize!(model)
+
+# After optimizing (assuming all went well)
+# The relative gap tolerance has not changed...
+MOI.get(model, MOI.RelativeGapTolerance())  # returns 1e-3
+# ... and the relative gap of the obtained solution is smaller or equal to the
+# tolerance
+MOI.get(model, MOI.RelativeGap())  # should return something ≤ 1e-3
Warning

The mathematical definition of "relative gap", and its allowed range, are solver-dependent. Typically, solvers expect a value between 0.0 and 1.0.

source

List of attributes useful for optimizers

MathOptInterface.TerminationStatusCodeType
TerminationStatusCode

An Enum of possible values for the TerminationStatus attribute. This attribute is meant to explain the reason why the optimizer stopped executing in the most recent call to optimize!.

Values

Possible values are:

  • OPTIMIZE_NOT_CALLED: The algorithm has not started.
  • OPTIMAL: The algorithm found a globally optimal solution.
  • INFEASIBLE: The algorithm concluded that no feasible solution exists.
  • DUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem. If, additionally, a feasible (primal) solution is known to exist, this status typically implies that the problem is unbounded, with some technical exceptions.
  • LOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, could not find directions for improvement, or otherwise completed its search without global guarantees.
  • LOCALLY_INFEASIBLE: The algorithm converged to an infeasible point or otherwise completed its search without finding a feasible solution, without guarantees that no feasible solution exists.
  • INFEASIBLE_OR_UNBOUNDED: The algorithm stopped because it decided that the problem is infeasible or unbounded; this occasionally happens during MIP presolve.
  • ALMOST_OPTIMAL: The algorithm found a globally optimal solution to relaxed tolerances.
  • ALMOST_INFEASIBLE: The algorithm concluded that no feasible solution exists within relaxed tolerances.
  • ALMOST_DUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem within relaxed tolerances.
  • ALMOST_LOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, or could not find directions for improvement within relaxed tolerances.
  • ITERATION_LIMIT: An iterative algorithm stopped after conducting the maximum number of iterations.
  • TIME_LIMIT: The algorithm stopped after a user-specified computation time.
  • NODE_LIMIT: A branch-and-bound algorithm stopped because it explored a maximum number of nodes in the branch-and-bound tree.
  • SOLUTION_LIMIT: The algorithm stopped because it found the required number of solutions. This is often used in MIPs to get the solver to return the first feasible solution it encounters.
  • MEMORY_LIMIT: The algorithm stopped because it ran out of memory.
  • OBJECTIVE_LIMIT: The algorithm stopped because it found a solution better than a minimum limit set by the user.
  • NORM_LIMIT: The algorithm stopped because the norm of an iterate became too large.
  • OTHER_LIMIT: The algorithm stopped due to a limit not covered by one of the _LIMIT_ statuses above.
  • SLOW_PROGRESS: The algorithm stopped because it was unable to continue making progress towards the solution.
  • NUMERICAL_ERROR: The algorithm stopped because it encountered unrecoverable numerical error.
  • INVALID_MODEL: The algorithm stopped because the model is invalid.
  • INVALID_OPTION: The algorithm stopped because it was provided an invalid option.
  • INTERRUPTED: The algorithm stopped because of an interrupt signal.
  • OTHER_ERROR: The algorithm stopped because of an error not covered by one of the statuses defined above.
source
MathOptInterface.DUAL_INFEASIBLEConstant
DUAL_INFEASIBLE::TerminationStatusCode

An instance of the TerminationStatusCode enum.

DUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem. If, additionally, a feasible (primal) solution is known to exist, this status typically implies that the problem is unbounded, with some technical exceptions.

source
MathOptInterface.LOCALLY_SOLVEDConstant
LOCALLY_SOLVED::TerminationStatusCode

An instance of the TerminationStatusCode enum.

LOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, could not find directions for improvement, or otherwise completed its search without global guarantees.

source
MathOptInterface.LOCALLY_INFEASIBLEConstant
LOCALLY_INFEASIBLE::TerminationStatusCode

An instance of the TerminationStatusCode enum.

LOCALLY_INFEASIBLE: The algorithm converged to an infeasible point or otherwise completed its search without finding a feasible solution, without guarantees that no feasible solution exists.

source
MathOptInterface.SOLUTION_LIMITConstant
SOLUTION_LIMIT::TerminationStatusCode

An instance of the TerminationStatusCode enum.

SOLUTION_LIMIT: The algorithm stopped because it found the required number of solutions. This is often used in MIPs to get the solver to return the first feasible solution it encounters.

source
MathOptInterface.DualStatusType
DualStatus(result_index::Int = 1)

A model attribute for the ResultStatusCode of the dual result result_index. If result_index is omitted, it defaults to 1.

See ResultCount for information on how the results are ordered.

If result_index is larger than the value of ResultCount then NO_SOLUTION is returned.

source
MathOptInterface.ResultCountType
ResultCount()

A model attribute for the number of results available.

Order of solutions

A number of attributes contain an index, result_index, which is used to refer to one of the available results. Thus, result_index must be an integer between 1 and the number of available results.

As a general rule, the first result (result_index=1) is the most important result (e.g., an optimal solution or an infeasibility certificate). Other results will typically be alternate solutions that the solver found during the search for the first result.

If a (local) optimal solution is available, i.e., TerminationStatus is OPTIMAL or LOCALLY_SOLVED, the first result must correspond to the (locally) optimal solution. Other results may be alternative optimal solutions, or they may be other suboptimal solutions; use ObjectiveValue to distingiush between them.

If a primal or dual infeasibility certificate is available, i.e., TerminationStatus is INFEASIBLE or DUAL_INFEASIBLE and the corresponding PrimalStatus or DualStatus is INFEASIBILITY_CERTIFICATE, then the first result must be a certificate. Other results may be alternate certificates, or infeasible points.

source
MathOptInterface.ObjectiveValueType
ObjectiveValue(result_index::Int = 1)

A model attribute for the objective value of the primal solution result_index.

If the solver does not have a primal value for the objective because the result_index is beyond the available solutions (whose number is indicated by the ResultCount attribute), getting this attribute must throw a ResultIndexBoundsError. Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check PrimalStatus before accessing the ObjectiveValue attribute.

See ResultCount for information on how the results are ordered.

source
MathOptInterface.DualObjectiveValueType
DualObjectiveValue(result_index::Int = 1)

A model attribute for the value of the objective function of the dual problem for the result_indexth dual result.

If the solver does not have a dual value for the objective because the result_index is beyond the available solutions (whose number is indicated by the ResultCount attribute), getting this attribute must throw a ResultIndexBoundsError. Otherwise, if the result is unavailable for another reason (for instance, only a primal solution is available), the result is undefined. Users should first check DualStatus before accessing the DualObjectiveValue attribute.

See ResultCount for information on how the results are ordered.

source
MathOptInterface.RelativeGapType
RelativeGap()

A model attribute for the final relative optimality gap.

Warning

The definition of this gap is solver-dependent. However, most solvers implementing this attribute define the relative gap as some variation of $\frac{|b-f|}{|f|}$, where $b$ is the best bound and $f$ is the best feasible objective value.

source
MathOptInterface.SimplexIterationsType
SimplexIterations()

A model attribute for the cumulative number of simplex iterations during the optimization process.

For a mixed-integer program (MIP), the return value is the total simplex iterations for all nodes.

source
MathOptInterface.NodeCountType
NodeCount()

A model attribute for the total number of branch-and-bound nodes explored while solving a mixed-integer program (MIP).

source

ResultStatusCode

MathOptInterface.ResultStatusCodeType
ResultStatusCode

An Enum of possible values for the PrimalStatus and DualStatus attributes.

The values indicate how to interpret the result vector.

Values

Possible values are:

  • NO_SOLUTION: the result vector is empty.
  • FEASIBLE_POINT: the result vector is a feasible point.
  • NEARLY_FEASIBLE_POINT: the result vector is feasible if some constraint tolerances are relaxed.
  • INFEASIBLE_POINT: the result vector is an infeasible point.
  • INFEASIBILITY_CERTIFICATE: the result vector is an infeasibility certificate. If the PrimalStatus is INFEASIBILITY_CERTIFICATE, then the primal result vector is a certificate of dual infeasibility. If the DualStatus is INFEASIBILITY_CERTIFICATE, then the dual result vector is a proof of primal infeasibility.
  • NEARLY_INFEASIBILITY_CERTIFICATE: the result satisfies a relaxed criterion for a certificate of infeasibility.
  • REDUCTION_CERTIFICATE: the result vector is an ill-posed certificate; see this article for details. If the PrimalStatus is REDUCTION_CERTIFICATE, then the primal result vector is a proof that the dual problem is ill-posed. If the DualStatus is REDUCTION_CERTIFICATE, then the dual result vector is a proof that the primal is ill-posed.
  • NEARLY_REDUCTION_CERTIFICATE: the result satisfies a relaxed criterion for an ill-posed certificate.
  • UNKNOWN_RESULT_STATUS: the result vector contains a solution with an unknown interpretation.
  • OTHER_RESULT_STATUS: the result vector contains a solution with an interpretation not covered by one of the statuses defined above
source
MathOptInterface.INFEASIBILITY_CERTIFICATEConstant
INFEASIBILITY_CERTIFICATE::ResultStatusCode

An instance of the ResultStatusCode enum.

INFEASIBILITY_CERTIFICATE: the result vector is an infeasibility certificate. If the PrimalStatus is INFEASIBILITY_CERTIFICATE, then the primal result vector is a certificate of dual infeasibility. If the DualStatus is INFEASIBILITY_CERTIFICATE, then the dual result vector is a proof of primal infeasibility.

source
MathOptInterface.REDUCTION_CERTIFICATEConstant
REDUCTION_CERTIFICATE::ResultStatusCode

An instance of the ResultStatusCode enum.

REDUCTION_CERTIFICATE: the result vector is an ill-posed certificate; see this article for details. If the PrimalStatus is REDUCTION_CERTIFICATE, then the primal result vector is a proof that the dual problem is ill-posed. If the DualStatus is REDUCTION_CERTIFICATE, then the dual result vector is a proof that the primal is ill-posed.

source

Conflict Status

MathOptInterface.compute_conflict!Function
compute_conflict!(optimizer::AbstractOptimizer)

Computes a minimal subset of constraints such that the model with the other constraint removed is still infeasible.

Some solvers call a set of conflicting constraints an Irreducible Inconsistent Subsystem (IIS).

See also ConflictStatus and ConstraintConflictStatus.

Note

If the model is modified after a call to compute_conflict!, the implementor is not obliged to purge the conflict. Any calls to the above attributes may return values for the original conflict without a warning. Similarly, when modifying the model, the conflict can be discarded.

source
MathOptInterface.ConflictStatusCodeType
ConflictStatusCode

An Enum of possible values for the ConflictStatus attribute. This attribute is meant to explain the reason why the conflict finder stopped executing in the most recent call to compute_conflict!.

Possible values are:

  • COMPUTE_CONFLICT_NOT_CALLED: the function compute_conflict! has not yet been called
  • NO_CONFLICT_EXISTS: there is no conflict because the problem is feasible
  • NO_CONFLICT_FOUND: the solver could not find a conflict
  • CONFLICT_FOUND: at least one conflict could be found
source
MathOptInterface.ConflictParticipationStatusCodeType
ConflictParticipationStatusCode

An Enum of possible values for the ConstraintConflictStatus attribute. This attribute is meant to indicate whether a given constraint participates or not in the last computed conflict.

Values

Possible values are:

  • NOT_IN_CONFLICT: the constraint does not participate in the conflict
  • IN_CONFLICT: the constraint participates in the conflict
  • MAYBE_IN_CONFLICT: the constraint may participate in the conflict, the solver was not able to prove that the constraint can be excluded from the conflict
source
diff --git a/previews/PR3536/moi/reference/modification/index.html b/previews/PR3536/moi/reference/modification/index.html new file mode 100644 index 00000000000..6306a182d62 --- /dev/null +++ b/previews/PR3536/moi/reference/modification/index.html @@ -0,0 +1,31 @@ + +Modifications · JuMP

Modifications

MathOptInterface.modifyFunction

Constraint Function

modify(model::ModelLike, ci::ConstraintIndex, change::AbstractFunctionModification)

Apply the modification specified by change to the function of constraint ci.

An ModifyConstraintNotAllowed error is thrown if modifying constraints is not supported by the model model.

Examples

modify(model, ci, ScalarConstantChange(10.0))

Objective Function

modify(model::ModelLike, ::ObjectiveFunction, change::AbstractFunctionModification)

Apply the modification specified by change to the objective function of model. To change the function completely, call set instead.

An ModifyObjectiveNotAllowed error is thrown if modifying objectives is not supported by the model model.

Examples

modify(model, ObjectiveFunction{ScalarAffineFunction{Float64}}(), ScalarConstantChange(10.0))

Multiple modifications in Constraint Functions

modify(
+    model::ModelLike,
+    cis::AbstractVector{<:ConstraintIndex},
+    changes::AbstractVector{<:AbstractFunctionModification},
+)

Apply multiple modifications specified by changes to the functions of constraints cis.

A ModifyConstraintNotAllowed error is thrown if modifying constraints is not supported by model.

Examples

modify(
+    model,
+    [ci, ci],
+    [
+        ScalarCoefficientChange{Float64}(VariableIndex(1), 1.0),
+        ScalarCoefficientChange{Float64}(VariableIndex(2), 0.5),
+    ],
+)

Multiple modifications in the Objective Function

modify(
+    model::ModelLike,
+    attr::ObjectiveFunction,
+    changes::AbstractVector{<:AbstractFunctionModification},
+)

Apply multiple modifications specified by changes to the functions of constraints cis.

A ModifyObjectiveNotAllowed error is thrown if modifying objective coefficients is not supported by model.

Examples

modify(
+    model,
+    ObjectiveFunction{ScalarAffineFunction{Float64}}(),
+    [
+        ScalarCoefficientChange{Float64}(VariableIndex(1), 1.0),
+        ScalarCoefficientChange{Float64}(VariableIndex(2), 0.5),
+    ],
+)
source
MathOptInterface.AbstractFunctionModificationType
AbstractFunctionModification

An abstract supertype for structs which specify partial modifications to functions, to be used for making small modifications instead of replacing the functions entirely.

source
diff --git a/previews/PR3536/moi/reference/nonlinear/index.html b/previews/PR3536/moi/reference/nonlinear/index.html new file mode 100644 index 00000000000..dd249c31ecb --- /dev/null +++ b/previews/PR3536/moi/reference/nonlinear/index.html @@ -0,0 +1,82 @@ + +Nonlinear programming · JuMP

Nonlinear programming

Types

MathOptInterface.NLPBoundsPairType
NLPBoundsPair(lower::Float64, upper::Float64)

A struct holding a pair of lower and upper bounds.

-Inf and Inf can be used to indicate no lower or upper bound, respectively.

source
MathOptInterface.NLPBlockDataType
struct NLPBlockData
+    constraint_bounds::Vector{NLPBoundsPair}
+    evaluator::AbstractNLPEvaluator
+    has_objective::Bool
+end

A struct encoding a set of nonlinear constraints of the form $lb \le g(x) \le ub$ and, if has_objective == true, a nonlinear objective function $f(x)$.

Nonlinear objectives override any objective set by using the ObjectiveFunction attribute.

The evaluator is a callback object that is used to query function values, derivatives, and expression graphs. If has_objective == false, then it is an error to query properties of the objective function, and in Hessian-of-the-Lagrangian queries, σ must be set to zero.

Note

Throughout the evaluator, all variables are ordered according to ListOfVariableIndices. Hence, MOI copies of nonlinear problems must not re-order variables.

source

Attributes

Functions

MathOptInterface.initializeFunction
initialize(
+    d::AbstractNLPEvaluator,
+    requested_features::Vector{Symbol},
+)::Nothing

Initialize d with the set of features in requested_features. Check features_available before calling initialize to see what features are supported by d.

Warning

This method must be called before any other methods.

Features

The following features are defined:

In all cases, including when requested_features is empty, eval_objective and eval_constraint are supported.

Examples

MOI.initialize(d, Symbol[])
+MOI.initialize(d, [:ExprGraph])
+MOI.initialize(d, MOI.features_available(d))
source
MathOptInterface.eval_constraintFunction
eval_constraint(d::AbstractNLPEvaluator,
+    g::AbstractVector{T},
+    x::AbstractVector{T},
+)::Nothing where {T}

Given a set of vector-valued constraints $l \le g(x) \le u$, evaluate the constraint function $g(x)$, storing the result in the vector g.

Implementation notes

When implementing this method, you must not assume that g is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.eval_objective_gradientFunction
eval_objective_gradient(
+    d::AbstractNLPEvaluator,
+    grad::AbstractVector{T},
+    x::AbstractVector{T},
+)::Nothing where {T}

Evaluate the gradient of the objective function $grad = \nabla f(x)$ as a dense vector, storing the result in the vector grad.

Implementation notes

When implementing this method, you must not assume that grad is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.jacobian_structureFunction
jacobian_structure(d::AbstractNLPEvaluator)::Vector{Tuple{Int64,Int64}}

Returns a vector of tuples, (row, column), where each indicates the position of a structurally nonzero element in the Jacobian matrix: $J_g(x) = \left[ \begin{array}{c} \nabla g_1(x) \\ \nabla g_2(x) \\ \vdots \\ \nabla g_m(x) \end{array}\right],$ where $g_i$ is the $i\text{th}$ component of the nonlinear constraints $g(x)$.

The indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.

The sparsity structure is assumed to be independent of the point $x$.

source
MathOptInterface.eval_constraint_gradientFunction
eval_constraint_gradient(
+    d::AbstractNLPEvaluator,
+    ∇g::AbstractVector{T},
+    x::AbstractVector{T},
+    i::Int,
+)::Nothing where {T}

Evaluate the gradient of constraint i, $\nabla g_i(x)$, and store the non-zero values in ∇g, corresponding to the structure returned by constraint_gradient_structure.

Implementation notes

When implementing this method, you must not assume that ∇g is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.constraint_gradient_structureFunction
constraint_gradient_structure(d::AbstractNLPEvaluator, i::Int)::Vector{Int64}

Returns a vector of indices, where each element indicates the position of a structurally nonzero element in the gradient of constraint $\nabla g_i(x)$.

The indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.

The sparsity structure is assumed to be independent of the point $x$.

source
MathOptInterface.eval_constraint_jacobianFunction
eval_constraint_jacobian(d::AbstractNLPEvaluator,
+    J::AbstractVector{T},
+    x::AbstractVector{T},
+)::Nothing where {T}

Evaluates the sparse Jacobian matrix $J_g(x) = \left[ \begin{array}{c} \nabla g_1(x) \\ \nabla g_2(x) \\ \vdots \\ \nabla g_m(x) \end{array}\right]$.

The result is stored in the vector J in the same order as the indices returned by jacobian_structure.

Implementation notes

When implementing this method, you must not assume that J is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.eval_constraint_jacobian_productFunction
eval_constraint_jacobian_product(
+    d::AbstractNLPEvaluator,
+    y::AbstractVector{T},
+    x::AbstractVector{T},
+    w::AbstractVector{T},
+)::Nothing where {T}

Computes the Jacobian-vector product $y = J_g(x)w$, storing the result in the vector y.

The vectors have dimensions such that length(w) == length(x), and length(y) is the number of nonlinear constraints.

Implementation notes

When implementing this method, you must not assume that y is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.eval_constraint_jacobian_transpose_productFunction
eval_constraint_jacobian_transpose_product(
+    d::AbstractNLPEvaluator,
+    y::AbstractVector{T},
+    x::AbstractVector{T},
+    w::AbstractVector{T},
+)::Nothing where {T}

Computes the Jacobian-transpose-vector product $y = J_g(x)^Tw$, storing the result in the vector y.

The vectors have dimensions such that length(y) == length(x), and length(w) is the number of nonlinear constraints.

Implementation notes

When implementing this method, you must not assume that y is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.hessian_lagrangian_structureFunction
hessian_lagrangian_structure(
+    d::AbstractNLPEvaluator,
+)::Vector{Tuple{Int64,Int64}}

Returns a vector of tuples, (row, column), where each indicates the position of a structurally nonzero element in the Hessian-of-the-Lagrangian matrix: $\nabla^2 f(x) + \sum_{i=1}^m \nabla^2 g_i(x)$.

The indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.

Any mix of lower and upper-triangular indices is valid. Elements (i,j) and (j,i), if both present, should be treated as duplicates.

The sparsity structure is assumed to be independent of the point $x$.

source
MathOptInterface.hessian_objective_structureFunction
hessian_objective_structure(
+    d::AbstractNLPEvaluator,
+)::Vector{Tuple{Int64,Int64}}

Returns a vector of tuples, (row, column), where each indicates the position of a structurally nonzero element in the Hessian matrix: $\nabla^2 f(x)$.

The indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.

Any mix of lower and upper-triangular indices is valid. Elements (i,j) and (j,i), if both present, should be treated as duplicates.

The sparsity structure is assumed to be independent of the point $x$.

source
MathOptInterface.hessian_constraint_structureFunction
hessian_constraint_structure(
+    d::AbstractNLPEvaluator,
+    i::Int64,
+)::Vector{Tuple{Int64,Int64}}

Returns a vector of tuples, (row, column), where each indicates the position of a structurally nonzero element in the Hessian matrix: $\nabla^2 g_i(x)$.

The indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.

Any mix of lower and upper-triangular indices is valid. Elements (i,j) and (j,i), if both present, should be treated as duplicates.

The sparsity structure is assumed to be independent of the point $x$.

source
MathOptInterface.eval_hessian_objectiveFunction
eval_hessian_objective(
+    d::AbstractNLPEvaluator,
+    H::AbstractVector{T},
+    x::AbstractVector{T},
+)::Nothing where {T}

This function computes the sparse Hessian matrix: $\nabla^2 f(x)$, storing the result in the vector H in the same order as the indices returned by hessian_objective_structure.

Implementation notes

When implementing this method, you must not assume that H is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.eval_hessian_constraintFunction
eval_hessian_constraint(
+    d::AbstractNLPEvaluator,
+    H::AbstractVector{T},
+    x::AbstractVector{T},
+    i::Int64,
+)::Nothing where {T}

This function computes the sparse Hessian matrix: $\nabla^2 g_i(x)$, storing the result in the vector H in the same order as the indices returned by hessian_constraint_structure.

Implementation notes

When implementing this method, you must not assume that H is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.eval_hessian_lagrangianFunction
eval_hessian_lagrangian(
+    d::AbstractNLPEvaluator,
+    H::AbstractVector{T},
+    x::AbstractVector{T},
+    σ::T,
+    μ::AbstractVector{T},
+)::Nothing where {T}

Given scalar weight σ and vector of constraint weights μ, this function computes the sparse Hessian-of-the-Lagrangian matrix: $\sigma\nabla^2 f(x) + \sum_{i=1}^m \mu_i \nabla^2 g_i(x)$, storing the result in the vector H in the same order as the indices returned by hessian_lagrangian_structure.

Implementation notes

When implementing this method, you must not assume that H is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.eval_hessian_lagrangian_productFunction
eval_hessian_lagrangian_product(
+    d::AbstractNLPEvaluator,
+    h::AbstractVector{T},
+    x::AbstractVector{T},
+    v::AbstractVector{T},
+    σ::T,
+    μ::AbstractVector{T},
+)::Nothing where {T}

Given scalar weight σ and vector of constraint weights μ, computes the Hessian-of-the-Lagrangian-vector product $h = \left(\sigma\nabla^2 f(x) + \sum_{i=1}^m \mu_i \nabla^2 g_i(x)\right)v$, storing the result in the vector h.

The vectors have dimensions such that length(h) == length(x) == length(v).

Implementation notes

When implementing this method, you must not assume that h is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.

source
MathOptInterface.objective_exprFunction
objective_expr(d::AbstractNLPEvaluator)::Expr

Returns a Julia Expr object representing the expression graph of the objective function.

Format

The expression has a number of limitations, compared with arbitrary Julia expressions:

  • All sums and products are flattened out as simple Expr(:+, ...) and Expr(:*, ...) objects.
  • All decision variables must be of the form Expr(:ref, :x, MOI.VariableIndex(i)), where i is the $i$th variable in ListOfVariableIndices.
  • There are currently no restrictions on recognized functions; typically these will be built-in Julia functions like ^, exp, log, cos, tan, sqrt, etc., but modeling interfaces may choose to extend these basic functions, or error if they encounter unsupported functions.

Examples

The expression $x_1+\sin(x_2/\exp(x_3))$ is represented as

:(x[MOI.VariableIndex(1)] + sin(x[MOI.VariableIndex(2)] / exp(x[MOI.VariableIndex[3]])))

or equivalently

Expr(
+    :call,
+    :+,
+    Expr(:ref, :x, MOI.VariableIndex(1)),
+    Expr(
+        :call,
+        :/,
+        Expr(:call, :sin, Expr(:ref, :x, MOI.VariableIndex(2))),
+        Expr(:call, :exp, Expr(:ref, :x, MOI.VariableIndex(3))),
+    ),
+)
source
MathOptInterface.constraint_exprFunction
constraint_expr(d::AbstractNLPEvaluator, i::Integer)::Expr

Returns a Julia Expr object representing the expression graph for the $i\text{th}$ nonlinear constraint.

Format

The format is the same as objective_expr, with an additional comparison operator indicating the sense of and bounds on the constraint.

For single-sided comparisons, the body of the constraint must be on the left-hand side, and the right-hand side must be a constant.

For double-sided comparisons (that is, $l \le f(x) \le u$), the body of the constraint must be in the middle, and the left- and right-hand sides must be constants.

The bounds on the constraints must match the NLPBoundsPairs passed to NLPBlockData.

Examples

:(x[MOI.VariableIndex(1)]^2 <= 1.0)
+:(x[MOI.VariableIndex(1)]^2 >= 2.0)
+:(x[MOI.VariableIndex(1)]^2 == 3.0)
+:(4.0 <= x[MOI.VariableIndex(1)]^2 <= 5.0)
source
diff --git a/previews/PR3536/moi/reference/standard_form/index.html b/previews/PR3536/moi/reference/standard_form/index.html new file mode 100644 index 00000000000..dd069b1667b --- /dev/null +++ b/previews/PR3536/moi/reference/standard_form/index.html @@ -0,0 +1,993 @@ + +Standard form · JuMP

Standard form

Functions

MathOptInterface.AbstractFunctionType
AbstractFunction

Abstract supertype for function objects.

Required methods

All functions must implement:

Abstract subtypes of AbstractFunction may require additional methods to be implemented.

source
MathOptInterface.constantFunction
constant(f::AbstractFunction[, ::Type{T}]) where {T}

Returns the constant term of a scalar-valued function, or the constant vector of a vector-valued function.

If f is untyped and T is provided, returns zero(T).

source
constant(set::Union{EqualTo,GreaterThan,LessThan,Parameter})

Returns the constant term of the set set.

Example

julia> import MathOptInterface as MOI
+
+julia> MOI.constant(MOI.GreaterThan(1.0))
+1.0
+
+julia> MOI.constant(MOI.LessThan(2.5))
+2.5
+
+julia> MOI.constant(MOI.EqualTo(3))
+3
+
+julia> MOI.constant(MOI.Parameter(4.5))
+4.5
source

Scalar functions

MathOptInterface.VariableIndexType
VariableIndex

A type-safe wrapper for Int64 for use in referencing variables in a model. To allow for deletion, indices need not be consecutive.

source
MathOptInterface.ScalarAffineTermType
ScalarAffineTerm{T}(coefficient::T, variable::VariableIndex) where {T}

Represents the scalar-valued term coefficient * variable.

Example

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1)
+MOI.VariableIndex(1)
+
+julia> MOI.ScalarAffineTerm(2.0, x)
+MathOptInterface.ScalarAffineTerm{Float64}(2.0, MOI.VariableIndex(1))
source
MathOptInterface.ScalarAffineFunctionType
ScalarAffineFunction{T}(terms::ScalarAffineTerm{T}, constant::T) where {T}

Represents the scalar-valued affine function $a^\top x + b$, where:

  • $a^\top x$ is represented by the vector of ScalarAffineTerms
  • $b$ is a scalar constant::T

Duplicates

Duplicate variable indices in terms are accepted, and the corresponding coefficients are summed together.

Example

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1)
+MOI.VariableIndex(1)
+
+julia> terms = [MOI.ScalarAffineTerm(2.0, x), MOI.ScalarAffineTerm(3.0, x)]
+2-element Vector{MathOptInterface.ScalarAffineTerm{Float64}}:
+ MathOptInterface.ScalarAffineTerm{Float64}(2.0, MOI.VariableIndex(1))
+ MathOptInterface.ScalarAffineTerm{Float64}(3.0, MOI.VariableIndex(1))
+
+julia> f = MOI.ScalarAffineFunction(terms, 4.0)
+4.0 + 2.0 MOI.VariableIndex(1) + 3.0 MOI.VariableIndex(1)
source
MathOptInterface.ScalarQuadraticTermType
ScalarQuadraticTerm{T}(
+    coefficient::T,
+    variable_1::VariableIndex,
+    variable_2::VariableIndex,
+) where {T}

Represents the scalar-valued term $c x_i x_j$ where $c$ is coefficient, $x_i$ is variable_1 and $x_j$ is variable_2.

Example

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1)
+MOI.VariableIndex(1)
+
+julia> MOI.ScalarQuadraticTerm(2.0, x, x)
+MathOptInterface.ScalarQuadraticTerm{Float64}(2.0, MOI.VariableIndex(1), MOI.VariableIndex(1))
source
MathOptInterface.ScalarQuadraticFunctionType
ScalarQuadraticFunction{T}(
+    quadratic_terms::Vector{ScalarQuadraticTerm{T}},
+    affine_terms::Vector{ScalarAffineTerm{T}},
+    constant::T,
+) wher {T}

The scalar-valued quadratic function $\frac{1}{2}x^\top Q x + a^\top x + b$, where:

Duplicates

Duplicate indices in quadratic_terms or affine_terms are accepted, and the corresponding coefficients are summed together.

In quadratic_terms, "mirrored" indices, (q, r) and (r, q) where r and q are VariableIndexes, are considered duplicates; only one needs to be specified.

The 0.5 factor

Coupled with the interpretation of mirrored indices, the 0.5 factor in front of the $Q$ matrix is a common source of bugs.

As a rule, to represent $a * x^2 + b * x * y$:

  • The coefficient $a$ in front of squared variables (diagonal elements in $Q$) must be doubled when creating a ScalarQuadraticTerm
  • The coefficient $b$ in front of off-diagonal elements in $Q$ should be left as $b$, be cause the mirrored index $b * y * x$ will be implicitly added.

Example

To represent the function $f(x, y) = 2 * x^2 + 3 * x * y + 4 * x + 5$, do:

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1);
+
+julia> y = MOI.VariableIndex(2);
+
+julia> constant = 5.0;
+
+julia> affine_terms = [MOI.ScalarAffineTerm(4.0, x)];
+
+julia> quadratic_terms = [
+           MOI.ScalarQuadraticTerm(4.0, x, x),  # Note the changed coefficient
+           MOI.ScalarQuadraticTerm(3.0, x, y),
+       ]
+2-element Vector{MathOptInterface.ScalarQuadraticTerm{Float64}}:
+ MathOptInterface.ScalarQuadraticTerm{Float64}(4.0, MOI.VariableIndex(1), MOI.VariableIndex(1))
+ MathOptInterface.ScalarQuadraticTerm{Float64}(3.0, MOI.VariableIndex(1), MOI.VariableIndex(2))
+
+julia> f = MOI.ScalarQuadraticFunction(quadratic_terms, affine_terms, constant)
+5.0 + 4.0 MOI.VariableIndex(1) + 2.0 MOI.VariableIndex(1)² + 3.0 MOI.VariableIndex(1)*MOI.VariableIndex(2)
source
MathOptInterface.ScalarNonlinearFunctionType
ScalarNonlinearFunction(head::Symbol, args::Vector{Any})

The scalar-valued nonlinear function head(args...), represented as a symbolic expression tree, with the call operator head and ordered arguments in args.

head

The head::Symbol must be an operator supported by the model.

The default list of supported univariate operators is given by:

and the default list of supported multivariate operators is given by:

Additional operators can be registered by setting a UserDefinedFunction attribute.

See the full list of operators supported by a ModelLike by querying ListOfSupportedNonlinearOperators.

args

The vector args contains the arguments to the nonlinear function. If the operator is univariate, it must contain one element. Otherwise, it may contain multiple elements.

Each element must be one of the following:

Unsupported operators

If the optimizer does not support head, an UnsupportedNonlinearOperator error will be thrown.

There is no guarantee about when this error will be thrown; it may be thrown when the function is first added to the model, or it may be thrown when optimize! is called.

Example

To represent the function $f(x) = sin(x)^2$, do:

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1)
+MOI.VariableIndex(1)
+
+julia> MOI.ScalarNonlinearFunction(
+           :^,
+           Any[MOI.ScalarNonlinearFunction(:sin, Any[x]), 2],
+       )
+^(sin(MOI.VariableIndex(1)), (2))
source

Vector functions

MathOptInterface.VectorOfVariablesType
VectorOfVariables(variables::Vector{VariableIndex}) <: AbstractVectorFunction

The vector-valued function f(x) = variables, where variables is a subset of VariableIndexes in the model.

The list of variables may contain duplicates.

Example

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex.(1:2)
+2-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+ MOI.VariableIndex(2)
+
+julia> f = MOI.VectorOfVariables([x[1], x[2], x[1]])
+┌                    ┐
+│MOI.VariableIndex(1)│
+│MOI.VariableIndex(2)│
+│MOI.VariableIndex(1)│
+└                    ┘
+
+julia> MOI.output_dimension(f)
+3
source
MathOptInterface.VectorAffineTermType
VectorAffineTerm{T}(
+    output_index::Int64,
+    scalar_term::ScalarAffineTerm{T},
+) where {T}

A VectorAffineTerm is a scalar_term that appears in the output_index row of the vector-valued VectorAffineFunction or VectorQuadraticFunction.

Example

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1);
+
+julia> MOI.VectorAffineTerm(Int64(2), MOI.ScalarAffineTerm(3.0, x))
+MathOptInterface.VectorAffineTerm{Float64}(2, MathOptInterface.ScalarAffineTerm{Float64}(3.0, MOI.VariableIndex(1)))
source
MathOptInterface.VectorAffineFunctionType
VectorAffineFunction{T}(
+    terms::Vector{VectorAffineTerm{T}},
+    constants::Vector{T},
+) where {T}

The vector-valued affine function $A x + b$, where:

  • $A x$ is the sparse matrix given by the vector of VectorAffineTerms
  • $b$ is the vector constants

Duplicates

Duplicate indices in the $A$ are accepted, and the corresponding coefficients are summed together.

Example

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1);
+
+julia> terms = [
+           MOI.VectorAffineTerm(Int64(1), MOI.ScalarAffineTerm(2.0, x)),
+           MOI.VectorAffineTerm(Int64(2), MOI.ScalarAffineTerm(3.0, x)),
+       ];
+
+julia> f = MOI.VectorAffineFunction(terms, [4.0, 5.0])
+┌                              ┐
+│4.0 + 2.0 MOI.VariableIndex(1)│
+│5.0 + 3.0 MOI.VariableIndex(1)│
+└                              ┘
+
+julia> MOI.output_dimension(f)
+2
source
MathOptInterface.VectorQuadraticTermType
VectorQuadraticTerm{T}(
+    output_index::Int64,
+    scalar_term::ScalarQuadraticTerm{T},
+) where {T}

A VectorQuadraticTerm is a ScalarQuadraticTerm scalar_term that appears in the output_index row of the vector-valued VectorQuadraticFunction.

Example

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1);
+
+julia> MOI.VectorQuadraticTerm(Int64(2), MOI.ScalarQuadraticTerm(3.0, x, x))
+MathOptInterface.VectorQuadraticTerm{Float64}(2, MathOptInterface.ScalarQuadraticTerm{Float64}(3.0, MOI.VariableIndex(1), MOI.VariableIndex(1)))
source
MathOptInterface.VectorQuadraticFunctionType
VectorQuadraticFunction{T}(
+    quadratic_terms::Vector{VectorQuadraticTerm{T}},
+    affine_terms::Vector{VectorAffineTerm{T}},
+    constants::Vector{T},
+) where {T}

The vector-valued quadratic function with ith component ("output index") defined as $\frac{1}{2}x^\top Q_i x + a_i^\top x + b_i$, where:

  • $\frac{1}{2}x^\top Q_i x$ is the symmetric matrix given by the VectorQuadraticTerm elements in quadratic_terms with output_index == i
  • $a_i^\top x$ is the sparse vector given by the VectorAffineTerm elements in affine_terms with output_index == i
  • $b_i$ is a scalar given by constants[i]

Duplicates

Duplicate indices in quadratic_terms and affine_terms with the same output_index are handled in the same manner as duplicates in ScalarQuadraticFunction.

Example

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1);
+
+julia> y = MOI.VariableIndex(2);
+
+julia> constants = [4.0, 5.0];
+
+julia> affine_terms = [
+           MOI.VectorAffineTerm(Int64(1), MOI.ScalarAffineTerm(2.0, x)),
+           MOI.VectorAffineTerm(Int64(2), MOI.ScalarAffineTerm(3.0, x)),
+       ];
+
+julia> quad_terms = [
+        MOI.VectorQuadraticTerm(Int64(1), MOI.ScalarQuadraticTerm(2.0, x, x)),
+        MOI.VectorQuadraticTerm(Int64(2), MOI.ScalarQuadraticTerm(3.0, x, y)),
+           ];
+
+julia> f = MOI.VectorQuadraticFunction(quad_terms, affine_terms, constants)
+┌                                                                              ┐
+│4.0 + 2.0 MOI.VariableIndex(1) + 1.0 MOI.VariableIndex(1)²                    │
+│5.0 + 3.0 MOI.VariableIndex(1) + 3.0 MOI.VariableIndex(1)*MOI.VariableIndex(2)│
+└                                                                              ┘
+
+julia> MOI.output_dimension(f)
+2
source
MathOptInterface.VectorNonlinearFunctionType
VectorNonlinearFunction(args::Vector{ScalarNonlinearFunction})

The vector-valued nonlinear function composed of a vector of ScalarNonlinearFunction.

args

The vector args contains the scalar elements of the nonlinear function. Each element must be a ScalarNonlinearFunction, but if you pass a Vector{Any}, the elements can be automatically converted from one of the following:

Example

To represent the function $f(x) = [sin(x)^2, x]$, do:

julia> import MathOptInterface as MOI
+
+julia> x = MOI.VariableIndex(1)
+MOI.VariableIndex(1)
+
+julia> g = MOI.ScalarNonlinearFunction(
+           :^,
+           Any[MOI.ScalarNonlinearFunction(:sin, Any[x]), 2.0],
+       )
+^(sin(MOI.VariableIndex(1)), 2.0)
+
+julia> MOI.VectorNonlinearFunction([g, x])
+┌                                 ┐
+│^(sin(MOI.VariableIndex(1)), 2.0)│
+│+(MOI.VariableIndex(1))          │
+└                                 ┘

Note the automatic conversion from x to +(x).

source

Sets

MathOptInterface.AbstractSetType
AbstractSet

Abstract supertype for set objects used to encode constraints.

Required methods

For sets of type S with isbitstype(S) == false, you must implement:

  • Base.copy(set::S)
  • Base.:(==)(x::S, y::S)

Subtypes of AbstractSet such as AbstractScalarSet and AbstractVectorSet may prescribe additional required methods.

Optional methods

You may optionally implement:

Note for developers

When creating a new set, the set struct must not contain any VariableIndex or ConstraintIndex objects.

source
MathOptInterface.AbstractVectorSetType
AbstractVectorSet

Abstract supertype for subsets of $\mathbb{R}^n$ for some $n$.

Required methods

All AbstractVectorSets of type S must implement:

  • dimension, unless the dimension is stored in the set.dimension field
  • Utilities.set_dot, unless the dot product between two vectors in the set is equivalent to LinearAlgebra.dot.
source

Utilities

MathOptInterface.dimensionFunction
dimension(set::AbstractSet)

Return the output_dimension that an AbstractFunction should have to be used with the set set.

Example

julia> import MathOptInterface as MOI
+
+julia> MOI.dimension(MOI.Reals(4))
+4
+
+julia> MOI.dimension(MOI.LessThan(3.0))
+1
+
+julia> MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(2))
+3
source
MathOptInterface.dual_setFunction
dual_set(set::AbstractSet)

Return the dual set of set, that is the dual cone of the set. This follows the definition of duality discussed in Duality.

See Dual cone for more information.

If the dual cone is not defined it returns an error.

Example

julia> import MathOptInterface as MOI
+
+julia> MOI.dual_set(MOI.Reals(4))
+MathOptInterface.Zeros(4)
+
+julia> MOI.dual_set(MOI.SecondOrderCone(5))
+MathOptInterface.SecondOrderCone(5)
+
+julia> MOI.dual_set(MOI.ExponentialCone())
+MathOptInterface.DualExponentialCone()
source
MathOptInterface.dual_set_typeFunction
dual_set_type(S::Type{<:AbstractSet})

Return the type of dual set of sets of type S, as returned by dual_set. If the dual cone is not defined it returns an error.

Example

julia> import MathOptInterface as MOI
+
+julia> MOI.dual_set_type(MOI.Reals)
+MathOptInterface.Zeros
+
+julia> MOI.dual_set_type(MOI.SecondOrderCone)
+MathOptInterface.SecondOrderCone
+
+julia> MOI.dual_set_type(MOI.ExponentialCone)
+MathOptInterface.DualExponentialCone
source
MathOptInterface.constantMethod
constant(set::Union{EqualTo,GreaterThan,LessThan,Parameter})

Returns the constant term of the set set.

Example

julia> import MathOptInterface as MOI
+
+julia> MOI.constant(MOI.GreaterThan(1.0))
+1.0
+
+julia> MOI.constant(MOI.LessThan(2.5))
+2.5
+
+julia> MOI.constant(MOI.EqualTo(3))
+3
+
+julia> MOI.constant(MOI.Parameter(4.5))
+4.5
source
MathOptInterface.supports_dimension_updateFunction
supports_dimension_update(S::Type{<:MOI.AbstractVectorSet})

Return a Bool indicating whether the elimination of any dimension of n-dimensional sets of type S give an n-1-dimensional set S. By default, this function returns false so it should only be implemented for sets that supports dimension update.

For instance, supports_dimension_update(MOI.Nonnegatives) is true because the elimination of any dimension of the n-dimensional nonnegative orthant gives the n-1-dimensional nonnegative orthant. However supports_dimension_update(MOI.ExponentialCone) is false.

source

Scalar sets

List of recognized scalar sets.

MathOptInterface.GreaterThanType
GreaterThan{T<:Real}(lower::T)

The set $[lower, \infty) \subseteq \mathbb{R}$.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(model, x, MOI.GreaterThan(0.0))
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.GreaterThan{Float64}}(1)
source
MathOptInterface.LessThanType
LessThan{T<:Real}(upper::T)

The set $(-\infty, upper] \subseteq \mathbb{R}$.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(model, x, MOI.LessThan(2.0))
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.LessThan{Float64}}(1)
source
MathOptInterface.EqualToType
EqualTo{T<:Number}(value::T)

The set containing the single point $\{value\} \subseteq \mathbb{R}$.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(model, x, MOI.EqualTo(2.0))
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.EqualTo{Float64}}(1)
source
MathOptInterface.IntervalType
Interval{T<:Real}(lower::T, upper::T)

The interval $[lower, upper] \subseteq \mathbb{R} \cup \{-\infty, +\infty\}$.

If lower or upper is -Inf or Inf, respectively, the set is interpreted as a one-sided interval.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(model, x, MOI.Interval(1.0, 2.0))
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}}(1)
source
MathOptInterface.IntegerType
Integer()

The set of integers, $\mathbb{Z}$.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(model, x, MOI.Integer())
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(1)
source
MathOptInterface.ZeroOneType
ZeroOne()

The set $\{0, 1\}$.

Variables belonging to the ZeroOne set are also known as "binary" variables.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(model, x, MOI.ZeroOne())
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(1)
source
MathOptInterface.SemicontinuousType
Semicontinuous{T<:Real}(lower::T, upper::T)

The set $\{0\} \cup [lower, upper]$.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(model, x, MOI.Semicontinuous(2.0, 3.0))
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Semicontinuous{Float64}}(1)
source
MathOptInterface.SemiintegerType
Semiinteger{T<:Real}(lower::T, upper::T)

The set $\{0\} \cup \{lower, lower+1, \ldots, upper-1, upper\}$.

Note that if lower and upper are not equivalent to an integer, then the solver may throw an error, or it may round up lower and round down upper to the nearest integers.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(model, x, MOI.Semiinteger(2.0, 3.0))
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Semiinteger{Float64}}(1)
source
MathOptInterface.ParameterType
Parameter{T<:Number}(value::T)

The set containing the single point $\{value\} \subseteq \mathbb{R}$.

The Parameter set is conceptually similar to the EqualTo set, except that a variable constrained to the Parameter set cannot have other constraints added to it, and the Parameter set can never be deleted. Thus, solvers are free to treat the variable as a constant, and they need not add it as a decision variable to the model.

Because of this behavior, you must add parameters using add_constrained_variable, and solvers should declare supports_add_constrained_variable and not supports_constraint for the Parameter set.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> p, ci = MOI.add_constrained_variable(model, MOI.Parameter(2.5))
+(MOI.VariableIndex(1), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Parameter{Float64}}(1))
+
+julia> MOI.set(model, MOI.ConstraintSet(), ci, MOI.Parameter(3.0))
+
+julia> MOI.get(model, MOI.ConstraintSet(), ci)
+MathOptInterface.Parameter{Float64}(3.0)
source

Vector sets

List of recognized vector sets.

MathOptInterface.RealsType
Reals(dimension::Int)

The set $\mathbb{R}^{dimension}$ (containing all points) of non-negative dimension dimension.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Reals(3))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Reals}(1)
source
MathOptInterface.ZerosType
Zeros(dimension::Int)

The set $\{ 0 \}^{dimension}$ (containing only the origin) of non-negative dimension dimension.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Zeros(3))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Zeros}(1)
source
MathOptInterface.NonnegativesType
Nonnegatives(dimension::Int)

The nonnegative orthant $\{ x \in \mathbb{R}^{dimension} : x \ge 0 \}$ of non-negative dimension dimension.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Nonnegatives(3))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Nonnegatives}(1)
source
MathOptInterface.NonpositivesType
Nonpositives(dimension::Int)

The nonpositive orthant $\{ x \in \mathbb{R}^{dimension} : x \le 0 \}$ of non-negative dimension dimension.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Nonpositives(3))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Nonpositives}(1)
source
MathOptInterface.NormInfinityConeType
NormInfinityCone(dimension::Int)

The $\ell_\infty$-norm cone $\{ (t,x) \in \mathbb{R}^{dimension} : t \ge \lVert x \rVert_\infty = \max_i \lvert x_i \rvert \}$ of dimension dimension.

The dimension must be at least 1.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables([t; x]), MOI.NormInfinityCone(4))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.NormInfinityCone}(1)
source
MathOptInterface.NormOneConeType
NormOneCone(dimension::Int)

The $\ell_1$-norm cone $\{ (t,x) \in \mathbb{R}^{dimension} : t \ge \lVert x \rVert_1 = \sum_i \lvert x_i \rvert \}$ of dimension dimension.

The dimension must be at least 1.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables([t; x]), MOI.NormOneCone(4))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.NormOneCone}(1)
source
MathOptInterface.NormConeType
NormCone(p::Float64, dimension::Int)

The $\ell_p$-norm cone $\{ (t,x) \in \mathbb{R}^{dimension} : t \ge \left(\sum\limits_i |x_i|^p\right)^{\frac{1}{p}} \}$ of dimension dimension.

The dimension must be at least 1.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables([t; x]), MOI.NormCone(3, 4))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.NormCone}(1)
source
MathOptInterface.SecondOrderConeType
SecondOrderCone(dimension::Int)

The second-order cone (or Lorenz cone or $\ell_2$-norm cone) $\{ (t,x) \in \mathbb{R}^{dimension} : t \ge \lVert x \rVert_2 \}$ of dimension dimension.

The dimension must be at least 1.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables([t; x]), MOI.SecondOrderCone(4))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.SecondOrderCone}(1)
source
MathOptInterface.RotatedSecondOrderConeType
RotatedSecondOrderCone(dimension::Int)

The rotated second-order cone $\{ (t,u,x) \in \mathbb{R}^{dimension} : 2tu \ge \lVert x \rVert_2^2, t,u \ge 0 \}$ of dimension dimension.

The dimension must be at least 2.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> u = MOI.add_variable(model)
+MOI.VariableIndex(2)
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables([t; u; x]),
+           MOI.RotatedSecondOrderCone(5),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.RotatedSecondOrderCone}(1)
source
MathOptInterface.GeometricMeanConeType
GeometricMeanCone(dimension::Int)

The geometric mean cone $\{ (t,x) \in \mathbb{R}^{n+1} : x \ge 0, t \le \sqrt[n]{x_1 x_2 \cdots x_n} \}$, where dimension = n + 1 >= 2.

Duality note

The dual of the geometric mean cone is $\{ (u, v) \in \mathbb{R}^{n+1} : u \le 0, v \ge 0, -u \le n \sqrt[n]{\prod_i v_i} \}$, where dimension = n + 1 >= 2.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables([t; x]),
+           MOI.GeometricMeanCone(4),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.GeometricMeanCone}(1)
source
MathOptInterface.ExponentialConeType
ExponentialCone()

The 3-dimensional exponential cone $\{ (x,y,z) \in \mathbb{R}^3 : y \exp (x/y) \le z, y > 0 \}$.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.ExponentialCone())
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.ExponentialCone}(1)
source
MathOptInterface.DualExponentialConeType
DualExponentialCone()

The 3-dimensional dual exponential cone $\{ (u,v,w) \in \mathbb{R}^3 : -u \exp (v/u) \le \exp(1) w, u < 0 \}$.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.DualExponentialCone())
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.DualExponentialCone}(1)
source
MathOptInterface.PowerConeType
PowerCone{T<:Real}(exponent::T)

The 3-dimensional power cone $\{ (x,y,z) \in \mathbb{R}^3 : x^{exponent} y^{1-exponent} \ge |z|, x \ge 0, y \ge 0 \}$ with parameter exponent.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.PowerCone(0.5))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.PowerCone{Float64}}(1)
source
MathOptInterface.DualPowerConeType
DualPowerCone{T<:Real}(exponent::T)

The 3-dimensional power cone $\{ (u,v,w) \in \mathbb{R}^3 : (\frac{u}{exponent})^{exponent} (\frac{v}{1-exponent})^{1-exponent} \ge |w|, u \ge 0, v \ge 0 \}$ with parameter exponent.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.DualPowerCone(0.5))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.DualPowerCone{Float64}}(1)
source
MathOptInterface.RelativeEntropyConeType
RelativeEntropyCone(dimension::Int)

The relative entropy cone $\{ (u, v, w) \in \mathbb{R}^{1+2n} : u \ge \sum_{i=1}^n w_i \log(\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}$, where dimension = 2n + 1 >= 1.

Duality note

The dual of the relative entropy cone is $\{ (u, v, w) \in \mathbb{R}^{1+2n} : \forall i, w_i \ge u (\log (\frac{u}{v_i}) - 1), v_i \ge 0, u > 0 \}$ of dimension dimension${}=2n+1$.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> u = MOI.add_variable(model);
+
+julia> v = MOI.add_variables(model, 3);
+
+julia> w = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables([u; v; w]),
+           MOI.RelativeEntropyCone(7),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.RelativeEntropyCone}(1)
source
MathOptInterface.NormSpectralConeType
NormSpectralCone(row_dim::Int, column_dim::Int)

The epigraph of the matrix spectral norm (maximum singular value function) $\{ (t, X) \in \mathbb{R}^{1 + row_dim \times column_dim} : t \ge \sigma_1(X) \}$, where $\sigma_i$ is the $i$th singular value of the matrix $X$ of non-negative row dimension row_dim and column dimension column_dim.

The matrix X is vectorized by stacking the columns, matching the behavior of Julia's vec function.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> X = reshape(MOI.add_variables(model, 6), 2, 3)
+2×3 Matrix{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(2)  MOI.VariableIndex(4)  MOI.VariableIndex(6)
+ MOI.VariableIndex(3)  MOI.VariableIndex(5)  MOI.VariableIndex(7)
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables([t; vec(X)]),
+           MOI.NormSpectralCone(2, 3),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.NormSpectralCone}(1)
source
MathOptInterface.NormNuclearConeType
NormNuclearCone(row_dim::Int, column_dim::Int)

The epigraph of the matrix nuclear norm (sum of singular values function) $\{ (t, X) \in \mathbb{R}^{1 + row_dim \times column_dim} : t \ge \sum_i \sigma_i(X) \}$, where $\sigma_i$ is the $i$th singular value of the matrix $X$ of non-negative row dimension row_dim and column dimension column_dim.

The matrix X is vectorized by stacking the columns, matching the behavior of Julia's vec function.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> X = reshape(MOI.add_variables(model, 6), 2, 3)
+2×3 Matrix{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(2)  MOI.VariableIndex(4)  MOI.VariableIndex(6)
+ MOI.VariableIndex(3)  MOI.VariableIndex(5)  MOI.VariableIndex(7)
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables([t; vec(X)]),
+           MOI.NormNuclearCone(2, 3),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.NormNuclearCone}(1)
source
MathOptInterface.SOS1Type
SOS1{T<:Real}(weights::Vector{T})

The set corresponding to the Special Ordered Set (SOS) constraint of Type I.

Of the variables in the set, at most one can be nonzero.

The weights induce an ordering of the variables such that the kth element in the set corresponds to the kth weight in weights. Solvers may use these weights to improve the efficiency of the solution process, but the ordering does not change the set of feasible solutions.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables(x),
+           MOI.SOS1([1.0, 3.0, 2.5]),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.SOS1{Float64}}(1)
source
MathOptInterface.SOS2Type
SOS2{T<:Real}(weights::Vector{T})

The set corresponding to the Special Ordered Set (SOS) constraint of Type II.

The weights induce an ordering of the variables such that the kth element in the set corresponds to the kth weight in weights. Therefore, the weights must be unique.

Of the variables in the set, at most two can be nonzero, and if two are nonzero, they must be adjacent in the ordering of the set.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables(x),
+           MOI.SOS2([1.0, 3.0, 2.5]),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.SOS2{Float64}}(1)
source
MathOptInterface.IndicatorType
Indicator{A<:ActivationCondition,S<:AbstractScalarSet}(set::S)

The set corresponding to an indicator constraint.

When A is ACTIVATE_ON_ZERO, this means: $\{(y, x) \in \{0, 1\} \times \mathbb{R}^n : y = 0 \implies x \in set\}$

When A is ACTIVATE_ON_ONE, this means: $\{(y, x) \in \{0, 1\} \times \mathbb{R}^n : y = 1 \implies x \in set\}$

Notes

Most solvers expect that the first row of the function is interpretable as a variable index x_i (e.g., 1.0 * x + 0.0). An error will be thrown if this is not the case.

Example

The constraint $\{(y, x) \in \{0, 1\} \times \mathbb{R}^2 : y = 1 \implies x_1 + x_2 \leq 9 \}$ is defined as

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 2)
+2-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+ MOI.VariableIndex(2)
+
+julia> y, _ = MOI.add_constrained_variable(model, MOI.ZeroOne())
+(MOI.VariableIndex(3), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(3))
+
+julia> f = MOI.VectorAffineFunction(
+           [
+               MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y)),
+               MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x[1])),
+               MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x[2])),
+           ],
+           [0.0, 0.0],
+       )
+┌                                                         ┐
+│0.0 + 1.0 MOI.VariableIndex(3)                           │
+│0.0 + 1.0 MOI.VariableIndex(1) + 1.0 MOI.VariableIndex(2)│
+└                                                         ┘
+
+julia> s = MOI.Indicator{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(9.0))
+MathOptInterface.Indicator{MathOptInterface.ACTIVATE_ON_ONE, MathOptInterface.LessThan{Float64}}(MathOptInterface.LessThan{Float64}(9.0))
+
+julia> MOI.add_constraint(model, f, s)
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Indicator{MathOptInterface.ACTIVATE_ON_ONE, MathOptInterface.LessThan{Float64}}}(1)
source
MathOptInterface.ComplementsType
Complements(dimension::Base.Integer)

The set corresponding to a mixed complementarity constraint.

Complementarity constraints should be specified with an AbstractVectorFunction-in-Complements(dimension) constraint.

The dimension of the vector-valued function F must be dimension. This defines a complementarity constraint between the scalar function F[i] and the variable in F[i + dimension/2]. Thus, F[i + dimension/2] must be interpretable as a single variable x_i (e.g., 1.0 * x + 0.0), and dimension must be even.

The mixed complementarity problem consists of finding x_i in the interval [lb, ub] (i.e., in the set Interval(lb, ub)), such that the following holds:

  1. F_i(x) == 0 if lb_i < x_i < ub_i
  2. F_i(x) >= 0 if lb_i == x_i
  3. F_i(x) <= 0 if x_i == ub_i

Classically, the bounding set for x_i is Interval(0, Inf), which recovers: 0 <= F_i(x) ⟂ x_i >= 0, where the operator implies F_i(x) * x_i = 0.

Example

The problem:

x -in- Interval(-1, 1)
+[-4 * x - 3, x] -in- Complements(2)

defines the mixed complementarity problem where the following holds:

  1. -4 * x - 3 == 0 if -1 < x < 1
  2. -4 * x - 3 >= 0 if x == -1
  3. -4 * x - 3 <= 0 if x == 1

There are three solutions:

  1. x = -3/4 with F(x) = 0
  2. x = -1 with F(x) = 1
  3. x = 1 with F(x) = -7
julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x, _ = MOI.add_constrained_variable(model, MOI.Interval(-1.0, 1.0));
+
+julia> MOI.add_constraint(
+            model,
+            MOI.Utilities.vectorize([-4.0 * x - 3.0, x]),
+            MOI.Complements(2),
+        )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Complements}(1)

The function F can also be defined in terms of single variables. For example, the problem:

[x_3, x_4] -in- Nonnegatives(2)
+[x_1, x_2, x_3, x_4] -in- Complements(4)

defines the complementarity problem where 0 <= x_1 ⟂ x_3 >= 0 and 0 <= x_2 ⟂ x_4 >= 0.

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 4);
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x[3:4]), MOI.Nonnegatives(2))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Nonnegatives}(1)
+
+julia> MOI.add_constraint(
+            model,
+            MOI.VectorOfVariables(x),
+            MOI.Complements(4),
+        )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Complements}(1)
source
MathOptInterface.HyperRectangleType
HyperRectangle(lower::Vector{T}, upper::Vector{T}) where {T}

The set $\{x \in \bar{\mathbb{R}}^d: x_i \in [lower_i, upper_i] \forall i=1,\ldots,d\}$.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3)
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+ MOI.VariableIndex(2)
+ MOI.VariableIndex(3)
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables(x),
+           MOI.HyperRectangle(zeros(3), ones(3)),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.HyperRectangle{Float64}}(1)
source
MathOptInterface.ScaledType
struct Scaled{S<:AbstractVectorSet} <: AbstractVectorSet
+    set::S
+end

Given a vector $a \in \mathbb{R}^d$ and a set representing the set $\mathcal{S} \in \mathbb{R}^d$ such that Utilities.set_dot for $x \in \mathcal{S}$ and $y \in \mathcal{S}^*$ is

\[\sum_{i=1}^d a_i x_i y_i\]

the set Scaled(set) is defined as

\[\{ (\sqrt{a_1} x_1, \sqrt{a_2} x_2, \ldots, \sqrt{a_d} x_d) : x \in S \}\]

Example

This can be used to scale a vector of numbers

julia> import MathOptInterface as MOI
+
+julia> set = MOI.PositiveSemidefiniteConeTriangle(2)
+MathOptInterface.PositiveSemidefiniteConeTriangle(2)
+
+julia> a = MOI.Utilities.SetDotScalingVector{Float64}(set)
+3-element MathOptInterface.Utilities.SetDotScalingVector{Float64, MathOptInterface.PositiveSemidefiniteConeTriangle}:
+ 1.0
+ 1.4142135623730951
+ 1.0
+
+julia> using LinearAlgebra
+
+julia> MOI.Utilities.operate(*, Float64, Diagonal(a), ones(3))
+3-element Vector{Float64}:
+ 1.0
+ 1.4142135623730951
+ 1.0

It can be also used to scale a vector of function

julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3);
+
+julia> func = MOI.VectorOfVariables(x)
+┌                    ┐
+│MOI.VariableIndex(1)│
+│MOI.VariableIndex(2)│
+│MOI.VariableIndex(3)│
+└                    ┘
+
+julia> set = MOI.PositiveSemidefiniteConeTriangle(2)
+MathOptInterface.PositiveSemidefiniteConeTriangle(2)
+
+julia> MOI.Utilities.operate(*, Float64, Diagonal(a), func)
+┌                                             ┐
+│0.0 + 1.0 MOI.VariableIndex(1)               │
+│0.0 + 1.4142135623730951 MOI.VariableIndex(2)│
+│0.0 + 1.0 MOI.VariableIndex(3)               │
+└                                             ┘
source

Constraint programming sets

MathOptInterface.AllDifferentType
AllDifferent(dimension::Int)

The set $\{x \in \mathbb{Z}^{d}\}$ such that no two elements in $x$ take the same value and dimension = d.

Also known as

This constraint is called all_different in MiniZinc, and is sometimes also called distinct.

Example

To enforce x[1] != x[2] AND x[1] != x[3] AND x[2] != x[3]:

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+ MOI.VariableIndex(2)
+ MOI.VariableIndex(3)
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.AllDifferent(3))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.AllDifferent}(1)
source
MathOptInterface.BinPackingType
BinPacking(c::T, w::Vector{T}) where {T}

The set $\{x \in \mathbb{Z}^d\}$ where d = length(w), such that each item i in 1:d of weight w[i] is put into bin x[i], and the total weight of each bin does not exceed c.

There are additional assumptions that the capacity, c, and the weights, w, must all be non-negative.

The bin numbers depend on the bounds of x, so they may be something other than the integers 1:d.

Also known as

This constraint is called bin_packing in MiniZinc.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> bins = MOI.add_variables(model, 5)
+5-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+ MOI.VariableIndex(2)
+ MOI.VariableIndex(3)
+ MOI.VariableIndex(4)
+ MOI.VariableIndex(5)
+
+julia> weights = Float64[1, 1, 2, 2, 3]
+5-element Vector{Float64}:
+ 1.0
+ 1.0
+ 2.0
+ 2.0
+ 3.0
+
+julia> MOI.add_constraint.(model, bins, MOI.Integer())
+5-element Vector{MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}}:
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(1)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(2)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(3)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(4)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(5)
+
+julia> MOI.add_constraint.(model, bins, MOI.Interval(4.0, 6.0))
+5-element Vector{MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}}}:
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}}(1)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}}(2)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}}(3)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}}(4)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}}(5)
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(bins), MOI.BinPacking(3.0, weights))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.BinPacking{Float64}}(1)
source
MathOptInterface.CircuitType
Circuit(dimension::Int)

The set $\{x \in \{1..d\}^d\}$ that constraints $x$ to be a circuit, such that $x_i = j$ means that $j$ is the successor of $i$, and dimension = d.

Graphs with multiple independent circuits, such as [2, 1, 3] and [2, 1, 4, 3], are not valid.

Also known as

This constraint is called circuit in MiniZinc, and it is equivalent to forming a (potentially sub-optimal) tour in the travelling salesperson problem.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+ MOI.VariableIndex(2)
+ MOI.VariableIndex(3)
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Circuit(3))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Circuit}(1)
source
MathOptInterface.CountAtLeastType
CountAtLeast(n::Int, d::Vector{Int}, set::Set{Int})

The set $\{x \in \mathbb{Z}^{d_1 + d_2 + \ldots d_N}\}$, where x is partitioned into N subsets ($\{x_1, \ldots, x_{d_1}\}$, $\{x_{d_1 + 1}, \ldots, x_{d_1 + d_2}\}$ and so on), and at least $n$ elements of each subset take one of the values in set.

Also known as

This constraint is called at_least in MiniZinc.

Example

To ensure that 3 appears at least once in each of the subsets {a, b} and {b, c}:

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> a, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(1), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(1))
+
+julia> b, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(2), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(2))
+
+julia> c, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(3), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(3))
+
+julia> x, d, set = [a, b, b, c], [2, 2], [3]
+(MathOptInterface.VariableIndex[MOI.VariableIndex(1), MOI.VariableIndex(2), MOI.VariableIndex(2), MOI.VariableIndex(3)], [2, 2], [3])
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.CountAtLeast(1, d, Set(set)))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.CountAtLeast}(1)
source
MathOptInterface.CountBelongsType
CountBelongs(dimenson::Int, set::Set{Int})

The set $\{(n, x) \in \mathbb{Z}^{1+d}\}$, such that n elements of the vector x take on of the values in set and dimension = 1 + d.

Also known as

This constraint is called among by MiniZinc.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> n, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(1), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(1))
+
+julia> x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(2)
+ MOI.VariableIndex(3)
+ MOI.VariableIndex(4)
+
+julia> set = Set([3, 4, 5])
+Set{Int64} with 3 elements:
+  5
+  4
+  3
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables([n; x]), MOI.CountBelongs(4, set))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.CountBelongs}(1)
source
MathOptInterface.CountDistinctType
CountDistinct(dimension::Int)

The set $\{(n, x) \in \mathbb{Z}^{1+d}\}$, such that the number of distinct values in x is n and dimension = 1 + d.

Also known as

This constraint is called nvalues in MiniZinc.

Example

To model:

  • if n == 1`, thenx[1] == x[2] == x[3]`
  • if n == 2, then
    • x[1] == x[2] != x[3] or
    • x[1] != x[2] == x[3] or
    • x[1] == x[3] != x[2]
  • if n == 3, then x[1] != x[2], x[2] != x[3] and x[3] != x[1]
julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> n, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(1), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(1))
+
+julia> x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(2)
+ MOI.VariableIndex(3)
+ MOI.VariableIndex(4)
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(vcat(n, x)), MOI.CountDistinct(4))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.CountDistinct}(1)

Relationship to AllDifferent

When the first element is d, CountDistinct is equivalent to an AllDifferent constraint.

source
MathOptInterface.CountGreaterThanType
CountGreaterThan(dimension::Int)

The set $\{(c, y, x) \in \mathbb{Z}^{1+1+d}\}$, such that c is strictly greater than the number of occurances of y in x and dimension = 1 + 1 + d.

Also known as

This constraint is called count_gt in MiniZinc.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> c, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(1), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(1))
+
+julia> y, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(2), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(2))
+
+julia> x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(3)
+ MOI.VariableIndex(4)
+ MOI.VariableIndex(5)
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables([c; y; x]), MOI.CountGreaterThan(5))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.CountGreaterThan}(1)
source
MathOptInterface.CumulativeType
Cumulative(dimension::Int)

The set $\{(s, d, r, b) \in \mathbb{Z}^{3n+1}\}$, representing the cumulative global constraint, where n == length(s) == length(r) == length(b) and dimension = 3n + 1.

Cumulative requires that a set of tasks given by start times $s$, durations $d$, and resource requirements $r$, never requires more than the global resource bound $b$ at any one time.

Also known as

This constraint is called cumulative in MiniZinc.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> s = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+ MOI.VariableIndex(2)
+ MOI.VariableIndex(3)
+
+julia> d = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(4)
+ MOI.VariableIndex(5)
+ MOI.VariableIndex(6)
+
+julia> r = [MOI.add_constrained_variable(model, MOI.Integer())[1] for _ in 1:3]
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(7)
+ MOI.VariableIndex(8)
+ MOI.VariableIndex(9)
+
+julia> b, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(10), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(10))
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables([s; d; r; b]), MOI.Cumulative(10))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Cumulative}(1)
source
MathOptInterface.PathType
Path(from::Vector{Int}, to::Vector{Int})

Given a graph comprised of a set of nodes 1..N and a set of arcs 1..E represented by an edge from node from[i] to node to[i], Path constrains the set $(s, t, ns, es) \in (1..N)\times(1..E)\times\{0,1\}^N\times\{0,1\}^E$, to form subgraph that is a path from node s to node t, where node n is in the path if ns[n] is 1, and edge e is in the path if es[e] is 1.

The path must be acyclic, and it must traverse all nodes n for which ns[n] is 1, and all edges e for which es[e] is 1.

Also known as

This constraint is called path in MiniZinc.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> N, E = 4, 5
+(4, 5)
+
+julia> from = [1, 1, 2, 2, 3]
+5-element Vector{Int64}:
+ 1
+ 1
+ 2
+ 2
+ 3
+
+julia> to = [2, 3, 3, 4, 4]
+5-element Vector{Int64}:
+ 2
+ 3
+ 3
+ 4
+ 4
+
+julia> s, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(1), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(1))
+
+julia> t, _ = MOI.add_constrained_variable(model, MOI.Integer())
+(MOI.VariableIndex(2), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}(2))
+
+julia> ns = MOI.add_variables(model, N)
+4-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(3)
+ MOI.VariableIndex(4)
+ MOI.VariableIndex(5)
+ MOI.VariableIndex(6)
+
+julia> MOI.add_constraint.(model, ns, MOI.ZeroOne())
+4-element Vector{MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}}:
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(3)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(4)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(5)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(6)
+
+julia> es = MOI.add_variables(model, E)
+5-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(7)
+ MOI.VariableIndex(8)
+ MOI.VariableIndex(9)
+ MOI.VariableIndex(10)
+ MOI.VariableIndex(11)
+
+julia> MOI.add_constraint.(model, es, MOI.ZeroOne())
+5-element Vector{MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}}:
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(7)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(8)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(9)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(10)
+ MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(11)
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables([s; t; ns; es]), MOI.Path(from, to))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Path}(1)
source
MathOptInterface.ReifiedType
Reified(set::AbstractSet)

The constraint $[z; f(x)] \in Reified(S)$ ensures that $f(x) \in S$ if and only if $z == 1$, where $z \in \{0, 1\}$.

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
+MOIU.UniversalFallback{MOIU.Model{Float64}}
+fallback for MOIU.Model{Float64}
+
+julia> z, _ = MOI.add_constrained_variable(model, MOI.ZeroOne())
+(MOI.VariableIndex(1), MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(1))
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(2)
+
+julia> MOI.add_constraint(
+           model,
+           MOI.Utilities.vectorize([z, 2.0 * x]),
+           MOI.Reified(MOI.GreaterThan(1.0)),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Reified{MathOptInterface.GreaterThan{Float64}}}(1)
source
MathOptInterface.TableType
Table(table::Matrix{T}) where {T}

The set $\{x \in \mathbb{R}^d\}$ where d = size(table, 2), such that x belongs to one row of table. That is, there exists some j in 1:size(table, 1), such that x[i] = table[j, i] for all i=1:size(table, 2).

Also known as

This constraint is called table in MiniZinc.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> x = MOI.add_variables(model, 3)
+3-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+ MOI.VariableIndex(2)
+ MOI.VariableIndex(3)
+
+julia> table = Float64[1 1 0; 0 1 1; 1 0 1; 1 1 1]
+4×3 Matrix{Float64}:
+ 1.0  1.0  0.0
+ 0.0  1.0  1.0
+ 1.0  0.0  1.0
+ 1.0  1.0  1.0
+
+julia> MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Table(table))
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.Table{Float64}}(1)
source

Matrix sets

Matrix sets are vectorized to be subtypes of AbstractVectorSet.

For sets of symmetric matrices, storing both the (i, j) and (j, i) elements is redundant. Use the AbstractSymmetricMatrixSetTriangle set to represent only the vectorization of the upper triangular part of the matrix.

When the matrix of expressions constrained to be in the set is not symmetric, and hence additional constraints are needed to force the equality of the (i, j) and (j, i) elements, use the AbstractSymmetricMatrixSetSquare set.

The Bridges.Constraint.SquareBridge can transform a set from the square form to the triangular_form by adding appropriate constraints if the (i, j) and (j, i) expressions are different.

MathOptInterface.AbstractSymmetricMatrixSetTriangleType
abstract type AbstractSymmetricMatrixSetTriangle <: AbstractVectorSet end

Abstract supertype for subsets of the (vectorized) cone of symmetric matrices, with side_dimension rows and columns. 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). A vectorized cone of dimension $n$ corresponds to a square matrix with side dimension $\sqrt{1/4 + 2 n} - 1/2$. (Because a $d \times d$ matrix has $d(d + 1) / 2$ elements in the upper or lower triangle.)

Example

The matrix

\[\begin{bmatrix} + 1 & 2 & 4\\ + 2 & 3 & 5\\ + 4 & 5 & 6 +\end{bmatrix}\]

has side_dimension 3 and vectorization $(1, 2, 3, 4, 5, 6)$.

Note

Two packed storage formats exist for symmetric matrices, the respective orders of the entries are:

  • upper triangular column by column (or lower triangular row by row);
  • lower triangular column by column (or upper triangular row by row).

The advantage of the first format is the mapping between the (i, j) matrix indices and the k index of the vectorized form. It is simpler and does not depend on the side dimension of the matrix. Indeed,

  • the entry of matrix indices (i, j) has vectorized index k = div((j - 1) * j, 2) + i if $i \leq j$ and k = div((i - 1) * i, 2) + j if $j \leq i$;
  • and the entry with vectorized index k has matrix indices i = div(1 + isqrt(8k - 7), 2) and j = k - div((i - 1) * i, 2) or j = div(1 + isqrt(8k - 7), 2) and i = k - div((j - 1) * j, 2).

Duality note

The scalar product for the symmetric matrix in its vectorized form is the sum of the pairwise product of the diagonal entries plus twice the sum of the pairwise product of the upper diagonal entries; see [p. 634, 1]. This has important consequence for duality.

Consider for example the following problem (PositiveSemidefiniteConeTriangle is a subtype of AbstractSymmetricMatrixSetTriangle)

\[\begin{align*} + & \max_{x \in \mathbb{R}} & x + \\ + & \;\;\text{s.t.} & + (1, -x, 1) & \in \text{PositiveSemidefiniteConeTriangle}(2). +\end{align*}\]

The dual is the following problem

\[\begin{align*} + & \min_{x \in \mathbb{R}^3} & y_1 + y_3 + \\ + & \;\;\text{s.t.} & 2y_2 & = 1\\ + & & y & \in \text{PositiveSemidefiniteConeTriangle}(2). +\end{align*}\]

Why do we use $2y_2$ in the dual constraint instead of $y_2$ ? The reason is that $2y_2$ is the scalar product between $y$ and the symmetric matrix whose vectorized form is $(0, 1, 0)$. Indeed, with our modified scalar products we have

\[\langle +(0, 1, 0), +(y_1, y_2, y_3) +\rangle += +\mathrm{trace} +\begin{pmatrix} + 0 & 1\\ + 1 & 0 +\end{pmatrix} +\begin{pmatrix} + y_1 & y_2\\ + y_2 & y_3 +\end{pmatrix} += 2y_2.\]

References

[1] Boyd, S. and Vandenberghe, L.. Convex optimization. Cambridge university press, 2004.

source
MathOptInterface.AbstractSymmetricMatrixSetSquareType
abstract type AbstractSymmetricMatrixSetSquare <: AbstractVectorSet end

Abstract supertype for subsets of the (vectorized) cone of symmetric matrices, with side_dimension rows and columns. The entries of the matrix are given column by column (or equivalently, row by row). The matrix is both constrained to be symmetric and to have its triangular_form belong to the corresponding set. That is, if the functions in entries $(i, j)$ and $(j, i)$ are different, then a constraint will be added to make sure that the entries are equal.

Example

PositiveSemidefiniteConeSquare is a subtype of AbstractSymmetricMatrixSetSquare and constraining the matrix

\[\begin{bmatrix} + 1 & -y\\ + -z & 0\\ +\end{bmatrix}\]

to be symmetric positive semidefinite can be achieved by constraining the vector $(1, -z, -y, 0)$ (or $(1, -y, -z, 0)$) to belong to the PositiveSemidefiniteConeSquare(2). It both constrains $y = z$ and $(1, -y, 0)$ (or $(1, -z, 0)$) to be in PositiveSemidefiniteConeTriangle(2), since triangular_form(PositiveSemidefiniteConeSquare) is PositiveSemidefiniteConeTriangle.

source

List of recognized matrix sets.

MathOptInterface.PositiveSemidefiniteConeSquareType
PositiveSemidefiniteConeSquare(side_dimension::Int) <: AbstractSymmetricMatrixSetSquare

The cone of symmetric positive semidefinite matrices, with non-negative side length side_dimension.

See AbstractSymmetricMatrixSetSquare for more details on the vectorized form.

The entries of the matrix are given column by column (or equivalently, row by row).

The matrix is both constrained to be symmetric and to be positive semidefinite. That is, if the functions in entries $(i, j)$ and $(j, i)$ are different, then a constraint will be added to make sure that the entries are equal.

Example

Constraining the matrix

\[\begin{bmatrix} + 1 & -y\\ + -z & 0\\ +\end{bmatrix}\]

to be symmetric positive semidefinite can be achieved by constraining the vector $(1, -z, -y, 0)$ (or $(1, -y, -z, 0)$) to belong to the PositiveSemidefiniteConeSquare(2).

It both constrains $y = z$ and $(1, -y, 0)$ (or $(1, -z, 0)$) to be in PositiveSemidefiniteConeTriangle(2).

source
MathOptInterface.HermitianPositiveSemidefiniteConeTriangleType
HermitianPositiveSemidefiniteConeTriangle(side_dimension::Int) <: AbstractVectorSet

The (vectorized) cone of Hermitian positive semidefinite matrices, with non-negative side_dimension rows and columns.

Becaue the matrix is Hermitian, the diagonal elements are real, and the complex-valued lower triangular entries are obtained as the conjugate of corresponding upper triangular entries.

Vectorization format

The vectorized form starts with real part of the entries of the upper triangular part of the matrix, given column by column as explained in AbstractSymmetricMatrixSetSquare.

It is then followed by the imaginary part of the off-diagonal entries of the upper triangular part, also given column by column.

For example, the matrix

\[\begin{bmatrix} + 1 & 2 + 7im & 4 + 8im\\ + 2 - 7im & 3 & 5 + 9im\\ + 4 - 8im & 5 - 9im & 6 +\end{bmatrix}\]

has side_dimension 3 and is represented as the vector $[1, 2, 3, 4, 5, 6, 7, 8, 9]$.

source
MathOptInterface.LogDetConeTriangleType
LogDetConeTriangle(side_dimension::Int)

The log-determinant cone $\{ (t, u, X) \in \mathbb{R}^{2 + d(d+1)/2} : t \le u \log(\det(X/u)), u > 0 \}$, where the matrix X is represented in the same symmetric packed format as in the PositiveSemidefiniteConeTriangle.

The non-negative argument side_dimension is the side dimension of the matrix X, i.e., its number of rows or columns.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> X = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables([t; X]),
+           MOI.LogDetConeTriangle(2),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.LogDetConeTriangle}(1)
source
MathOptInterface.LogDetConeSquareType
LogDetConeSquare(side_dimension::Int)

The log-determinant cone $\{ (t, u, X) \in \mathbb{R}^{2 + d^2} : t \le u \log(\det(X/u)), X \text{ symmetric}, u > 0 \}$, where the matrix X is represented in the same format as in the PositiveSemidefiniteConeSquare.

Similarly to PositiveSemidefiniteConeSquare, constraints are added to ensure that X is symmetric.

The non-negative argument side_dimension is the side dimension of the matrix X, i.e., its number of rows or columns.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> X = reshape(MOI.add_variables(model, 4), 2, 2)
+2×2 Matrix{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(2)  MOI.VariableIndex(4)
+ MOI.VariableIndex(3)  MOI.VariableIndex(5)
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables([t; vec(X)]),
+           MOI.LogDetConeSquare(2),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.LogDetConeSquare}(1)
source
MathOptInterface.RootDetConeTriangleType
RootDetConeTriangle(side_dimension::Int)

The root-determinant cone $\{ (t, X) \in \mathbb{R}^{1 + d(d+1)/2} : t \le \det(X)^{1/d} \}$, where the matrix X is represented in the same symmetric packed format as in the PositiveSemidefiniteConeTriangle.

The non-negative argument side_dimension is the side dimension of the matrix X, i.e., its number of rows or columns.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> X = MOI.add_variables(model, 3);
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables([t; X]),
+           MOI.RootDetConeTriangle(2),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.RootDetConeTriangle}(1)
source
MathOptInterface.RootDetConeSquareType
RootDetConeSquare(side_dimension::Int)

The root-determinant cone $\{ (t, X) \in \mathbb{R}^{1 + d^2} : t \le \det(X)^{1/d}, X \text{ symmetric} \}$, where the matrix X is represented in the same format as PositiveSemidefiniteConeSquare.

Similarly to PositiveSemidefiniteConeSquare, constraints are added to ensure that X is symmetric.

The non-negative argument side_dimension is the side dimension of the matrix X, i.e., its number of rows or columns.

Example

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> t = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> X = reshape(MOI.add_variables(model, 4), 2, 2)
+2×2 Matrix{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(2)  MOI.VariableIndex(4)
+ MOI.VariableIndex(3)  MOI.VariableIndex(5)
+
+julia> MOI.add_constraint(
+           model,
+           MOI.VectorOfVariables([t; vec(X)]),
+           MOI.RootDetConeSquare(2),
+       )
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.RootDetConeSquare}(1)
source
diff --git a/previews/PR3536/moi/reference/variables/index.html b/previews/PR3536/moi/reference/variables/index.html new file mode 100644 index 00000000000..43dd3554852 --- /dev/null +++ b/previews/PR3536/moi/reference/variables/index.html @@ -0,0 +1,28 @@ + +Variables · JuMP

Variables

Functions

MathOptInterface.add_variablesFunction
add_variables(model::ModelLike, n::Int)::Vector{VariableIndex}

Add n scalar variables to the model, returning a vector of variable indices.

A AddVariableNotAllowed error is thrown if adding variables cannot be done in the current state of the model model.

source
MathOptInterface.add_constrained_variableFunction
add_constrained_variable(
+    model::ModelLike,
+    set::AbstractScalarSet
+)::Tuple{MOI.VariableIndex,
+         MOI.ConstraintIndex{MOI.VariableIndex, typeof(set)}}

Add to model a scalar variable constrained to belong to set, returning the index of the variable created and the index of the constraint constraining the variable to belong to set.

By default, this function falls back to creating a free variable with add_variable and then constraining it to belong to set with add_constraint.

source
MathOptInterface.add_constrained_variablesFunction
add_constrained_variables(
+    model::ModelLike,
+    sets::AbstractVector{<:AbstractScalarSet}
+)::Tuple{
+    Vector{MOI.VariableIndex},
+    Vector{MOI.ConstraintIndex{MOI.VariableIndex,eltype(sets)}},
+}

Add to model scalar variables constrained to belong to sets, returning the indices of the variables created and the indices of the constraints constraining the variables to belong to each set in sets. That is, if it returns variables and constraints, constraints[i] is the index of the constraint constraining variable[i] to belong to sets[i].

By default, this function falls back to calling add_constrained_variable on each set.

source
add_constrained_variables(
+    model::ModelLike,
+    set::AbstractVectorSet,
+)::Tuple{
+    Vector{MOI.VariableIndex},
+    MOI.ConstraintIndex{MOI.VectorOfVariables,typeof(set)},
+}

Add to model a vector of variables constrained to belong to set, returning the indices of the variables created and the index of the constraint constraining the vector of variables to belong to set.

By default, this function falls back to creating free variables with add_variables and then constraining it to belong to set with add_constraint.

source
MathOptInterface.supports_add_constrained_variableFunction
supports_add_constrained_variable(
+    model::ModelLike,
+    S::Type{<:AbstractScalarSet}
+)::Bool

Return a Bool indicating whether model supports constraining a variable to belong to a set of type S either on creation of the variable with add_constrained_variable or after the variable is created with add_constraint.

By default, this function falls back to supports_add_constrained_variables(model, Reals) && supports_constraint(model, MOI.VariableIndex, S) which is the correct definition for most models.

Example

Suppose that a solver supports only two kind of variables: binary variables and continuous variables with a lower bound. If the solver decides not to support VariableIndex-in-Binary and VariableIndex-in-GreaterThan constraints, it only has to implement add_constrained_variable for these two sets which prevents the user to add both a binary constraint and a lower bound on the same variable. Moreover, if the user adds a VariableIndex-in-GreaterThan constraint, implementing this interface (i.e., supports_add_constrained_variables) enables the constraint to be transparently bridged into a supported constraint.

source
MathOptInterface.supports_add_constrained_variablesFunction
supports_add_constrained_variables(
+    model::ModelLike,
+    S::Type{<:AbstractVectorSet}
+)::Bool

Return a Bool indicating whether model supports constraining a vector of variables to belong to a set of type S either on creation of the vector of variables with add_constrained_variables or after the variable is created with add_constraint.

By default, if S is Reals then this function returns true and otherwise, it falls back to supports_add_constrained_variables(model, Reals) && supports_constraint(model, MOI.VectorOfVariables, S) which is the correct definition for most models.

Example

In the standard conic form (see Duality), the variables are grouped into several cones and the constraints are affine equality constraints. If Reals is not one of the cones supported by the solvers then it needs to implement supports_add_constrained_variables(::Optimizer, ::Type{Reals}) = false as free variables are not supported. The solvers should then implement supports_add_constrained_variables(::Optimizer, ::Type{<:SupportedCones}) = true where SupportedCones is the union of all cone types that are supported; it does not have to implement the method supports_constraint(::Type{VectorOfVariables}, Type{<:SupportedCones}) as it should return false and it's the default. This prevents the user to constrain the same variable in two different cones. When a VectorOfVariables-in-S is added, the variables of the vector have already been created so they already belong to given cones. If bridges are enabled, the constraint will therefore be bridged by adding slack variables in S and equality constraints ensuring that the slack variables are equal to the corresponding variables of the given constraint function.

Note that there may also be sets for which !supports_add_constrained_variables(model, S) and supports_constraint(model, MOI.VectorOfVariables, S). For instance, suppose a solver supports positive semidefinite variable constraints and two types of variables: binary variables and nonnegative variables. Then the solver should support adding VectorOfVariables-in-PositiveSemidefiniteConeTriangle constraints, but it should not support creating variables constrained to belong to the PositiveSemidefiniteConeTriangle because the variables in PositiveSemidefiniteConeTriangle should first be created as either binary or non-negative.

source
MathOptInterface.is_validMethod
is_valid(model::ModelLike, index::Index)::Bool

Return a Bool indicating whether this index refers to a valid object in the model model.

source
MathOptInterface.deleteMethod
delete(model::ModelLike, index::Index)

Delete the referenced object from the model. Throw DeleteNotAllowed if if index cannot be deleted.

The following modifications also take effect if Index is VariableIndex:

  • If index used in the objective function, it is removed from the function, i.e., it is substituted for zero.
  • For each func-in-set constraint of the model:
    • If func isa VariableIndex and func == index then the constraint is deleted.
    • If func isa VectorOfVariables and index in func.variables then
      • if length(func.variables) == 1 is one, the constraint is deleted;
      • if length(func.variables) > 1 and supports_dimension_update(set) then then the variable is removed from func and set is replaced by update_dimension(set, MOI.dimension(set) - 1).
      • Otherwise, a DeleteNotAllowed error is thrown.
    • Otherwise, the variable is removed from func, i.e., it is substituted for zero.
source
MathOptInterface.deleteMethod
delete(model::ModelLike, indices::Vector{R<:Index}) where {R}

Delete the referenced objects in the vector indices from the model. It may be assumed that R is a concrete type. The default fallback sequentially deletes the individual items in indices, although specialized implementations may be more efficient.

source

Attributes

MathOptInterface.VariableNameType
VariableName()

A variable attribute for a string identifying the variable. It is valid for two variables to have the same name; however, variables with duplicate names cannot be looked up using get. It has a default value of "" if not set`.

source
MathOptInterface.VariablePrimalStartType
VariablePrimalStart()

A variable attribute for the initial assignment to some primal variable's value that the optimizer may use to warm-start the solve. May be a number or nothing (unset).

source
MathOptInterface.VariablePrimalType
VariablePrimal(result_index::Int = 1)

A variable attribute for the assignment to some primal variable's value in result result_index. If result_index is omitted, it is 1 by default.

If the solver does not have a primal value for the variable because the result_index is beyond the available solutions (whose number is indicated by the ResultCount attribute), getting this attribute must throw a ResultIndexBoundsError. Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check PrimalStatus before accessing the VariablePrimal attribute.

See ResultCount for information on how the results are ordered.

source
MathOptInterface.VariableBasisStatusType
VariableBasisStatus(result_index::Int = 1)

A variable attribute for the BasisStatusCode of a variable in result result_index, with respect to an available optimal solution basis.

If the solver does not have a basis statue for the variable because the result_index is beyond the available solutions (whose number is indicated by the ResultCount attribute), getting this attribute must throw a ResultIndexBoundsError. Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check PrimalStatus before accessing the VariableBasisStatus attribute.

See ResultCount for information on how the results are ordered.

source
diff --git a/previews/PR3536/moi/release_notes/index.html b/previews/PR3536/moi/release_notes/index.html new file mode 100644 index 00000000000..8b31e20bd95 --- /dev/null +++ b/previews/PR3536/moi/release_notes/index.html @@ -0,0 +1,34 @@ + +Release notes · JuMP

Release notes

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

v1.20.1 (September 24, 2023)

Fixed

Other

  • Added MathOptSetDistances to solver-tests.yml (#2265)
  • Updated Documenter (#2266)
  • Fixed various JET errors (#2267) (#2269) (#2270) (#2271) (#2276) (#2277) (#2289)
  • Various style improvements
    • Replaced using Package with import Package where possible (#2274)
    • Removed Utilities.EMPTYSTRING (#2283)
    • Removed unnecessary const acronyms in Utilities (#2280) (#2281)
    • Removed invalid and unused method (#2286)
  • Refactored src/Utilities/model.jl (#2287)

v1.20.0 (September 7, 2023)

Added

Other

v1.19.0 (August 15, 2023)

Added

Fixed

Other

  • Added extensions to solver-tests.yml (#2229)
  • Refactored test/Benchmarks (#2234)
  • Fixed warnings in tests (#2241) (#2243)
  • Small refactoring of bridges for upcoming VectorNonlinearFunction (#2244) (#2245)
  • Fixed various typos (#2251) (#2255)
  • Partitioned how we run the tests on GitHub actions (#2252) (#2253)

v1.18.0 (June 23, 2023)

Added

Fixed

  • Fixed a missing @require in MOI.Test (#2195) (#2196)
  • Fixed incorrect usage of Utilities.operate! in bridges (#2207) (#2216)
  • Fixed splatting nonlinear expression with univariate operator (#2221)

Other

  • Removed unused argument names (#2199)
  • Reduced memory requirement for tests (#2204)
  • Refactored Utilities.promote_operation (#2206)
  • Improved code style in Utilities/mutable_arithmetics.jl (#2209)
  • Refactored various methods in Utilities/functions.jl (#2208) (#2212) (#2213) (#2214) (#2215)

v1.17.1 (June 6, 2023)

Fixed

Other

  • Added documentation for enum instances (#2186)
  • Updated chatroom links in documentation (#2188)
  • Changed the documentation to build on Julia v1.9 (#2191)

v1.17.0 (June 1, 2023)

Added

Fixed

  • Fixed support for external sets in Utilities.loadfromstring! (#2177)
  • Fixed promote_operation for ScalarNonlinearFunction (#2179)
  • Fixed two issues in FileFormats.LP when reading files with quadratic functions (#2182) (#2184)

v1.16.0 (May 16, 2023)

Added

Fixed

Other

  • Fixed solver-tests.yml (#2157)
  • Updated documentation links to developer chatroom (#2160)
  • Added various tests for bridges (#2156)
  • Added checklists to the developer documentation (#2167) (#2168)

v1.15.1 (April 25, 2023)

Fixed

  • Fixed deleting a variable in a bridged objective (#2150)

v1.15.0 (April 19, 2023)

Added

Fixed

Other

  • Add a test for variables in one-sided open Interval sets (#2133)
  • Minor style fixes in the source code (#2148)

v1.14.1 (April 6, 2023)

Fixed

Other

v1.14.0 (April 4, 2023)

Added

Fixed

v1.13.2 (March 21, 2023)

Fixed

Other

  • Fixed typos in the documentation (#2114)
  • Functions now print to the REPL in algebraic form. This is potentially breaking if you have tests which rely on a specific String form of MOI functions. (#2112) (#2126)

v1.13.1 (March 3, 2023)

Other

  • Added the Google style guide to the documentation linter Vale, and fixed the resulting warnings (#2110)
  • Improved the docstrings in src/functions.jl (#2108)

v1.13.0 (February 28, 2023)

Added

Fixed

Other

  • Added tests for vector-valued objective functions in FileFormats.MOF (#2093)
  • Used and documented preference for import MathOptInterface as MOI (#2096)
  • Fix and test links in the documentation with linkcheck = true (#2098)
  • Improved docstrings of sets in src/sets.jl (#2099)
  • Skip checking flakey links in documentation with linkcheck_ignore (#2103)

v1.12.0 (February 10, 2023)

Added

Fixed

  • Fixed a number of constraint bridges so that Bridges.final_touch can be called multiple times without forcing a rebuild of the reformulation (#2089)

Other

v1.11.5 (January 24, 2023)

Fixed

  • Fixed a bug writing .lp files with an off-diagonal quadratic objective (#2082)

Other

  • Added SnoopPrecompile directives for reduced time-to-first-X in Julia v1.9 (#2080)

v1.11.4 (January 12, 2023)

Fixed

  • Fixed a bug reading .lp files with an Integer section (#2078)

v1.11.3 (January 12, 2023)

Fixed

  • Fixed a performance bug when deleting a vector of constraints (#2072)
  • Fixed a bug reading .lp files with terms like x -1 y (#2076)

Other

v1.11.2 (January 2, 2023)

Fixed

  • Fixed a bug reading .mof.json files with ConstraintName set for VariableIndex constraints (#2066)
  • Fixed a bug reading .mof.json files with nonlinear objectives and no constraints (#2068)

v1.11.1 (December 22, 2022)

Fixed

  • Fixed a bug reading .mof.json files with integer coefficients for affine and quadratic functions (#2063)

v1.11.0 (December 2, 2022)

Added

Other

  • Tidied these release notes (#2055)

v1.10.0 (November 22, 2022)

Added

Fixed

  • Fixed Bridges.Objective.SlackBridge when the objective function is complex-valued (#2036) (#2038)
  • Fixed docstring of Test.runtests to clarify the warn_unsupported argument (#2037)
  • Fixed reading of free variables in FileFormats.LP (#2044)
  • Fixed numerous edge cases reading files from QPLIB using FileFormats.LP (#2042) (#2044)
  • Fixed situations in which x^y returns a complex value in Nonlinear (#2050)

Other

  • Improved the error message thrown when a user-defined nonlinear function does not accept splatted input (#2032)
  • Removed specialized iterators for keys and values in Utilities.CleverDicts (#2051)

v1.9.0 (October 29, 2022)

Added

Fixed

  • Fixed Constraint.ZeroOneBridge by adding new bounds as affine constraints instead of variable bounds (#1879)
  • Fixed reading free rows in FileFormats.MPS files (#2009)
  • Fixed parsing of OBJSENSE blocks in FileFormats.MPS files (#2016) (#2019)
  • Fixed the parsing of deeply nested nonlinear expressions by removing the use of recursion (#2020)
  • Fixed the requirements check in Test.test_constrainnt_get_ConstraintIndex (#2024)

v1.8.2 (September 20, 2022)

Documentation

  • Added vale as a documentation linter (#2002)
  • Improved styling of code blocks in the PDF (#1999) (#2000)
  • Fixed a number of typos in the documentation (#2001) (#2003)

v1.8.1 (September 12, 2022)

Fixed

  • Fixed a bug in supports(::AbstractBridgeOptimizer for constraint attributes (#1991) (#1992)

v1.8.0 (September 1, 2022)

Added

Fixed

  • Lazily construct expressions in Nonlinear so that expressions are updated when Nonlinear.Parameter values are updated (#1984)
  • Allow NORM_LIMIT as a TerminationStatus for unbounded problems in Test (#1990)

v1.7.0 (August 16, 2022)

Added

Fixed

  • Fixed some missing promotion rules

Other

  • Improved the performance of Jacobian products in Nonlinear
  • Removed an un-needed copy in Utilities.modify_function
  • Various clean-ups in Bridges/bridge_optimizer.jl

v1.6.1 (July 23, 2022)

Fixed

  • Added support for ExponentialCone in MatrixOfConstraints
  • Fix PSDSquare_3 test to reflect a previously fixed bug getting the ConstraintDual of a PositiveSemidefiniteConeSquare constraint

v1.6.0 (July 2, 2022)

Added

  • Added Bridges.needs_final_touch and Bridges.final_touch
  • Added new bridges from constraint programming sets to mixed-integer linear programs:
    • AllDifferentToCountDistinctBridge
    • CountAtLeastToCountBelongsBridge
    • CountBelongsToMILPBridge
    • CountDistinctToMILPBridge
    • CountGreaterThanToMILPBridge
    • CircuitToMILPBridge

Fixed

  • Relax an instance of ::Vector to ::AbstractVector in MOI.Nonlinear
  • Fix BinPackingToMILPBridge to respect variable bounds
  • Fix SemiToBinaryBridge to throw error if other bounds are set

v1.5.0 (June 27, 2022)

Added

  • Added GetAttributeNotAllowed for solvers to indicate when getting an attribute encounters an error
  • Added Utilities.get_fallback support for ObjectiveValue and DualObjectiveValue
  • Added new bridges:
    • RootDetConeSquare to RootDetConeTriangle
    • LogDetConeSquare to LogDetConeTriangle
    • BinPacking to a mixed-integer linear program
    • Table to a mixed-integer linear program
  • Added Bridges.print_active_bridges to display the current optimal hyper-path in a Bridges.LazyBridgeOptimizer

Fixed

  • Fixed ZeroOne tests with lower and upper bounds
  • Fixed error in FileFormats.LP when reading a malformed file
  • Fixed reading of nonlinear programs in FileFormats.MOF
  • Fixed bug in ConstraintDual when using SquareBridge

Other

  • Improved documentation of nonlinear API
  • Documented duality convention for PositiveSemidefiniteConeSquare sets
  • Fixed typo in Bridges.Constraint.QuadToSOCBridge docstring

v1.4.0 (June 9, 2022)

Added

  • Added a number of sets for constraint programming:
    • AllDifferent
    • BinPacking
    • Circuit
    • CountAtLeast
    • CountBelongs
    • CountDistinct
    • CountGreaterThan
    • Cumulative
    • Path
    • Table
  • Added support for user-defined hessians in Nonlinear
  • Added Bridges.runtests to simplify the testing of bridge implementations

Fixed

  • Fixed a bug in FileFormats.NL when writing univariate *

Other

  • Began a large refactoring of the Bridges submodule, with greatly improved documentation.

v1.3.0 (May 27, 2022)

Added

  • Add MOI.Nonlinear submodule. This is a large new submodule that has been refactored from code that was in JuMP. For now, it should be considered experimental.
  • Add FileFormats.NL.SolFileResults(::IO, ::Model)
  • Add FileFormats.NL.read!(::IO, ::Model)
  • Add MOI.modify that accepts a vector of modifications

Fixed

  • Fixed a bug in Test which attempted to include non-.jl files
  • Fixed a bug in FileFormats for models with open interval constraints

Other

  • Fixed a performance issue in Utilities.DoubleDict
  • Various minor improvements to the documentation

v1.2.0 (April 25, 2022)

Added

  • Add support for the FORMAT_REW/.rew file format in FileFormats.

Fixed

  • Fix bug handling of default variable bounds in FileFormats.LP
  • Fix FileFormats.MPS to not write OBJSENSE by default since this is only supported by some readers.

v1.1.2 (March 31, 2022)

Fixed

  • Fix a range of bugs in FileFormats.LP
  • Fix reading of problem dimensions in FileFormats.SDPA

v1.1.1 (March 23, 2022)

Fixed

  • Fix bug in test_model_UpperBoundAlreadySet
  • Fix bug in test_infeasible_ tests
  • Fix bug in test_objective_ObjectiveFunction_blank
  • Relax restriction of MOI.AbstractOptimizer to MOI.ModelLike in Utilities.CachingOptimizer and instantiate.

New tests

  • Add test_conic_empty_matrix that checks conic solvers support problems with no variables.

v1.1.0 (March 2, 2022)

Added

  • Added MOI.Utilities.throw_unsupported(::UniversalFallback) for simplifying solver wrappers which copy from a UniversalFallback.

v1.0.2 (March 1, 2022)

Fixed

  • Fixed a bug in the test_model_ScalarFunctionConstantNotZero test
  • Fixed the error type when an AbstractFunctionConversionBridge cannot get or set an attribute
  • Identified a correctness bug in RSOCtoPSDBridge. We now thrown an error instead of returning an incorrect result.

v1.0.1 (February 25, 2022)

Fixed

  • Fixed a bug in which OptimizerAttributes were not copied in CachingOptimizer
  • Fixed a bug in which shift_constant did not promote mixed types of coefficients
  • Fixed a bug in which deleting a constraint of a bridged variable threw ErrorException instead of MOI.DeleteNotAllowed
  • Fixed a bug in which add_constraint in MatrixOfConstraints did not canonicalize the function
  • Fixed a bug when modifying scalar constants of a function containing a bridged variable
  • Fixed a bug in which final_touch was not always called with a CachingOptimizer

v1.0.0 (February 17, 2022)

Although tagged as a breaking release, v1.0.0 is v0.10.9 with deprecations removed, similar to how Julia 1.0 was Julia 0.7 with deprecations removed.

Breaking

  • Julia 1.6 is now the minimum supported version
  • All deprecations have been removed

Troubleshooting problems when updating

If you experience problems when updating, you are likely using previously deprecated features. (By default, Julia does not warn when you use deprecated features.)

To find the deprecated features you are using, start Julia with --depwarn=yes:

$ julia --depwarn=yes

Then install MathOptInterface v0.10.9:

julia> using Pkg
+julia> pkg"add MathOptInterface@0.10"

And then run your code. Apply any suggestions, or search the release notes below for advice on updating a specific deprecated feature.

v0.10.9 (February 16, 2022)

Added

  • Added MOI.Utilities.FreeVariables as a new VariablesConstrainer for conic solvers
  • Added MOI.default_cache for specifying the model used in CachingOptimizer

Fixed

  • Fixed LaTeX printing of MOI.Interval sets

Other

  • Added Aqua.jl as a CI check, and fixed suggested issues
  • The constructors of GeoMeanBridge, StructOfConstraints, and CachingOptimizer were changed from outer to inner constructors. This change is technically breaking, but does not impact users who followed the documented API.

v0.10.8 (February 3, 2022)

Added

  • Added a Base.read! for FileFormats.LP.

Fixed

  • Fixed a bug in MutableSparseMatrix
  • Fixed a bug when calling operate!(vcat, ...) with Number arguments
  • Removed unintended export of deprecated symbols
  • Fixed a bug with PowerCone and DualPowerCone in MatrixOfConstraints.

v0.10.7 (January 5, 2022)

Added

  • Added test for modifying the constant vector in a VectorAffineFunction-in-Zeros constraint.

Fixed

  • Fixed the order in which sets are added to a LazyBridgeOptimizer. Compared to v0.10.6, this may result in bridged models being created with a different number (and order) of variables and constraints. However, it was necessary to fix cases which were previously rejected as unsupported, even though there was a valid bridge transformation.
  • Fixed an error message in FileFormats.CBF
  • Fixed comparison in test_linear_integration_Interval
  • Fixed errors for ConstraintPrimal in a CachingOptimizer
  • Fixed printing of models with non-Float64 coefficients.

Other

  • Various improvements to reduce time-to-first-solve latency
  • Improved error message when an optimizer does not support compute_conflict!

v0.10.6 (November 30, 2021)

Added

  • Added new documentation and tests for infeasibility certificates
  • Added a version control system for the tests in MOI.Test.runtests. Pass exclude_tests_after = v"0.10.5" to run tests added in v0.10.5 and earlier.
  • MOI.Test.runtests now supports generic number types. To specify the number type T, pass MOI.Test.Config(T).
  • Added infeasible_status to MOI.Test.Config for solvers which return LOCALLY_INFEASIBLE
  • CachingOptimizers now use a fallback for ConstraintPrimal. This should enable solvers using a CachingOptimizer to pass tests requiring ConstraintPrimal.

Fixed

  • Fixed a StackOverflow bug in copy_to
  • Fixed error thrown when nonconvex quadratic constraints cannot be bridged
  • Fixed a bug in copy_to for FileFormats.NL.Model
  • Fixed a bug in FileFormats.NL when printing large integers
  • Remove a common test failure for LowerBoundAlreadySet tests
  • Utilities.num_rows is now exported
  • Remove parts of failing test_model_copy_to_xxx tests due to bridges

v0.10.5 (November 7, 2021)

Fixed

  • Fixed getter in UniversalFallback
  • Fixed test_solve_conflict_zeroone_ii

Other

  • Make normalize_and_add_constraint more flexible
  • Update paper BibTeX

v0.10.4 (October 26, 2021)

Added

  • Add SolverVersion attribute
  • Add new tests:
    • test_solve_conflict_zeroone_ii
    • test_nonlinear_objective
  • Utilities.VariablesContainer now supports ConstraintFunction and ConstraintSet
  • The documentation is now available as a PDF

Other

  • Update to MutableArithmetics 0.3
  • Various improvements to the documentation

v0.10.3 (September 18, 2021)

Fixed

  • Fixed bug which prevented callbacks from working through a CachingOptimizer
  • Fixed bug in Test submodule

v0.10.2 (September 16, 2021)

  • Updated MathOptFormat to v1.0
  • Updated JSONSchema to v1.0
  • Added Utilities.set_with_dimension
  • Added two-argument optimize!(::AbstractOptimizer, ::ModelLike)
  • The experimental feature copy_to_and_optimize! has been removed
  • Det bridges now support getting ConstraintFunction and ConstraintSet
  • Various minor bug fixes identified by improved testing

v0.10.1 (September 8, 2021)

  • Various fixes to MOI.Test

v0.10.0 (September 6, 2021)

MOI v0.10 is a significant breaking release. There are a large number of user-visible breaking changes and code refactors, as well as a substantial number of new features.

Breaking in MOI

  • SingleVariable has been removed; use VariableIndex instead
  • SingleVariableConstraintNameError has been renamed to VariableIndexConstraintNameError
  • SettingSingleVariableFunctionNotAllowed has been renamed to SettingVariableIndexFunctionNotAllowed
  • VariableIndex constraints should not support ConstraintName
  • VariableIndex constraints should not support ConstraintBasisStatus; implement VariableBasisStatus instead
  • ListOfConstraints has been renamed to ListOfConstraintTypesPresent
  • ListOfConstraintTypesPresent should now return Tuple{Type,Type} instead of Tuple{DataType,DataType}
  • SolveTime has been renamed to SolveTimeSec
  • IndicatorSet has been renamed to Indicator
  • RawParameter has been renamed to RawOptimizerAttribute and now takes String instead of Any as the only argument
  • The .N field in result attributes has been renamed to .result_index
  • The .variable_index field in ScalarAffineTerm has been renamed to .variable
  • The .variable_index_1 field in ScalarQuadraticTerm has been renamed to .variable_1
  • The .variable_index_2 field in ScalarQuadraticTerm has been renamed to .variable_2
  • The order of affine_terms and quadratic_terms in ScalarQuadraticFunction and VectorQuadraticFunction have been reversed. Both functions now accept quadratic, affine, and constant terms in that order.
  • The index_value function has been removed. Use .value instead.
  • isapprox has been removed for SOS1 and SOS2.
  • The dimension argument to Complements(dimension::Int) should now be the length of the corresponding function, instead of half the length. An ArgumentError is thrown if dimension is not even.
  • copy_to no longer takes keyword arguments:
    • copy_names: now copy names if they are supported by the destination solver
    • filter_constraints: use Utilities.ModelFilter instead
    • warn_attributes: never warn about optimizer attributes

Breaking in Bridges

  • Constraint.RSOCBridge has been renamed to Constraint.RSOCtoSOCBridge
  • Constraint.SOCRBridge has been renamed to Constraint.SOCtoRSOCBridge
  • Bridges now return vectors that can be modified by the user. Previously, some bridges returned views instead of copies.
  • Bridges.IndexInVector has been unified into a single type. Previously, there was a different type for each submodule within Bridges
  • The signature of indicator bridges has been fixed. Use MOI.Bridges.Constraint.IndicatortoSOS1{Float64}(model).

Breaking in FileFormats

  • FileFormats.MOF.Model no longer accepts validate argument. Use the JSONSchema package to validate the MOF file. See the documentation for more information.

Breaking in Utilities

  • The datastructure of Utilities.Model (and models created with Utilities.@model) has been significantly refactored in a breaking way. This includes the way that objective functions and variable-related information is stored.
  • Utilities.supports_default_copy has been renamed to supports_incremental_interface
  • Utilities.automatic_copy_to has been renamed to Utilities.default_copy_to
  • The allocate-load API has been removed
  • CachingOptimizers are now initialized as EMPTY_OPTIMIZER instead of ATTACHED_OPTIMIZER. If your code relies on the optimizer being attached, call MOIU.attach_optimizer(model) after creation.
  • The field names of Utilities.IndexMap have been renamed to var_map and con_map. Accessing these fields directly is considered a private detail that may change. Use the public getindex and setindex! API instead.
  • The size argument to Utilities.CleverDicts.CleverDict(::Integer) has been removed.
  • The size argument to Utilities.IndexMap(::Integer) has been removed.
  • Utilities.DoubleDicts have been significantly refactored. Consult the source code for details.
  • Utilities.test_models_equal has been moved to MOI.Test

Breaking in Test

  • MOI.Test has been renamed to MOI.DeprecatedTest
  • An entirely new MOI.Test submodule has been written. See the documentation for details. The new MOI.Test submodule may find many bugs in the implementations of existing solvers that were previously untested.

Other changes:

  • attribute_value_type has been added
  • copy_to_and_optimize! has been added
  • VariableBasisStatus has been added
  • print(model) now prints a human-readable description of the model
  • Various improvements to the FileFormats submodule
    • FileFormats.CBF was refactored and received bugfixes
    • Support for MathOptFormat v0.6 was added in FileFormats.MOF
    • FileFormats.MPS has had bugfixes and support for more features such as OBJSENSE and objective constants.
    • FileFormats.NL has been added to support nonlinear files
  • Improved type inference throughout to reduce latency

Updating

A helpful script when updating is:

for (root, dirs, files) in walkdir(".")
+    for file in files
+        if !endswith(file, ".jl")
+            continue
+        end
+        path = joinpath(root, file)
+        s = read(path, String)
+        for pair in [
+            ".variable_index" => ".variable",
+            "RawParameter" => "RawOptimizerAttribute",
+            "ListOfConstraints" => "ListOfConstraintTypesPresent",
+            "TestConfig" => "Config",
+            "attr.N" => "attr.result_index",
+            "SolveTime" => "SolveTimeSec",
+            "DataType" => "Type",
+            "Utilities.supports_default_copy_to" =>
+                "supports_incremental_interface",
+            "SingleVariableConstraintNameError" =>
+                "VariableIndexConstraintNameError",
+            "SettingSingleVariableFunctionNotAllowed" =>
+                "SettingVariableIndexFunctionNotAllowed",
+            "automatic_copy_to" => "default_copy_to",
+        ]
+            s = replace(s, pair)
+        end
+        write(path, s)
+    end
+end

v0.9.22 (May 22, 2021)

This release contains backports from the ongoing development of the v0.10 release.

  • Improved type inference in Utilities, Bridges and FileFormats submodules to reduce latency.
  • Improved performance of Utilities.is_canonical.
  • Fixed Utilities.pass_nonvariable_constraints with bridged variables.
  • Fixed performance regression of Utilities.Model.
  • Fixed ordering of objective setting in parser.

v0.9.21 (April 23, 2021)

  • Added supports_shift_constant.
  • Improve performance of bridging quadratic constraints.
  • Add precompilation statements.
  • Large improvements to the documentation.
  • Fix a variety of inference issues, benefiting precompilation and reducing initial latency.
  • RawParameters are now ignored when resetting a CachingOptimizer. Previously, changing the underlying optimizer after RawParameters were set would throw an error.
  • Utilities.AbstractModel is being refactored. This may break users interacting with private fields of a model generated using @model.

v0.9.20 (February 20, 2021)

  • Improved performance of Utilities.ScalarFunctionIterator
  • Added support for compute_conflict to MOI layers
  • Added test with zero off-diagonal quadratic term in objective
  • Fixed double deletion of nested bridged SingleVariable/VectorOfVariables constraints
  • Fixed modification of un-set objective
  • Fixed function modification with duplicate terms
  • Made unit tests abort without failing if the problem class is not supported
  • Formatted code with JuliaFormatter
  • Clarified BasisStatusCode's docstring

v0.9.19 (December 1, 2020)

  • Added CallbackNodeStatus attribute
  • Added bridge from GreaterThan or LessThan to Interval
  • Added tests for infeasibility certificates and double optimize
  • Fixed support for Julia v1.6
  • Re-organized MOI docs and added documentation for adding a test

v0.9.18 (November 3, 2020)

  • Various improvements for working with complex numbers
  • Added GeoMeantoRelEntrBridge to bridge a GeometricMeanCone constraint to a relative entropy constraint

v0.9.17 (September 21, 2020)

  • Fixed CleverDict with variable of negative index value
  • Implement supports_add_constrained_variable for MockOptimizer

v0.9.16 (September 17, 2020)

  • Various fixes:
    • 32-bit support
    • CleverDict with abstract value type
    • Checks in test suite

v0.9.15 (September 14, 2020)

  • Bridges improvements:
    • (R)SOCtoNonConvexQuad bridge
    • ZeroOne bridge
    • Use supports_add_constrained_variable in LazyBridgeOptimizer
    • Exposed VariableBridgeCost and ConstraintBridgeCost attributes
    • Prioritize constraining variables on creation according to these costs
    • Refactor bridge debugging
  • Large performance improvements across all submodules
  • Lots of documentation improvements
  • FileFormats improvements:
    • Update MathOptFormat to v0.5
    • Fix supported objectives in FileFormats
  • Testing improvements:
    • Add name option for basic_constraint_test
  • Bug fixes and missing methods
    • Add length for iterators
    • Fix bug with duplicate terms
    • Fix order of LinearOfConstraintIndices

v0.9.14 (May 30, 2020)

  • Add a solver-independent interface for accessing the set of conflicting constraints an Irreducible Inconsistent Subsystem (#1056).
  • Bump JSONSchema dependency from v0.2 to v0.3 (#1090).
  • Documentation improvements:
    • Fix typos (#1054, #1060, #1061, #1064, #1069, #1070).
    • Remove the outdated recommendation for a package implementing MOI for a solver XXX to be called MathOptInterfaceXXX (#1087).
  • Utilities improvements:
    • Fix is_canonical for quadratic functions (#1081, #1089).
    • Implement add_constrained_variable[s] for CachingOptimizer so that it is added as constrained variables to the underlying optimizer (#1084).
    • Add support for custom objective functions for UniversalFallback (#1086).
    • Deterministic ordering of constraints in UniversalFallback (#1088).
  • Testing improvements:
    • Add NormOneCone/NormInfinityCone tests (#1045).
  • Bridges improvements:
    • Add bridges from Semiinteger and Semicontinuous (#1059).
    • Implement getting ConstraintSet for Variable.FlipSignBridge (#1066).
    • Fix setting ConstraintFunction for Constraint.ScalarizeBridge (#1093).
    • Fix NormOne/NormInf bridges with nonzero constants (#1045).
    • Fix StackOverflow in debug (#1063).
  • FileFormats improvements:
    • [SDPA] Implement the extension for integer variables (#1079).
    • [SDPA] Ignore comments after m and nblocks and detect dat-s extension (#1077).
    • [SDPA] No scaling of off-diagonal coefficient (#1076).
    • [SDPA] Add missing negation of constant (#1075).

v0.9.13 (March 24, 2020)

  • Added tests for Semicontinuous and Semiinteger variables (#1033).
  • Added tests for using ExprGraphs from NLP evaluators (#1043).
  • Update version compatibilities of dependencies (#1034, #1051, #1052).
  • Fixed typos in documentation (#1044).

v0.9.12 (February 28, 2020)

  • Fixed writing NLPBlock in MathOptFormat (#1037).
  • Fixed MockOptimizer for result attributes with non-one result index (#1039).
  • Updated test template with instantiate (#1032).

v0.9.11 (February 21, 2020)

  • Add an option for the model created by Utilities.@model to be a subtype of AbstractOptimizer (#1031).
  • Described dual cone in docstrings of GeoMeanCone and RelativeEntropyCone (#1018, #1028).
  • Fixed typos in documentation (#1022, #1024).
  • Fixed warning of unsupported attribute (#1027).
  • Added more rootdet/logdet conic tests (#1026).
  • Implemented ConstraintDual for Constraint.GeoMeanBridge, Constraint.RootDetBridge and Constraint.LogDetBridge and test duals in tests with GeoMeanCone and RootDetConeTriangle and LogDetConeTriangle cones (#1025, #1026).

v0.9.10 (January 31, 2020)

  • Added OptimizerWithAttributes grouping an optimizer constructor and a list of optimizer attributes (#1008).
  • Added RelativeEntropyCone with corresponding bridge into exponential cone constraints (#993).
  • Added NormSpectralCone and NormNuclearCone with corresponding bridges into positive semidefinite constraints (#976).
  • Added supports_constrained_variable(s) (#1004).
  • Added dual_set_type (#1002).
  • Added tests for vector specialized version of delete (#989, #1011).
  • Added PSD3 test (#1007).
  • Clarified dual solution of Tests.pow1v and Tests.pow1f (#1013).
  • Added support for EqualTo and Zero in Bridges.Constraint.SplitIntervalBridge (#1005).
  • Fixed Utilities.vectorize for empty vector (#1003).
  • Fixed free variables in LP writer (#1006).

v0.9.9 (December 29, 2019)

  • Incorporated MathOptFormat.jl as the FileFormats submodule. FileFormats provides readers and writers for a number of standard file formats and MOF, a file format specialized for MOI (#969).
  • Improved performance of deletion of vector of variables in MOI.Utilities.Model (#983).
  • Updated to MutableArithmetics v0.2 (#981).
  • Added MutableArithmetics.promote_operation allocation tests (#975).
  • Fixed inference issue on Julia v1.1 (#982).

v0.9.8 (December 19, 2019)

  • Implemented MutableArithmetics API (#924).
  • Fixed callbacks with CachingOptimizer (#959).
  • Fixed MOI.dimension for MOI.Complements (#948).
  • Added fallback for add_variables (#972).
  • Added is_diagonal_vectorized_index utility (#965).
  • Improved linear constraints display in manual (#963, #964).
  • Bridges improvements:
    • Added IndicatorSet to SOS1 bridge (#877).
    • Added support for starting values for Variable.VectorizeBridge (#944).
    • Fixed MOI.add_constraints with non-bridged variable constraint on bridged variable (#951).
    • Fixed corner cases and docstring of GeoMeanBridge (#961, #962, #966).
    • Fixed choice between variable or constraint bridges for constrained variables (#973).
    • Improve performance of bridge shortest path (#945, #946, #956).
    • Added docstring for test_delete_bridge (#954).
    • Added Variable bridge tests (#952).

v0.9.7 (October 30, 2019)

  • Implemented _result_index_field for NLPBlockDual (#934).
  • Fixed copy of model with starting values for vector constraints (#941).
  • Bridges improvements:
    • Improved performance of add_bridge and added has_bridge (#935).
    • Added AbstractSetMapBridge for bridges between sets S1, S2 such that there is a linear map A such that A*S1 = S2 (#933).
    • Added support for starting values for FlipSignBridge, VectorizeBridge, ScalarizeBridge, SlackBridge, SplitIntervalBridge, RSOCBridge, SOCRBridge NormInfinityBridge, SOCtoPSDBridge and RSOCtoPSDBridge (#933, #936, #937, #938, #939).

v0.9.6 (October 25, 2019)

  • Added complementarity constraints (#913).
  • Allowed ModelLike objects as value of attributes (#928).
  • Testing improvements:
    • Added dual_objective_value option to MOI.Test.TestConfig (#922).
    • Added InvalidIndex tests in basic_constraint_tests (#921).
    • Added tests for the constant term in indicator constraint (#929).
  • Bridges improvements:
    • Added support for starting values for Functionize bridges (#923).
    • Added variable indices context to variable bridges (#920).
    • Fixed a typo in printing o debug_supports (#927).

v0.9.5 (October 9, 2019)

  • Clarified PrimalStatus/DualStatus to be NO_SOLUTION if result_index is out of bounds (#912).
  • Added tolerance for checks and use ResultCount + 1 for the result_index in MOI.Test.solve_result_status (#910, #917).
  • Use 0.5 instead of 2.0 for power in PowerCone in basic_constraint_test (#916).
  • Bridges improvements:
    • Added debug utilities for unsupported variable/constraint/objective (#861).
    • Fixed deletion of variables in bridged VectorOfVariables constraints (#909).
    • Fixed result_index with objective bridges (#911).

v0.9.4 (October 2, 2019)

  • Added solver-independent MIP callbacks (#782).
  • Implements submit for Utilities.CachingOptimizer and Bridges.AbstractBridgeOptimizer (#906).
  • Added tests for result count of solution attributes (#901, #904).
  • Added NumberOfThreads attribute (#892).
  • Added Utilities.get_bounds to get the bounds on a variable (#890).
  • Added a note on duplicate coefficients in documentation (#581).
  • Added result index in ConstraintBasisStatus (#898).
  • Added extension dictionary to Utilities.Model (#884, #895).
  • Fixed deletion of constrained variables for CachingOptimizer (#905).
  • Implemented Utilities.shift_constraint for Test.UnknownScalarSet (#896).
  • Bridges improvements:
    • Added Variable.RSOCtoSOCBridge (#907).
    • Implemented MOI.get for ConstraintFunction/ConstraintSet for Bridges.Constraint.SquareBridge (#899).

v0.9.3 (September 20, 2019)

  • Fixed ambiguity detected in Julia v1.3 (#891, #893).
  • Fixed missing sets from ListOfSupportedConstraints (#880).
  • Fixed copy of VectorOfVariables constraints with duplicate indices (#886).
  • Added extension dictionary to MOIU.Model (#884).
  • Implemented MOI.get for function and set for GeoMeanBridge (#888).
  • Updated documentation for SingleVariable indices and bridges (#885).
  • Testing improvements:
    • Added more comprehensive tests for names (#882).
    • Added tests for SingleVariable duals (#883).
    • Added tests for DualExponentialCone and DualPowerCone (#873).
  • Improvements for arbitrary coefficient type:
    • Fixed == for sets with mutable fields (#887).
    • Removed some Float64 assumptions in bridges (#878).
    • Automatic selection of Constraint.[Scalar|Vector]FunctionizeBridge (#889).

v0.9.2 (September 5, 2019)

  • Implemented model printing for MOI.ModelLike and specialized it for models defined in MOI (864).
  • Generalized contlinear tests for arbitrary coefficient type (#855).
  • Fixed supports_constraint for Semiinteger and Semicontinuous and supports for ObjectiveFunction (#859).
  • Fixed Allocate-Load copy for single variable constraints (#856).
  • Bridges improvements:
    • Add objective bridges (#789).
    • Fixed Variable.RSOCtoPSDBridge for dimension 2 (#869).
    • Added Variable.SOCtoRSOCBridge (#865).
    • Added Constraint.SOCRBridge and disable MOI.Bridges.Constraint.SOCtoPSDBridge (#751).
    • Fixed added_constraint_types for Contraint.LogDetBridge and Constraint.RootDetBridge (#870).

v0.9.1 (August 22, 2019)

  • Fix support for Julia v1.2 (#834).
  • L1 and L∞ norm epigraph cones and corresponding bridges to LP were added (#818).
  • Added tests to MOI.Test.nametest (#833).
  • Fix MOI.Test.soc3test for solvers not supporting infeasibility certificates (#839).
  • Implements operate for operators * and / between vector function and constant (#837).
  • Implements show for MOI.Utilities.IndexMap (#847).
  • Fix corner cases for mapping of variables in MOI.Utilities.CachingOptimizer and substitution of variables in MOI.Bridges.AbstractBridgeOptimizer (#848).
  • Fix transformation of constant terms for MOI.Bridges.Constraint.SOCtoPSDBridge and MOI.Bridges.Constraint.RSOCtoPSDBridge (#840).

v0.9.0 (August 13, 2019)

  • Support for Julia v0.6 and v0.7 was dropped (#714, #717).
  • A MOI.Utilities.Model implementation of ModelLike, this should replace most use cases of MOI.Utilities.@model (#781).
  • add_constrained_variable and add_constrained_variables were added (#759).
  • Support for indicator constraints was added (#709, #712).
  • DualObjectiveValue attribute was added (#473).
  • RawParameter attribute was added (#733).
  • A dual_set function was added (#804).
  • A Benchmarks submodule was added to facilitate solver benchmarking (#769).
  • A submit function was added, this may for instance allow the user to submit solutions or cuts to the solver from a callback (#775).
  • The field of ObjectiveValue was renamed to result_index (#729).
  • The _constant and Utilities.getconstant function were renamed to constant
  • REDUCTION_CERTIFICATE result status was added (#734).
  • Abstract matrix sets were added (#731).
  • Testing improvements:
    • The testing guideline was updated (#728).
    • Quadratic tests were added (#697).
    • Unit tests for RawStatusString, SolveTime, Silent and SolverName were added (#726, #741).
    • A rotated second-order cone test was added (#759).
    • A power cone test was added (#768).
    • Tests for ZeroOne variables with variable bounds were added (#772).
    • An unbounded test was added (#773).
    • Existing tests had a few updates (#702, #703, #763).
  • Documentation improvements:
    • Added a section on CachingOptimizer (#777).
    • Added a section on UniversalFallback, Model and @model (#762).
    • Transition the knapsack example to a doctest with MockOptimizer (#786).
  • Utilities improvements:
    • A CleverDict utility was added for a vector that automatically transform into a dictionary once a first index is removed (#767).
    • The Utilities.constant function was renamed to Utilities.constant_vector (#740).
    • Implement optimizer attributes for CachingOptimizer (#745).
    • Rename Utilities.add_scalar_constraint to Utilities.normalize_and_add_constraint (#801).
    • operate with vcat, SingleVariable and VectorOfVariables now returns a VectorOfVariables (#616).
    • Fix a type piracy of operate (#784).
    • The load_constraint fallback signature was fixed (#760).
    • The set_dot function was extended to work with sparse arrays (#805).
  • Bridges improvements:
    • The bridges no longer store the constraint function and set before it is bridged, the bridges now have to implement ConstraintFunction and ConstraintSet if the user wants to recover them. As a consequence, the @bridge macro was removed (#722).
    • Bridge are now instantiated with a bridge_constraint function instead of using a constructor (#730).
    • Fix constraint attributes for bridges (#699).
    • Constraint bridges were moved to the Bridges/Constraint submodule so they should now inherit from MOI.Bridges.Constraint.Abstract and should implement MOI.Bridges.Constraint.concrete_bridge_type instead of MOI.Bridges.concrete_bridge_type (#756).
    • Variable bridges were added in (#759).
    • Various improvements (#746, #747).

v0.8.4 (March 13, 2019)

  • Performance improvement in default_copy_to and bridge optimizer (#696).
  • Add Silent and implement setting optimizer attributes in caching and mock optimizers (#695).
  • Add Functionize bridges (SingleVariable and VectorOfVariables) (#659).
  • Minor typo fixes (#694).

v0.8.3 (March 6, 2019)

  • Use zero constant in scalar constraint function of MOI.Test.copytest (#691).
  • Fix variable deletion with SingleVariable objective function (#690).
  • Fix LazyBridgeOptimizer with bridges that add no constraints (#689).
  • Error message improvements (#673, #685, #686, #688).
  • Documentation improvements (#682, #683, #687).
  • Basis status:
    • Remove VariableBasisStatus (#679).
    • Test ConstraintBasisStatus and implement it in bridges (#678).
  • Fix inference of NumberOfVariables and NumberOfConstraints (#677).
  • Implement division between a quadratic function and a number (#675).

v0.8.2 (February 7, 2019)

  • Add RawStatusString attribute (#629).
  • Do not set names to the optimizer but only to the cache in CachingOptimizer (#638).
  • Make scalar MOI functions act as scalars in broadcast (#646).
  • Add function utilities:
    • Implement Base.zero (#634), Base.iszero (#643), add missing arithmetic operations (#644, #645) and fix division (#648).
    • Add a vectorize function that turns a vector of ScalarAffineFunction into a VectorAffineFunction (#642).
  • Improve support for starting values:
    • Show a warning in copy when starting values are not supported instead of throwing an error (#630).
    • Fix UniversalFallback for getting an variable or constraint attribute set to no indices (#623).
    • Add a test in contlineartest with partially set VariablePrimalStart.
  • Bridges improvements:
    • Fix StackOverFlow in LazyBridgeOptimizer when there is a cycle in the graph of bridges.
    • Add Slack bridges (#610, #650).
    • Add FlipSign bridges (#658).
  • Add tests with duplicate coefficients in ScalarAffineFunction and VectorAffineFunction (#639).
  • Use tolerance to compare VariablePrimal in rotatedsoc1 test (#632).
  • Use a zero constant in ScalarAffineFunction of constraints in psdt2 (#622).

v0.8.1 (January 7, 2019)

  • Adding an NLP objective now overrides any objective set using the ObjectiveFunction attribute (#619).
  • Rename fullbridgeoptimizer into full_bridge_optimizer (#621).
  • Allow custom constraint types with full_bridge_optimizer (#617).
  • Add Vectorize bridge which transforms scalar linear constraints into vector linear constraints (#615).

v0.8.0 (December 18, 2018)

  • Rename all enum values to follow the JuMP naming guidelines for constants, for example, Optimal becomes OPTIMAL, and DualInfeasible becomes DUAL_INFEASIBLE.
  • Rename CachingOptimizer methods for style compliance.
  • Add an MOI.TerminationStatusCode called ALMOST_DUAL_INFEASIBLE.

v0.7.0 (December 13, 2018)

  • Test that MOI.TerminationStatus is MOI.OptimizeNotCalled before MOI.optimize! is called.
  • Check supports_default_copy_to in tests (#594).
  • Key pieces of information like optimality, infeasibility, etc., are now reported through TerminationStatusCode. It is typically no longer necessary to check the result statuses in addition to the termination status.
  • Add perspective dimension to log-det cone (#593).

v0.6.4 (November 27, 2018)

  • Add OptimizeNotCalled termination status (#577) and improve documentation of other statuses (#575).
  • Add a solver naming guideline (#578).
  • Make FeasibilitySense the default ObjectiveSense (#579).
  • Fix Utilities.@model and Bridges.@bridge macros for functions and sets defined outside MOI (#582).
  • Document solver-specific attributes (#580) and implement them in Utilities.CachingOptimizer (#565).

v0.6.3 (November 16, 2018)

  • Variables and constraints are now allowed to have duplicate names. An error is thrown only on lookup. This change breaks some existing tests. (#549)
  • Attributes may now be partially set (some values could be nothing). (#563)
  • Performance improvements in Utilities.Model (#549, #567, #568)
  • Fix bug in QuadtoSOC (#558).
  • New supports_default_copy_to method that optimizers should implement to control caching behavior.
  • Documentation improvements.

v0.6.2 (October 26, 2018)

  • Improve hygiene of @model macro (#544).
  • Fix bug in copy tests (#543).
  • Fix bug in UniversalFallback attribute getter (#540).
  • Allow all correct solutions for solve_blank_obj unit test (#537).
  • Add errors for Allocate-Load and bad constraints (#534).
  • [performance] Add specialized implementation of hash for VariableIndex (#533).
  • [performance] Construct the name to object dictionaries lazily in model (#535).
  • Add the QuadtoSOC bridge which transforms ScalarQuadraticFunction constraints into RotatedSecondOrderCone (#483).

v0.6.1 (September 22, 2018)

  • Enable PositiveSemidefiniteConeSquare set and quadratic functions in MOIB.fullbridgeoptimizer (#524).
  • Add warning in the bridge between PositiveSemidefiniteConeSquare and PositiveSemidefiniteConeTriangle when the matrix is almost symmetric (#522).
  • Modify MOIT.copytest to not add multiples constraints on the same variable (#521).
  • Add missing keyword argument in one of MOIU.add_scalar_constraint methods (#520).

v0.6.0 (August 30, 2018)

  • The MOIU.@model and MOIB.@bridge macros now support functions and sets defined in external modules. As a consequence, function and set names in the macro arguments need to be prefixed by module name.
  • Rename functions according to the JuMP style guide:
    • copy! with keyword arguments copynames and warnattributes -> copy_to with keyword arguments copy_names and warn_attributes;
    • set! -> set;
    • addvariable[s]! -> add_variable[s];
    • supportsconstraint -> supports_constraint;
    • addconstraint[s]! -> add_constraint[s];
    • isvalid -> is_valid;
    • isempty -> is_empty;
    • Base.delete! -> delete;
    • modify! -> modify;
    • transform! -> transform;
    • initialize! -> initialize;
    • write -> write_to_file; and
    • read! -> read_from_file.
  • Remove free! (use Base.finalize instead).
  • Add the SquarePSD bridge which transforms PositiveSemidefiniteConeTriangle constraints into PositiveSemidefiniteConeTriangle.
  • Add result fallback for ConstraintDual of variable-wise constraint, ConstraintPrimal and ObjectiveValue.
  • Add tests for ObjectiveBound.
  • Add test for empty rows in vector linear constraint.
  • Rework errors: CannotError has been renamed NotAllowedError and the distinction between UnsupportedError and NotAllowedError is now about whether the element is not supported (for example, it cannot be copied a model containing this element) or the operation is not allowed (either because it is not implemented, because it cannot be performed in the current state of the model, or because it cannot be performed for a specific index)
  • canget is removed. NoSolution is added as a result status to indicate that the solver does not have either a primal or dual solution available (See #479).

v0.5.0 (August 5, 2018)

  • Fix names with CachingOptimizer.
  • Cleanup thanks to @mohamed82008.
  • Added a universal fallback for constraints.
  • Fast utilities for function canonicalization thanks to @rdeits.
  • Renamed dimension field to side_dimension in the context of matrix-like sets.
  • New and improved tests for cases like duplicate terms and ObjectiveBound.
  • Removed cantransform, canaddconstraint, canaddvariable, canset, canmodify, and candelete functions from the API. They are replaced by a new set of errors that are thrown: Subtypes of UnsupportedError indicate unsupported operations, while subtypes of CannotError indicate operations that cannot be performed in the current state.
  • The API for copy! is updated to remove the CopyResult type.
  • Updates for the new JuMP style guide.

v0.4.1 (June 28, 2018)

  • Fixes vector function modification on 32 bits.
  • Fixes Bellman-Ford algorithm for bridges.
  • Added an NLP test with FeasibilitySense.
  • Update modification documentation.

v0.4.0 (June 23, 2018)

  • Helper constructors for VectorAffineTerm and VectorQuadraticTerm.
  • Added modify_lhs to TestConfig.
  • Additional unit tests for optimizers.
  • Added a type parameter to CachingOptimizer for the optimizer field.
  • New API for problem modification (#388)
  • Tests pass without deprecation warnings on Julia 0.7.
  • Small fixes and documentation updates.

v0.3.0 (May 25, 2018)

  • Functions have been redefined to use arrays-of-structs instead of structs-of-arrays.
  • Improvements to MockOptimizer.
  • Significant changes to Bridges.
  • New and improved unit tests.
  • Fixes for Julia 0.7.

v0.2.0 (April 24, 2018)

  • Improvements to and better coverage of Tests.
  • Documentation fixes.
  • SolverName attribute.
  • Changes to the NLP interface (new definition of variable order and arrays of structs for bound pairs and sparsity patterns).
  • Addition of NLP tests.
  • Introduction of UniversalFallback.
  • copynames keyword argument to MOI.copy!.
  • Add Bridges submodule.

v0.1.0 (February 28, 2018)

  • Initial public release.
  • The framework for MOI was developed at the JuMP-dev workshop at MIT in June 2017 as a sorely needed replacement for MathProgBase.
diff --git a/previews/PR3536/moi/submodules/Benchmarks/overview/index.html b/previews/PR3536/moi/submodules/Benchmarks/overview/index.html new file mode 100644 index 00000000000..07b9bda8184 --- /dev/null +++ b/previews/PR3536/moi/submodules/Benchmarks/overview/index.html @@ -0,0 +1,24 @@ + +Overview · JuMP

The Benchmarks submodule

To aid the development of efficient solver wrappers, MathOptInterface provides benchmarking capability. Benchmarking a wrapper follows a two-step process.

First, prior to making changes, create a baseline for the benchmark results on a given benchmark suite as follows:

using SolverPackage  # Replace with your choice of solver.
+import MathOptInterface as MOI
+
+suite = MOI.Benchmarks.suite() do
+    SolverPackage.Optimizer()
+end
+
+MOI.Benchmarks.create_baseline(
+    suite, "current"; directory = "/tmp", verbose = true
+)

Use the exclude argument to Benchmarks.suite to exclude benchmarks that the solver doesn't support.

Second, after making changes to the package, re-run the benchmark suite and compare to the prior saved results:

using SolverPackage
+import MathOptInterface as MOI
+
+suite = MOI.Benchmarks.suite() do
+    SolverPackage.Optimizer()
+end
+
+MOI.Benchmarks.compare_against_baseline(
+    suite, "current"; directory = "/tmp", verbose = true
+)

This comparison will create a report detailing improvements and regressions.

diff --git a/previews/PR3536/moi/submodules/Benchmarks/reference/index.html b/previews/PR3536/moi/submodules/Benchmarks/reference/index.html new file mode 100644 index 00000000000..0a295170bb2 --- /dev/null +++ b/previews/PR3536/moi/submodules/Benchmarks/reference/index.html @@ -0,0 +1,21 @@ + +API Reference · JuMP

Benchmarks

Functions to help benchmark the performance of solver wrappers. See The Benchmarks submodule for more details.

MathOptInterface.Benchmarks.suiteFunction
suite(
+    new_model::Function;
+    exclude::Vector{Regex} = Regex[]
+)

Create a suite of benchmarks. new_model should be a function that takes no arguments, and returns a new instance of the optimizer you wish to benchmark.

Use exclude to exclude a subset of benchmarks.

Examples

suite() do
+    GLPK.Optimizer()
+end
+suite(exclude = [r"delete"]) do
+    Gurobi.Optimizer(OutputFlag=0)
+end
source
MathOptInterface.Benchmarks.create_baselineFunction
create_baseline(suite, name::String; directory::String = ""; kwargs...)

Run all benchmarks in suite and save to files called name in directory.

Extra kwargs are based to BenchmarkTools.run.

Examples

my_suite = suite(() -> GLPK.Optimizer())
+create_baseline(my_suite, "glpk_master"; directory = "/tmp", verbose = true)
source
MathOptInterface.Benchmarks.compare_against_baselineFunction
compare_against_baseline(
+    suite, name::String; directory::String = "",
+    report_filename::String = "report.txt"
+)

Run all benchmarks in suite and compare against files called name in directory that were created by a call to create_baseline.

A report summarizing the comparison is written to report_filename in directory.

Extra kwargs are based to BenchmarkTools.run.

Examples

my_suite = suite(() -> GLPK.Optimizer())
+compare_against_baseline(
+    my_suite, "glpk_master"; directory = "/tmp", verbose = true
+)
source
diff --git a/previews/PR3536/moi/submodules/Bridges/list_of_bridges/index.html b/previews/PR3536/moi/submodules/Bridges/list_of_bridges/index.html new file mode 100644 index 00000000000..3963ed2decb --- /dev/null +++ b/previews/PR3536/moi/submodules/Bridges/list_of_bridges/index.html @@ -0,0 +1,114 @@ + +List of bridges · JuMP

List of bridges

This section describes the Bridges.AbstractBridges that are implemented in MathOptInterface.

Constraint bridges

These bridges are subtypes of Bridges.Constraint.AbstractBridge.

MathOptInterface.Bridges.Constraint.VectorizeBridgeType
VectorizeBridge{T,F,S,G} <: Bridges.Constraint.AbstractBridge

VectorizeBridge implements the following reformulations:

  • $g(x) \ge a$ into $[g(x) - a] \in \mathbb{R}_+$
  • $g(x) \le a$ into $[g(x) - a] \in \mathbb{R}_-$
  • $g(x) == a$ into $[g(x) - a] \in \{0\}$

where T is the coefficient type of g(x) - a.

Source node

VectorizeBridge supports:

Target nodes

VectorizeBridge creates:

source
MathOptInterface.Bridges.Constraint.ScalarizeBridgeType
ScalarizeBridge{T,F,S}

ScalarizeBridge implements the following reformulations:

  • $f(x) - a \in \mathbb{R}_+$ into $f_i(x) \ge a_i$ for all $i$
  • $f(x) - a \in \mathbb{R}_-$ into $f_i(x) \le a_i$ for all $i$
  • $f(x) - a \in \{0\}$ into $f_i(x) == a_i$ for all $i$

Source node

ScalarizeBridge supports:

Target nodes

ScalarizeBridge creates:

source
MathOptInterface.Bridges.Constraint.FunctionConversionBridgeType
FunctionConversionBridge{T,F,G,S} <: AbstractFunctionConversionBridge{G,S}

FunctionConversionBridge implements the following reformulations:

  • $g(x) \in S$ into $f(x) \in S$

for these pairs of functions:

Source node

FunctionConversionBridge supports:

  • G in S

Target nodes

FunctionConversionBridge creates:

  • F in S
source
MathOptInterface.Bridges.Constraint.SplitComplexEqualToBridgeType
SplitComplexEqualToBridge{T,F,G} <: Bridges.Constraint.AbstractBridge

SplitComplexEqualToBridge implements the following reformulation:

  • $f(x) + g(x) * im = a + b * im$ into $f(x) = a$ and $g(x) = b$

Source node

SplitComplexEqualToBridge supports:

where G is a function with Complex coefficients.

Target nodes

SplitComplexEqualToBridge creates:

where F is the type of the real/imaginary part of G.

source
MathOptInterface.Bridges.Constraint.SplitComplexZerosBridgeType
SplitComplexZerosBridge{T,F,G} <: Bridges.Constraint.AbstractBridge

SplitComplexZerosBridge implements the following reformulation:

  • $f(x) \in \{0\}^n$ into $\text{Re}(f(x)) \in \{0\}^n$ and $\text{Im}(f(x)) \in \{0\}^n$

Source node

SplitComplexZerosBridge supports:

where G is a function with Complex coefficients.

Target nodes

SplitComplexZerosBridge creates:

where F is the type of the real/imaginary part of G.

source
MathOptInterface.Bridges.Constraint.SplitIntervalBridgeType
SplitIntervalBridge{T,F,S,LS,US} <: Bridges.Constraint.AbstractBridge

SplitIntervalBridge implements the following reformulations:

  • $l \le f(x) \le u$ into $f(x) \ge l$ and $f(x) \le u$
  • $f(x) = b$ into $f(x) \ge b$ and $f(x) \le b$
  • $f(x) \in \{0\}$ into $f(x) \in \mathbb{R}_+$ and $f(x) \in \mathbb{R}_-$

Source node

SplitIntervalBridge supports:

Target nodes

SplitIntervalBridge creates:

or

Note

If T<:AbstractFloat and S is MOI.Interval{T} then no lower (resp. upper) bound constraint is created if the lower (resp. upper) bound is typemin(T) (resp. typemax(T)). Similarly, when MOI.ConstraintSet is set, a lower or upper bound constraint may be deleted or created accordingly.

source
MathOptInterface.Bridges.Constraint.SOCtoNonConvexQuadBridgeType
SOCtoNonConvexQuadBridge{T} <: Bridges.Constraint.AbstractBridge

SOCtoNonConvexQuadBridge implements the following reformulations:

  • $||x||_2 \le t$ into $\sum x^2 - t^2 \le 0$ and $1t + 0 \ge 0$

The MOI.ScalarAffineFunction $1t + 0$ is used in case the variable has other bound constraints.

Warning

This transformation starts from a convex constraint and creates a non-convex constraint. Unless the solver has explicit support for detecting second-order cones in quadratic form, this may (wrongly) be interpreted by the solver as being non-convex. Therefore, this bridge is not added automatically by MOI.Bridges.full_bridge_optimizer. Care is recommended when adding this bridge to a optimizer.

Source node

SOCtoNonConvexQuadBridge supports:

Target nodes

SOCtoNonConvexQuadBridge creates:

source
MathOptInterface.Bridges.Constraint.RSOCtoNonConvexQuadBridgeType
RSOCtoNonConvexQuadBridge{T} <: Bridges.Constraint.AbstractBridge

RSOCtoNonConvexQuadBridge implements the following reformulations:

  • $||x||_2^2 \le 2tu$ into $\sum x^2 - 2tu \le 0$, $1t + 0 \ge 0$, and $1u + 0 \ge 0$.

The MOI.ScalarAffineFunctions $1t + 0$ and $1u + 0$ are used in case the variables have other bound constraints.

Warning

This transformation starts from a convex constraint and creates a non-convex constraint. Unless the solver has explicit support for detecting rotated second-order cones in quadratic form, this may (wrongly) be interpreted by the solver as being non-convex. Therefore, this bridge is not added automatically by MOI.Bridges.full_bridge_optimizer. Care is recommended when adding this bridge to a optimizer.

Source node

RSOCtoNonConvexQuadBridge supports:

Target nodes

RSOCtoNonConvexQuadBridge creates:

source
MathOptInterface.Bridges.Constraint.QuadtoSOCBridgeType
QuadtoSOCBridge{T} <: Bridges.Constraint.AbstractBridge

QuadtoSOCBridge converts quadratic inequalities

\[\frac{1}{2}x^T Q x + a^T x \le ub\]

into MOI.RotatedSecondOrderCone constraints, but it only applies when $Q$ is positive definite.

This is because, if Q is positive definite, there exists U such that $Q = U^T U$, and so the inequality can then be rewritten as;

\[\|U x\|_2^2 \le 2 (-a^T x + ub)\]

Therefore, QuadtoSOCBridge implements the following reformulations:

  • $\frac{1}{2}x^T Q x + a^T x \le ub$ into $(1, -a^T x + ub, Ux) \in RotatedSecondOrderCone$ where $Q = U^T U$
  • $\frac{1}{2}x^T Q x + a^T x \ge lb$ into $(1, a^T x - lb, Ux) \in RotatedSecondOrderCone$ where $-Q = U^T U$

Source node

QuadtoSOCBridge supports:

Target nodes

RelativeEntropyBridge creates:

Errors

This bridge errors if Q is not positive definite.

source
MathOptInterface.Bridges.Constraint.SOCtoPSDBridgeType
SOCtoPSDBridge{T,F,G} <: Bridges.Constraint.AbstractBridge

SOCtoPSDBridge implements the following reformulation:

  • $||x||_2 \le t$ into $\left[\begin{array}{c c}t & x^\top \\ x & t \mathbf{I}\end{array}\right]\succeq 0$
Warning

This bridge is not added by default by MOI.Bridges.full_bridge_optimizer because bridging second order cone constraints to semidefinite constraints can be achieved by the SOCtoRSOCBridge followed by the RSOCtoPSDBridge, while creating a smaller semidefinite constraint.

Source node

SOCtoPSDBridge supports:

Target nodes

SOCtoPSDBridge creates:

source
MathOptInterface.Bridges.Constraint.NormToPowerBridgeType
NormToPowerBridge{T,F} <: Bridges.Constraint.AbstractBridge

NormToPowerBridge implements the following reformulation:

  • $(t, x) \in NormCone(p, 1+d)$ into $(r_i, t, x_i) \in PowerCone(1 / p)$ for all $i$, and $\sum\limits_i r_i == t$.

For details, see Alizadeh, F., and Goldfarb, D. (2001). "Second-order cone programming." Mathematical Programming, Series B, 95:3-51.

Source node

NormToPowerBridge supports:

Target nodes

NormToPowerBridge creates:

source
MathOptInterface.Bridges.Constraint.GeoMeantoRelEntrBridgeType
GeoMeantoRelEntrBridge{T,F,G,H} <: Bridges.Constraint.AbstractBridge

GeoMeantoRelEntrBridge implements the following reformulation:

  • $(u, w) \in GeometricMeanCone$ into $(0, w, (u + y)\mathbf{1})\in RelativeEntropyCone$ and $y \ge 0$

Source node

GeoMeantoRelEntrBridge supports:

Target nodes

GeoMeantoRelEntrBridge creates:

Derivation

The derivation of the bridge is as follows:

\[\begin{aligned} +(u, w) \in GeometricMeanCone \iff & u \le \left(\prod_{i=1}^n w_i\right)^{1/n} \\ +\iff & 0 \le u + y \le \left(\prod_{i=1}^n w_i\right)^{1/n}, y \ge 0 \\ +\iff & 1 \le \frac{\left(\prod_{i=1}^n w_i\right)^{1/n}}{u + y}, y \ge 0 \\ +\iff & 1 \le \left(\prod_{i=1}^n \frac{w_i}{u + y}\right)^{1/n}, y \ge 0 \\ +\iff & 0 \le \sum_{i=1}^n \log\left(\frac{w_i}{u + y}\right), y \ge 0 \\ +\iff & 0 \ge \sum_{i=1}^n \log\left(\frac{u + y}{w_i}\right), y \ge 0 \\ +\iff & 0 \ge \sum_{i=1}^n (u + y) \log\left(\frac{u + y}{w_i}\right), y \ge 0 \\ +\iff & (0, w, (u + y)\mathbf{1}) \in RelativeEntropyCone, y \ge 0 \\ +\end{aligned}\]

This derivation assumes that $u + y > 0$, which is enforced by the relative entropy cone.

source
MathOptInterface.Bridges.Constraint.GeoMeanToPowerBridgeType
GeoMeanToPowerBridge{T,F} <: Bridges.Constraint.AbstractBridge

GeoMeanToPowerBridge implements the following reformulation:

  • $(y, x...) \in GeometricMeanCone(1+d)$ into $(x_1, t, y) \in PowerCone(1/d)$ and $(t, x_2, ..., x_d) in GeometricMeanCone(d)$, which is then recursively expanded into more PowerCone constraints.

Source node

GeoMeanToPowerBridge supports:

Target nodes

GeoMeanToPowerBridge creates:

source
MathOptInterface.Bridges.Constraint.GeoMeanBridgeType
GeoMeanBridge{T,F,G,H} <: Bridges.Constraint.AbstractBridge

GeoMeanBridge implements a reformulation from MOI.GeometricMeanCone into MOI.RotatedSecondOrderCone.

The reformulation is best described in an example.

Consider the cone of dimension 4:

\[t \le \sqrt[3]{x_1 x_2 x_3}\]

This can be rewritten as $\exists y \ge 0$ such that:

\[\begin{align*} + t & \le y,\\ + y^4 & \le x_1 x_2 x_3 y. +\end{align*}\]

Note that we need to create $y$ and not use $t^4$ directly because $t$ is allowed to be negative.

This is equivalent to:

\[\begin{align*} + t & \le \frac{y_1}{\sqrt{4}},\\ + y_1^2 & \le 2y_2 y_3,\\ + y_2^2 & \le 2x_1 x_2, \\ + y_3^2 & \le 2x_3(y_1/\sqrt{4}) \\ + y & \ge 0. +\end{align*}\]

More generally, you can show how the geometric mean code is recursively expanded into a set of new variables $y$ in MOI.Nonnegatives, a set of MOI.RotatedSecondOrderCone constraints, and a MOI.LessThan constraint between $t$ and $y_1$.

Source node

GeoMeanBridge supports:

Target nodes

GeoMeanBridge creates:

source
MathOptInterface.Bridges.Constraint.RelativeEntropyBridgeType
RelativeEntropyBridge{T,F,G,H} <: Bridges.Constraint.AbstractBridge

RelativeEntropyBridge implements the following reformulation that converts a MOI.RelativeEntropyCone into an MOI.ExponentialCone:

  • $u \ge \sum_{i=1}^n w_i \log \left(\frac{w_i}{v_i}\right)$ into $y_i \ge 0$, $u \ge \sum_{i=1}^n y_i$, and $(-y_i, w_i, v_i) \in ExponentialCone$.

Source node

RelativeEntropyBridge supports:

Target nodes

RelativeEntropyBridge creates:

source
MathOptInterface.Bridges.Constraint.SquareBridgeType
SquareBridge{T,F,G,TT,ST} <: Bridges.Constraint.AbstractBridge

SquareBridge implements the following reformulations:

  • $(t, u, X) \in LogDetConeSquare$ into $(t, u, Y) in LogDetConeTriangle$
  • $(t, X) \in RootDetConeSquare$ into $(t, Y) in RootDetConeTriangle$
  • $X \in AbstractSymmetricMatrixSetSquare$ into $Y in AbstractSymmetricMatrixSetTriangle$

where $Y$ is the upper triangluar component of $X$.

In addition, constraints are added as necessary to constrain the matrix $X$ to be symmetric. For example, the constraint for the matrix:

\[\begin{pmatrix} + 1 & 1 + x & 2 - 3x\\ + 1 + x & 2 + x & 3 - x\\ + 2 - 3x & 2 + x & 2x +\end{pmatrix}\]

can be broken down to the constraint of the symmetric matrix

\[\begin{pmatrix} + 1 & 1 + x & 2 - 3x\\ + \cdot & 2 + x & 3 - x\\ + \cdot & \cdot & 2x +\end{pmatrix}\]

and the equality constraint between the off-diagonal entries (2, 3) and (3, 2) $3 - x == 2 + x$. Note that no symmetrization constraint needs to be added between the off-diagonal entries (1, 2) and (2, 1) or between (1, 3) and (3, 1) because the expressions are the same.

Source node

SquareBridge supports:

  • F in ST

Target nodes

SquareBridge creates:

  • G in TT
source
MathOptInterface.Bridges.Constraint.HermitianToSymmetricPSDBridgeType
HermitianToSymmetricPSDBridge{T,F,G} <: Bridges.Constraint.AbstractBridge

HermitianToSymmetricPSDBridge implements the following reformulation:

  • Hermitian positive semidefinite n x n complex matrix to a symmetric positive semidefinite 2n x 2n real matrix.

See also MOI.Bridges.Variable.HermitianToSymmetricPSDBridge.

Source node

HermitianToSymmetricPSDBridge supports:

Target node

HermitianToSymmetricPSDBridge creates:

Reformulation

The reformulation is best described by example.

The Hermitian matrix:

\[\begin{bmatrix} + x_{11} & x_{12} + y_{12}im & x_{13} + y_{13}im\\ + x_{12} - y_{12}im & x_{22} & x_{23} + y_{23}im\\ + x_{13} - y_{13}im & x_{23} - y_{23}im & x_{33} +\end{bmatrix}\]

is positive semidefinite if and only if the symmetric matrix:

\[\begin{bmatrix} + x_{11} & x_{12} & x_{13} & 0 & y_{12} & y_{13} \\ + & x_{22} & x_{23} & -y_{12} & 0 & y_{23} \\ + & & x_{33} & -y_{13} & -y_{23} & 0 \\ + & & & x_{11} & x_{12} & x_{13} \\ + & & & & x_{22} & x_{23} \\ + & & & & & x_{33} +\end{bmatrix}\]

is positive semidefinite.

The bridge achieves this reformulation by constraining the above matrix to belong to the MOI.PositiveSemidefiniteConeTriangle(6).

source
MathOptInterface.Bridges.Constraint.RootDetBridgeType
RootDetBridge{T,F,G,H} <: Bridges.Constraint.AbstractBridge

The MOI.RootDetConeTriangle is representable by MOI.PositiveSemidefiniteConeTriangle and MOI.GeometricMeanCone constraints, see [1, p. 149].

Indeed, $t \le \det(X)^{1/n}$ if and only if there exists a lower triangular matrix $Δ$ such that:

\[\begin{align*} + \begin{pmatrix} + X & Δ\\ + Δ^\top & \mathrm{Diag}(Δ) + \end{pmatrix} & \succeq 0\\ + (t, \mathrm{Diag}(Δ)) & \in GeometricMeanCone +\end{align*}\]

Source node

RootDetBridge supports:

Target nodes

RootDetBridge creates:

[1] Ben-Tal, Aharon, and Arkadi Nemirovski. Lectures on modern convex optimization: analysis, algorithms, and engineering applications. Society for Industrial and Applied Mathematics, 2001.

source
MathOptInterface.Bridges.Constraint.LogDetBridgeType
LogDetBridge{T,F,G,H,I} <: Bridges.Constraint.AbstractBridge

The MOI.LogDetConeTriangle is representable by MOI.PositiveSemidefiniteConeTriangle and MOI.ExponentialCone constraints.

Indeed, $\log\det(X) = \sum\limits_{i=1}^n \log(\delta_i)$ where $\delta_i$ are the eigenvalues of $X$.

Adapting the method from [1, p. 149], we see that $t \le u \log(\det(X/u))$ for $u > 0$ if and only if there exists a lower triangular matrix $Δ$ such that

\[\begin{align*} + \begin{pmatrix} + X & Δ\\ + Δ^\top & \mathrm{Diag}(Δ) + \end{pmatrix} & \succeq 0\\ + t - \sum_{i=1}^n u \log\left(\frac{Δ_{ii}}{u}\right) & \le 0 +\end{align*}\]

Which we reformulate further into

\[\begin{align*} + \begin{pmatrix} + X & Δ\\ + Δ^\top & \mathrm{Diag}(Δ) + \end{pmatrix} & \succeq 0\\ + (l_i, u , Δ_{ii}) & \in ExponentialCone\quad \forall i \\ + t - \sum_{i=1}^n l_i & \le 0 +\end{align*}\]

Source node

LogDetBridge supports:

Target nodes

LogDetBridge creates:

[1] Ben-Tal, Aharon, and Arkadi Nemirovski. Lectures on modern convex optimization: analysis, algorithms, and engineering applications. Society for Industrial and Applied Mathematics, 2001.

source
MathOptInterface.Bridges.Constraint.IndicatorActiveOnFalseBridgeType
IndicatorActiveOnFalseBridge{T,F,S} <: Bridges.Constraint.AbstractBridge

IndicatorActiveOnFalseBridge implements the following reformulation:

  • $\neg z \implies {f(x) \in S}$ into $y \implies {f(x) \in S}$, $z + y = 1$, and $y \in \{0, 1\}$

Source node

IndicatorActiveOnFalseBridge supports:

Target nodes

IndicatorActiveOnFalseBridge creates:

source
MathOptInterface.Bridges.Constraint.IndicatorGreaterToLessThanBridgeType
IndicatorGreaterToLessThanBridge{T,A} <: Bridges.Constraint.AbstractBridge

IndicatorGreaterToLessThanBridge implements the following reformulation:

  • $z \implies {f(x) \ge l}$ into $z \implies {-f(x) \le -l}$

Source node

IndicatorGreaterToLessThanBridge supports:

Target nodes

IndicatorGreaterToLessThanBridge creates:

source
MathOptInterface.Bridges.Constraint.IndicatorLessToGreaterThanBridgeType
IndicatorLessToGreaterThanBridge{T,A} <: Bridges.Constraint.AbstractBridge

IndicatorLessToGreaterThanBridge implements the following reformulations:

  • $z \implies {f(x) \le u}$ into $z \implies {-f(x) \ge -u}$

Source node

IndicatorLessToGreaterThanBridge supports:

Target nodes

IndicatorLessToGreaterThanBridge creates:

source
MathOptInterface.Bridges.Constraint.IndicatorSOS1BridgeType
IndicatorSOS1Bridge{T,S} <: Bridges.Constraint.AbstractBridge

IndicatorSOS1Bridge implements the following reformulation:

  • $z \implies {f(x) \in S}$ into $f(x) + y \in S$, $SOS1(y, z)$
Warning

This bridge assumes that the solver supports MOI.SOS1{T} constraints in which one of the variables ($y$) is continuous.

Source node

IndicatorSOS1Bridge supports:

Target nodes

IndicatorSOS1Bridge creates:

source
MathOptInterface.Bridges.Constraint.SemiToBinaryBridgeType
SemiToBinaryBridge{T,S} <: Bridges.Constraint.AbstractBridge

SemiToBinaryBridge implements the following reformulations:

  • $x \in \{0\} \cup [l, u]$ into

    \[\begin{aligned} +x \leq z u \\ +x \geq z l \\ +z \in \{0, 1\} +\end{aligned}\]

  • $x \in \{0\} \cup \{l, \ldots, u\}$ into

    \[\begin{aligned} +x \leq z u \\ +x \geq z l \\ +z \in \{0, 1\} \\ +x \in \mathbb{Z} +\end{aligned}\]

Source node

SemiToBinaryBridge supports:

Target nodes

SemiToBinaryBridge creates:

source
MathOptInterface.Bridges.Constraint.ZeroOneBridgeType
ZeroOneBridge{T} <: Bridges.Constraint.AbstractBridge

ZeroOneBridge implements the following reformulation:

  • $x \in \{0, 1\}$ into $x \in \mathbb{Z}$, $1x \in [0, 1]$.
Note

ZeroOneBridge adds a linear constraint instead of adding variable bounds to avoid conflicting with bounds set by the user.

Source node

ZeroOneBridge supports:

Target nodes

ZeroOneBridge creates:

source
MathOptInterface.Bridges.Constraint.IntegerToZeroOneBridgeType
IntegerToZeroOneBridge{T} <: Bridges.Constraint.AbstractBridge

IntegerToZeroOneBridge implements the following reformulation:

  • $x \in \mathbf{Z}$ into $y_i \in \{0, 1\}$, $x == lb + \sum 2^{i-1} y_i$.

Source node

IntegerToZeroOneBridge supports:

Target nodes

IntegerToZeroOneBridge creates:

Developer note

This bridge is implemented as a constraint bridge instead of a variable bridge because we don't want to substitute the linear combination of y for every instance of x. Doing so would be expensive and greatly reduce the sparsity of the constraints.

source
MathOptInterface.Bridges.Constraint.NumberConversionBridgeType
NumberConversionBridge{T,F1,S1,F2,S2} <: Bridges.Constraint.AbstractBridge

NumberConversionBridge implements the following reformulation:

  • $f1(x) \in S1$ to $f2(x) \in S2$

where f and S are the same functional form, but differ in their coefficient type.

Source node

NumberConversionBridge supports:

  • F1 in S1

Target node

NumberConversionBridge creates:

  • F2 in S2
source
MathOptInterface.Bridges.Constraint.AllDifferentToCountDistinctBridgeType
AllDifferentToCountDistinctBridge{T,F} <: Bridges.Constraint.AbstractBridge

AllDifferentToCountDistinctBridge implements the following reformulations:

  • $x \in \textsf{AllDifferent}(d)$ to $(n, x) \in \textsf{CountDistinct}(1+d)$ and $n = d$
  • $f(x) \in \textsf{AllDifferent}(d)$ to $(d, f(x)) \in \textsf{CountDistinct}(1+d)$

Source node

AllDifferentToCountDistinctBridge supports:

where F is MOI.VectorOfVariables or MOI.VectorAffineFunction{T}.

Target nodes

AllDifferentToCountDistinctBridge creates:

source
MathOptInterface.Bridges.Constraint.ReifiedAllDifferentToCountDistinctBridgeType
ReifiedAllDifferentToCountDistinctBridge{T,F} <:
+Bridges.Constraint.AbstractBridge

ReifiedAllDifferentToCountDistinctBridge implements the following reformulations:

  • $r \iff x \in \textsf{AllDifferent}(d)$ to $r \iff (n, x) \in \textsf{CountDistinct}(1+d)$ and $n = d$
  • $r \iff f(x) \in \textsf{AllDifferent}(d)$ to $r \iff (d, f(x)) \in \textsf{CountDistinct}(1+d)$

Source node

ReifiedAllDifferentToCountDistinctBridge supports:

where F is MOI.VectorOfVariables or MOI.VectorAffineFunction{T}.

Target nodes

ReifiedAllDifferentToCountDistinctBridge creates:

source
MathOptInterface.Bridges.Constraint.BinPackingToMILPBridgeType
BinPackingToMILPBridge{T,F} <: Bridges.Constraint.AbstractBridge

BinPackingToMILPBridge implements the following reformulation:

  • $x \in BinPacking(c, w)$ into a mixed-integer linear program.

Reformulation

The reformulation is non-trivial, and it depends on the finite domain of each variable $x_i$, which we as define $S_i = \{l_i,\ldots,u_i\}$.

First, we introduce new binary variables $z_{ij}$, which are $1$ if variable $x_i$ takes the value $j$ in the optimal solution and $0$ otherwise:

\[\begin{aligned} +z_{ij} \in \{0, 1\} & \;\; \forall i \in 1\ldots d, j \in S_i \\ +x_i - \sum\limits_{j\in S_i} j \cdot z_{ij} = 0 & \;\; \forall i \in 1\ldots d \\ +\sum\limits_{j\in S_i} z_{ij} = 1 & \;\; \forall i \in 1\ldots d \\ +\end{aligned}\]

Then, we add the capacity constraint for all possible bins $j$:

\[\sum\limits_{i} w_i z_{ij} \le c \forall j \in \bigcup_{i=1,\ldots,d} S_i\]

Source node

BinPackingToMILPBridge supports:

Target nodes

BinPackingToMILPBridge creates:

source
MathOptInterface.Bridges.Constraint.CircuitToMILPBridgeType
CircuitToMILPBridge{T,F} <: Bridges.Constraint.AbstractBridge

CircuitToMILPBridge implements the following reformulation:

  • $x \in \textsf{Circuit}(d)$ to the Miller-Tucker-Zemlin formulation of the Traveling Salesperson Problem.

Source node

CircuitToMILPBridge supports:

where F is MOI.VectorOfVariables or MOI.VectorAffineFunction{T}.

Target nodes

CircuitToMILPBridge creates:

source
MathOptInterface.Bridges.Constraint.CountAtLeastToCountBelongsBridgeType
CountAtLeastToCountBelongsBridge{T,F} <: Bridges.Constraint.AbstractBridge

CountAtLeastToCountBelongsBridge implements the following reformulation:

  • $x \in \textsf{CountAtLeast}(n, d, \mathcal{S})$ to $(n_i, x_{d_i}) \in \textsf{CountBelongs}(1+d, \mathcal{S})$ and $n_i \ge n$ for all $i$.

Source node

CountAtLeastToCountBelongsBridge supports:

where F is MOI.VectorOfVariables or MOI.VectorAffineFunction{T}.

Target nodes

CountAtLeastToCountBelongsBridge creates:

source
MathOptInterface.Bridges.Constraint.CountBelongsToMILPBridgeType
CountBelongsToMILPBridge{T,F} <: Bridges.Constraint.AbstractBridge

CountBelongsToMILPBridge implements the following reformulation:

  • $(n, x) \in \textsf{CountBelongs}(1+d, \mathcal{S})$ into a mixed-integer linear program.

Reformulation

The reformulation is non-trivial, and it depends on the finite domain of each variable $x_i$, which we as define $S_i = \{l_i,\ldots,u_i\}$.

First, we introduce new binary variables $z_{ij}$, which are $1$ if variable $x_i$ takes the value $j$ in the optimal solution and $0$ otherwise:

\[\begin{aligned} +z_{ij} \in \{0, 1\} & \;\; \forall i \in 1\ldots d, j \in S_i \\ +x_i - \sum\limits_{j\in S_i} j \cdot z_{ij} = 0 & \;\; \forall i \in 1\ldots d \\ +\sum\limits_{j\in S_i} z_{ij} = 1 & \;\; \forall i \in 1\ldots d \\ +\end{aligned}\]

Finally, $n$ is constrained to be the number of $z_{ij}$ elements that are in $\mathcal{S}$:

\[n - \sum\limits_{i\in 1\ldots d, j \in \mathcal{S}} z_{ij} = 0\]

Source node

CountBelongsToMILPBridge supports:

where F is MOI.VectorOfVariables or MOI.VectorAffineFunction{T}.

Target nodes

CountBelongsToMILPBridge creates:

source
MathOptInterface.Bridges.Constraint.CountDistinctToMILPBridgeType
CountDistinctToMILPBridge{T,F} <: Bridges.Constraint.AbstractBridge

CountDistinctToMILPBridge implements the following reformulation:

  • $(n, x) \in \textsf{CountDistinct}(1+d)$ into a mixed-integer linear program.

Reformulation

The reformulation is non-trivial, and it depends on the finite domain of each variable $x_i$, which we as define $S_i = \{l_i,\ldots,u_i\}$.

First, we introduce new binary variables $z_{ij}$, which are $1$ if variable $x_i$ takes the value $j$ in the optimal solution and $0$ otherwise:

\[\begin{aligned} +z_{ij} \in \{0, 1\} & \;\; \forall i \in 1\ldots d, j \in S_i \\ +x_i - \sum\limits_{j\in S_i} j \cdot z_{ij} = 0 & \;\; \forall i \in 1\ldots d \\ +\sum\limits_{j\in S_i} z_{ij} = 1 & \;\; \forall i \in 1\ldots d \\ +\end{aligned}\]

Then, we introduce new binary variables $y_j$, which are $1$ if a variable takes the value $j$ in the optimal solution and $0$ otherwise.

\[\begin{aligned} +y_{j} \in \{0, 1\} & \;\; \forall j \in \bigcup_{i=1,\ldots,d} S_i \\ +y_j \le \sum\limits_{i \in 1\ldots d: j \in S_i} z_{ij} \le M y_j & \;\; \forall j \in \bigcup_{i=1,\ldots,d} S_i\\ +\end{aligned}\]

Finally, $n$ is constrained to be the number of $y_j$ elements that are non-zero:

\[n - \sum\limits_{j \in \bigcup_{i=1,\ldots,d} S_i} y_{j} = 0\]

Source node

CountDistinctToMILPBridge supports:

where F is MOI.VectorOfVariables or MOI.VectorAffineFunction{T}.

Target nodes

CountDistinctToMILPBridge creates:

source
MathOptInterface.Bridges.Constraint.ReifiedCountDistinctToMILPBridgeType
ReifiedCountDistinctToMILPBridge{T,F} <: Bridges.Constraint.AbstractBridge

ReifiedCountDistinctToMILPBridge implements the following reformulation:

  • $r \iff (n, x) \in \textsf{CountDistinct}(1+d)$ into a mixed-integer linear program.

Reformulation

The reformulation is non-trivial, and it depends on the finite domain of each variable $x_i$, which we as define $S_i = \{l_i,\ldots,u_i\}$.

First, we introduce new binary variables $z_{ij}$, which are $1$ if variable $x_i$ takes the value $j$ in the optimal solution and $0$ otherwise:

\[\begin{aligned} +z_{ij} \in \{0, 1\} & \;\; \forall i \in 1\ldots d, j \in S_i \\ +x_i - \sum\limits_{j\in S_i} j \cdot z_{ij} = 0 & \;\; \forall i \in 1\ldots d \\ +\sum\limits_{j\in S_i} z_{ij} = 1 & \;\; \forall i \in 1\ldots d \\ +\end{aligned}\]

Then, we introduce new binary variables $y_j$, which are $1$ if a variable takes the value $j$ in the optimal solution and $0$ otherwise.

\[\begin{aligned} +y_{j} \in \{0, 1\} & \;\; \forall j \in \bigcup_{i=1,\ldots,d} S_i \\ +y_j \le \sum\limits_{i \in 1\ldots d: j \in S_i} z_{ij} \le M y_j & \;\; \forall j \in \bigcup_{i=1,\ldots,d} S_i\\ +\end{aligned}\]

Finally, $n$ is constrained to be the number of $y_j$ elements that are non-zero, with some slack:

\[n - \sum\limits_{j \in \bigcup_{i=1,\ldots,d} S_i} y_{j} = \delta^+ - \delta^-\]

And then the slack is constrained to respect the reif variable $r$:

\[\begin{aligned} +d_1 \le \delta^+ \le M d_1 \\ +d_2 \le \delta^- \le M d_s \\ +d_1 + d_2 + r = 1 \\ +d_1, d_2 \in \{0, 1\} +\end{aligned}\]

Source node

ReifiedCountDistinctToMILPBridge supports:

where F is MOI.VectorOfVariables or MOI.VectorAffineFunction{T}.

Target nodes

ReifiedCountDistinctToMILPBridge creates:

source
MathOptInterface.Bridges.Constraint.CountGreaterThanToMILPBridgeType
CountGreaterThanToMILPBridge{T,F} <: Bridges.Constraint.AbstractBridge

CountGreaterThanToMILPBridge implements the following reformulation:

  • $(c, y, x) \in CountGreaterThan()$ into a mixed-integer linear program.

Source node

CountGreaterThanToMILPBridge supports:

Target nodes

CountGreaterThanToMILPBridge creates:

source
MathOptInterface.Bridges.Constraint.TableToMILPBridgeType
TableToMILPBridge{T,F} <: Bridges.Constraint.AbstractBridge

TableToMILPBridge implements the following reformulation:

  • $x \in Table(t)$ into

    \[\begin{aligned} +z_{j} \in \{0, 1\} & \quad \forall i, j \\ +\sum\limits_{j=1}^n z_{j} = 1 \\ +\sum\limits_{j=1}^n t_{ij} z_{j} = x_i & \quad \forall i +\end{aligned}\]

Source node

TableToMILPBridge supports:

Target nodes

TableToMILPBridge creates:

source

Objective bridges

These bridges are subtypes of Bridges.Objective.AbstractBridge.

MathOptInterface.Bridges.Objective.QuadratizeBridgeType
QuadratizeBridge{T}

QuadratizeBridge implements the following reformulations:

  • $\min \{a^\top x + b\}$ into $\min\{x^\top \mathbf{0} x + a^\top x + b\}$
  • $\max \{a^\top x + b\}$ into $\max\{x^\top \mathbf{0} x + a^\top x + b\}$

where T is the coefficient type of 0.

Source node

QuadratizeBridge supports:

Target nodes

QuadratizeBridge creates:

source
MathOptInterface.Bridges.Objective.SlackBridgeType
SlackBridge{T,F,G}

SlackBridge implements the following reformulations:

  • $\min\{f(x)\}$ into $\min\{y\;|\; f(x) - y \le 0\}$
  • $\max\{f(x)\}$ into $\max\{y\;|\; f(x) - y \ge 0\}$

where F is the type of f(x) - y, G is the type of f(x), and T is the coefficient type of f(x).

Source node

SlackBridge supports:

Target nodes

SlackBridge creates:

Warning

When using this bridge, changing the optimization sense is not supported. Set the sense to MOI.FEASIBILITY_SENSE first to delete the bridge, then set MOI.ObjectiveSense and re-add the objective.

source
MathOptInterface.Bridges.Objective.VectorSlackBridgeType
VectorSlackBridge{T,F,G}

VectorSlackBridge implements the following reformulations:

  • $\min\{f(x)\}$ into $\min\{y\;|\; y - f(x) \in \mathbb{R}_+ \}$
  • $\max\{f(x)\}$ into $\max\{y\;|\; f(x) - y \in \mathbb{R}_+ \}$

where F is the type of f(x) - y, G is the type of f(x), and T is the coefficient type of f(x).

Source node

VectorSlackBridge supports:

Target nodes

VectorSlackBridge creates:

Warning

When using this bridge, changing the optimization sense is not supported. Set the sense to MOI.FEASIBILITY_SENSE first to delete the bridge, then set MOI.ObjectiveSense and re-add the objective.

source

Variable bridges

These bridges are subtypes of Bridges.Variable.AbstractBridge.

MathOptInterface.Bridges.Variable.RSOCtoPSDBridgeType
RSOCtoPSDBridge{T} <: Bridges.Variable.AbstractBridge

RSOCtoPSDBridge implements the following reformulation:

  • $||x||_2^2 \le 2tu$ where $t, u \ge 0$ into $Y \succeq 0$, with the substitution rule: $Y = \left[\begin{array}{c c}t & x^\top \\ x & 2u \mathbf{I}\end{array}\right].$

Additional bounds are added to ensure the off-diagonals of the $2uI$ submatrix are 0, and linear constraints are added to ennsure the diagonal of $2uI$ takes the same values.

As a special case, if $|x|| = 0$, then RSOCtoPSDBridge reformulates into $(t, u) \in \mathbb{R}_+$.

Source node

RSOCtoPSDBridge supports:

Target nodes

RSOCtoPSDBridge creates:

source
MathOptInterface.Bridges.Variable.RSOCtoSOCBridgeType
RSOCtoSOCBridge{T} <: Bridges.Variable.AbstractBridge

RSOCtoSOCBridge implements the following reformulation:

  • $||x||_2^2 \le 2tu$ into $||v||_2 \le w$, with the substitution rules $t = \frac{w}{\sqrt 2} + \frac{v_1}{\sqrt 2}$, $u = \frac{w}{\sqrt 2} - \frac{v_1}{\sqrt 2}$, and $x = (v_2,\ldots,v_N)$.

Source node

RSOCtoSOCBridge supports:

Target node

RSOCtoSOCBridge creates:

source
MathOptInterface.Bridges.Variable.SOCtoRSOCBridgeType
SOCtoRSOCBridge{T} <: Bridges.Variable.AbstractBridge

SOCtoRSOCBridge implements the following reformulation:

  • $||x||_2 \le t$ into $2uv \ge ||w||_2^2$, with the substitution rules $t = \frac{u}{\sqrt 2} + \frac{v}{\sqrt 2}$, $x = (\frac{u}{\sqrt 2} - \frac{v}{\sqrt 2}, w)$.

Assumptions

  • SOCtoRSOCBridge assumes that $|x| \ge 1$.

Source node

SOCtoRSOCBridge supports:

Target node

SOCtoRSOCBridge creates:

source
MathOptInterface.Bridges.Variable.VectorizeBridgeType
VectorizeBridge{T,S} <: Bridges.Variable.AbstractBridge

VectorizeBridge implements the following reformulations:

  • $x \ge a$ into $[y] \in \mathbb{R}_+$ with the substitution rule $x = a + y$
  • $x \le a$ into $[y] \in \mathbb{R}_-$ with the substitution rule $x = a + y$
  • $x == a$ into $[y] \in \{0\}$ with the substitution rule $x = a + y$

where T is the coefficient type of a + y.

Source node

VectorizeBridge supports:

Target nodes

VectorizeBridge creates:

source
MathOptInterface.Bridges.Variable.ZerosBridgeType
ZerosBridge{T} <: Bridges.Variable.AbstractBridge

ZerosBridge implements the following reformulation:

  • $x \in \{0\}$ into the substitution rule $x = 0$,

where T is the coefficient type of 0.

Source node

ZerosBridge supports:

Target nodes

ZerosBridge does not create target nodes. It replaces all instances of x with 0 via substitution. This means that no variables are created in the underlying model.

Caveats

The bridged variables are similar to parameters with zero values. Parameters with non-zero values can be created with constrained variables in MOI.EqualTo by combining a VectorizeBridge and this bridge.

However, functions modified by ZerosBridge cannot be unbridged. That is, for a given function, we cannot determine if the bridged variables were used.

A related implication is that this bridge does not support MOI.ConstraintDual. However, if a MOI.Utilities.CachingOptimizer is used, the dual can be determined by the bridged optimizer using MOI.Utilities.get_fallback because the caching optimizer records the unbridged function.

source
MathOptInterface.Bridges.Variable.HermitianToSymmetricPSDBridgeType
HermitianToSymmetricPSDBridge{T} <: Bridges.Variable.AbstractBridge

HermitianToSymmetricPSDBridge implements the following reformulation:

  • Hermitian positive semidefinite n x n complex matrix to a symmetric positive semidefinite 2n x 2n real matrix satisfying equality constraints described below.

Source node

HermitianToSymmetricPSDBridge supports:

Target node

HermitianToSymmetricPSDBridge creates:

Reformulation

The reformulation is best described by example.

The Hermitian matrix:

\[\begin{bmatrix} + x_{11} & x_{12} + y_{12}im & x_{13} + y_{13}im\\ + x_{12} - y_{12}im & x_{22} & x_{23} + y_{23}im\\ + x_{13} - y_{13}im & x_{23} - y_{23}im & x_{33} +\end{bmatrix}\]

is positive semidefinite if and only if the symmetric matrix:

\[\begin{bmatrix} + x_{11} & x_{12} & x_{13} & 0 & y_{12} & y_{13} \\ + & x_{22} & x_{23} & -y_{12} & 0 & y_{23} \\ + & & x_{33} & -y_{13} & -y_{23} & 0 \\ + & & & x_{11} & x_{12} & x_{13} \\ + & & & & x_{22} & x_{23} \\ + & & & & & x_{33} +\end{bmatrix}\]

is positive semidefinite.

The bridge achieves this reformulation by adding a new set of variables in MOI.PositiveSemidefiniteConeTriangle(6), and then adding three groups of equality constraints to:

  • constrain the two x blocks to be equal
  • force the diagonal of the y blocks to be 0
  • force the lower triangular of the y block to be the negative of the upper triangle.
source
diff --git a/previews/PR3536/moi/submodules/Bridges/overview/index.html b/previews/PR3536/moi/submodules/Bridges/overview/index.html new file mode 100644 index 00000000000..65ddc21f40b --- /dev/null +++ b/previews/PR3536/moi/submodules/Bridges/overview/index.html @@ -0,0 +1,57 @@ + +Overview · JuMP

The Bridges submodule

The Bridges module simplifies the process of converting models between equivalent formulations.

Tip

Read our paper for more details on how bridges are implemented.

Why bridges?

A constraint can often be written in a number of equivalent formulations. For example, the constraint $l \le a^\top x \le u$ (ScalarAffineFunction-in-Interval) could be re-formulated as two constraints: $a^\top x \ge l$ (ScalarAffineFunction-in-GreaterThan) and $a^\top x \le u$ (ScalarAffineFunction-in-LessThan). An alternative re-formulation is to add a dummy variable y with the constraints $l \le y \le u$ (VariableIndex-in-Interval) and $a^\top x - y = 0$ (ScalarAffineFunction-in-EqualTo).

To avoid each solver having to code these transformations manually, MathOptInterface provides bridges.

A bridge is a small transformation from one constraint type to another (potentially collection of) constraint type.

Because these bridges are included in MathOptInterface, they can be re-used by any optimizer. Some bridges also implement constraint modifications and constraint primal and dual translations.

Several bridges can be used in combination to transform a single constraint into a form that the solver may understand. Choosing the bridges to use takes the form of finding a shortest path in the hyper-graph of bridges. The methodology is detailed in the MOI paper.

The three types of bridges

There are three types of bridges in MathOptInterface:

  1. Constraint bridges
  2. Variable bridges
  3. Objective bridges

Constraint bridges

Constraint bridges convert constraints formulated by the user into an equivalent form supported by the solver. Constraint bridges are subtypes of Bridges.Constraint.AbstractBridge.

The equivalent formulation may add constraints (and possibly also variables) in the underlying model.

In particular, constraint bridges can focus on rewriting the function of a constraint, and do not change the set. Function bridges are subtypes of Bridges.Constraint.AbstractFunctionConversionBridge.

Read the list of implemented constraint bridges for more details on the types of transformations that are available. Function bridges are Bridges.Constraint.ScalarFunctionizeBridge and Bridges.Constraint.VectorFunctionizeBridge.

Variable bridges

Variable bridges convert variables added by the user, either free with add_variable/add_variables, or constrained with add_constrained_variable/add_constrained_variables, into an equivalent form supported by the solver. Variable bridges are subtypes of Bridges.Variable.AbstractBridge.

The equivalent formulation may add constraints (and possibly also variables) in the underlying model.

Read the list of implemented variable bridges for more details on the types of transformations that are available.

Objective bridges

Objective bridges convert the ObjectiveFunction set by the user into an equivalent form supported by the solver. Objective bridges are subtypes of Bridges.Objective.AbstractBridge.

The equivalent formulation may add constraints (and possibly also variables) in the underlying model.

Read the list of implemented objective bridges for more details on the types of transformations that are available.

Bridges.full_bridge_optimizer

Tip

Unless you have an advanced use-case, this is probably the only function you need to care about.

To enable the full power of MathOptInterface's bridges, wrap an optimizer in a Bridges.full_bridge_optimizer.

julia> inner_optimizer = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> optimizer = MOI.Bridges.full_bridge_optimizer(inner_optimizer, Float64)
+MOIB.LazyBridgeOptimizer{MOIU.Model{Float64}}
+with 0 variable bridges
+with 0 constraint bridges
+with 0 objective bridges
+with inner model MOIU.Model{Float64}

Now, use optimizer as normal, and bridging will happen lazily behind the scenes. By lazily, we mean that bridging will happen if and only if the constraint is not supported by the inner_optimizer.

Info

Most bridges are added by default in Bridges.full_bridge_optimizer. However, for technical reasons, some bridges are not added by default. Three examples include Bridges.Constraint.SOCtoPSDBridge, Bridges.Constraint.SOCtoNonConvexQuadBridge and Bridges.Constraint.RSOCtoNonConvexQuadBridge. See the docs of those bridges for more information.

Add a single bridge

If you don't want to use Bridges.full_bridge_optimizer, you can wrap an optimizer in a single bridge.

However, this will force the constraint to be bridged, even if the inner_optimizer supports it.

julia> inner_optimizer = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> optimizer = MOI.Bridges.Constraint.SplitInterval{Float64}(inner_optimizer)
+MOIB.Constraint.SingleBridgeOptimizer{MOIB.Constraint.SplitIntervalBridge{Float64}, MOIU.Model{Float64}}
+with 0 constraint bridges
+with inner model MOIU.Model{Float64}
+
+julia> x = MOI.add_variable(optimizer)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(optimizer, x, MOI.Interval(0.0, 1.0))
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}}(1)
+
+julia> MOI.get(optimizer, MOI.ListOfConstraintTypesPresent())
+1-element Vector{Tuple{Type, Type}}:
+ (MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64})
+
+julia> MOI.get(inner_optimizer, MOI.ListOfConstraintTypesPresent())
+2-element Vector{Tuple{Type, Type}}:
+ (MathOptInterface.VariableIndex, MathOptInterface.GreaterThan{Float64})
+ (MathOptInterface.VariableIndex, MathOptInterface.LessThan{Float64})

Bridges.LazyBridgeOptimizer

If you don't want to use Bridges.full_bridge_optimizer, but you need more than a single bridge (or you want the bridging to happen lazily), you can manually construct a Bridges.LazyBridgeOptimizer.

First, wrap an inner optimizer:

julia> inner_optimizer = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> optimizer = MOI.Bridges.LazyBridgeOptimizer(inner_optimizer)
+MOIB.LazyBridgeOptimizer{MOIU.Model{Float64}}
+with 0 variable bridges
+with 0 constraint bridges
+with 0 objective bridges
+with inner model MOIU.Model{Float64}

Then use Bridges.add_bridge to add individual bridges:

julia> MOI.Bridges.add_bridge(optimizer, MOI.Bridges.Constraint.SplitIntervalBridge{Float64})
+
+julia> MOI.Bridges.add_bridge(optimizer, MOI.Bridges.Objective.FunctionizeBridge{Float64})

Now the constraints will be bridged only if needed:

julia> x = MOI.add_variable(optimizer)
+MOI.VariableIndex(1)
+
+julia> MOI.add_constraint(optimizer, x, MOI.Interval(0.0, 1.0))
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}}(1)
+
+julia> MOI.get(optimizer, MOI.ListOfConstraintTypesPresent())
+1-element Vector{Tuple{Type, Type}}:
+ (MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64})
+
+julia> MOI.get(inner_optimizer, MOI.ListOfConstraintTypesPresent())
+1-element Vector{Tuple{Type, Type}}:
+ (MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64})
diff --git a/previews/PR3536/moi/submodules/Bridges/reference/index.html b/previews/PR3536/moi/submodules/Bridges/reference/index.html new file mode 100644 index 00000000000..d2ace5a3a14 --- /dev/null +++ b/previews/PR3536/moi/submodules/Bridges/reference/index.html @@ -0,0 +1,199 @@ + +API Reference · JuMP

Bridges

AbstractBridge API

MathOptInterface.Bridges.AbstractBridgeType
abstract type AbstractBridge end

An abstract type representing a bridged constraint or variable in a MOI.Bridges.AbstractBridgeOptimizer.

All bridges must implement:

Subtypes of AbstractBridge may have additional requirements. Consult their docstrings for details.

In addition, all subtypes may optionally implement the following constraint attributes with the bridge in place of the constraint index:

source
MathOptInterface.Bridges.added_constrained_variable_typesFunction
added_constrained_variable_types(
+    BT::Type{<:AbstractBridge},
+)::Vector{Tuple{Type}}

Return a list of the types of constrained variables that bridges of concrete type BT add.

Implementation notes

  • This method depends only on the type of the bridge, not the runtime value. If the bridge may add a constrained variable, the type must be included in the return vector.
  • If the bridge adds a free variable via MOI.add_variable or MOI.add_variables, the return vector must include (MOI.Reals,).

Example

julia> MOI.Bridges.added_constrained_variable_types(
+           MOI.Bridges.Variable.NonposToNonnegBridge{Float64},
+       )
+1-element Vector{Tuple{Type}}:
+ (MathOptInterface.Nonnegatives,)
source
MathOptInterface.Bridges.added_constraint_typesFunction
added_constraint_types(
+    BT::Type{<:AbstractBridge},
+)::Vector{Tuple{Type,Type}}

Return a list of the types of constraints that bridges of concrete type BT add.

Implementation notes

  • This method depends only on the type of the bridge, not the runtime value. If the bridge may add a constraint, the type must be included in the return vector.

Example

julia> MOI.Bridges.added_constraint_types(
+           MOI.Bridges.Constraint.ZeroOneBridge{Float64},
+       )
+2-element Vector{Tuple{Type, Type}}:
+ (MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.Interval{Float64})
+ (MathOptInterface.VariableIndex, MathOptInterface.Integer)
source
MathOptInterface.getMethod
MOI.get(b::AbstractBridge, ::MOI.NumberOfVariables)::Int64

Return the number of variables created by the bridge b in the model.

See also MOI.NumberOfConstraints.

Implementation notes

  • There is a default fallback, so you need only implement this if the bridge adds new variables.
source
MathOptInterface.getMethod
MOI.get(b::AbstractBridge, ::MOI.ListOfVariableIndices)

Return the list of variables created by the bridge b.

See also MOI.ListOfVariableIndices.

Implementation notes

  • There is a default fallback, so you need only implement this if the bridge adds new variables.
source
MathOptInterface.getMethod
MOI.get(b::AbstractBridge, ::MOI.NumberOfConstraints{F,S})::Int64 where {F,S}

Return the number of constraints of the type F-in-S created by the bridge b.

See also MOI.NumberOfConstraints.

Implementation notes

  • There is a default fallback, so you need only implement this for the constraint types returned by added_constraint_types.
source
MathOptInterface.getMethod
MOI.get(b::AbstractBridge, ::MOI.ListOfConstraintIndices{F,S}) where {F,S}

Return a Vector{ConstraintIndex{F,S}} with indices of all constraints of type F-in-S created by the bride b.

See also MOI.ListOfConstraintIndices.

Implementation notes

  • There is a default fallback, so you need only implement this for the constraint types returned by added_constraint_types.
source
MathOptInterface.Bridges.final_touchFunction
final_touch(bridge::AbstractBridge, model::MOI.ModelLike)::Nothing

A function that is called immediately prior to MOI.optimize! to allow bridges to modify their reformulations with repsect to other variables and constraints in model.

For example, if the correctness of bridge depends on the bounds of a variable or the fact that variables are integer, then the bridge can implement final_touch to check assumptions immediately before a call to MOI.optimize!.

If you implement this method, you must also implement needs_final_touch.

source
MathOptInterface.Bridges.bridging_costFunction
bridging_cost(b::AbstractBridgeOptimizer, S::Type{<:MOI.AbstractSet}})

Return the cost of bridging variables constrained in S on creation, is_bridged(b, S) is assumed to be true.

bridging_cost(
+    b::AbstractBridgeOptimizer,
+    F::Type{<:MOI.AbstractFunction},
+    S::Type{<:MOI.AbstractSet},
+)

Return the cost of bridging F-in-S constraints.

is_bridged(b, S) is assumed to be true.

source

Constraint bridge API

MathOptInterface.supports_constraintMethod
MOI.supports_constraint(
+    BT::Type{<:AbstractBridge},
+    F::Type{<:MOI.AbstractFunction},
+    S::Type{<:MOI.AbstractSet},
+)::Bool

Return a Bool indicating whether the bridges of type BT support bridging F-in-S constraints.

Implementation notes

  • This method depends only on the type of the inputs, not the runtime values.
  • There is a default fallback, so you need only implement this method for constraint types that the bridge implements.
source
MathOptInterface.Bridges.Constraint.concrete_bridge_typeFunction
concrete_bridge_type(
+    BT::Type{<:AbstractBridge},
+    F::Type{<:MOI.AbstractFunction},
+    S::Type{<:MOI.AbstractSet}
+)::Type

Return the concrete type of the bridge supporting F-in-S constraints.

This function can only be called if MOI.supports_constraint(BT, F, S) is true.

Example

The SplitIntervalBridge bridges a MOI.VariableIndex-in-MOI.Interval constraint into a MOI.VariableIndex-in-MOI.GreaterThan and a MOI.VariableIndex-in-MOI.LessThan constraint.

julia> MOI.Bridges.Constraint.concrete_bridge_type(
+           MOI.Bridges.Constraint.SplitIntervalBridge{Float64},
+           MOI.VariableIndex,
+           MOI.Interval{Float64},
+       )
+MathOptInterface.Bridges.Constraint.SplitIntervalBridge{Float64, MathOptInterface.VariableIndex, MathOptInterface.Interval{Float64}, MathOptInterface.GreaterThan{Float64}, MathOptInterface.LessThan{Float64}}
source
MathOptInterface.Bridges.Constraint.bridge_constraintFunction
bridge_constraint(
+    BT::Type{<:AbstractBridge},
+    model::MOI.ModelLike,
+    func::AbstractFunction,
+    set::MOI.AbstractSet,
+)::BT

Bridge the constraint func-in-set using bridge BT to model and returns a bridge object of type BT.

Implementation notes

  • The bridge type BT should be a concrete type, that is, all the type parameters of the bridge must be set.
source
MathOptInterface.Bridges.Constraint.SingleBridgeOptimizerType
SingleBridgeOptimizer{BT<:AbstractBridge}(model::MOI.ModelLike)

Return AbstractBridgeOptimizer that always bridges any objective function supported by the bridge BT.

This is in contrast with the MOI.Bridges.LazyBridgeOptimizer, which only bridges the objective function if it is supported by the bridge BT and unsupported by model.

Example

julia> struct MyNewBridge{T} <: MOI.Bridges.Constraint.AbstractBridge end
+
+julia> bridge = MOI.Bridges.Constraint.SingleBridgeOptimizer{MyNewBridge{Float64}}(
+           MOI.Utilities.Model{Float64}(),
+       )
+MOIB.Constraint.SingleBridgeOptimizer{MyNewBridge{Float64}, MOIU.Model{Float64}}
+with 0 constraint bridges
+with inner model MOIU.Model{Float64}

Implementation notes

All bridges should simplify the creation of SingleBridgeOptimizers by defining a constant that wraps the bridge in a SingleBridgeOptimizer.

julia> const MyNewBridgeModel{T,OT<:MOI.ModelLike} =
+           MOI.Bridges.Constraint.SingleBridgeOptimizer{MyNewBridge{T},OT};

This enables users to create bridged models as follows:

julia> MyNewBridgeModel{Float64}(MOI.Utilities.Model{Float64}())
+MOIB.Constraint.SingleBridgeOptimizer{MyNewBridge{Float64}, MOIU.Model{Float64}}
+with 0 constraint bridges
+with inner model MOIU.Model{Float64}
source
MathOptInterface.Bridges.Constraint.SetMapBridgeType
abstract type SetMapBridge{T,S2,S1,F,G} <: AbstractBridge end

Consider two type of sets, S1 and S2, and a linear mapping A such that the image of a set of type S1 under A is a set of type S2.

A SetMapBridge{T,S2,S1,F,G} is a bridge that maps G-in-S1 constraints into F-in-S2 by mapping the function through A.

The linear map A is described by;

Implementing a method for these two functions is sufficient to bridge constraints. However, in order for the getters and setters of attributes such as dual solutions and starting values to work as well, a method for the following functions must be implemented:

See the docstrings of each function to see which feature would be missing if it was not implemented for a given bridge.

source

Objective bridge API

MathOptInterface.Bridges.Objective.supports_objective_functionFunction
supports_objective_function(
+    BT::Type{<:MOI.Bridges.Objective.AbstractBridge},
+    F::Type{<:MOI.AbstractFunction},
+)::Bool

Return a Bool indicating whether the bridges of type BT support bridging objective functions of type F.

Implementation notes

  • This method depends only on the type of the inputs, not the runtime values.
  • There is a default fallback, so you need only implement this method For objective functions that the bridge implements.
source
MathOptInterface.Bridges.set_objective_function_typeFunction
set_objective_function_type(
+    BT::Type{<:Objective.AbstractBridge},
+)::Type{<:MOI.AbstractFunction}

Return the type of objective function that bridges of concrete type BT set.

Implementation notes

  • This method depends only on the type of the bridge, not the runtime value.

Example

julia> MOI.Bridges.set_objective_function_type(
+           MOI.Bridges.Objective.FunctionizeBridge{Float64},
+       )
+MathOptInterface.ScalarAffineFunction{Float64}
source
MathOptInterface.Bridges.Objective.concrete_bridge_typeFunction
concrete_bridge_type(
+    BT::Type{<:MOI.Bridges.Objective.AbstractBridge},
+    F::Type{<:MOI.AbstractFunction},
+)::Type

Return the concrete type of the bridge supporting objective functions of type F.

This function can only be called if MOI.supports_objective_function(BT, F) is true.

source
MathOptInterface.Bridges.Objective.bridge_objectiveFunction
bridge_objective(
+    BT::Type{<:MOI.Bridges.Objective.AbstractBridge},
+    model::MOI.ModelLike,
+    func::MOI.AbstractFunction,
+)::BT

Bridge the objective function func using bridge BT to model and returns a bridge object of type BT.

Implementation notes

  • The bridge type BT must be a concrete type, that is, all the type parameters of the bridge must be set.
source
MathOptInterface.Bridges.Objective.SingleBridgeOptimizerType
SingleBridgeOptimizer{BT<:AbstractBridge}(model::MOI.ModelLike)

Return AbstractBridgeOptimizer that always bridges any objective function supported by the bridge BT.

This is in contrast with the MOI.Bridges.LazyBridgeOptimizer, which only bridges the objective function if it is supported by the bridge BT and unsupported by model.

Example

julia> struct MyNewBridge{T} <: MOI.Bridges.Objective.AbstractBridge end
+
+julia> bridge = MOI.Bridges.Objective.SingleBridgeOptimizer{MyNewBridge{Float64}}(
+           MOI.Utilities.Model{Float64}(),
+       )
+MOIB.Objective.SingleBridgeOptimizer{MyNewBridge{Float64}, MOIU.Model{Float64}}
+with 0 objective bridges
+with inner model MOIU.Model{Float64}

Implementation notes

All bridges should simplify the creation of SingleBridgeOptimizers by defining a constant that wraps the bridge in a SingleBridgeOptimizer.

julia> const MyNewBridgeModel{T,OT<:MOI.ModelLike} =
+           MOI.Bridges.Objective.SingleBridgeOptimizer{MyNewBridge{T},OT};

This enables users to create bridged models as follows:

julia> MyNewBridgeModel{Float64}(MOI.Utilities.Model{Float64}())
+MOIB.Objective.SingleBridgeOptimizer{MyNewBridge{Float64}, MOIU.Model{Float64}}
+with 0 objective bridges
+with inner model MOIU.Model{Float64}
source

Variable bridge API

MathOptInterface.Bridges.Variable.supports_constrained_variableFunction
supports_constrained_variable(
+    BT::Type{<:AbstractBridge},
+    S::Type{<:MOI.AbstractSet},
+)::Bool

Return a Bool indicating whether the bridges of type BT support bridging constrained variables in S. That is, it returns true if the bridge of type BT converts constrained variables of type S into a form supported by the solver.

Implementation notes

  • This method depends only on the type of the bridge and set, not the runtime values.
  • There is a default fallback, so you need only implement this method for sets that the bridge implements.

Example

julia> MOI.Bridges.Variable.supports_constrained_variable(
+           MOI.Bridges.Variable.NonposToNonnegBridge{Float64},
+           MOI.Nonpositives,
+       )
+true
+
+julia> MOI.Bridges.Variable.supports_constrained_variable(
+           MOI.Bridges.Variable.NonposToNonnegBridge{Float64},
+           MOI.Nonnegatives,
+       )
+false
source
MathOptInterface.Bridges.Variable.concrete_bridge_typeFunction
concrete_bridge_type(
+    BT::Type{<:AbstractBridge},
+    S::Type{<:MOI.AbstractSet},
+)::Type

Return the concrete type of the bridge supporting variables in S constraints.

This function can only be called if MOI.supports_constrained_variable(BT, S) is true.

Examples

As a variable in MOI.GreaterThan is bridged into variables in MOI.Nonnegatives by the VectorizeBridge:

julia> MOI.Bridges.Variable.concrete_bridge_type(
+           MOI.Bridges.Variable.VectorizeBridge{Float64},
+           MOI.GreaterThan{Float64},
+       )
+MathOptInterface.Bridges.Variable.VectorizeBridge{Float64, MathOptInterface.Nonnegatives}
source
MathOptInterface.Bridges.Variable.bridge_constrained_variableFunction
bridge_constrained_variable(
+    BT::Type{<:AbstractBridge},
+    model::MOI.ModelLike,
+    set::MOI.AbstractSet,
+)::BT

Bridge the constrained variable in set using bridge BT to model and returns a bridge object of type BT.

Implementation notes

  • The bridge type BT must be a concrete type, that is, all the type parameters of the bridge must be set.
source
MathOptInterface.Bridges.Variable.SingleBridgeOptimizerType
SingleBridgeOptimizer{BT<:AbstractBridge}(model::MOI.ModelLike)

Return MOI.Bridges.AbstractBridgeOptimizer that always bridges any variables constrained on creation supported by the bridge BT.

This is in contrast with the MOI.Bridges.LazyBridgeOptimizer, which only bridges the variables constrained on creation if they are supported by the bridge BT and unsupported by model.

Warning

Two SingleBridgeOptimizers cannot be used together as both of them assume that the underlying model only returns variable indices with nonnegative values. Use MOI.Bridges.LazyBridgeOptimizer instead.

Example

julia> struct MyNewBridge{T} <: MOI.Bridges.Variable.AbstractBridge end
+
+julia> bridge = MOI.Bridges.Variable.SingleBridgeOptimizer{MyNewBridge{Float64}}(
+           MOI.Utilities.Model{Float64}(),
+       )
+MOIB.Variable.SingleBridgeOptimizer{MyNewBridge{Float64}, MOIU.Model{Float64}}
+with 0 variable bridges
+with inner model MOIU.Model{Float64}

Implementation notes

All bridges should simplify the creation of SingleBridgeOptimizers by defining a constant that wraps the bridge in a SingleBridgeOptimizer.

julia> const MyNewBridgeModel{T,OT<:MOI.ModelLike} =
+           MOI.Bridges.Variable.SingleBridgeOptimizer{MyNewBridge{T},OT};

This enables users to create bridged models as follows:

julia> MyNewBridgeModel{Float64}(MOI.Utilities.Model{Float64}())
+MOIB.Variable.SingleBridgeOptimizer{MyNewBridge{Float64}, MOIU.Model{Float64}}
+with 0 variable bridges
+with inner model MOIU.Model{Float64}
source
MathOptInterface.Bridges.Variable.SetMapBridgeType
abstract type SetMapBridge{T,S1,S2} <: AbstractBridge end

Consider two type of sets, S1 and S2, and a linear mapping A such that the image of a set of type S1 under A is a set of type S2.

A SetMapBridge{T,S1,S2} is a bridge that substitutes constrained variables in S2 into the image through A of constrained variables in S1.

The linear map A is described by:

Implementing a method for these two functions is sufficient to bridge constrained variables. However, in order for the getters and setters of attributes such as dual solutions and starting values to work as well, a method for the following functions must be implemented:

See the docstrings of each function to see which feature would be missing if it was not implemented for a given bridge.

source
MathOptInterface.Bridges.Variable.unbridged_mapFunction
unbridged_map(
+   bridge::MOI.Bridges.Variable.AbstractBridge,
+    vi::MOI.VariableIndex,
+)

For a bridged variable in a scalar set, return a tuple of pairs mapping the variables created by the bridge to an affine expression in terms of the bridged variable vi.

unbridged_map(
+    bridge::MOI.Bridges.Variable.AbstractBridge,
+    vis::Vector{MOI.VariableIndex},
+)

For a bridged variable in a vector set, return a tuple of pairs mapping the variables created by the bridge to an affine expression in terms of the bridged variable vis. If this method is not implemented, it falls back to calling the following method for every variable of vis.

unbridged_map(
+    bridge::MOI.Bridges.Variable.AbstractBridge,
+    vi::MOI.VariableIndex,
+    i::MOI.Bridges.IndexInVector,
+)

For a bridged variable in a vector set, return a tuple of pairs mapping the variables created by the bridge to an affine expression in terms of the bridged variable vi corresponding to the ith variable of the vector.

If there is no way to recover the expression in terms of the bridged variable(s) vi(s), return nothing. See ZerosBridge for an example of bridge returning nothing.

source

AbstractBridgeOptimizer API

MathOptInterface.Bridges.bridged_variable_functionFunction
bridged_variable_function(
+    b::AbstractBridgeOptimizer,
+    vi::MOI.VariableIndex,
+)

Return a MOI.AbstractScalarFunction of variables of b.model that equals vi. That is, if the variable vi is bridged, it returns its expression in terms of the variables of b.model. Otherwise, it returns vi.

source
MathOptInterface.Bridges.unbridged_variable_functionFunction
unbridged_variable_function(
+    b::AbstractBridgeOptimizer,
+    vi::MOI.VariableIndex,
+)

Return a MOI.AbstractScalarFunction of variables of b that equals vi. That is, if the variable vi is an internal variable of b.model created by a bridge but not visible to the user, it returns its expression in terms of the variables of bridged variables. Otherwise, it returns vi.

source
MathOptInterface.Bridges.recursive_modelFunction
recursive_model(b::AbstractBridgeOptimizer)

If a variable, constraint, or objective is bridged, return the context of the inner variables. For most optimizers, this should be b.model.

source

LazyBridgeOptimizer API

MathOptInterface.Bridges.LazyBridgeOptimizerType
LazyBridgeOptimizer(model::MOI.ModelLike)

The LazyBridgeOptimizer is a bridge optimizer that supports multiple bridges, and only bridges things which are not supported by the internal model.

Internally, the LazyBridgeOptimizer solves a shortest hyper-path problem to determine which bridges to use.

In general, you should use full_bridge_optimizer instead of this constructor because full_bridge_optimizer automatically adds a large number of supported bridges.

See also: add_bridge, remove_bridge, has_bridge and full_bridge_optimizer.

Example

julia> model = MOI.Bridges.LazyBridgeOptimizer(MOI.Utilities.Model{Float64}())
+MOIB.LazyBridgeOptimizer{MOIU.Model{Float64}}
+with 0 variable bridges
+with 0 constraint bridges
+with 0 objective bridges
+with inner model MOIU.Model{Float64}
+
+julia> MOI.Bridges.add_bridge(model, MOI.Bridges.Variable.FreeBridge{Float64})
+
+julia> MOI.Bridges.has_bridge(model, MOI.Bridges.Variable.FreeBridge{Float64})
+true
source
MathOptInterface.Bridges.full_bridge_optimizerFunction
full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where {T}

Returns a LazyBridgeOptimizer bridging model for every bridge defined in this package (see below for the few exceptions) and for the coefficient type T, as well as the bridges in the list returned by the ListOfNonstandardBridges attribute.

Example

julia> model = MOI.Utilities.Model{Float64}();
+
+julia> bridged_model = MOI.Bridges.full_bridge_optimizer(model, Float64);

Exceptions

The following bridges are not added by full_bridge_optimizer, except if they are in the list returned by the ListOfNonstandardBridges attribute:

See the docstring of the each bridge for the reason they are not added.

source
MathOptInterface.Bridges.ListOfNonstandardBridgesType
ListOfNonstandardBridges{T}() <: MOI.AbstractOptimizerAttribute

Any optimizer can be wrapped in a LazyBridgeOptimizer using full_bridge_optimizer. However, by default LazyBridgeOptimizer uses a limited set of bridges that are:

  1. implemented in MOI.Bridges
  2. generally applicable for all optimizers.

For some optimizers however, it is useful to add additional bridges, such as those that are implemented in external packages (e.g., within the solver package itself) or only apply in certain circumstances (e.g., Constraint.SOCtoNonConvexQuadBridge).

Such optimizers should implement the ListOfNonstandardBridges attribute to return a vector of bridge types that are added by full_bridge_optimizer in addition to the list of default bridges.

Note that optimizers implementing ListOfNonstandardBridges may require package-specific functions or sets to be used if the non-standard bridges are not added. Therefore, you are recommended to use model = MOI.instantiate(Package.Optimizer; with_bridge_type = T) instead of model = MOI.instantiate(Package.Optimizer). See MOI.instantiate.

Examples

An optimizer using a non-default bridge in MOI.Bridges

Solvers supporting MOI.ScalarQuadraticFunction can support MOI.SecondOrderCone and MOI.RotatedSecondOrderCone by defining:

function MOI.get(::MyQuadraticOptimizer, ::ListOfNonstandardBridges{Float64})
+    return Type[
+        MOI.Bridges.Constraint.SOCtoNonConvexQuadBridge{Float64},
+        MOI.Bridges.Constraint.RSOCtoNonConvexQuadBridge{Float64},
+    ]
+end

An optimizer defining an internal bridge

Suppose an optimizer can exploit specific structure of a constraint, e.g., it can exploit the structure of the matrix A in the linear system of equations A * x = b.

The optimizer can define the function:

struct MatrixAffineFunction{T} <: MOI.AbstractVectorFunction
+    A::SomeStructuredMatrixType{T}
+    b::Vector{T}
+end

and then a bridge

struct MatrixAffineFunctionBridge{T} <: MOI.Constraint.AbstractBridge
+    # ...
+end
+# ...

from VectorAffineFunction{T} to the MatrixAffineFunction. Finally, it defines:

function MOI.get(::Optimizer{T}, ::ListOfNonstandardBridges{T}) where {T}
+    return Type[MatrixAffineFunctionBridge{T}]
+end
source
MathOptInterface.Bridges.print_active_bridgesFunction
print_active_bridges([io::IO=stdout,] b::MOI.Bridges.LazyBridgeOptimizer)

Print the set of bridges that are active in the model b.

source
print_active_bridges(
+    [io::IO=stdout,]
+    b::MOI.Bridges.LazyBridgeOptimizer,
+    F::Type{<:MOI.AbstractFunction}
+)

Print the set of bridges required for an objective function of type F.

source
print_active_bridges(
+    [io::IO=stdout,]
+    b::MOI.Bridges.LazyBridgeOptimizer,
+    F::Type{<:MOI.AbstractFunction},
+    S::Type{<:MOI.AbstractSet},
+)

Print the set of bridges required for a constraint of type F-in-S.

source
print_active_bridges(
+    [io::IO=stdout,]
+    b::MOI.Bridges.LazyBridgeOptimizer,
+    S::Type{<:MOI.AbstractSet}
+)

Print the set of bridges required for a variable constrained to set S.

source
MathOptInterface.Bridges.print_graphFunction
print_graph([io::IO = stdout,] b::LazyBridgeOptimizer)

Print the hyper-graph containing all variable, constraint, and objective types that could be obtained by bridging the variables, constraints, and objectives that are present in the model by all the bridges added to b.

Each node in the hyper-graph corresponds to a variable, constraint, or objective type.

  • Variable nodes are indicated by [ ]
  • Constraint nodes are indicated by ( )
  • Objective nodes are indicated by | |

The number inside each pair of brackets is an index of the node in the hyper-graph.

Note that this hyper-graph is the full list of possible transformations. When the bridged model is created, we select the shortest hyper-path(s) from this graph, so many nodes may be un-used.

To see which nodes are used, call print_active_bridges.

For more information, see Legat, B., Dowson, O., Garcia, J., and Lubin, M. (2020). "MathOptInterface: a data structure for mathematical optimization problems." URL: https://arxiv.org/abs/2002.03447

source

SetMap API

MathOptInterface.Bridges.map_functionFunction
map_function(::Type{BT}, func) where {BT}

Return the image of func through the linear map A defined in Variable.SetMapBridge and Constraint.SetMapBridge. This is used for getting the MOI.ConstraintPrimal of variable bridges. For constraint bridges, this is used for bridging the constraint, setting the MOI.ConstraintFunction and MOI.ConstraintPrimalStart and modifying the function with MOI.modify.

map_function(::Type{BT}, func, i::IndexInVector) where {BT}

Return the scalar function at the ith index of the vector function that would be returned by map_function(BT, func) except that it may compute the ith element. This is used by bridged_function and for getting the MOI.VariablePrimal and MOI.VariablePrimalStart of variable bridges.

source

Bridging graph API

MathOptInterface.Bridges.GraphType
Graph()

A type-stable datastructure for computing the shortest hyperpath problem.

Nodes

There are three types of nodes in the graph:

Add nodes to the graph using add_node.

Edges

There are two types of edges in the graph:

Add edges to the graph using add_edge.

For the ability to add a variable constrained on creation as a free variable followed by a constraint, use set_variable_constraint_node.

Optimal hyper-edges

Use bridge_index to compute the minimum-cost bridge leaving a node.

Note that bridge_index lazy runs a Bellman-Ford algorithm to compute the set of minimum cost edges. Thus, the first call to bridge_index after adding new nodes or edges will take longer than subsequent calls.

source
MathOptInterface.Bridges.add_nodeFunction
add_node(graph::Graph, ::Type{VariableNode})::VariableNode
+add_node(graph::Graph, ::Type{ConstraintNode})::ConstraintNode
+add_node(graph::Graph, ::Type{ObjectiveNode})::ObjectiveNode

Add a new node to graph.

source
MathOptInterface.Bridges.add_edgeFunction
add_edge(graph::Graph, node::VariableNode, edge::Edge)::Nothing
+add_edge(graph::Graph, node::ConstraintNode, edge::Edge)::Nothing
+add_edge(graph::Graph, node::ObjectiveNode, edge::ObjectiveEdge)::Nothing

Add edge to graph, where edge starts at node and connects to the nodes defined in edge.

source
MathOptInterface.Bridges.set_variable_constraint_nodeFunction
set_variable_constraint_node(
+    graph::Graph,
+    variable_node::VariableNode,
+    constraint_node::ConstraintNode,
+    cost::Int,
+)

As an alternative to variable_node, add a virtual edge to graph that represents adding a free variable, followed by a constraint of type constraint_node, with bridging cost cost.

Why is this needed?

Variables can either be added as a variable constrained on creation, or as a free variable which then has a constraint added to it.

source
MathOptInterface.Bridges.bridge_indexFunction
bridge_index(graph::Graph, node::VariableNode)::Int
+bridge_index(graph::Graph, node::ConstraintNode)::Int
+bridge_index(graph::Graph, node::ObjectiveNode)::Int

Return the optimal index of the bridge to chose from node.

source
MathOptInterface.Bridges.is_variable_edge_bestFunction
is_variable_edge_best(graph::Graph, node::VariableNode)::Bool

Return a Bool indicating whether node should be added as a variable constrained on creation, or as a free variable followed by a constraint.

source
diff --git a/previews/PR3536/moi/submodules/FileFormats/overview/index.html b/previews/PR3536/moi/submodules/FileFormats/overview/index.html new file mode 100644 index 00000000000..4694d736fd6 --- /dev/null +++ b/previews/PR3536/moi/submodules/FileFormats/overview/index.html @@ -0,0 +1,141 @@ + +Overview · JuMP

The FileFormats submodule

The FileFormats module provides functions for reading and writing MOI models using write_to_file and read_from_file.

Supported file types

You must read and write files to a FileFormats.Model object. Specific the file-type by passing a FileFormats.FileFormat enum. For example:

The Conic Benchmark Format

julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_CBF)
+A Conic Benchmark Format (CBF) model

The LP file format

julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_LP)
+A .LP-file model

The MathOptFormat file format

julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_MOF)
+A MathOptFormat Model

The MPS file format

julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_MPS)
+A Mathematical Programming System (MPS) model

The NL file format

julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_NL)
+An AMPL (.nl) model

The REW file format

julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_REW)
+A Mathematical Programming System (MPS) model

Note that the REW format is identical to the MPS file format, except that all names are replaced with generic identifiers.

The SDPA file format

julia> model = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_SDPA)
+A SemiDefinite Programming Algorithm Format (SDPA) model

Write to file

To write a model src to a MathOptFormat file, use:

julia> src = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> MOI.add_variable(src)
+MOI.VariableIndex(1)
+
+julia> dest = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_MOF)
+A MathOptFormat Model
+
+julia> MOI.copy_to(dest, src)
+MathOptInterface.Utilities.IndexMap with 1 entry:
+  MOI.VariableIndex(1) => MOI.VariableIndex(1)
+
+julia> MOI.write_to_file(dest, "file.mof.json")
+
+julia> print(read("file.mof.json", String))
+{
+  "name": "MathOptFormat Model",
+  "version": {
+    "major": 1,
+    "minor": 5
+  },
+  "variables": [
+    {
+      "name": "x1"
+    }
+  ],
+  "objective": {
+    "sense": "feasibility"
+  },
+  "constraints": []
+}

Read from file

To read a MathOptFormat file, use:

julia> dest = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_MOF)
+A MathOptFormat Model
+
+julia> MOI.read_from_file(dest, "file.mof.json")
+
+julia> MOI.get(dest, MOI.ListOfVariableIndices())
+1-element Vector{MathOptInterface.VariableIndex}:
+ MOI.VariableIndex(1)
+
+julia> rm("file.mof.json")  # Clean up after ourselves.

Detecting the file-type automatically

Instead of the format keyword, you can also use the filename keyword argument to FileFormats.Model. This will attempt to automatically guess the format from the file extension. For example:

julia> src = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> dest = MOI.FileFormats.Model(filename = "file.cbf.gz")
+A Conic Benchmark Format (CBF) model
+
+julia> MOI.copy_to(dest, src)
+MathOptInterface.Utilities.IndexMap()
+
+julia> MOI.write_to_file(dest, "file.cbf.gz")
+
+julia> src_2 = MOI.FileFormats.Model(filename = "file.cbf.gz")
+A Conic Benchmark Format (CBF) model
+
+julia> src = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> dest = MOI.FileFormats.Model(filename = "file.cbf.gz")
+A Conic Benchmark Format (CBF) model
+
+julia> MOI.copy_to(dest, src)
+MathOptInterface.Utilities.IndexMap()
+
+julia> MOI.write_to_file(dest, "file.cbf.gz")
+
+julia> src_2 = MOI.FileFormats.Model(filename = "file.cbf.gz")
+A Conic Benchmark Format (CBF) model
+
+julia> MOI.read_from_file(src_2, "file.cbf.gz")
+
+julia> rm("file.cbf.gz")  # Clean up after ourselves.

Note how the compression format (GZip) is also automatically detected from the filename.

Unsupported constraints

In some cases src may contain constraints that are not supported by the file format (for example, the CBF format supports integer variables but not binary). If so, copy src to a bridged model using Bridges.full_bridge_optimizer:

src = MOI.Utilities.Model{Float64}()
+x = MOI.add_variable(model)
+MOI.add_constraint(model, x, MOI.ZeroOne())
+dest = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_CBF)
+bridged = MOI.Bridges.full_bridge_optimizer(dest, Float64)
+MOI.copy_to(bridged, src)
+MOI.write_to_file(dest, "my_model.cbf")
Note

Even after bridging, it may still not be possible to write the model to file because of unsupported constraints (for example, PSD variables in the LP file format).

Read and write to io

In addition to write_to_file and read_from_file, you can read and write directly from IO streams using Base.write and Base.read!:

julia> src = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
+
+julia> dest = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_MPS)
+A Mathematical Programming System (MPS) model
+
+julia> MOI.copy_to(dest, src)
+MathOptInterface.Utilities.IndexMap()
+
+julia> io = IOBuffer();
+
+julia> write(io, dest)
+
+julia> seekstart(io);
+
+julia> src_2 = MOI.FileFormats.Model(format = MOI.FileFormats.FORMAT_MPS)
+A Mathematical Programming System (MPS) model
+
+julia> read!(io, src_2);

Validating MOF files

MathOptFormat files are governed by a schema. Use JSONSchema.jl to check if a .mof.json file satisfies the schema.

First, construct the schema object as follows:

julia> import JSON, JSONSchema
+
+julia> schema = JSONSchema.Schema(JSON.parsefile(MOI.FileFormats.MOF.SCHEMA_PATH))
+A JSONSchema

Then, check if a model file is valid using isvalid:

julia> good_model = JSON.parse("""
+       {
+         "version": {
+           "major": 1,
+           "minor": 5
+         },
+         "variables": [{"name": "x"}],
+         "objective": {"sense": "feasibility"},
+         "constraints": []
+       }
+       """);
+
+julia> isvalid(schema, good_model)
+true

If we construct an invalid file, for example by mis-typing name as NaMe, the validation fails:

julia> bad_model = JSON.parse("""
+       {
+         "version": {
+           "major": 1,
+           "minor": 5
+         },
+         "variables": [{"NaMe": "x"}],
+         "objective": {"sense": "feasibility"},
+         "constraints": []
+       }
+       """);
+
+julia> isvalid(schema, bad_model)
+false

Use JSONSchema.validate to obtain more insight into why the validation failed:

julia> JSONSchema.validate(schema, bad_model)
+Validation failed:
+path:         [variables][1]
+instance:     Dict{String, Any}("NaMe" => "x")
+schema key:   required
+schema value: Any["name"]
diff --git a/previews/PR3536/moi/submodules/FileFormats/reference/index.html b/previews/PR3536/moi/submodules/FileFormats/reference/index.html new file mode 100644 index 00000000000..fbc18c13fc1 --- /dev/null +++ b/previews/PR3536/moi/submodules/FileFormats/reference/index.html @@ -0,0 +1,22 @@ + +API Reference · JuMP

File Formats

Functions to help read and write MOI models to/from various file formats. See The FileFormats submodule for more details.

MathOptInterface.FileFormats.ModelFunction
Model(
+    ;
+    format::FileFormat = FORMAT_AUTOMATIC,
+    filename::Union{Nothing, String} = nothing,
+    kwargs...
+)

Return model corresponding to the FileFormat format, or, if format == FORMAT_AUTOMATIC, guess the format from filename.

The filename argument is only needed if format == FORMAT_AUTOMATIC.

kwargs are passed to the underlying model constructor.

source
MathOptInterface.FileFormats.FileFormatType
FileFormat

List of accepted export formats.

  • FORMAT_AUTOMATIC: try to detect the file format based on the file name
  • FORMAT_CBF: the Conic Benchmark format
  • FORMAT_LP: the LP file format
  • FORMAT_MOF: the MathOptFormat file format
  • FORMAT_MPS: the MPS file format
  • FORMAT_NL: the AMPL .nl file format
  • FORMAT_REW: the .rew file format, which is MPS with generic names
  • FORMAT_SDPA: the SemiDefinite Programming Algorithm format
source
MathOptInterface.FileFormats.LP.ModelType
Model(; kwargs...)

Create an empty instance of FileFormats.LP.Model.

Keyword arguments are:

  • maximum_length::Int=255: the maximum length for the name of a variable. lp_solve 5.0 allows only 16 characters, while CPLEX 12.5+ allow 255.

  • warn::Bool=false: print a warning when variables or constraints are renamed.

source
MathOptInterface.FileFormats.MOF.ModelType
Model(; kwargs...)

Create an empty instance of FileFormats.MOF.Model.

Keyword arguments are:

  • print_compact::Bool=false: print the JSON file in a compact format without spaces or newlines.
  • warn::Bool=false: print a warning when variables or constraints are renamed
  • differentiation_backend::MOI.Nonlinear.AbstractAutomaticDifferentiation = MOI.Nonlinear.SparseReverseMode(): automatic differentiation backend to use when reading models with nonlinear constraints and objectives.
source
MathOptInterface.FileFormats.MPS.ModelType
Model(; kwargs...)

Create an empty instance of FileFormats.MPS.Model.

Keyword arguments are:

  • warn::Bool=false: print a warning when variables or constraints are renamed.
  • print_objsense::Bool=false: print the OBJSENSE section when writing
  • generic_names::Bool=false: strip all names in the model and replace them with the generic names C$i and R$i for the i'th column and row respectively.
  • quadratic_format::QuadraticFormat = kQuadraticFormatGurobi: specify the solver-specific extension used when writing the quadratic components of the model. Options are kQuadraticFormatGurobi, kQuadraticFormatCPLEX, and kQuadraticFormatMosek.
source
MathOptInterface.FileFormats.SDPA.ModelType
Model(; number_type::Type = Float64)

Create an empty instance of FileFormats.SDPA.Model{number_type}.

It is important to be aware that the SDPA file format is interpreted in geometric form and not standard conic form. The standard conic form and geometric conic form are two dual standard forms for semidefinite programs (SDPs). The geometric conic form of an SDP is as follows:

\[\begin{align} +& \min_{y \in \mathbb{R}^m} & b^T y +\\ +& \;\;\text{s.t.} & \sum_{i=1}^m A_i y_i - C & \in \mathbb{K} +\end{align}\]

where $\mathcal{K}$ is a cartesian product of nonnegative orthant and positive semidefinite matrices that align with a block diagonal structure shared with the matrices A_i and C.

In other words, the geometric conic form contains free variables and affine constraints in either the nonnegative orthant or the positive semidefinite cone. That is, in the MathOptInterface's terminology, MOI.VectorAffineFunction-in-MOI.Nonnegatives and MOI.VectorAffineFunction-in-MOI.PositiveSemidefiniteConeTriangle constraints.

The corresponding standard conic form of the dual SDP is as follows:

\[\begin{align} +& \max_{X \in \mathbb{K}} & \text{tr}(CX) +\\ +& \;\;\text{s.t.} & \text{tr}(A_iX) & = b_i & i = 1, \ldots, m. +\end{align}\]

In other words, the standard conic form contains nonnegative and positive semidefinite variables with equality constraints. That is, in the MathOptInterface's terminology, MOI.VectorOfVariables-in-MOI.Nonnegatives, MOI.VectorOfVariables-in-MOI.PositiveSemidefiniteConeTriangle and MOI.ScalarAffineFunction-in-MOI.EqualTo constraints.

If a model is in standard conic form, use Dualization.jl to transform it into the geometric conic form before writting it. Otherwise, the nonnegative (resp. positive semidefinite) variables will be bridged into free variables with affine constraints constraining them to belong to the nonnegative orthant (resp. positive semidefinite cone) by the MOI.Bridges.Constraint.VectorFunctionizeBridge. Moreover, equality constraints will be bridged into pairs of affine constraints in the nonnegative orthant by the MOI.Bridges.Constraint.SplitIntervalBridge and then the MOI.Bridges.Constraint.VectorizeBridge.

If a solver is in standard conic form, use Dualization.jl to transform the model read into standard conic form before copying it to the solver. Otherwise, the free variables will be bridged into pairs of variables in the nonnegative orthant by the MOI.Bridges.Variable.FreeBridge and affine constraints will be bridged into equality constraints by creating a slack variable by the MOI.Bridges.Constraint.VectorSlackBridge.

source

Other helpers

MathOptInterface.FileFormats.NL.SolFileResultsType
SolFileResults(filename::String, model::Model)

Parse the .sol file filename created by solving model and return a SolFileResults struct.

The returned struct supports the MOI.get API for querying result attributes such as MOI.TerminationStatus, MOI.VariablePrimal, and MOI.ConstraintDual.

source
SolFileResults(
+    raw_status::String,
+    termination_status::MOI.TerminationStatusCode,
+)

Return a SolFileResults struct with MOI.RawStatusString set to raw_status, MOI.TerminationStatus set to termination_status, and MOI.PrimalStatus and MOI.DualStatus set to NO_SOLUTION.

All other attributes are un-set.

source
diff --git a/previews/PR3536/moi/submodules/Nonlinear/overview/index.html b/previews/PR3536/moi/submodules/Nonlinear/overview/index.html new file mode 100644 index 00000000000..1c22fa7a4ab --- /dev/null +++ b/previews/PR3536/moi/submodules/Nonlinear/overview/index.html @@ -0,0 +1,187 @@ + +Overview · JuMP

Nonlinear

Warning

The Nonlinear submodule is experimental. Until this message is removed, breaking changes may be introduced in any minor or patch release of MathOptInterface.

The Nonlinear submodule contains data structures and functions for working with a nonlinear optimization problem in the form of an expression graph. This page explains the API and describes the rationale behind its design.

Standard form

Nonlinear programs (NLPs) are a class of optimization problems in which some of the constraints or the objective function are nonlinear:

\[\begin{align} + \min_{x \in \mathbb{R}^n} & f_0(x) \\ + \;\;\text{s.t.} & l_j \le f_j(x) \le u_j & j = 1 \ldots m +\end{align}\]

There may be additional constraints, as well as things like variable bounds and integrality restrictions, but we do not consider them here because they are best dealt with by other components of MathOptInterface.

API overview

The core element of the Nonlinear submodule is Nonlinear.Model:

julia> const Nonlinear = MOI.Nonlinear;
+
+julia> model = Nonlinear.Model()
+A Nonlinear.Model with:
+ 0 objectives
+ 0 parameters
+ 0 expressions
+ 0 constraints

Nonlinear.Model is a mutable struct that stores all of the nonlinear information added to the model.

Decision variables

Decision variables are represented by VariableIndexes. The user is responsible for creating these using MOI.VariableIndex(i), where i is the column associated with the variable.

Expressions

The input data structure is a Julia Expr. The input expressions can incorporate VariableIndexes, but these must be interpolated into the expression with $:

julia> x = MOI.VariableIndex(1)
+MOI.VariableIndex(1)
+
+julia> input = :(1 + sin($x)^2)
+:(1 + sin(MathOptInterface.VariableIndex(1)) ^ 2)

There are a number of restrictions on the input Expr:

  • It cannot contain macros
  • It cannot contain broadcasting
  • It cannot contain splatting (except in limited situations)
  • It cannot contain linear algebra, such as matrix-vector products
  • It cannot contain generator expressions, including sum(i for i in S)

Given an input expression, add an expression using Nonlinear.add_expression:

julia> expr = Nonlinear.add_expression(model, input)
+MathOptInterface.Nonlinear.ExpressionIndex(1)

The return value, expr, is a Nonlinear.ExpressionIndex that can then be interpolated into other input expressions.

Looking again at model, we see:

julia> model
+A Nonlinear.Model with:
+ 0 objectives
+ 0 parameters
+ 1 expression
+ 0 constraints

Parameters

In addition to constant literals like 1 or 1.23, you can create parameters. Parameters are placeholders whose values can change before passing the expression to the solver. Create a parameter using Nonlinear.add_parameter, which accepts a default value:

julia> p = Nonlinear.add_parameter(model, 1.23)
+MathOptInterface.Nonlinear.ParameterIndex(1)

The return value, p, is a Nonlinear.ParameterIndex that can then be interpolated into other input expressions.

Looking again at model, we see:

julia> model
+A Nonlinear.Model with:
+ 0 objectives
+ 1 parameter
+ 1 expression
+ 0 constraints

Update a parameter as follows:

julia> model[p]
+1.23
+
+julia> model[p] = 4.56
+4.56
+
+julia> model[p]
+4.56

Objectives

Set a nonlinear objective using Nonlinear.set_objective:

julia> Nonlinear.set_objective(model, :($p + $expr + $x))
+
+julia> model
+A Nonlinear.Model with:
+ 1 objective
+ 1 parameter
+ 1 expression
+ 0 constraints

Clear a nonlinear objective by passing nothing:

julia> Nonlinear.set_objective(model, nothing)
+
+julia> model
+A Nonlinear.Model with:
+ 0 objectives
+ 1 parameter
+ 1 expression
+ 0 constraints

But we'll re-add the objective for later:

julia> Nonlinear.set_objective(model, :($p + $expr + $x));

Constraints

Add a constraint using Nonlinear.add_constraint:

julia> c = Nonlinear.add_constraint(model, :(1 + sqrt($x)), MOI.LessThan(2.0))
+MathOptInterface.Nonlinear.ConstraintIndex(1)
+
+julia> model
+A Nonlinear.Model with:
+ 1 objective
+ 1 parameter
+ 1 expression
+ 1 constraint

The return value, c, is a Nonlinear.ConstraintIndex that is a unique identifier for the constraint. Interval constraints are also supported:

julia> c2 = Nonlinear.add_constraint(model, :(1 + sqrt($x)), MOI.Interval(-1.0, 2.0))
+MathOptInterface.Nonlinear.ConstraintIndex(2)
+
+julia> model
+A Nonlinear.Model with:
+ 1 objective
+ 1 parameter
+ 1 expression
+ 2 constraints

Delete a constraint using Nonlinear.delete:

julia> Nonlinear.delete(model, c2)
+
+julia> model
+A Nonlinear.Model with:
+ 1 objective
+ 1 parameter
+ 1 expression
+ 1 constraint

User-defined operators

By default, Nonlinear supports a wide range of univariate and multivariate operators. However, you can also define your own operators by registering them.

Univariate operators

Register a univariate user-defined operator using Nonlinear.register_operator:

julia> f(x) = 1 + sin(x)^2
+f (generic function with 1 method)
+
+julia> Nonlinear.register_operator(model, :my_f, 1, f)

Now, you can use :my_f in expressions:

julia> new_expr = Nonlinear.add_expression(model, :(my_f($x + 1)))
+MathOptInterface.Nonlinear.ExpressionIndex(2)

By default, Nonlinear will compute first- and second-derivatives of the registered operator using ForwardDiff.jl. Override this by passing functions which compute the respective derivative:

julia> f′(x) = 2 * sin(x) * cos(x)
+f′ (generic function with 1 method)
+
+julia> Nonlinear.register_operator(model, :my_f2, 1, f, f′)

or

julia> f′′(x) = 2 * (cos(x)^2 - sin(x)^2)
+f′′ (generic function with 1 method)
+
+julia> Nonlinear.register_operator(model, :my_f3, 1, f, f′, f′′)

Multivariate operators

Register a multivariate user-defined operator using Nonlinear.register_operator:

julia> g(x...) = x[1]^2 + x[1] * x[2] + x[2]^2
+g (generic function with 1 method)
+
+julia> Nonlinear.register_operator(model, :my_g, 2, g)

Now, you can use :my_g in expressions:

julia> new_expr = Nonlinear.add_expression(model, :(my_g($x + 1, $x)))
+MathOptInterface.Nonlinear.ExpressionIndex(3)

By default, Nonlinear will compute the gradient of the registered operator using ForwardDiff.jl. (Hessian information is not supported.) Override this by passing a function to compute the gradient:

julia> function ∇g(ret, x...)
+           ret[1] = 2 * x[1] + x[2]
+           ret[2] = x[1] + 2 * x[2]
+           return
+       end
+∇g (generic function with 1 method)
+
+julia> Nonlinear.register_operator(model, :my_g2, 2, g, ∇g)

MathOptInterface

MathOptInterface communicates the nonlinear portion of an optimization problem to solvers using concrete subtypes of AbstractNLPEvaluator, which implement the Nonlinear programming API.

Create an AbstractNLPEvaluator from Nonlinear.Model using Nonlinear.Evaluator.

Nonlinear.Evaluator requires an Nonlinear.AbstractAutomaticDifferentiation backend and an ordered list of the variables that are included in the model.

There following backends are available to choose from within MOI, although other packages may add more options by sub-typing Nonlinear.AbstractAutomaticDifferentiation:

julia> evaluator = Nonlinear.Evaluator(model, Nonlinear.ExprGraphOnly(), [x])
+Nonlinear.Evaluator with available features:
+  * :ExprGraph

The functions of the Nonlinear programming API implemented by Nonlinear.Evaluator depends upon the chosen Nonlinear.AbstractAutomaticDifferentiation backend.

The :ExprGraph feature means we can call objective_expr and constraint_expr to retrieve the expression graph of the problem. However, we cannot call gradient terms such as eval_objective_gradient because Nonlinear.ExprGraphOnly does not have the capability to differentiate a nonlinear expression.

If, instead, we pass Nonlinear.SparseReverseMode, then we get access to :Grad, the gradient of the objective function, :Jac, the Jacobian matrix of the constraints, :JacVec, the ability to compute Jacobian-vector products, and :ExprGraph.

julia> evaluator = Nonlinear.Evaluator(
+           model,
+           Nonlinear.SparseReverseMode(),
+           [x],
+       )
+Nonlinear.Evaluator with available features:
+  * :Grad
+  * :Jac
+  * :JacVec
+  * :ExprGraph

However, before using the evaluator, we need to call initialize:

julia> MOI.initialize(evaluator, [:Grad, :Jac, :JacVec, :ExprGraph])

Now we can call methods like eval_objective:

julia> x = [1.0]
+1-element Vector{Float64}:
+ 1.0
+
+julia> MOI.eval_objective(evaluator, x)
+7.268073418273571

and eval_objective_gradient:

julia> grad = [0.0]
+1-element Vector{Float64}:
+ 0.0
+
+julia> MOI.eval_objective_gradient(evaluator, grad, x)
+
+julia> grad
+1-element Vector{Float64}:
+ 1.909297426825682

Instead of passing Nonlinear.Evaluator directly to solvers, solvers query the NLPBlock attribute, which returns an NLPBlockData. This object wraps an Nonlinear.Evaluator and includes other information such as constraint bounds and whether the evaluator has a nonlinear objective. Create and set NLPBlockData as follows:

julia> block = MOI.NLPBlockData(evaluator);
+
+julia> model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}());
+
+julia> MOI.set(model, MOI.NLPBlock(), block);
Warning

Only call NLPBlockData once you have finished modifying the problem in model.

Putting everything together, you can create a nonlinear optimization problem in MathOptInterface as follows:

import MathOptInterface as MOI
+
+function build_model(
+    model::MOI.ModelLike;
+    backend::MOI.Nonlinear.AbstractAutomaticDifferentiation,
+)
+    x = MOI.add_variable(model)
+    y = MOI.add_variable(model)
+    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
+    nl_model = MOI.Nonlinear.Model()
+    MOI.Nonlinear.set_objective(nl_model, :($x^2 + $y^2))
+    evaluator = MOI.Nonlinear.Evaluator(nl_model, backend, [x, y])
+    MOI.set(model, MOI.NLPBlock(), MOI.NLPBlockData(evaluator))
+    return
+end
+
+# Replace `model` and `backend` with your optimizer and backend of choice.
+model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
+build_model(model; backend = MOI.Nonlinear.SparseReverseMode())

Expression-graph representation

Nonlinear.Model stores nonlinear expressions in Nonlinear.Expressions. This section explains the design of the expression graph data structure in Nonlinear.Expression.

Given a nonlinear function like f(x) = sin(x)^2 + x, a conceptual aid for thinking about the graph representation of the expression is to convert it into Polish prefix notation:

f(x, y) = (+ (^ (sin x) 2) x)

This format identifies each operator (function), as well as a list of arguments. Operators can be univariate, like sin, or multivariate, like +.

A common way of representing Polish prefix notation in code is as follows:

julia> x = MOI.VariableIndex(1);
+
+julia> struct ExprNode
+           op::Symbol
+           children::Vector{Union{ExprNode,Float64,MOI.VariableIndex}}
+       end
+
+julia> expr = ExprNode(:+, [ExprNode(:^, [ExprNode(:sin, [x]), 2.0]), x]);

This data structure follows our Polish prefix notation very closely, and we can easily identify the arguments to an operator. However, it has a significant draw-back: each node in the graph requires a Vector, which is heap-allocated and tracked by Julia's garbage collector (GC). For large models, we can expect to have millions of nodes in the expression graph, so this overhead quickly becomes prohibitive for computation.

An alternative is to record the expression as a linear tape:

julia> expr = Any[:+, 2, :^, 2, :sin, 1, x, 2.0, x]
+9-element Vector{Any}:
+  :+
+ 2
+  :^
+ 2
+  :sin
+ 1
+  MOI.VariableIndex(1)
+ 2.0
+  MOI.VariableIndex(1)

The Int after each operator Symbol specifies the number of arguments.

This data-structure is a single vector, which resolves our problem with the GC, but each element is the abstract type, Any, and so any operations on it will lead to slower dynamic dispatch. It's also hard to identify the children of each operation without reading the entire tape.

To summarize, representing expression graphs in Julia has the following challenges:

  • Nodes in the expression graph should not contain a heap-allocated object
  • All data-structures should be concretely typed
  • It should be easy to identify the children of a node

Sketch of the design in Nonlinear

Nonlinear overcomes these problems by decomposing the data structure into a number of different concrete-typed vectors.

First, we create vectors of the supported uni- and multivariate operators.

julia> const UNIVARIATE_OPERATORS = [:sin];
+
+julia> const MULTIVARIATE_OPERATORS = [:+, :^];

In practice, there are many more supported operations than the ones listed here.

Second, we create an enum to represent the different types of nodes present in the expression graph:

julia> @enum(
+           NodeType,
+           NODE_CALL_MULTIVARIATE,
+           NODE_CALL_UNIVARIATE,
+           NODE_VARIABLE,
+           NODE_VALUE,
+       )

In practice, there are node types other than the ones listed here.

Third, we create two concretely typed structs as follows:

julia> struct Node
+           type::NodeType
+           parent::Int
+           index::Int
+       end
+
+julia> struct Expression
+           nodes::Vector{Node}
+           values::Vector{Float64}
+       end

For each node node in the .nodes field, if node.type is:

  • NODE_CALL_MULTIVARIATE, we look up MULTIVARIATE_OPERATORS[node.index] to retrieve the operator
  • NODE_CALL_UNIVARIATE, we look up UNIVARIATE_OPERATORS[node.index] to retrieve the operator
  • NODE_VARIABLE, we create MOI.VariableIndex(node.index)
  • NODE_VALUE, we look up values[node.index]

The .parent field of each node is the integer index of the parent node in .nodes. For the first node, the parent is -1 by convention.

Therefore, we can represent our function as:

julia> expr = Expression(
+           [
+               Node(NODE_CALL_MULTIVARIATE, -1, 1),
+               Node(NODE_CALL_MULTIVARIATE, 1, 2),
+               Node(NODE_CALL_UNIVARIATE, 2, 1),
+               Node(NODE_VARIABLE, 3, 1),
+               Node(NODE_VALUE, 2, 1),
+               Node(NODE_VARIABLE, 1, 1),
+           ],
+           [2.0],
+       );

This is less readable than the other options, but does this data structure meet our design goals?

Instead of a heap-allocated object for each node, we only have two Vectors for each expression, nodes and values, as well as two constant vectors for the OPERATORS. In addition, all fields are concretely typed, and there are no Union or Any types.

For our third goal, it is not easy to identify the children of a node, but it is easy to identify the parent of any node. Therefore, we can use Nonlinear.adjacency_matrix to compute a sparse matrix that maps parents to their children.

The tape is also ordered topologically, so that a reverse pass of the nodes evaluates all children nodes before their parent.

The design in practice

In practice, Node and Expression are exactly Nonlinear.Node and Nonlinear.Expression. However, Nonlinear.NodeType has more fields to account for comparison operators such as :>= and :<=, logic operators such as :&& and :||, nonlinear parameters, and nested subexpressions.

Moreover, instead of storing the operators as global constants, they are stored in Nonlinear.OperatorRegistry, and it also stores a vector of logic operators and a vector of comparison operators. In addition to Nonlinear.DEFAULT_UNIVARIATE_OPERATORS and Nonlinear.DEFAULT_MULTIVARIATE_OPERATORS, you can register user-defined functions using Nonlinear.register_operator.

Nonlinear.Model is a struct that stores the Nonlinear.OperatorRegistry, as well as a list of parameters and subexpressions in the model.

ReverseAD

Nonlinear.ReverseAD is a submodule for computing derivatives of a nonlinear optimization problem using sparse reverse-mode automatic differentiation (AD).

This section does not attempt to explain how sparse reverse-mode AD works, but instead explains why MOI contains its own implementation, and highlights notable differences from similar packages.

Warning

Don't use the API in ReverseAD to compute derivatives. Instead, create a Nonlinear.Evaluator object with Nonlinear.SparseReverseMode as the backend, and then query the MOI API methods.

Design goals

The JuliaDiff organization maintains a list of packages for doing AD in Julia. At last count, there were at least ten packages——not including ReverseAD——for reverse-mode AD in Julia. ReverseAD exists because it has a different set of design goals.

  • Goal: handle scale and sparsity. The types of nonlinear optimization problems that MOI represents can be large scale (10^5 or more functions across 10^5 or more variables) with very sparse derivatives. The ability to compute a sparse Hessian matrix is essential. To the best of our knowledge, ReverseAD is the only reverse-mode AD system in Julia that handles sparsity by default.
  • Goal: limit the scope to improve robustness. Most other AD packages accept arbitrary Julia functions as input and then trace an expression graph using operator overloading. This means they must deal (or detect and ignore) with control flow, I/O, and other vagaries of Julia. In contrast, ReverseAD only accepts functions in the form of Nonlinear.Expression, which greatly limits the range of syntax that it must deal with. By reducing the scope of what we accept as input to functions relevant for mathematical optimization, we can provide a simpler implementation with various performance optimizations.
  • Goal: provide outputs which match what solvers expect. Other AD packages focus on differentiating individual Julia functions. In contrast, ReverseAD has a very specific use-case: to generate outputs needed by the MOI nonlinear API. This means it needs to efficiently compute sparse Hessians, and it needs subexpression handling to avoid recomputing subexpressions that are shared between functions.

History

ReverseAD started life as ReverseDiffSparse.jl, development of which began in early 2014(!). This was well before the other AD packages started development. Because we had a well-tested, working AD in JuMP, there was less motivation to contribute to and explore other AD packages. The lack of historical interaction also meant that other packages were not optimized for the types of problems that JuMP is built for (that is, large-scale sparse problems). When we first created MathOptInterface, we kept the AD in JuMP to simplify the transition, and post-poned the development of a first-class nonlinear interface in MathOptInterface.

Prior to the introduction of Nonlinear, JuMP's nonlinear implementation was a confusing mix of functions and types spread across the code base and in the private _Derivatives submodule. This made it hard to swap the AD system for another. The main motivation for refactoring JuMP to create the Nonlinear submodule in MathOptInterface was to abstract the interface between JuMP and the AD system, allowing us to swap-in and test new AD systems in the future.

diff --git a/previews/PR3536/moi/submodules/Nonlinear/reference/index.html b/previews/PR3536/moi/submodules/Nonlinear/reference/index.html new file mode 100644 index 00000000000..e98bbb347de --- /dev/null +++ b/previews/PR3536/moi/submodules/Nonlinear/reference/index.html @@ -0,0 +1,151 @@ + +API Reference · JuMP

Nonlinear Modeling

More information can be found in the Nonlinear section of the manual.

MathOptInterface.NonlinearModule
Nonlinear
Warning

The Nonlinear submodule is experimental. Until this message is removed, breaking changes may be introduced in any minor or patch release of MathOptInterface.

source
MathOptInterface.Nonlinear.ModelType
Model()

The core datastructure for representing a nonlinear optimization problem.

It has the following fields:

  • objective::Union{Nothing,Expression} : holds the nonlinear objective function, if one exists, otherwise nothing.
  • expressions::Vector{Expression} : a vector of expressions in the model.
  • constraints::OrderedDict{ConstraintIndex,Constraint} : a map from ConstraintIndex to the corresponding Constraint. An OrderedDict is used instead of a Vector to support constraint deletion.
  • parameters::Vector{Float64} : holds the current values of the parameters.
  • operators::OperatorRegistry : stores the operators used in the model.
source

Expressions

Parameters

MathOptInterface.Nonlinear.add_parameterFunction
add_parameter(model::Model, value::Float64)::ParameterIndex

Add a new parameter to model with the default value value. Returns a ParameterIndex that can be interpolated into other input expressions and used to modify the value of the parameter.

Examples

model = Model()
+x = MOI.VariableIndex(1)
+p = add_parameter(model, 1.2)
+c = add_constraint(model, :($x^2 - $p), MOI.LessThan(0.0))
source

Objectives

MathOptInterface.Nonlinear.set_objectiveFunction
set_objective(model::Model, obj)::Nothing

Parse obj into a Expression and set as the objective function of model.

obj must be a type that is supported by parse_expression.

To remove the objective, pass nothing.

Examples

model = Model()
+x = MOI.VariableIndex(1)
+set_objective(model, :($x^2 + 1))
+set_objective(model, x)
+set_objective(model, nothing)
source

Constraints

MathOptInterface.Nonlinear.add_constraintFunction
add_constraint(
+    model::Model,
+    func,
+    set::Union{
+        MOI.GreaterThan{Float64},
+        MOI.LessThan{Float64},
+        MOI.Interval{Float64},
+        MOI.EqualTo{Float64},
+    },
+)

Parse func and set into a Constraint and add to model. Returns a ConstraintIndex that can be used to delete the constraint or query solution information.

Examples

model = Model()
+x = MOI.VariableIndex(1)
+c = add_constraint(model, :($x^2), MOI.LessThan(1.0))
source
MathOptInterface.Nonlinear.deleteFunction
delete(model::Model, c::ConstraintIndex)::Nothing

Delete the constraint index c from model.

Examples

model = Model()
+x = MOI.VariableIndex(1)
+c = add_constraint(model, :($x^2), MOI.LessThan(1.0))
+delete(model, c)
source

User-defined operators

MathOptInterface.Nonlinear.DEFAULT_UNIVARIATE_OPERATORSConstant
DEFAULT_UNIVARIATE_OPERATORS

The list of univariate operators that are supported by default.

Example

julia> import MathOptInterface as MOI
+
+julia> MOI.Nonlinear.DEFAULT_UNIVARIATE_OPERATORS
+72-element Vector{Symbol}:
+ :+
+ :-
+ :abs
+ :sqrt
+ :cbrt
+ :abs2
+ :inv
+ :log
+ :log10
+ :log2
+ ⋮
+ :airybi
+ :airyaiprime
+ :airybiprime
+ :besselj0
+ :besselj1
+ :bessely0
+ :bessely1
+ :erfcx
+ :dawson
source
MathOptInterface.Nonlinear.DEFAULT_MULTIVARIATE_OPERATORSConstant
DEFAULT_MULTIVARIATE_OPERATORS

The list of multivariate operators that are supported by default.

Example

julia> import MathOptInterface as MOI
+
+julia> MOI.Nonlinear.DEFAULT_MULTIVARIATE_OPERATORS
+9-element Vector{Symbol}:
+ :+
+ :-
+ :*
+ :^
+ :/
+ :ifelse
+ :atan
+ :min
+ :max
source
MathOptInterface.Nonlinear.register_operatorFunction
register_operator(
+    model::Model,
+    op::Symbol,
+    nargs::Int,
+    f::Function,
+    [∇f::Function],
+    [∇²f::Function],
+)

Register the user-defined operator op with nargs input arguments in model.

Univariate functions

  • f(x::T)::T must be a function that takes a single input argument x and returns the function evaluated at x. If ∇f and ∇²f are not provided, f must support any Real input type T.
  • ∇f(x::T)::T is a function that takes a single input argument x and returns the first derivative of f with respect to x. If ∇²f is not provided, ∇f must support any Real input type T.
  • ∇²f(x::T)::T is a function that takes a single input argument x and returns the second derivative of f with respect to x.

Multivariate functions

  • f(x::T...)::T must be a function that takes a nargs input arguments x and returns the function evaluated at x. If ∇f and ∇²f are not provided, f must support any Real input type T.
  • ∇f(g::AbstractVector{T}, x::T...)::T is a function that takes a cache vector g of length length(x), and fills each element g[i] with the partial derivative of f with respect to x[i].
  • ∇²f(H::AbstractMatrix, x::T...)::T is a function that takes a matrix H and fills the lower-triangular components H[i, j] with the Hessian of f with respect to x[i] and x[j] for i >= j.

Notes for multivariate Hessians

  • H has size(H) == (length(x), length(x)), but you must not access elements H[i, j] for i > j.
  • H is dense, but you do not need to fill structural zeros.
source
MathOptInterface.Nonlinear.eval_multivariate_gradientFunction
eval_multivariate_gradient(
+    registry::OperatorRegistry,
+    op::Symbol,
+    g::AbstractVector{T},
+    x::AbstractVector{T},
+) where {T}

Evaluate the gradient of operator g .= ∇op(x), where op is a multivariate function in registry.

source
MathOptInterface.Nonlinear.eval_multivariate_hessianFunction
eval_multivariate_hessian(
+    registry::OperatorRegistry,
+    op::Symbol,
+    H::AbstractMatrix,
+    x::AbstractVector{T},
+) where {T}

Evaluate the Hessian of operator ∇²op(x), where op is a multivariate function in registry.

The Hessian is stored in the lower-triangular part of the matrix H.

Note

Implementations of the Hessian operators will not fill structural zeros. Therefore, before calling this function you should pre-populate the matrix H with 0.

source

Automatic-differentiation backends

MathOptInterface.Nonlinear.EvaluatorType
Evaluator(
+    model::Model,
+    backend::AbstractAutomaticDifferentiation,
+    ordered_variables::Vector{MOI.VariableIndex},
+)

Create Evaluator, a subtype of MOI.AbstractNLPEvaluator, from Model.

source
MathOptInterface.Nonlinear.SparseReverseModeType
SparseReverseMode() <: AbstractAutomaticDifferentiation

An implementation of AbstractAutomaticDifferentiation that uses sparse reverse-mode automatic differentiation to compute derivatives. Supports all features in the MOI nonlinear interface.

source

Data-structure

MathOptInterface.Nonlinear.NodeType
struct Node
+    type::NodeType
+    index::Int
+    parent::Int
+end

A single node in a nonlinear expression tree. Used by Expression.

See the MathOptInterface documentation for information on how the nodes and values form an expression tree.

source
MathOptInterface.Nonlinear.NodeTypeType
NodeType

An enum describing the possible node types. Each Node has a .index field, which should be interpreted as follows:

  • NODE_CALL_MULTIVARIATE: the index into operators.multivariate_operators
  • NODE_CALL_UNIVARIATE: the index into operators.univariate_operators
  • NODE_LOGIC: the index into operators.logic_operators
  • NODE_COMPARISON: the index into operators.comparison_operators
  • NODE_MOI_VARIABLE: the value of MOI.VariableIndex(index) in the user's space of the model.
  • NODE_VARIABLE: the 1-based index of the internal vector
  • NODE_VALUE: the index into the .values field of Expression
  • NODE_PARAMETER: the index into data.parameters
  • NODE_SUBEXPRESSION: the index into data.expressions
source
MathOptInterface.Nonlinear.ExpressionType
struct Expression
+    nodes::Vector{Node}
+    values::Vector{Float64}
+end

The core type that represents a nonlinear expression. See the MathOptInterface documentation for information on how the nodes and values form an expression tree.

source
MathOptInterface.Nonlinear.ConstraintType
struct Constraint
+    expression::Expression
+    set::Union{
+        MOI.LessThan{Float64},
+        MOI.GreaterThan{Float64},
+        MOI.EqualTo{Float64},
+        MOI.Interval{Float64},
+    }
+end

A type to hold information relating to the nonlinear constraint f(x) in S, where f(x) is defined by .expression, and S is .set.

source
MathOptInterface.Nonlinear.adjacency_matrixFunction
adjacency_matrix(nodes::Vector{Node})

Compute the sparse adjacency matrix describing the parent-child relationships in nodes.

The element (i, j) is true if there is an edge from node[j] to node[i]. Since we get a column-oriented matrix, this gives us a fast way to look up the edges leaving any node (i.e., the children).

source
MathOptInterface.Nonlinear.parse_expressionFunction
parse_expression(data::Model, input)::Expression

Parse input into a Expression.

source
parse_expression(
+    data::Model,
+    expr::Expression,
+    input::Any,
+    parent_index::Int,
+)::Expression

Parse input into a Expression, and add it to expr as a child of expr.nodes[parent_index]. Existing subexpressions and parameters are stored in data.

You can extend parsing support to new types of objects by overloading this method with a different type on input::Any.

source
MathOptInterface.Nonlinear.convert_to_exprFunction
convert_to_expr(data::Model, expr::Expression)

Convert the Expression expr into a Julia Expr.

source
convert_to_expr(
+    evaluator::Evaluator,
+    expr::Expression;
+    moi_output_format::Bool,
+)

Convert the Expression expr into a Julia Expr.

If moi_output_format = true:

  • subexpressions will be converted to Julia Expr and substituted into the output expression.
  • the current value of each parameter will be interpolated into the expression
  • variables will be represented in the form x[MOI.VariableIndex(i)]

If moi_output_format = false:

Warning

To use moi_output_format = true, you must have first called MOI.initialize with :ExprGraph as a requested feature.

source
MathOptInterface.Nonlinear.ordinal_indexFunction
ordinal_index(evaluator::Evaluator, c::ConstraintIndex)::Int

Return the 1-indexed value of the constraint index c in evaluator.

Examples

model = Model()
+x = MOI.VariableIndex(1)
+c1 = add_constraint(model, :($x^2), MOI.LessThan(1.0))
+c2 = add_constraint(model, :($x^2), MOI.LessThan(1.0))
+evaluator = Evaluator(model)
+MOI.initialize(evaluator, Symbol[])
+ordinal_index(evaluator, c2)  # Returns 2
+delete(model, c1)
+evaluator = Evaluator(model)
+MOI.initialize(evaluator, Symbol[])
+ordinal_index(model, c2)  # Returns 1
source
diff --git a/previews/PR3536/moi/submodules/Test/overview/index.html b/previews/PR3536/moi/submodules/Test/overview/index.html new file mode 100644 index 00000000000..dd06a67743d --- /dev/null +++ b/previews/PR3536/moi/submodules/Test/overview/index.html @@ -0,0 +1,170 @@ + +Overview · JuMP

The Test submodule

The Test submodule provides tools to help solvers implement unit tests in order to ensure they implement the MathOptInterface API correctly, and to check for solver-correctness.

We use a centralized repository of tests, so that if we find a bug in one solver, instead of adding a test to that particular repository, we add it here so that all solvers can benefit.

How to test a solver

The skeleton below can be used for the wrapper test file of a solver named FooBar.

# ============================ /test/MOI_wrapper.jl ============================
+module TestFooBar
+
+import FooBar
+using Test
+
+import MathOptInterface as MOI
+
+const OPTIMIZER = MOI.instantiate(
+    MOI.OptimizerWithAttributes(FooBar.Optimizer, MOI.Silent() => true),
+)
+
+const BRIDGED = MOI.instantiate(
+    MOI.OptimizerWithAttributes(FooBar.Optimizer, MOI.Silent() => true),
+    with_bridge_type = Float64,
+)
+
+# See the docstring of MOI.Test.Config for other arguments.
+const CONFIG = MOI.Test.Config(
+    # Modify tolerances as necessary.
+    atol = 1e-6,
+    rtol = 1e-6,
+    # Use MOI.LOCALLY_SOLVED for local solvers.
+    optimal_status = MOI.OPTIMAL,
+    # Pass attributes or MOI functions to `exclude` to skip tests that
+    # rely on this functionality.
+    exclude = Any[MOI.VariableName, MOI.delete],
+)
+
+"""
+    runtests()
+
+This function runs all functions in the this Module starting with `test_`.
+"""
+function runtests()
+    for name in names(@__MODULE__; all = true)
+        if startswith("$(name)", "test_")
+            @testset "$(name)" begin
+                getfield(@__MODULE__, name)()
+            end
+        end
+    end
+end
+
+"""
+    test_runtests()
+
+This function runs all the tests in MathOptInterface.Test.
+
+Pass arguments to `exclude` to skip tests for functionality that is not
+implemented or that your solver doesn't support.
+"""
+function test_runtests()
+    MOI.Test.runtests(
+        BRIDGED,
+        CONFIG,
+        exclude = [
+            "test_attribute_NumberOfThreads",
+            "test_quadratic_",
+        ],
+        # This argument is useful to prevent tests from failing on future
+        # releases of MOI that add new tests. Don't let this number get too far
+        # behind the current MOI release though. You should periodically check
+        # for new tests to fix bugs and implement new features.
+        exclude_tests_after = v"0.10.5",
+    )
+    return
+end
+
+"""
+    test_SolverName()
+
+You can also write new tests for solver-specific functionality. Write each new
+test as a function with a name beginning with `test_`.
+"""
+function test_SolverName()
+    @test MOI.get(FooBar.Optimizer(), MOI.SolverName()) == "FooBar"
+    return
+end
+
+end # module TestFooBar
+
+# This line at tne end of the file runs all the tests!
+TestFooBar.runtests()

Then modify your runtests.jl file to include the MOI_wrapper.jl file:

# ============================ /test/runtests.jl ============================
+
+using Test
+
+@testset "MOI" begin
+    include("test/MOI_wrapper.jl")
+end
Info

The optimizer BRIDGED constructed with instantiate automatically bridges constraints that are not supported by OPTIMIZER using the bridges listed in Bridges. It is recommended for an implementation of MOI to only support constraints that are natively supported by the solver and let bridges transform the constraint to the appropriate form. For this reason it is expected that tests may not pass if OPTIMIZER is used instead of BRIDGED.

How to debug a failing test

When writing a solver, it's likely that you will initially fail many tests. Some failures will be bugs, but other failures you may choose to exclude.

There are two ways to exclude tests:

  • Exclude tests whose names contain a string using:

    MOI.Test.runtests(
    +    model,
    +    config;
    +    exclude = String["test_to_exclude", "test_conic_"],
    +)

    This will exclude tests whose name contains either of the two strings provided.

  • Exclude tests which rely on specific functionality using:

    MOI.Test.Config(exclude = Any[MOI.VariableName, MOI.optimize!])

    This will exclude tests which use the MOI.VariableName attribute, or which call MOI.optimize!.

Each test that fails can be independently called as:

model = FooBar.Optimizer()
+config = MOI.Test.Config()
+MOI.empty!(model)
+MOI.Test.test_category_name_that_failed(model, config)

You can look-up the source code of the test that failed by searching for it in the src/Test/test_category.jl file.

Tip

Each test function also has a docstring that explains what the test is for. Use ? MOI.Test.test_category_name_that_failed from the REPL to read it.

Periodically, you should re-run excluded tests to see if they now pass. The easiest way to do this is to swap the exclude keyword argument of runtests to include. For example:

MOI.Test.runtests(
+    model,
+    config;
+    exclude = String["test_to_exclude", "test_conic_"],
+)

becomes

MOI.Test.runtests(
+    model,
+    config;
+    include = String["test_to_exclude", "test_conic_"],
+)

How to add a test

To detect bugs in solvers, we add new tests to MOI.Test.

As an example, ECOS errored calling optimize! twice in a row. (See ECOS.jl PR #72.) We could add a test to ECOS.jl, but that would only stop us from re-introducing the bug to ECOS.jl in the future, but it would not catch other solvers in the ecosystem with the same bug. Instead, if we add a test to MOI.Test, then all solvers will also check that they handle a double optimize call.

For this test, we care about correctness, rather than performance. therefore, we don't expect solvers to efficiently decide that they have already solved the problem, only that calling optimize! twice doesn't throw an error or give the wrong answer.

Step 1

Install the MathOptInterface julia package in dev mode:

julia> ]
+(@v1.6) pkg> dev MathOptInterface

Step 2

From here on, proceed with making the following changes in the ~/.julia/dev/MathOptInterface folder (or equivalent dev path on your machine).

Step 3

Since the double-optimize error involves solving an optimization problem, add a new test to src/Test/test_solve.jl:

"""
+    test_unit_optimize!_twice(model::MOI.ModelLike, config::Config)
+
+Test that calling `MOI.optimize!` twice does not error.
+
+This problem was first detected in ECOS.jl PR#72:
+https://github.com/jump-dev/ECOS.jl/pull/72
+"""
+function test_unit_optimize!_twice(
+    model::MOI.ModelLike,
+    config::Config{T},
+) where {T}
+    # Use the `@requires` macro to check conditions that the test function
+    # requires to run. Models failing this `@requires` check will silently skip
+    # the test.
+    @requires MOI.supports_constraint(
+        model,
+        MOI.VariableIndex,
+        MOI.GreaterThan{Float64},
+    )
+    @requires _supports(config, MOI.optimize!)
+    # If needed, you can test that the model is empty at the start of the test.
+    # You can assume that this will be the case for tests run via `runtests`.
+    # User's calling tests individually need to call `MOI.empty!` themselves.
+    @test MOI.is_empty(model)
+    # Create a simple model. Try to make this as simple as possible so that the
+    # majority of solvers can run the test.
+    x = MOI.add_variable(model)
+    MOI.add_constraint(model, x, MOI.GreaterThan(one(T)))
+    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
+    MOI.set(
+        model,
+        MOI.ObjectiveFunction{MOI.VariableIndex}(),
+        x,
+    )
+    # The main component of the test: does calling `optimize!` twice error?
+    MOI.optimize!(model)
+    MOI.optimize!(model)
+    # Check we have a solution.
+    @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
+    # There is a three-argument version of `Base.isapprox` for checking
+    # approximate equality based on the tolerances defined in `config`:
+    @test isapprox(MOI.get(model, MOI.VariablePrimal(), x), one(T), config)
+    # For code-style, these tests should always `return` `nothing`.
+    return
+end
Info

Make sure the function is agnostic to the number type T; don't assume it is a Float64 capable solver.

We also need to write a test for the test. Place this function immediately below the test you just wrote in the same file:

function setup_test(
+    ::typeof(test_unit_optimize!_twice),
+    model::MOI.Utilities.MockOptimizer,
+    ::Config,
+)
+    MOI.Utilities.set_mock_optimize!(
+        model,
+        (mock::MOI.Utilities.MockOptimizer) -> MOIU.mock_optimize!(
+            mock,
+            MOI.OPTIMAL,
+            (MOI.FEASIBLE_POINT, [1.0]),
+        ),
+    )
+    return
+end

Finally, you also need to implement Test.version_added. If we added this test when the latest released version of MOI was v0.10.5, define:

version_added(::typeof(test_unit_optimize!_twice)) = v"0.10.6"

Step 6

Commit the changes to git from ~/.julia/dev/MathOptInterface and submit the PR for review.

Tip

If you need help writing a test, open an issue on GitHub, or ask the Developer Chatroom.

diff --git a/previews/PR3536/moi/submodules/Test/reference/index.html b/previews/PR3536/moi/submodules/Test/reference/index.html new file mode 100644 index 00000000000..ec4623c0ca8 --- /dev/null +++ b/previews/PR3536/moi/submodules/Test/reference/index.html @@ -0,0 +1,54 @@ + +API Reference · JuMP

The Test submodule

Functions to help test implementations of MOI. See The Test submodule for more details.

MathOptInterface.Test.ConfigType
Config(
+    ::Type{T} = Float64;
+    atol::Real = Base.rtoldefault(T),
+    rtol::Real = Base.rtoldefault(T),
+    optimal_status::MOI.TerminationStatusCode = MOI.OPTIMAL,
+    infeasible_status::MOI.TerminationStatusCode = MOI.INFEASIBLE,
+    exclude::Vector{Any} = Any[],
+) where {T}

Return an object that is used to configure various tests.

Configuration arguments

  • atol::Real = Base.rtoldefault(T): Control the absolute tolerance used when comparing solutions.
  • rtol::Real = Base.rtoldefault(T): Control the relative tolerance used when comparing solutions.
  • optimal_status = MOI.OPTIMAL: Set to MOI.LOCALLY_SOLVED if the solver cannot prove global optimality.
  • infeasible_status = MOI.INFEASIBLE: Set to MOI.LOCALLY_INFEASIBLE if the solver cannot prove global infeasibility.
  • exclude = Vector{Any}: Pass attributes or functions to exclude to skip parts of tests that require certain functionality. Common arguments include:
    • MOI.delete to skip deletion-related tests
    • MOI.optimize! to skip optimize-related tests
    • MOI.ConstraintDual to skip dual-related tests
    • MOI.VariableName to skip setting variable names
    • MOI.ConstraintName to skip setting constraint names

Examples

For a nonlinear solver that finds local optima and does not support finding dual variables or constraint names:

Config(
+    Float64;
+    optimal_status = MOI.LOCALLY_SOLVED,
+    exclude = Any[
+        MOI.ConstraintDual,
+        MOI.VariableName,
+        MOI.ConstraintName,
+        MOI.delete,
+    ],
+)
source
MathOptInterface.Test.runtestsFunction
runtests(
+    model::MOI.ModelLike,
+    config::Config;
+    include::Vector{Union{String,Regex}} = String[],
+    exclude::Vector{Union{String,Regex}} = String[],
+    warn_unsupported::Bool = false,
+    exclude_tests_after::VersionNumber = v"999.0.0",
+)

Run all tests in MathOptInterface.Test on model.

Configuration arguments

  • config is a Test.Config object that can be used to modify the behavior of tests.
  • If include is not empty, only run tests if an element from include occursin the name of the test.
  • If exclude is not empty, skip tests if an element from exclude occursin the name of the test.
  • exclude takes priority over include.
  • If warn_unsupported is false, runtests will silently skip tests that fail with a MOI.NotAllowedError, MOI.UnsupportedError, or RequirementUnmet error. (The latter is thrown when an @requires statement returns false.) When warn_unsupported is true, a warning will be printed. For most cases the default behavior, false, is what you want, since these tests likely test functionality that is not supported by model. However, it can be useful to run warn_unsupported = true to check you are not skipping tests due to a missing supports_constraint method or equivalent.
  • exclude_tests_after is a version number that excludes any tests to MOI added after that version number. This is useful for solvers who can declare a fixed set of tests, and not cause their tests to break if a new patch of MOI is released with a new test.

See also: setup_test.

Example

config = MathOptInterface.Test.Config()
+MathOptInterface.Test.runtests(
+    model,
+    config;
+    include = ["test_linear_", r"^test_model_Name$"],
+    exclude = ["VariablePrimalStart"],
+    warn_unsupported = true,
+    exclude_tests_after = v"0.10.5",
+)
source
MathOptInterface.Test.setup_testFunction
setup_test(::typeof(f), model::MOI.ModelLike, config::Config)

Overload this method to modify model before running the test function f on model with config. You can also modify the fields in config (e.g., to loosen the default tolerances).

This function should either return nothing, or return a function which, when called with zero arguments, undoes the setup to return the model to its previous state. You do not need to undo any modifications to config.

This function is most useful when writing new tests of the tests for MOI, but it can also be used to set test-specific tolerances, etc.

See also: runtests

Example

function MOI.Test.setup_test(
+    ::typeof(MOI.Test.test_linear_VariablePrimalStart_partial),
+    mock::MOIU.MockOptimizer,
+    ::MOI.Test.Config,
+)
+    MOIU.set_mock_optimize!(
+        mock,
+        (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0]),
+    )
+    mock.eval_variable_constraint_dual = false
+
+    function reset_function()
+        mock.eval_variable_constraint_dual = true
+        return
+    end
+    return reset_function
+end
source
MathOptInterface.Test.version_addedFunction
version_added(::typeof(function_name))

Returns the version of MOI in which the test function_name was added.

This method should be implemented for all new tests.

See the exclude_tests_after keyword of runtests for more details.

source
MathOptInterface.Test.@requiresMacro
@requires(x)

Check that the condition x is true. Otherwise, throw an RequirementUnmet error to indicate that the model does not support something required by the test function.

Examples

@requires MOI.supports(model, MOI.Silent())
+@test MOI.get(model, MOI.Silent())
source
diff --git a/previews/PR3536/moi/submodules/Utilities/overview/index.html b/previews/PR3536/moi/submodules/Utilities/overview/index.html new file mode 100644 index 00000000000..6d3b977b10a --- /dev/null +++ b/previews/PR3536/moi/submodules/Utilities/overview/index.html @@ -0,0 +1,254 @@ + +Overview · JuMP

The Utilities submodule

The Utilities submodule provides a variety of functions and datastructures for managing MOI.ModelLike objects.

Utilities.Model

Utilities.Model provides an implementation of a ModelLike that efficiently supports all functions and sets defined within MOI. However, given the extensibility of MOI, this might not cover all use cases.

Create a model as follows:

julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}

Utilities.UniversalFallback

Utilities.UniversalFallback is a layer that sits on top of any ModelLike and provides non-specialized (slower) fallbacks for constraints and attributes that the underlying ModelLike does not support.

For example, Utilities.Model doesn't support some variable attributes like VariablePrimalStart, so JuMP uses a combination of Universal fallback and Utilities.Model as a generic problem cache:

julia> model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
+MOIU.UniversalFallback{MOIU.Model{Float64}}
+fallback for MOIU.Model{Float64}
Warning

Adding a UniversalFallback means that your model will now support all constraints, even if the inner-model does not. This can lead to unexpected behavior.

Utilities.@model

For advanced use cases that need efficient support for functions and sets defined outside of MOI (but still known at compile time), we provide the Utilities.@model macro.

The @model macro takes a name (for a new type, which must not exist yet), eight tuples specifying the types of constraints that are supported, and then a Bool indicating the type is a subtype of MOI.AbstractOptimizer (if true) or MOI.ModelLike (if false).

The eight tuples are in the following order:

  1. Un-typed scalar sets, for example, Integer
  2. Typed scalar sets, for example, LessThan
  3. Un-typed vector sets, for example, Nonnegatives
  4. Typed vector sets, for example, PowerCone
  5. Un-typed scalar functions, for example, VariableIndex
  6. Typed scalar functions, for example, ScalarAffineFunction
  7. Un-typed vector functions, for example, VectorOfVariables
  8. Typed vector functions, for example, VectorAffineFunction

The tuples can contain more than one element. Typed-sets must be specified without their type parameter, for example, MOI.LessThan, not MOI.LessThan{Float64}.

Here is an example:

julia> MOI.Utilities.@model(
+           MyNewModel,
+           (MOI.Integer,),                  # Un-typed scalar sets
+           (MOI.GreaterThan,),              # Typed scalar sets
+           (MOI.Nonnegatives,),             # Un-typed vector sets
+           (MOI.PowerCone,),                # Typed vector sets
+           (MOI.VariableIndex,),            # Un-typed scalar functions
+           (MOI.ScalarAffineFunction,),     # Typed scalar functions
+           (MOI.VectorOfVariables,),        # Un-typed vector functions
+           (MOI.VectorAffineFunction,),     # Typed vector functions
+           true,                            # <:MOI.AbstractOptimizer?
+       )
+MathOptInterface.Utilities.GenericOptimizer{T, MathOptInterface.Utilities.ObjectiveContainer{T}, MathOptInterface.Utilities.VariablesContainer{T}, MyNewModelFunctionConstraints{T}} where T
+
+julia> model = MyNewModel{Float64}()
+MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MyNewModelFunctionConstraints{Float64}}
Warning

MyNewModel supports every VariableIndex-in-Set constraint, as well as VariableIndex, ScalarAffineFunction, and ScalarQuadraticFunction objective functions. Implement MOI.supports as needed to forbid constraint and objective function combinations.

As another example, PATHSolver, which only supports VectorAffineFunction-in-Complements defines its optimizer as:

julia> MOI.Utilities.@model(
+           PathOptimizer,
+           (),  # Scalar sets
+           (),  # Typed scalar sets
+           (MOI.Complements,),  # Vector sets
+           (),  # Typed vector sets
+           (),  # Scalar functions
+           (),  # Typed scalar functions
+           (),  # Vector functions
+           (MOI.VectorAffineFunction,),  # Typed vector functions
+           true,  # is_optimizer
+       )
+MathOptInterface.Utilities.GenericOptimizer{T, MathOptInterface.Utilities.ObjectiveContainer{T}, MathOptInterface.Utilities.VariablesContainer{T}, MathOptInterface.Utilities.VectorOfConstraints{MathOptInterface.VectorAffineFunction{T}, MathOptInterface.Complements}} where T

However, PathOptimizer does not support some VariableIndex-in-Set constraints, so we must explicitly define:

julia> function MOI.supports_constraint(
+           ::PathOptimizer,
+           ::Type{MOI.VariableIndex},
+           ::Type{Union{<:MOI.Semiinteger,MOI.Semicontinuous,MOI.ZeroOne,MOI.Integer}}
+       )
+           return false
+       end

Finally, PATH doesn't support an objective function, so we need to add:

julia> MOI.supports(::PathOptimizer, ::MOI.ObjectiveFunction) = false
Warning

This macro creates a new type, so it must be called from the top-level of a module, for example, it cannot be called from inside a function.

Utilities.CachingOptimizer

A [Utilities.CachingOptimizer] is an MOI layer that abstracts the difference between solvers that support incremental modification (for example, they support adding variables one-by-one), and solvers that require the entire problem in a single API call (for example, they only accept the A, b and c matrices of a linear program).

It has two parts:

  1. A cache, where the model can be built and modified incrementally
  2. An optimizer, which is used to solve the problem
julia> model = MOI.Utilities.CachingOptimizer(
+           MOI.Utilities.Model{Float64}(),
+           PathOptimizer{Float64}(),
+       )
+MOIU.CachingOptimizer{MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}, MOIU.Model{Float64}}
+in state EMPTY_OPTIMIZER
+in mode AUTOMATIC
+with model cache MOIU.Model{Float64}
+with optimizer MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}

A Utilities.CachingOptimizer may be in one of three possible states:

  • NO_OPTIMIZER: The CachingOptimizer does not have any optimizer.
  • EMPTY_OPTIMIZER: The CachingOptimizer has an empty optimizer, and it is not synchronized with the cached model. Modifications are forwarded to the cache, but not to the optimizer.
  • ATTACHED_OPTIMIZER: The CachingOptimizer has an optimizer, and it is synchronized with the cached model. Modifications are forwarded to the optimizer. If the optimizer does not support modifications, and error will be thrown.

Use Utilities.attach_optimizer to go from EMPTY_OPTIMIZER to ATTACHED_OPTIMIZER:

julia> MOI.Utilities.attach_optimizer(model)
+
+julia> model
+MOIU.CachingOptimizer{MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}, MOIU.Model{Float64}}
+in state ATTACHED_OPTIMIZER
+in mode AUTOMATIC
+with model cache MOIU.Model{Float64}
+with optimizer MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}
Info

You must be in ATTACHED_OPTIMIZER to use optimize!.

Use Utilities.reset_optimizer to go from ATTACHED_OPTIMIZER to EMPTY_OPTIMIZER:

julia> MOI.Utilities.reset_optimizer(model)
+
+julia> model
+MOIU.CachingOptimizer{MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}, MOIU.Model{Float64}}
+in state EMPTY_OPTIMIZER
+in mode AUTOMATIC
+with model cache MOIU.Model{Float64}
+with optimizer MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}
Info

Calling MOI.empty!(model) also resets the state to EMPTY_OPTIMIZER. So after emptying a model, the modification will only be applied to the cache.

Use Utilities.drop_optimizer to go from any state to NO_OPTIMIZER:

julia> MOI.Utilities.drop_optimizer(model)
+
+julia> model
+MOIU.CachingOptimizer{MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}, MOIU.Model{Float64}}
+in state NO_OPTIMIZER
+in mode AUTOMATIC
+with model cache MOIU.Model{Float64}
+with optimizer nothing

Pass an empty optimizer to Utilities.reset_optimizer to go from NO_OPTIMIZER to EMPTY_OPTIMIZER:

julia> MOI.Utilities.reset_optimizer(model, PathOptimizer{Float64}())
+
+julia> model
+MOIU.CachingOptimizer{MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}, MOIU.Model{Float64}}
+in state EMPTY_OPTIMIZER
+in mode AUTOMATIC
+with model cache MOIU.Model{Float64}
+with optimizer MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}

Deciding when to attach and reset the optimizer is tedious, and you will often write code like this:

try
+    # modification
+catch
+    MOI.Utilities.reset_optimizer(model)
+    # Re-try modification
+end

To make this easier, Utilities.CachingOptimizer has two modes of operation:

  • AUTOMATIC: The CachingOptimizer changes its state when necessary. Attempting to add a constraint or perform a modification not supported by the optimizer results in a drop to EMPTY_OPTIMIZER mode.
  • MANUAL: The user must change the state of the CachingOptimizer. Attempting to perform an operation in the incorrect state results in an error.

By default, AUTOMATIC mode is chosen. However, you can create a CachingOptimizer in MANUAL mode as follows:

julia> model = MOI.Utilities.CachingOptimizer(
+           MOI.Utilities.Model{Float64}(),
+           MOI.Utilities.MANUAL,
+       )
+MOIU.CachingOptimizer{MOI.AbstractOptimizer, MOIU.Model{Float64}}
+in state NO_OPTIMIZER
+in mode MANUAL
+with model cache MOIU.Model{Float64}
+with optimizer nothing
+
+julia> MOI.Utilities.reset_optimizer(model, PathOptimizer{Float64}())
+
+julia> model
+MOIU.CachingOptimizer{MOI.AbstractOptimizer, MOIU.Model{Float64}}
+in state EMPTY_OPTIMIZER
+in mode MANUAL
+with model cache MOIU.Model{Float64}
+with optimizer MOIU.GenericOptimizer{Float64, MOIU.ObjectiveContainer{Float64}, MOIU.VariablesContainer{Float64}, MOIU.VectorOfConstraints{MOI.VectorAffineFunction{Float64}, MOI.Complements}}

Printing

Use print to print the formulation of the model.

julia> model = MOI.Utilities.Model{Float64}();
+
+julia> x = MOI.add_variable(model)
+MOI.VariableIndex(1)
+
+julia> MOI.set(model, MOI.VariableName(), x, "x_var")
+
+julia> MOI.add_constraint(model, x, MOI.ZeroOne())
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.ZeroOne}(1)
+
+julia> MOI.set(model, MOI.ObjectiveFunction{typeof(x)}(), x)
+
+julia> MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
+
+julia> print(model)
+Maximize VariableIndex:
+ x_var
+
+Subject to:
+
+VariableIndex-in-ZeroOne
+ x_var ∈ {0, 1}

Use Utilities.latex_formulation to display the model in LaTeX form:

julia> MOI.Utilities.latex_formulation(model)
+$$ \begin{aligned}
+\max\quad & x\_var \\
+\text{Subject to}\\
+ & \text{VariableIndex-in-ZeroOne} \\
+ & x\_var \in \{0, 1\} \\
+\end{aligned} $$
Tip

In IJulia, calling print or ending a cell with Utilities.latex_formulation will render the model in LaTeX.

Utilities.PenaltyRelaxation

Pass Utilities.PenaltyRelaxation to modify to relax the problem by adding penalized slack variables to the constraints. This is helpful when debugging sources of infeasible models.

julia> model = MOI.Utilities.Model{Float64}();
+
+julia> x = MOI.add_variable(model);
+
+julia> MOI.set(model, MOI.VariableName(), x, "x")
+
+julia> c = MOI.add_constraint(model, 1.0 * x, MOI.LessThan(2.0));
+
+julia> map = MOI.modify(model, MOI.Utilities.PenaltyRelaxation(Dict(c => 2.0)));
+
+julia> print(model)
+Minimize ScalarAffineFunction{Float64}:
+ 0.0 + 2.0 v[2]
+
+Subject to:
+
+ScalarAffineFunction{Float64}-in-LessThan{Float64}
+ 0.0 + 1.0 x - 1.0 v[2] <= 2.0
+
+VariableIndex-in-GreaterThan{Float64}
+ v[2] >= 0.0
+
+julia> map[c]
+0.0 + 1.0 MOI.VariableIndex(2)

You can also modify a single constraint using Utilities.ScalarPenaltyRelaxation:

julia> model = MOI.Utilities.Model{Float64}();
+
+julia> x = MOI.add_variable(model);
+
+julia> MOI.set(model, MOI.VariableName(), x, "x")
+
+julia> c = MOI.add_constraint(model, 1.0 * x, MOI.LessThan(2.0));
+
+julia> f = MOI.modify(model, c, MOI.Utilities.ScalarPenaltyRelaxation(2.0));
+
+julia> print(model)
+Minimize ScalarAffineFunction{Float64}:
+ 0.0 + 2.0 v[2]
+
+Subject to:
+
+ScalarAffineFunction{Float64}-in-LessThan{Float64}
+ 0.0 + 1.0 x - 1.0 v[2] <= 2.0
+
+VariableIndex-in-GreaterThan{Float64}
+ v[2] >= 0.0
+
+julia> f
+0.0 + 1.0 MOI.VariableIndex(2)

Utilities.MatrixOfConstraints

The constraints of Utilities.Model are stored as a vector of tuples of function and set in a Utilities.VectorOfConstraints. Other representations can be used by parameterizing the type Utilities.GenericModel (resp. Utilities.GenericOptimizer). For instance, if all non-VariableIndex constraints are affine, the coefficients of all the constraints can be stored in a single sparse matrix using Utilities.MatrixOfConstraints. The constraints storage can even be customized up to a point where it exactly matches the storage of the solver of interest, in which case copy_to can be implemented for the solver by calling copy_to to this custom model.

For instance, Clp defines the following model:

MOI.Utilities.@product_of_scalar_sets(LP, MOI.EqualTo{T}, MOI.LessThan{T}, MOI.GreaterThan{T})
+const Model = MOI.Utilities.GenericModel{
+    Float64,
+    MOI.Utilities.MatrixOfConstraints{
+        Float64,
+        MOI.Utilities.MutableSparseMatrixCSC{Float64,Cint,MOI.Utilities.ZeroBasedIndexing},
+        MOI.Utilities.Hyperrectangle{Float64},
+        LP{Float64},
+    },
+}

The copy_to operation can now be implemented as follows:

function _copy_to(dest::Optimizer, src::Model)
+    @assert MOI.is_empty(dest)
+    A = src.constraints.coefficients
+    row_bounds = src.constraints.constants
+    Clp_loadProblem(
+        dest,
+        A.n,
+        A.m,
+        A.colptr,
+        A.rowval,
+        A.nzval,
+        src.lower_bound,
+        src.upper_bound,
+        # (...) objective vector (omitted),
+        row_bounds.lower,
+        row_bounds.upper,
+    )
+    # Set objective sense and constant (omitted)
+    return
+end
+
+function MOI.copy_to(dest::Optimizer, src::Model)
+    _copy_to(dest, src)
+    return MOI.Utilities.identity_index_map(src)
+end
+
+function MOI.copy_to(
+    dest::Optimizer,
+    src::MOI.Utilities.UniversalFallback{Model},
+)
+    # Copy attributes from `src` to `dest` and error in case any unsupported
+    # constraints or attributes are set in `UniversalFallback`.
+    return MOI.copy_to(dest, src.model)
+end
+
+function MOI.copy_to(
+    dest::Optimizer,
+    src::MOI.ModelLike,
+)
+    model = Model()
+    index_map = MOI.copy_to(model, src)
+    _copy_to(dest, model)
+    return index_map
+end

ModelFilter

Utilities provides Utilities.ModelFilter as a useful tool to copy a subset of a model. For example, given an infeasible model, we can copy the irreducible infeasible subsystem (for models implementing ConstraintConflictStatus) as follows:

my_filter(::Any) = true
+function my_filter(ci::MOI.ConstraintIndex)
+    status = MOI.get(dest, MOI.ConstraintConflictStatus(), ci)
+    return status != MOI.NOT_IN_CONFLICT
+end
+filtered_src = MOI.Utilities.ModelFilter(my_filter, src)
+index_map = MOI.copy_to(dest, filtered_src)

Fallbacks

The value of some attributes can be inferred from the value of other attributes.

For example, the value of ObjectiveValue can be computed using ObjectiveFunction and VariablePrimal.

When a solver gives direct access to an attribute, it is better to return this value. However, if this is not the case, Utilities.get_fallback can be used instead. For example:

function MOI.get(model::Optimizer, attr::MOI.ObjectiveFunction)
+    return MOI.Utilities.get_fallback(model, attr)
+end

DoubleDicts

When writing MOI interfaces, we often need to handle situations in which we map ConstraintIndexs to different values. For example, to a string for ConstraintName.

One option is to use a dictionary like Dict{MOI.ConstraintIndex,String}. However, this incurs a performance cost because the key is not a concrete type.

The DoubleDicts submodule helps this situation by providing two types main types Utilities.DoubleDicts.DoubleDict and Utilities.DoubleDicts.IndexDoubleDict. These types act like normal dictionaries, but internally they use more efficient dictionaries specialized to the type of the function-set pair.

The most common usage of a DoubleDict is in the index_map returned by copy_to. Performance can be improved, by using a function barrier. That is, instead of code like:

index_map = MOI.copy_to(dest, src)
+for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent())
+    for ci in MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
+        dest_ci = index_map[ci]
+        # ...
+    end
+end

use instead:

function function_barrier(
+    dest,
+    src,
+    index_map::MOI.Utilities.DoubleDicts.IndexDoubleDictInner{F,S},
+) where {F,S}
+    for ci in MOI.get(src, MOI.ListOfConstraintIndices{F,S}())
+        dest_ci = index_map[ci]
+        # ...
+    end
+    return
+end
+
+index_map = MOI.copy_to(dest, src)
+for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent())
+    function_barrier(dest, src, index_map[F, S])
+end
diff --git a/previews/PR3536/moi/submodules/Utilities/reference/index.html b/previews/PR3536/moi/submodules/Utilities/reference/index.html new file mode 100644 index 00000000000..c8a73af2117 --- /dev/null +++ b/previews/PR3536/moi/submodules/Utilities/reference/index.html @@ -0,0 +1,268 @@ + +API Reference · JuMP

Utilities.Model

MathOptInterface.Utilities.ModelType
MOI.Utilities.Model{T}() where {T}

An implementation of ModelLike that supports all functions and sets defined in MOI. It is parameterized by the coefficient type.

Examples

julia> import MathOptInterface as MOI
+
+julia> model = MOI.Utilities.Model{Float64}()
+MOIU.Model{Float64}
source

Utilities.UniversalFallback

MathOptInterface.Utilities.UniversalFallbackType
UniversalFallback

The UniversalFallback can be applied on a MOI.ModelLike model to create the model UniversalFallback(model) supporting any constraint and attribute. This allows to have a specialized implementation in model for performance critical constraints and attributes while still supporting other attributes with a small performance penalty. Note that model is unaware of constraints and attributes stored by UniversalFallback so this is not appropriate if model is an optimizer (for this reason, MOI.optimize! has not been implemented). In that case, optimizer bridges should be used instead.

source

Utilities.@model

MathOptInterface.Utilities.@modelMacro
macro model(
+    model_name,
+    scalar_sets,
+    typed_scalar_sets,
+    vector_sets,
+    typed_vector_sets,
+    scalar_functions,
+    typed_scalar_functions,
+    vector_functions,
+    typed_vector_functions,
+    is_optimizer = false
+)

Creates a type model_name implementing the MOI model interface and supporting all combinations of the provided functions and sets.

Each typed_ scalar/vector sets/functions argument is a tuple of types. A type is "typed" if it has a coefficient {T} as the first type parameter.

Tuple syntax

To give no set/function, write (). To give one set or function X, write (X,).

is_optimizer

If is_optimizer = true, the resulting struct is a of GenericOptimizer, which is a subtype of MOI.AbstractOptimizer, otherwise, it is a GenericModel, which is a subtype of MOI.ModelLike.

VariableIndex

Examples

The model describing a linear program would be:

@model(
+    LPModel,                                          # model_name
+    (),                                               # untyped scalar sets
+    (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), #   typed scalar sets
+    (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives),  # untyped vector sets
+    (),                                               #   typed vector sets
+    (),                                               # untyped scalar functions
+    (MOI.ScalarAffineFunction,),                      #   typed scalar functions
+    (MOI.VectorOfVariables,),                         # untyped vector functions
+    (MOI.VectorAffineFunction,),                      #   typed vector functions
+    false,                                            # is_optimizer
+)
source
MathOptInterface.Utilities.GenericModelType
mutable struct GenericModel{T,O,V,C} <: AbstractModelLike{T}

Implements a model supporting coefficients of type T and:

  • An objective function stored in .objective::O
  • Variables and VariableIndex constraints stored in .variable_bounds::V
  • F-in-S constraints (excluding VariableIndex constraints) stored in .constraints::C

All interactions take place via the MOI interface, so the types O, V, and C must implement the API as needed for their functionality.

source
MathOptInterface.Utilities.GenericOptimizerType
mutable struct GenericOptimizer{T,O,V,C} <: AbstractOptimizer{T}

Implements a model supporting coefficients of type T and:

  • An objective function stored in .objective::O
  • Variables and VariableIndex constraints stored in .variable_bounds::V
  • F-in-S constraints (excluding VariableIndex constraints) stored in .constraints::C

All interactions take place via the MOI interface, so the types O, V, and C must implement the API as needed for their functionality.

source

.objective

.variables

MathOptInterface.Utilities.VariablesContainerType
struct VariablesContainer{T} <: AbstractVectorBounds
+    set_mask::Vector{UInt16}
+    lower::Vector{T}
+    upper::Vector{T}
+end

A struct for storing variables and VariableIndex-related constraints. Used in MOI.Utilities.Model by default.

source
MathOptInterface.Utilities.FreeVariablesType
mutable struct FreeVariables <: MOI.ModelLike
+    n::Int64
+    FreeVariables() = new(0)
+end

A struct for storing free variables that can be used as the variables field of GenericModel or GenericModel. It represents a model that does not support any constraint nor objective function.

Example

The following model type represents a conic model in geometric form. As opposed to VariablesContainer, FreeVariables does not support constraint bounds so they are bridged into an affine constraint in the MOI.Nonnegatives cone as expected for the geometric conic form.

julia> MOI.Utilities.@product_of_sets(
+    Cones,
+    MOI.Zeros,
+    MOI.Nonnegatives,
+    MOI.SecondOrderCone,
+    MOI.PositiveSemidefiniteConeTriangle,
+);
+
+julia> const ConicModel{T} = MOI.Utilities.GenericOptimizer{
+    T,
+    MOI.Utilities.ObjectiveContainer{T},
+    MOI.Utilities.FreeVariables,
+    MOI.Utilities.MatrixOfConstraints{
+        T,
+        MOI.Utilities.MutableSparseMatrixCSC{
+            T,
+            Int,
+            MOI.Utilities.OneBasedIndexing,
+        },
+        Vector{T},
+        Cones{T},
+    },
+};
+
+julia> model = MOI.instantiate(ConicModel{Float64}, with_bridge_type=Float64);
+
+julia> x = MOI.add_variable(model)
+MathOptInterface.VariableIndex(1)
+
+julia> c = MOI.add_constraint(model, x, MOI.GreaterThan(1.0))
+MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.GreaterThan{Float64}}(1)
+
+julia> MOI.Bridges.is_bridged(model, c)
+true
+
+julia> bridge = MOI.Bridges.bridge(model, c)
+MathOptInterface.Bridges.Constraint.VectorizeBridge{Float64, MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives, MathOptInterface.VariableIndex}(MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives}(1), 1.0)
+
+julia> bridge.vector_constraint
+MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives}(1)
+
+julia> MOI.Bridges.is_bridged(model, bridge.vector_constraint)
+false
source

.constraints

MathOptInterface.Utilities.VectorOfConstraintsType
mutable struct VectorOfConstraints{
+    F<:MOI.AbstractFunction,
+    S<:MOI.AbstractSet,
+} <: MOI.ModelLike
+    constraints::CleverDicts.CleverDict{
+        MOI.ConstraintIndex{F,S},
+        Tuple{F,S},
+        typeof(CleverDicts.key_to_index),
+        typeof(CleverDicts.index_to_key),
+    }
+end

A struct storing F-in-S constraints as a mapping between the constraint indices to the corresponding tuple of function and set.

source
MathOptInterface.Utilities.@struct_of_constraints_by_function_typesMacro
Utilities.@struct_of_constraints_by_function_types(name, func_types...)

Given a vector of n function types (F1, F2,..., Fn) in func_types, defines a subtype of StructOfConstraints of name name and which type parameters {T, C1, C2, ..., Cn}. It contains n field where the ith field has type Ci and stores the constraints of function type Fi.

The expression Fi can also be a union in which case any constraint for which the function type is in the union is stored in the field with type Ci.

source
MathOptInterface.Utilities.@struct_of_constraints_by_set_typesMacro
Utilities.@struct_of_constraints_by_set_types(name, func_types...)

Given a vector of n set types (S1, S2,..., Sn) in func_types, defines a subtype of StructOfConstraints of name name and which type parameters {T, C1, C2, ..., Cn}. It contains n field where the ith field has type Ci and stores the constraints of set type Si. The expression Si can also be a union in which case any constraint for which the set type is in the union is stored in the field with type Ci. This can be useful if Ci is a MatrixOfConstraints in order to concatenate the coefficients of constraints of several different set types in the same matrix.

source
MathOptInterface.Utilities.struct_of_constraint_codeFunction
struct_of_constraint_code(struct_name, types, field_types = nothing)

Given a vector of n Union{SymbolFun,_UnionSymbolFS{SymbolFun}} or Union{SymbolSet,_UnionSymbolFS{SymbolSet}} in types, defines a subtype of StructOfConstraints of name name and which type parameters {T, F1, F2, ..., Fn} if field_types is nothing and a {T} otherwise. It contains n field where the ith field has type Ci if field_types is nothing and type field_types[i] otherwise. If types is vector of Union{SymbolFun,_UnionSymbolFS{SymbolFun}} (resp. Union{SymbolSet,_UnionSymbolFS{SymbolSet}}) then the constraints of that function (resp. set) type are stored in the corresponding field.

This function is used by the macros @model, @struct_of_constraints_by_function_types and @struct_of_constraints_by_set_types.

source

Caching optimizer

MathOptInterface.Utilities.CachingOptimizerType
CachingOptimizer

CachingOptimizer is an intermediate layer that stores a cache of the model and links it with an optimizer. It supports incremental model construction and modification even when the optimizer doesn't.

Constructors

    CachingOptimizer(cache::MOI.ModelLike, optimizer::AbstractOptimizer)

Creates a CachingOptimizer in AUTOMATIC mode, with the optimizer optimizer.

The type of the optimizer returned is CachingOptimizer{typeof(optimizer), typeof(cache)} so it does not support the function reset_optimizer(::CachingOptimizer, new_optimizer) if the type of new_optimizer is different from the type of optimizer.

    CachingOptimizer(cache::MOI.ModelLike, mode::CachingOptimizerMode)

Creates a CachingOptimizer in the NO_OPTIMIZER state and mode mode.

The type of the optimizer returned is CachingOptimizer{MOI.AbstractOptimizer,typeof(cache)} so it does support the function reset_optimizer(::CachingOptimizer, new_optimizer) if the type of new_optimizer is different from the type of optimizer.

About the type

States

A CachingOptimizer may be in one of three possible states (CachingOptimizerState):

  • NO_OPTIMIZER: The CachingOptimizer does not have any optimizer.
  • EMPTY_OPTIMIZER: The CachingOptimizer has an empty optimizer. The optimizer is not synchronized with the cached model.
  • ATTACHED_OPTIMIZER: The CachingOptimizer has an optimizer, and it is synchronized with the cached model.

Modes

A CachingOptimizer has two modes of operation (CachingOptimizerMode):

  • MANUAL: The only methods that change the state of the CachingOptimizer are Utilities.reset_optimizer, Utilities.drop_optimizer, and Utilities.attach_optimizer. Attempting to perform an operation in the incorrect state results in an error.
  • AUTOMATIC: The CachingOptimizer changes its state when necessary. For example, optimize! will automatically call attach_optimizer (an optimizer must have been previously set). Attempting to add a constraint or perform a modification not supported by the optimizer results in a drop to EMPTY_OPTIMIZER mode.
source
MathOptInterface.Utilities.attach_optimizerFunction
attach_optimizer(model::CachingOptimizer)

Attaches the optimizer to model, copying all model data into it. Can be called only from the EMPTY_OPTIMIZER state. If the copy succeeds, the CachingOptimizer will be in state ATTACHED_OPTIMIZER after the call, otherwise an error is thrown; see MOI.copy_to for more details on which errors can be thrown.

source
MOIU.attach_optimizer(model::GenericModel)

Call MOIU.attach_optimizer on the backend of model.

Cannot be called in direct mode.

source
MathOptInterface.Utilities.reset_optimizerFunction
reset_optimizer(m::CachingOptimizer, optimizer::MOI.AbstractOptimizer)

Sets or resets m to have the given empty optimizer optimizer.

Can be called from any state. An assertion error will be thrown if optimizer is not empty.

The CachingOptimizer m will be in state EMPTY_OPTIMIZER after the call.

source
reset_optimizer(m::CachingOptimizer)

Detaches and empties the current optimizer. Can be called from ATTACHED_OPTIMIZER or EMPTY_OPTIMIZER state. The CachingOptimizer will be in state EMPTY_OPTIMIZER after the call.

source
MOIU.reset_optimizer(model::GenericModel, optimizer::MOI.AbstractOptimizer)

Call MOIU.reset_optimizer on the backend of model.

Cannot be called in direct mode.

source
MOIU.reset_optimizer(model::GenericModel)

Call MOIU.reset_optimizer on the backend of model.

Cannot be called in direct mode.

source
MathOptInterface.Utilities.drop_optimizerFunction
drop_optimizer(m::CachingOptimizer)

Drops the optimizer, if one is present. Can be called from any state. The CachingOptimizer will be in state NO_OPTIMIZER after the call.

source
MOIU.drop_optimizer(model::GenericModel)

Call MOIU.drop_optimizer on the backend of model.

Cannot be called in direct mode.

source

Mock optimizer

Printing

MathOptInterface.Utilities.latex_formulationFunction
latex_formulation(model::MOI.ModelLike; kwargs...)

Wrap model in a type so that it can be pretty-printed as text/latex in a notebook like IJulia, or in Documenter.

To render the model, end the cell with latex_formulation(model), or call display(latex_formulation(model)) in to force the display of the model from inside a function.

Possible keyword arguments are:

  • simplify_coefficients : Simplify coefficients if possible by omitting them or removing trailing zeros.
  • default_name : The name given to variables with an empty name.
  • print_types : Print the MOI type of each function and set for clarity.
source

Copy utilities

MathOptInterface.Utilities.ModelFilterType
ModelFilter(filter::Function, model::MOI.ModelLike)

A layer to filter out various components of model.

The filter function takes a single argument, which is each element from the list returned by the attributes below. It returns true if the element should be visible in the filtered model and false otherwise.

The components that are filtered are:

  • Entire constraint types via:
    • MOI.ListOfConstraintTypesPresent
  • Individual constraints via:
    • MOI.ListOfConstraintIndices{F,S}
  • Specific attributes via:
    • MOI.ListOfModelAttributesSet
    • MOI.ListOfConstraintAttributesSet
    • MOI.ListOfVariableAttributesSet
Warning

The list of attributes filtered may change in a future release. You should write functions that are generic and not limited to the five types listed above. Thus, you should probably define a fallback filter(::Any) = true.

See below for examples of how this works.

Note

This layer has a limited scope. It is intended by be used in conjunction with MOI.copy_to.

Example: copy model excluding integer constraints

Use the do syntax to provide a single function.

filtered_src = MOI.Utilities.ModelFilter(src) do item
+    return item != (MOI.VariableIndex, MOI.Integer)
+end
+MOI.copy_to(dest, filtered_src)

Example: copy model excluding names

Use type dispatch to simplify the implementation:

my_filter(::Any) = true  # Note the generic fallback!
+my_filter(::MOI.VariableName) = false
+my_filter(::MOI.ConstraintName) = false
+filtered_src = MOI.Utilities.ModelFilter(my_filter, src)
+MOI.copy_to(dest, filtered_src)

Example: copy irreducible infeasible subsystem

my_filter(::Any) = true  # Note the generic fallback!
+function my_filter(ci::MOI.ConstraintIndex)
+    status = MOI.get(dest, MOI.ConstraintConflictStatus(), ci)
+    return status != MOI.NOT_IN_CONFLICT
+end
+filtered_src = MOI.Utilities.ModelFilter(my_filter, src)
+MOI.copy_to(dest, filtered_src)
source

Penalty relaxation

MathOptInterface.Utilities.PenaltyRelaxationType
PenaltyRelaxation(
+    penalties = Dict{MOI.ConstraintIndex,Float64}();
+    default::Union{Nothing,T} = 1.0,
+)

A problem modifier that, when passed to MOI.modify, destructively modifies the model in-place to create a penalized relaxation of the constraints.

Warning

This is a destructive routine that modifies the model in-place. If you don't want to modify the original model, use JuMP.copy_model to create a copy before calling MOI.modify.

Reformulation

See Utilities.ScalarPenaltyRelaxation for details of the reformulation.

For each constraint ci, the penalty passed to Utilities.ScalarPenaltyRelaxation is get(penalties, ci, default). If the value is nothing, because ci does not exist in penalties and default = nothing, then the constraint is skipped.

Return value

MOI.modify(model, PenaltyRelaxation()) returns a Dict{MOI.ConstraintIndex,MOI.ScalarAffineFunction} that maps each constraint index to the corresponding y + z as a MOI.ScalarAffineFunction. In an optimal solution, query the value of these functions to compute the violation of each constraint.

Relax a subset of constraints

To relax a subset of constraints, pass a penalties dictionary and set default = nothing.

Supported constraint types

The penalty relaxation is currently limited to modifying MOI.ScalarAffineFunction and MOI.ScalarQuadraticFunction constraints in the linear sets MOI.LessThan, MOI.GreaterThan, MOI.EqualTo and MOI.Interval.

It does not include variable bound or integrality constraints, because these cannot be modified in-place.

To modify variable bounds, rewrite them as linear constraints.

Examples

julia> model = MOI.Utilities.Model{Float64}();
+
+julia> x = MOI.add_variable(model);
+
+julia> c = MOI.add_constraint(model, 1.0 * x, MOI.LessThan(2.0));
+
+julia> map = MOI.modify(model, MOI.Utilities.PenaltyRelaxation(default = 2.0));
+
+julia> print(model)
+Minimize ScalarAffineFunction{Float64}:
+ 0.0 + 2.0 v[2]
+
+Subject to:
+
+ScalarAffineFunction{Float64}-in-LessThan{Float64}
+ 0.0 + 1.0 v[1] - 1.0 v[2] <= 2.0
+
+VariableIndex-in-GreaterThan{Float64}
+ v[2] >= 0.0
+
+julia> map[c] isa MOI.ScalarAffineFunction{Float64}
+true
julia> model = MOI.Utilities.Model{Float64}();
+
+julia> x = MOI.add_variable(model);
+
+julia> c = MOI.add_constraint(model, 1.0 * x, MOI.LessThan(2.0));
+
+julia> map = MOI.modify(model, MOI.Utilities.PenaltyRelaxation(Dict(c => 3.0)));
+
+julia> print(model)
+Minimize ScalarAffineFunction{Float64}:
+ 0.0 + 3.0 v[2]
+
+Subject to:
+
+ScalarAffineFunction{Float64}-in-LessThan{Float64}
+ 0.0 + 1.0 v[1] - 1.0 v[2] <= 2.0
+
+VariableIndex-in-GreaterThan{Float64}
+ v[2] >= 0.0
+
+julia> map[c] isa MOI.ScalarAffineFunction{Float64}
+true
source
MathOptInterface.Utilities.ScalarPenaltyRelaxationType
ScalarPenaltyRelaxation(penalty::T) where {T}

A problem modifier that, when passed to MOI.modify, destructively modifies the constraint in-place to create a penalized relaxation of the constraint.

Warning

This is a destructive routine that modifies the constraint in-place. If you don't want to modify the original model, use JuMP.copy_model to create a copy before calling MOI.modify.

Reformulation

The penalty relaxation modifies constraints of the form $f(x) \in S$ into $f(x) + y - z \in S$, where $y, z \ge 0$, and then it introduces a penalty term into the objective of $a \times (y + z)$ (if minimizing, else $-a$), where $a$ is penalty

When S is MOI.LessThan or MOI.GreaterThan, we omit y or z respectively as a performance optimization.

Return value

MOI.modify(model, ci, ScalarPenaltyRelaxation(penalty)) returns y + z as a MOI.ScalarAffineFunction. In an optimal solution, query the value of this function to compute the violation of the constraint.

Examples

julia> model = MOI.Utilities.Model{Float64}();
+
+julia> x = MOI.add_variable(model);
+
+julia> c = MOI.add_constraint(model, 1.0 * x, MOI.LessThan(2.0));
+
+julia> f = MOI.modify(model, c, MOI.Utilities.ScalarPenaltyRelaxation(2.0));
+
+julia> print(model)
+Minimize ScalarAffineFunction{Float64}:
+ 0.0 + 2.0 v[2]
+
+Subject to:
+
+ScalarAffineFunction{Float64}-in-LessThan{Float64}
+ 0.0 + 1.0 v[1] - 1.0 v[2] <= 2.0
+
+VariableIndex-in-GreaterThan{Float64}
+ v[2] >= 0.0
+
+julia> f isa MOI.ScalarAffineFunction{Float64}
+true
source

MatrixOfConstraints

MathOptInterface.Utilities.MatrixOfConstraintsType
mutable struct MatrixOfConstraints{T,AT,BT,ST} <: MOI.ModelLike
+    coefficients::AT
+    constants::BT
+    sets::ST
+    caches::Vector{Any}
+    are_indices_mapped::Vector{BitSet}
+    final_touch::Bool
+end

Represent ScalarAffineFunction and VectorAffinefunction constraints in a matrix form where the linear coefficients of the functions are stored in the coefficients field, the constants of the functions or sets are stored in the constants field. Additional information about the sets are stored in the sets field.

This model can only be used as the constraints field of a MOI.Utilities.AbstractModel.

When the constraints are added, they are stored in the caches field. They are only loaded in the coefficients and constants fields once MOI.Utilities.final_touch is called. For this reason, MatrixOfConstraints should not be used by an incremental interface. Use MOI.copy_to instead.

The constraints can be added in two different ways:

  1. With add_constraint, in which case a canonicalized copy of the function is stored in caches.
  2. With pass_nonvariable_constraints, in which case the functions and sets are stored themselves in caches without mapping the variable indices. The corresponding index in caches is added in are_indices_mapped. This avoids doing a copy of the function in case the getter of CanonicalConstraintFunction does not make a copy for the source model, e.g., this is the case of VectorOfConstraints.

We illustrate this with an example. Suppose a model is copied from a src::MOI.Utilities.Model to a bridged model with a MatrixOfConstraints. For all the types that are not bridged, the constraints will be copied with pass_nonvariable_constraints. Hence the functions stored in caches are exactly the same as the ones stored in src. This is ok since this is only during the copy_to operation during which src cannot be modified. On the other hand, for the types that are bridged, the functions added may contain duplicates even if the functions did not contain duplicates in src so duplicates are removed with MOI.Utilities.canonical.

Interface

The .coefficients::AT type must implement:

The .constants::BT type must implement:

The .sets::ST type must implement:

source

.coefficients

MathOptInterface.Utilities.load_termsFunction
load_terms(coefficients, index_map, func, offset)::Nothing

Loads the terms of func to coefficients, mapping the variable indices with index_map.

The ith dimension of func is loaded at the (offset + i)th row of coefficients.

The function must be allocated first with allocate_terms.

The function func must be canonicalized, see is_canonical.

source
MathOptInterface.Utilities.final_touchFunction
final_touch(coefficients)::Nothing

Informs the coefficients that all functions have been added with load_terms. No more modification is allowed unless MOI.empty! is called.

final_touch(sets)::Nothing

Informs the sets that all functions have been added with add_set. No more modification is allowed unless MOI.empty! is called.

source
MathOptInterface.Utilities.extract_functionFunction
extract_function(coefficients, row::Integer, constant::T) where {T}

Return the MOI.ScalarAffineFunction{T} function corresponding to row row in coefficients.

extract_function(
+    coefficients,
+    rows::UnitRange,
+    constants::Vector{T},
+) where{T}

Return the MOI.VectorAffineFunction{T} function corresponding to rows rows in coefficients.

source
MathOptInterface.Utilities.MutableSparseMatrixCSCType
mutable struct MutableSparseMatrixCSC{Tv,Ti<:Integer,I<:AbstractIndexing}
+    indexing::I
+    m::Int
+    n::Int
+    colptr::Vector{Ti}
+    rowval::Vector{Ti}
+    nzval::Vector{Tv}
+    nz_added::Vector{Ti}
+end

Matrix type loading sparse matrices in the Compressed Sparse Column format. The indexing used is indexing, see AbstractIndexing. The other fields have the same meaning than for SparseArrays.SparseMatrixCSC except that the indexing is different unless indexing is OneBasedIndexing. In addition, nz_added is used to cache the number of non-zero terms that have been added to each column due to the incremental nature of load_terms.

The matrix is loaded in 5 steps:

  1. MOI.empty! is called.
  2. MOI.Utilities.add_column and MOI.Utilities.allocate_terms are called in any order.
  3. MOI.Utilities.set_number_of_rows is called.
  4. MOI.Utilities.load_terms is called for each affine function.
  5. MOI.Utilities.final_touch is called.
source
MathOptInterface.Utilities.ZeroBasedIndexingType
struct ZeroBasedIndexing <: AbstractIndexing end

Zero-based indexing: the ith row or column has index i - 1. This is useful when the vectors of row and column indices need to be communicated to a library using zero-based indexing such as C libraries.

source

.constants

MathOptInterface.Utilities.load_constantsFunction
load_constants(constants, offset, func_or_set)::Nothing

This function loads the constants of func_or_set in constants at an offset of offset. Where offset is the sum of the dimensions of the constraints already loaded. The storage should be preallocated with resize! before calling this function.

This function should be implemented to be usable as storage of constants for MatrixOfConstraints.

The constants are loaded in three steps:

  1. Base.empty! is called.
  2. Base.resize! is called with the sum of the dimensions of all constraints.
  3. MOI.Utilities.load_constants is called for each function for vector constraint or set for scalar constraint.
source

.sets

MathOptInterface.Utilities.set_indexFunction
set_index(sets, ::Type{S})::Union{Int,Nothing} where {S<:MOI.AbstractSet}

Return an integer corresponding to the index of the set type in the list given by set_types.

If S is not part of the list, return nothing.

source
MathOptInterface.Utilities.add_setFunction
add_set(sets, i)::Int64

Add a scalar set of type index i.

add_set(sets, i, dim)::Int64

Add a vector set of type index i and dimension dim.

Both methods return a unique Int64 of the set that can be used to reference this set.

source
MathOptInterface.Utilities.rowsFunction
rows(sets, ci::MOI.ConstraintIndex)::Union{Int,UnitRange{Int}}

Return the rows in 1:MOI.dimension(sets) corresponding to the set of id ci.value.

For scalar sets, this returns an Int. For vector sets, this returns an UnitRange{Int}.

source
MathOptInterface.Utilities.num_rowsFunction
num_rows(sets::OrderedProductOfSets, ::Type{S}) where {S}

Return the number of rows corresponding to a set of type S. That is, it is the sum of the dimensions of the sets of type S.

source

Fallbacks

MathOptInterface.Utilities.get_fallbackFunction
get_fallback(model::MOI.ModelLike, ::MOI.ObjectiveValue)

Compute the objective function value using the VariablePrimal results and the ObjectiveFunction value.

source
get_fallback(model::MOI.ModelLike, ::MOI.DualObjectiveValue, T::Type)::T

Compute the dual objective value of type T using the ConstraintDual results and the ConstraintFunction and ConstraintSet values. Note that the nonlinear part of the model is ignored.

source
get_fallback(model::MOI.ModelLike, ::MOI.ConstraintPrimal,
+             constraint_index::MOI.ConstraintIndex)

Compute the value of the function of the constraint of index constraint_index using the VariablePrimal results and the ConstraintFunction values.

source
get_fallback(model::MOI.ModelLike, attr::MOI.ConstraintDual,
+             ci::MOI.ConstraintIndex{Union{MOI.VariableIndex,
+                                           MOI.VectorOfVariables}})

Compute the dual of the constraint of index ci using the ConstraintDual of other constraints and the ConstraintFunction values. Throws an error if some constraints are quadratic or if there is one another MOI.VariableIndex-in-S or MOI.VectorOfVariables-in-S constraint with one of the variables in the function of the constraint ci.

source

Function utilities

The following utilities are available for functions:

MathOptInterface.Utilities.eval_variablesFunction
eval_variables(value_fn::Function, f::MOI.AbstractFunction)

Returns the value of function f if each variable index vi is evaluated as value_fn(vi).

Note that value_fn must return a Number. See substitute_variables for a similar function where value_fn returns an MOI.AbstractScalarFunction.

Warning

The two-argument version of eval_variables is deprecated and may be removed in MOI v2.0.0. Use the three-argument method eval_variables(::Function, ::MOI.ModelLike, ::MOI.AbstractFunction) instead.

source
MathOptInterface.Utilities.map_indicesFunction
map_indices(index_map::Function, attr::MOI.AnyAttribute, x::X)::X where {X}

Substitute any MOI.VariableIndex (resp. MOI.ConstraintIndex) in x by the MOI.VariableIndex (resp. MOI.ConstraintIndex) of the same type given by index_map(x).

When to implement this method for new types X

This function is used by implementations of MOI.copy_to on constraint functions, attribute values and submittable values. If you define a new attribute whose values x::X contain variable or constraint indices, you must also implement this function.

source
map_indices(
+    variable_map::AbstractDict{T,T},
+    x::X,
+)::X where {T<:MOI.Index,X}

Shortcut for map_indices(vi -> variable_map[vi], x).

source
MathOptInterface.Utilities.substitute_variablesFunction
substitute_variables(variable_map::Function, x)

Substitute any MOI.VariableIndex in x by variable_map(x). The variable_map function returns either MOI.VariableIndex or MOI.ScalarAffineFunction, see eval_variables for a similar function where variable_map returns a number.

This function is used by bridge optimizers on constraint functions, attribute values and submittable values when at least one variable bridge is used hence it needs to be implemented for custom types that are meant to be used as attribute or submittable value.

Note

When implementing a new method, don't use substitute_variables(::Function, because Julia will not specialize on it. Use instead substitute_variables(::F, ...) where {F<:Function}.

source
MathOptInterface.Utilities.filter_variablesFunction
filter_variables(keep::Function, f::AbstractFunction)

Return a new function f with the variable vi such that !keep(vi) removed.

WARNING: Don't define filter_variables(::Function, ...) because Julia will not specialize on this. Define instead filter_variables(::F, ...) where {F<:Function}.

source
MathOptInterface.Utilities.remove_variableFunction
remove_variable(f::AbstractFunction, vi::VariableIndex)

Return a new function f with the variable vi removed.

source
remove_variable(
+    f::MOI.AbstractFunction,
+    s::MOI.AbstractSet,
+    vi::MOI.VariableIndex,
+)

Return a tuple (g, t) representing the constraint f-in-s with the variable vi removed. That is, the terms containing the variable vi in the function f are removed and the dimension of the set s is updated if needed (e.g. when f is a VectorOfVariables with vi being one of the variables).

source
MathOptInterface.Utilities.all_coefficientsFunction
all_coefficients(p::Function, f::MOI.AbstractFunction)

Determine whether predicate p returns true for all coefficients of f, returning false as soon as the first coefficient of f for which p returns false is encountered (short-circuiting). Similar to all.

source
MathOptInterface.Utilities.unsafe_addFunction
unsafe_add(t1::MOI.ScalarAffineTerm, t2::MOI.ScalarAffineTerm)

Sums the coefficients of t1 and t2 and returns an output MOI.ScalarAffineTerm. It is unsafe because it uses the variable of t1 as the variable of the output without checking that it is equal to that of t2.

source
unsafe_add(t1::MOI.ScalarQuadraticTerm, t2::MOI.ScalarQuadraticTerm)

Sums the coefficients of t1 and t2 and returns an output MOI.ScalarQuadraticTerm. It is unsafe because it uses the variable's of t1 as the variable's of the output without checking that they are the same (up to permutation) to those of t2.

source
unsafe_add(t1::MOI.VectorAffineTerm, t2::MOI.VectorAffineTerm)

Sums the coefficients of t1 and t2 and returns an output MOI.VectorAffineTerm. It is unsafe because it uses the output_index and variable of t1 as the output_index and variable of the output term without checking that they are equal to those of t2.

source
MathOptInterface.Utilities.isapprox_zeroFunction
isapprox_zero(f::MOI.AbstractFunction, tol)

Return a Bool indicating whether the function f is approximately zero using tol as a tolerance.

Important note

This function assumes that f does not contain any duplicate terms, you might want to first call canonical if that is not guaranteed. For instance, given

f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, -1], [x, x]), 0)

then isapprox_zero(f) is false but isapprox_zero(MOIU.canonical(f)) is true.

source
MathOptInterface.Utilities.zero_with_output_dimensionFunction
zero_with_output_dimension(::Type{T}, output_dimension::Integer) where {T}

Create an instance of type T with the output dimension output_dimension.

This is mostly useful in Bridges, when code needs to be agnostic to the type of vector-valued function that is passed in.

source

The following functions can be used to canonicalize a function:

MathOptInterface.Utilities.is_canonicalFunction
is_canonical(f::Union{ScalarAffineFunction, VectorAffineFunction})

Returns a Bool indicating whether the function is in canonical form. See canonical.

source
is_canonical(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction})

Returns a Bool indicating whether the function is in canonical form. See canonical.

source
MathOptInterface.Utilities.canonicalFunction
canonical(f::MOI.AbstractFunction)

Returns the function in a canonical form, i.e.

  • A term appear only once.
  • The coefficients are nonzero.
  • The terms appear in increasing order of variable where there the order of the variables is the order of their value.
  • For a AbstractVectorFunction, the terms are sorted in ascending order of output index.

The output of canonical can be assumed to be a copy of f, even for VectorOfVariables.

Examples

If x (resp. y, z) is VariableIndex(1) (resp. 2, 3). The canonical representation of ScalarAffineFunction([y, x, z, x, z], [2, 1, 3, -2, -3], 5) is ScalarAffineFunction([x, y], [-1, 2], 5).

source
MathOptInterface.Utilities.canonicalize!Function
canonicalize!(f::Union{ScalarAffineFunction, VectorAffineFunction})

Convert a function to canonical form in-place, without allocating a copy to hold the result. See canonical.

source
canonicalize!(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction})

Convert a function to canonical form in-place, without allocating a copy to hold the result. See canonical.

source

The following functions can be used to manipulate functions with basic algebra:

MathOptInterface.Utilities.scalarizeFunction
scalarize(func::MOI.VectorOfVariables, ignore_constants::Bool = false)

Returns a vector of scalar functions making up the vector function in the form of a Vector{MOI.SingleVariable}.

See also eachscalar.

source
scalarize(func::MOI.VectorAffineFunction{T}, ignore_constants::Bool = false)

Returns a vector of scalar functions making up the vector function in the form of a Vector{MOI.ScalarAffineFunction{T}}.

See also eachscalar.

source
scalarize(func::MOI.VectorQuadraticFunction{T}, ignore_constants::Bool = false)

Returns a vector of scalar functions making up the vector function in the form of a Vector{MOI.ScalarQuadraticFunction{T}}.

See also eachscalar.

source
MathOptInterface.Utilities.promote_operationFunction
promote_operation(
+    op::Function,
+    ::Type{T},
+    ArgsTypes::Type{<:Union{T,AbstractVector{T},MOI.AbstractFunction}}...,
+) where {T<:Number}

Compute the return type of the call operate(op, T, args...), where the types of the arguments args are ArgsTypes.

One assumption is that the element type T is invariant under each operation. That is, op(::T, ::T)::T where op is a +, -, *, and /.

There are six methods for which we implement Utilities.promote_operation:

  1. + a. promote_operation(::typeof(+), ::Type{T}, ::Type{F1}, ::Type{F2})
  2. - a. promote_operation(::typeof(-), ::Type{T}, ::Type{F}) b. promote_operation(::typeof(-), ::Type{T}, ::Type{F1}, ::Type{F2})
  3. * a. promote_operation(::typeof(*), ::Type{T}, ::Type{T}, ::Type{F}) b. promote_operation(::typeof(*), ::Type{T}, ::Type{F}, ::Type{T}) c. promote_operation(::typeof(*), ::Type{T}, ::Type{F1}, ::Type{F2}) where F1 and F2 are VariableIndex or ScalarAffineFunction d. promote_operation(::typeof(*), ::Type{T}, ::Type{<:Diagonal{T}}, ::Type{F}
  4. / a. promote_operation(::typeof(/), ::Type{T}, ::Type{F}, ::Type{T})
  5. vcat a. promote_operation(::typeof(vcat), ::Type{T}, ::Type{F}...)
  6. imag a. promote_operation(::typeof(imag), ::Type{T}, ::Type{F}) where F is VariableIndex or VectorOfVariables

In each case, F (or F1 and F2) is one of the ten supported types, with a restriction that the mathematical operation makes sense, for example, we don't define promote_operation(-, T, F1, F2) where F1 is a scalar-valued function and F2 is a vector-valued function. The ten supported types are:

  1. ::T
  2. ::VariableIndex
  3. ::ScalarAffineFunction{T}
  4. ::ScalarQuadraticFunction{T}
  5. ::ScalarNonlinearFunction
  6. ::AbstractVector{T}
  7. ::VectorOfVariables
  8. ::VectorAffineFunction{T}
  9. ::VectorQuadraticFunction{T}
  10. ::VectorNonlinearFunction
source
MathOptInterface.Utilities.operateFunction
operate(
+    op::Function,
+    ::Type{T},
+    args::Union{T,MOI.AbstractFunction}...,
+)::MOI.AbstractFunction where {T<:Number}

Returns an MOI.AbstractFunction representing the function resulting from the operation op(args...) on functions of coefficient type T.

No argument can be modified.

Methods

  1. + a. operate(::typeof(+), ::Type{T}, ::F1) b. operate(::typeof(+), ::Type{T}, ::F1, ::F2) c. operate(::typeof(+), ::Type{T}, ::F1...)
  2. - a. operate(::typeof(-), ::Type{T}, ::F) b. operate(::typeof(-), ::Type{T}, ::F1, ::F2)
  3. * a. operate(::typeof(*), ::Type{T}, ::T, ::F) b. operate(::typeof(*), ::Type{T}, ::F, ::T) c. operate(::typeof(*), ::Type{T}, ::F1, ::F2) where F1 and F2 are VariableIndex or ScalarAffineFunction d. operate(::typeof(*), ::Type{T}, ::Diagonal{T}, ::F)
  4. / a. operate(::typeof(/), ::Type{T}, ::F, ::T)
  5. vcat a. operate(::typeof(vcat), ::Type{T}, ::F...)
  6. imag a. operate(::typeof(imag), ::Type{T}, ::F) where F is VariableIndex or VectorOfVariables

One assumption is that the element type T is invariant under each operation. That is, op(::T, ::T)::T where op is a +, -, *, and /.

In each case, F (or F1 and F2) is one of the ten supported types, with a restriction that the mathematical operation makes sense, for example, we don't define promote_operation(-, T, F1, F2) where F1 is a scalar-valued function and F2 is a vector-valued function. The ten supported types are:

  1. ::T
  2. ::VariableIndex
  3. ::ScalarAffineFunction{T}
  4. ::ScalarQuadraticFunction{T}
  5. ::ScalarNonlinearFunction
  6. ::AbstractVector{T}
  7. ::VectorOfVariables
  8. ::VectorAffineFunction{T}
  9. ::VectorQuadraticFunction{T}
  10. ::VectorNonlinearFunction
source
MathOptInterface.Utilities.operate!Function
operate!(
+    op::Function,
+    ::Type{T},
+    args::Union{T,MOI.AbstractFunction}...,
+)::MOI.AbstractFunction where {T<:Number}

Returns an MOI.AbstractFunction representing the function resulting from the operation op(args...) on functions of coefficient type T.

The first argument may be modified, in which case the return value is identical to the first argument. For operations which cannot be implemented in-place, this function returns a new object.

source
MathOptInterface.Utilities.operate_output_index!Function
operate_output_index!(
+    op::Union{typeof(+),typeof(-)},
+    ::Type{T},
+    output_index::Integer,
+    f::Union{AbstractVector{T},MOI.AbstractVectorFunction}
+    g::Union{T,MOI.AbstractScalarFunction}...
+) where {T<:Number}

Return an MOI.AbstractVectorFunction in which the scalar function in row output_index is the result of op(f[output_index], g).

The functions at output index different to output_index are the same as the functions at the same output index in func. The first argument may be modified.

Methods

  1. + a. operate_output_index!(+, ::Type{T}, ::Int, ::VectorF, ::ScalarF)
  2. - a. operate_output_index!(-, ::Type{T}, ::Int, ::VectorF, ::ScalarF)
source
MathOptInterface.Utilities.vectorizeFunction
vectorize(x::AbstractVector{<:Number})

Returns x.

source
vectorize(x::AbstractVector{MOI.VariableIndex})

Returns the vector of scalar affine functions in the form of a MOI.VectorAffineFunction{T}.

source
vectorize(funcs::AbstractVector{MOI.ScalarAffineFunction{T}}) where T

Returns the vector of scalar affine functions in the form of a MOI.VectorAffineFunction{T}.

source
vectorize(funcs::AbstractVector{MOI.ScalarQuadraticFunction{T}}) where T

Returns the vector of scalar quadratic functions in the form of a MOI.VectorQuadraticFunction{T}.

source

Constraint utilities

The following utilities are available for moving the function constant to the set for scalar constraints:

MathOptInterface.Utilities.shift_constantFunction
shift_constant(set::MOI.AbstractScalarSet, offset)

Returns a new scalar set new_set such that func-in-set is equivalent to func + offset-in-new_set.

Only define this function if it makes sense to!

Use supports_shift_constant to check if the set supports shifting:

if supports_shift_constant(typeof(old_set))
+    new_set = shift_constant(old_set, offset)
+    f.constant = 0
+    add_constraint(model, f, new_set)
+else
+    add_constraint(model, f, old_set)
+end

See also supports_shift_constant.

Examples

The call shift_constant(MOI.Interval(-2, 3), 1) is equal to MOI.Interval(-1, 4).

source
MathOptInterface.Utilities.normalize_and_add_constraintFunction
normalize_and_add_constraint(
+    model::MOI.ModelLike,
+    func::MOI.AbstractScalarFunction,
+    set::MOI.AbstractScalarSet;
+    allow_modify_function::Bool = false,
+)

Adds the scalar constraint obtained by moving the constant term in func to the set in model. If allow_modify_function is true then the function func can be modified.

source

The following utility identifies those constraints imposing bounds on a given variable, and returns those bound values:

MathOptInterface.Utilities.get_boundsFunction
get_bounds(model::MOI.ModelLike, ::Type{T}, x::MOI.VariableIndex)

Return a tuple (lb, ub) of type Tuple{T, T}, where lb and ub are lower and upper bounds, respectively, imposed on x in model.

source

The following utilities are useful when working with symmetric matrix cones.

Set utilities

The following utilities are available for sets:

MathOptInterface.Utilities.ProjectionUpperBoundDistanceType
ProjectionUpperBoundDistance() <: AbstractDistance

An upper bound on the minimum distance between point and the closest feasible point in set.

Definition of distance

The minimum distance is computed as:

\[d(x, \mathcal{K}) = \min_{y \in \mathcal{K}} || x - y ||\]

where $x$ is point and $\mathcal{K}$ is set. The norm is computed as:

\[||x|| = \sqrt{f(x, x, \mathcal{K})}\]

where $f$ is Utilities.set_dot.

In the default case, where the set does not have a specialized method for Utilities.set_dot, the norm is equivalent to the Euclidean norm $||x|| = \sqrt{\sum x_i^2}$.

Why an upper bound?

In most cases, distance_to_set should return the smallest upper bound, but it may return a larger value if the smallest upper bound is expensive to compute.

For example, given an epigraph from of a conic set, $\{(t, x) | f(x) \le t\}$, it may be simpler to return $\delta$ such that $f(x) \le t + \delta$, rather than computing the nearest projection onto the set.

If the distance is not the smallest upper bound, the docstring of the appropriate distance_to_set method must describe the way that the distance is computed.

source
MathOptInterface.Utilities.distance_to_setFunction
distance_to_set(
+    [d::AbstractDistance = ProjectionUpperBoundDistance()],]
+    point::T,
+    set::MOI.AbstractScalarSet,
+) where {T}
+
+distance_to_set(
+    [d::AbstractDistance = ProjectionUpperBoundDistance(),]
+    point::AbstractVector{T},
+    set::MOI.AbstractVectorSet,
+) where {T}

Compute the distance between point and set using the distance metric d. If point is in the set set, this function must return zero(T).

If d is omitted, the default distance is Utilities.ProjectionUpperBoundDistance.

source
MathOptInterface.Utilities.set_dotFunction
set_dot(x::AbstractVector, y::AbstractVector, set::AbstractVectorSet)

Return the scalar product between a vector x of the set set and a vector y of the dual of the set s.

source
set_dot(x, y, set::AbstractScalarSet)

Return the scalar product between a number x of the set set and a number y of the dual of the set s.

source

DoubleDicts

MathOptInterface.Utilities.DoubleDicts.DoubleDictType
DoubleDict{V}

An optimized dictionary to map MOI.ConstraintIndex to values of type V.

Works as a AbstractDict{MOI.ConstraintIndex,V} with minimal differences.

If V is also a MOI.ConstraintIndex, use IndexDoubleDict.

Note that MOI.ConstraintIndex is not a concrete type, opposed to MOI.ConstraintIndex{MOI.VariableIndex, MOI.Integers}, which is a concrete type.

When looping through multiple keys of the same Function-in-Set type, use

inner = dict[F, S]

to return a type-stable DoubleDictInner.

source
MathOptInterface.Utilities.DoubleDicts.outer_keysFunction
outer_keys(d::AbstractDoubleDict)

Return an iterator over the outer keys of the AbstractDoubleDict d. Each outer key is a Tuple{Type,Type} so that a double loop can be easily used:

for (F, S) in DoubleDicts.outer_keys(dict)
+    for (k, v) in dict[F, S]
+        # ...
+    end
+end

For performance, it is recommended that the inner loop lies in a separate function to gurantee type-stability. Some outer keys (F, S) might lead to an empty dict[F, S]. If you want only nonempty dict[F, S], use nonempty_outer_keys.

source
MathOptInterface.Utilities.DoubleDicts.nonempty_outer_keysFunction
nonempty_outer_keys(d::AbstractDoubleDict)

Return a vector of outer keys of the AbstractDoubleDict d.

Only outer keys that have a nonempty set of inner keys will be returned.

Each outer key is a Tuple{Type,Type} so that a double loop can be easily used

for (F, S) in DoubleDicts.nonempty_outer_keys(dict)
+    for (k, v) in dict[F, S]
+        # ...
+    end
+end
+For performance, it is recommended that the inner loop lies in a separate
+function to gurantee type-stability.
+
+If you want an iterator of all current outer keys, use [`outer_keys`](@ref).
source
diff --git a/previews/PR3536/moi/tutorials/bridging_constraint/index.html b/previews/PR3536/moi/tutorials/bridging_constraint/index.html new file mode 100644 index 00000000000..4d38a524001 --- /dev/null +++ b/previews/PR3536/moi/tutorials/bridging_constraint/index.html @@ -0,0 +1,106 @@ + +Implementing a constraint bridge · JuMP

Implementing a constraint bridge

This guide outlines the basic steps to create a new bridge from a constraint expressed in the formalism Function-in-Set.

Preliminaries

First, decide on the set you want to bridge. Then, study its properties: the most important one is whether the set is scalar or vector, which impacts the dimensionality of the functions that can be used with the set.

  • A scalar function only has one dimension. MOI defines three types of scalar functions: a variable (VariableIndex), an affine function (ScalarAffineFunction), or a quadratic function (ScalarQuadraticFunction).
  • A vector function has several dimensions (at least one). MOI defines three types of vector functions: several variables (VectorOfVariables), an affine function (VectorAffineFunction), or a quadratic function (VectorQuadraticFunction). The main difference with scalar functions is that the order of dimensions can be very important: for instance, in an indicator constraint (Indicator), the first dimension indicates whether the constraint about the second dimension is active.

To explain how to implement a bridge, we present the example of Bridges.Constraint.FlipSignBridge. This bridge maps <= (LessThan) constraints to >= (GreaterThan) constraints. This corresponds to reversing the sign of the inequality. We focus on scalar affine functions (we disregard the cases of a single variable or of quadratic functions). This example is a simplified version of the code included in MOI.

Four mandatory parts in a constraint bridge

The first part of a constraint bridge is a new concrete subtype of Bridges.Constraint.AbstractBridge. This type must have fields to store all the new variables and constraints that the bridge will add. Typically, these types are parametrized by the type of the coefficients in the model.

Then, three sets of functions must be defined:

  1. Bridges.Constraint.bridge_constraint: this function implements the bridge and creates the required variables and constraints.
  2. supports_constraint: these functions must return true when the combination of function and set is supported by the bridge. By default, the base implementation always returns false and the bridge does not have to provide this implementation.
  3. Bridges.added_constrained_variable_types and Bridges.added_constraint_types: these functions return the types of variables and constraints that this bridge adds. They are used to compute the set of other bridges that are required to use the one you are defining, if need be.

More functions can be implemented, for instance to retrieve properties from the bridge or deleting a bridged constraint.

1. Structure for the bridge

A typical struct behind a bridge depends on the type of the coefficients that are used for the model (typically Float64, but coefficients might also be integers or complex numbers).

This structure must hold a reference to all the variables and the constraints that are created as part of the bridge.

The type of this structure is used throughout MOI as an identifier for the bridge. It is passed as argument to most functions related to bridges.

The best practice is to have the name of this type end with Bridge.

In our example, the bridge maps any ScalarAffineFunction{T}-in-LessThan{T} constraint to a single ScalarAffineFunction{T}-in-GreaterThan{T} constraint. The affine function has coefficients of type T. The bridge is parametrized with T, so that the constraint that the bridge creates also has coefficients of type T.

struct SignBridge{T<:Number} <: Bridges.Constraint.AbstractBridge
+    constraint::ConstraintIndex{ScalarAffineFunction{T}, GreaterThan{T}}
+end

2. Bridge creation

The function Bridges.Constraint.bridge_constraint is called whenever the bridge is instantiated for a specific model, with the given function and set. The arguments to bridge_constraint are similar to add_constraint, with the exception of the first argument: it is the Type of the struct defined in the first step (for our example, Type{SignBridge{T}}).

bridge_constraint returns an instance of the struct defined in the first step. the first step.

In our example, the bridge constraint could be defined as:

function Bridges.Constraint.bridge_constraint(
+    ::Type{SignBridge{T}}, # Bridge to use.
+    model::ModelLike, # Model to which the constraint is being added.
+    f::ScalarAffineFunction{T}, # Function to rewrite.
+    s::LessThan{T}, # Set to rewrite.
+) where {T}
+    # Create the variables and constraints required for the bridge.
+    con = add_constraint(model, -f, GreaterThan(-s.upper))
+
+    # Return an instance of the bridge type with a reference to all the
+    # variables and constraints that were created in this function.
+    return SignBridge(con)
+end

3. Supported constraint types

The function supports_constraint determines whether the bridge type supports a given combination of function and set.

This function must closely match bridge_constraint, because it will not be called if supports_constraint returns false.

function supports_constraint(
+    ::Type{SignBridge{T}}, # Bridge to use.
+    ::Type{ScalarAffineFunction{T}}, # Function to rewrite.
+    ::Type{LessThan{T}}, # Set to rewrite.
+) where {T}
+    # Do some computation to ensure that the constraint is supported.
+    # Typically, you can directly return true.
+    return true
+end

4. Metadata about the bridge

To determine whether a bridge can be used, MOI uses a shortest-path algorithm that uses the variable types and the constraints that the bridge can create. This information is communicated from the bridge to MOI using the functions Bridges.added_constrained_variable_types and Bridges.added_constraint_types. Both return lists of tuples: either a list of 1-tuples containing the variable types (typically, ZeroOne or Integer) or a list of 2-tuples contained the functions and sets (like ScalarAffineFunction{T}-GreaterThan).

For our example, the bridge does not create any constrained variables, and only ScalarAffineFunction{T}-in-GreaterThan{T} constraints:

function Bridges.added_constrained_variable_types(::Type{SignBridge{T}}) where {T}
+    # The bridge does not create variables, return an empty list of tuples:
+    return Tuple{Type}[]
+end
+
+function Bridges.added_constraint_types(::Type{SignBridge{T}}) where {T}
+    return Tuple{Type,Type}[
+        # One element per F-in-S the bridge creates.
+        (ScalarAffineFunction{T}, GreaterThan{T}),
+    ]
+end

A bridge that creates binary variables would rather have this definition of added_constrained_variable_types:

function Bridges.added_constrained_variable_types(::Type{SomeBridge{T}}) where {T}
+    # The bridge only creates binary variables:
+    return Tuple{Type}[(ZeroOne,)]
+end
Warning

If you declare the creation of constrained variables in added_constrained_variable_types, the corresponding constraint type VariableIndex must not be indicated in added_constraint_types. This would restrict the use of the bridge to solvers that can add such a constraint after the variable is created.

More concretely, if you declare in added_constrained_variable_types that your bridge creates binary variables (ZeroOne), and if you never add such a constraint afterward (you do not call add_constraint(model, var, ZeroOne())), then you must not list (VariableIndex, ZeroOne) in added_constraint_types.

Typically, the function Bridges.Constraint.concrete_bridge_type does not have to be defined for most bridges.

Bridge registration

For a bridge to be used by MOI, it must be known by MOI.

SingleBridgeOptimizer

The first way to do so is to create a single-bridge optimizer. This type of optimizer wraps another optimizer and adds the possibility to use only one bridge. It is especially useful when unit testing bridges.

It is common practice to use the same name as the type defined for the bridge (SignBridge, in our example) without the suffix Bridge.

const Sign{T,OT<: ModelLike} =
+    SingleBridgeOptimizer{SignBridge{T}, OT}

In the context of unit tests, this bridge is used in conjunction with a Utilities.MockOptimizer:

mock = Utilities.MockOptimizer(
+    Utilities.UniversalFallback(Utilities.Model{Float64}()),
+)
+bridged_mock = Sign{Float64}(mock)

New bridge for a LazyBridgeOptimizer

Typical user-facing models for MOI are based on Bridges.LazyBridgeOptimizer. For instance, this type of model is returned by Bridges.full_bridge_optimizer. These models can be added more bridges by using Bridges.add_bridge:

inner_optimizer = Utilities.Model{Float64}()
+optimizer = Bridges.full_bridge_optimizer(inner_optimizer, Float64)
+Bridges.add_bridge(optimizer, SignBridge{Float64})

Bridge improvements

Attribute retrieval

Like models, bridges have attributes that can be retrieved using get and set. The most important ones are the number of variables and constraints, but also the lists of variables and constraints.

In our example, we only have one constraint and only have to implement the NumberOfConstraints and ListOfConstraintIndices attributes:

function get(
+    ::SignBridge{T},
+    ::NumberOfConstraints{
+        ScalarAffineFunction{T},
+        GreaterThan{T},
+    },
+) where {T}
+    return 1
+end
+
+function get(
+    bridge::SignBridge{T},
+    ::ListOfConstraintIndices{
+        ScalarAffineFunction{T},
+        GreaterThan{T},
+    },
+) where {T}
+    return [bridge.constraint]
+end

You must implement one such pair of functions for each type of constraint the bridge adds to the model.

Warning

Avoid returning a list from the bridge object without copying it. Users must be able to change the contents of the returned list without altering the bridge object.

For variables, the situation is simpler. If your bridge creates new variables, you must implement the NumberOfVariables and ListOfVariableIndices attributes. However, these attributes do not have parameters, unlike their constraint counterparts. Only two functions suffice:

function get(
+    ::SignBridge{T},
+    ::NumberOfVariables,
+) where {T}
+    return 0
+end
+
+function get(
+    ::SignBridge{T},
+    ::ListOfVariableIndices,
+) where {T}
+    return VariableIndex[]
+end

In order for the user to be able to access the function and set of the original constraint, the bridge needs to implement getters for the ConstraintFunction and ConstraintSet attributes:

function get(
+    model::MOI.ModelLike,
+    attr::MOI.ConstraintFunction,
+    bridge::SignBridge,
+)
+    return -MOI.get(model, attr, bridge.constraint)
+end
+
+function get(
+    model::MOI.ModelLike,
+    attr::MOI.ConstraintSet,
+    bridge::SignBridge,
+)
+    set = MOI.get(model, attr, bridge.constraint)
+    return MOI.LessThan(-set.lower)
+end
Warning

Alternatively, one could store the original function and set in SignBridge during Bridges.Constraint.bridge_constraint to make these getters simpler and more efficient. On the other hand, this will increase the memory footprint of the bridges as the garbage collector won't be able to delete that object. The convention is to not store the function in the bridge and not care too much about the efficiency of these getters. If the user needs efficient getters for ConstraintFunction then they should use a Utilities.CachingOptimizer.

Model modifications

To avoid copying the model when the user request to change a constraint, MOI provides modify. Bridges can also implement this API to allow certain changes, such as coefficient changes.

In our case, a modification of a coefficient in the original constraint (for example, replacing the value of the coefficient of a variable in the affine function) must be transmitted to the constraint created by the bridge, but with a sign change.

function modify(
+    model::ModelLike,
+    bridge::SignBridge,
+    change::ScalarCoefficientChange,
+)
+    modify(
+        model,
+        bridge.constraint,
+        ScalarCoefficientChange(change.variable, -change.new_coefficient),
+    )
+    return
+end

Bridge deletion

When a bridge is deleted, the constraints it added must be deleted too.

function delete(model::ModelLike, bridge::SignBridge)
+    delete(model, bridge.constraint)
+    return
+end
diff --git a/previews/PR3536/moi/tutorials/example/index.html b/previews/PR3536/moi/tutorials/example/index.html new file mode 100644 index 00000000000..d0786e54de7 --- /dev/null +++ b/previews/PR3536/moi/tutorials/example/index.html @@ -0,0 +1,49 @@ + +Solving a problem using MathOptInterface · JuMP

Solving a problem using MathOptInterface

In this tutorial we demonstrate how to use MathOptInterface to solve the binary-constrained knapsack problem:

\[\begin{aligned} +\max \; & c^\top x \\ +s.t. \; & w^\top x \le C \\ + & x_i \in \{0,1\},\quad \forall i=1,\ldots,n +\end{aligned}\]

Required packages

Load the MathOptInterface module and define the shorthand MOI:

import MathOptInterface as MOI

As an optimizer, we choose GLPK:

using GLPK
+optimizer = GLPK.Optimizer()

Define the data

We first define the constants of the problem:

julia> c = [1.0, 2.0, 3.0]
+3-element Vector{Float64}:
+ 1.0
+ 2.0
+ 3.0
+
+julia> w = [0.3, 0.5, 1.0]
+3-element Vector{Float64}:
+ 0.3
+ 0.5
+ 1.0
+
+julia> C = 3.2
+3.2

Add the variables

julia> x = MOI.add_variables(optimizer, length(c));

Set the objective

julia> MOI.set(
+           optimizer,
+           MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
+           MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(c, x), 0.0),
+       );
+
+julia> MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE)
Tip

MOI.ScalarAffineTerm.(c, x) is a shortcut for [MOI.ScalarAffineTerm(c[i], x[i]) for i = 1:3]. This is Julia's broadcast syntax in action, and is used quite often throughout MOI.

Add the constraints

We add the knapsack constraint and integrality constraints:

julia> MOI.add_constraint(
+           optimizer,
+           MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(w, x), 0.0),
+           MOI.LessThan(C),
+       );

Add integrality constraints:

julia> for x_i in x
+           MOI.add_constraint(optimizer, x_i, MOI.ZeroOne())
+       end

Optimize the model

julia> MOI.optimize!(optimizer)

Understand why the solver stopped

The first thing to check after optimization is why the solver stopped, for example, did it stop because of a time limit or did it stop because it found the optimal solution?

julia> MOI.get(optimizer, MOI.TerminationStatus())
+OPTIMAL::TerminationStatusCode = 1

Looks like we found an optimal solution.

Understand what solution was returned

julia> MOI.get(optimizer, MOI.ResultCount())
+1
+
+julia> MOI.get(optimizer, MOI.PrimalStatus())
+FEASIBLE_POINT::ResultStatusCode = 1
+
+julia> MOI.get(optimizer, MOI.DualStatus())
+NO_SOLUTION::ResultStatusCode = 0

Query the objective

What is its objective value?

julia> MOI.get(optimizer, MOI.ObjectiveValue())
+6.0

Query the primal solution

And what is the value of the variables x?

julia> MOI.get(optimizer, MOI.VariablePrimal(), x)
+3-element Vector{Float64}:
+ 1.0
+ 1.0
+ 1.0
diff --git a/previews/PR3536/moi/tutorials/implementing/index.html b/previews/PR3536/moi/tutorials/implementing/index.html new file mode 100644 index 00000000000..d87f9f25223 --- /dev/null +++ b/previews/PR3536/moi/tutorials/implementing/index.html @@ -0,0 +1,118 @@ + +Implementing a solver interface · JuMP

Implementing a solver interface

This guide outlines the basic steps to implement an interface to MathOptInterface for a new solver.

Danger

Implementing an interface to MathOptInterface for a new solver is a lot of work. Before starting, we recommend that you join the Developer chatroom and explain a little bit about the solver you are wrapping. If you have questions that are not answered by this guide, please ask them in the Developer chatroom so we can improve this guide.

A note on the API

The API of MathOptInterface is large and varied. In order to support the diversity of solvers and use-cases, we make heavy use of duck-typing. That is, solvers are not expected to implement the full API, nor is there a well-defined minimal subset of what must be implemented. Instead, you should implement the API as necessary to make the solver function as you require.

The main reason for using duck-typing is that solvers work in different ways and target different use-cases.

For example:

  • Some solvers support incremental problem construction, support modification after a solve, and have native support for things like variable names.
  • Other solvers are "one-shot" solvers that require all of the problem data to construct and solve the problem in a single function call. They do not support modification or things like variable names.
  • Other "solvers" are not solvers at all, but things like file readers. These may only support functions like read_from_file, and may not even support the ability to add variables or constraints directly.
  • Finally, some "solvers" are layers which take a problem as input, transform it according to some rules, and pass the transformed problem to an inner solver.

Preliminaries

Before starting on your wrapper, you should do some background research and make the solver accessible via Julia.

Decide if MathOptInterface is right for you

The first step in writing a wrapper is to decide whether implementing an interface is the right thing to do.

MathOptInterface is an abstraction layer for unifying constrained mathematical optimization solvers. If your solver doesn't fit in the category, for example, it implements a derivative-free algorithm for unconstrained objective functions, MathOptInterface may not be the right tool for the job.

Tip

If you're not sure whether you should write an interface, ask in the Developer chatroom.

Find a similar solver already wrapped

The next step is to find (if possible) a similar solver that is already wrapped. Although not strictly necessary, this will be a good place to look for inspiration when implementing your wrapper.

The JuMP documentation has a good list of solvers, along with the problem classes they support.

Tip

If you're not sure which solver is most similar, ask in the Developer chatroom.

Create a low-level interface

Before writing a MathOptInterface wrapper, you first need to be able to call the solver from Julia.

Wrapping solvers written in Julia

If your solver is written in Julia, there's nothing to do here. Go to the next section.

Wrapping solvers written in C

Julia is well suited to wrapping solvers written in C.

Info

This is not true for C++. If you have a solver written in C++, first write a C interface, then wrap the C interface.

Before writing a MathOptInterface wrapper, there are a few extra steps.

Create a JLL

If the C code is publicly available under an open source license, create a JLL package via Yggdrasil. The easiest way to do this is to copy an existing solver. Good examples to follow are the COIN-OR solvers.

Warning

Building the solver via Yggdrasil is non-trivial. Please ask the Developer chatroom for help.

If the code is commercial or not publicly available, the user will need to manually install the solver. See Gurobi.jl or CPLEX.jl for examples of how to structure this.

Use Clang.jl to wrap the C API

The next step is to use Clang.jl to automatically wrap the C API. The easiest way to do this is to follow an example. Good examples to follow are Cbc.jl and HiGHS.jl.

Sometimes, you will need to make manual modifications to the resulting files.

Solvers written in other languages

Ask the Developer chatroom for advice. You may be able to use one of the JuliaInterop packages to call out to the solver.

For example, SeDuMi.jl uses MATLAB.jl to call the SeDuMi solver written in MATLAB.

Structuring the package

Structure your wrapper as a Julia package. Consult the Julia documentation if you haven't done this before.

MOI solver interfaces may be in the same package as the solver itself (either the C wrapper if the solver is accessible through C, or the Julia code if the solver is written in Julia, for example), or in a separate package which depends on the solver package.

Note

The JuMP core contributors request that you do not use "JuMP" in the name of your package without prior consent.

Your package should have the following structure:

/.github
+    /workflows
+        ci.yml
+        format_check.yml
+        TagBot.yml
+/gen
+    gen.jl  # Code to wrap the C API
+/src
+    NewSolver.jl
+    /gen
+        libnewsolver_api.jl
+        libnewsolver_common.jl
+    /MOI_wrapper
+        MOI_wrapper.jl
+        other_files.jl
+/test
+    runtests.jl
+    /MOI_wrapper
+        MOI_wrapper.jl
+.gitignore
+.JuliaFormatter.toml
+README.md
+LICENSE.md
+Project.toml
  • The /.github folder contains the scripts for GitHub actions. The easiest way to write these is to copy the ones from an existing solver.
  • The /gen and /src/gen folders are only needed if you are wrapping a solver written in C.
  • The /src/MOI_wrapper folder contains the Julia code for the MOI wrapper.
  • The /test folder contains code for testing your package. See Setup tests for more information.
  • The .JuliaFormatter.toml and .github/workflows/format_check.yml enforce code formatting using JuliaFormatter.jl. Check existing solvers or JuMP.jl for details.

Documentation

Your package must include documentation explaining how to use the package. The easiest approach is to include documentation in your README.md. A more involved option is to use Documenter.jl.

Examples of packages with README-based documentation include:

Examples of packages with Documenter-based documentation include:

Setup tests

The best way to implement an interface to MathOptInterface is via test-driven development.

The MOI.Test submodule contains a large test suite to help check that you have implemented things correctly.

Follow the guide How to test a solver to set up the tests for your package.

Tip

Run the tests frequently when developing. However, at the start there is going to be a lot of errors. Start by excluding large classes of tests (for example, exclude = ["test_basic_", "test_model_"], implement any missing methods until the tests pass, then remove an exclusion and repeat.

Initial code

By this point, you should have a package setup with tests, formatting, and access to the underlying solver. Now it's time to start writing the wrapper.

The Optimizer object

The first object to create is a subtype of AbstractOptimizer. This type is going to store everything related to the problem.

By convention, these optimizers should not be exported and should be named PackageName.Optimizer.

import MathOptInterface as MOI
+
+struct Optimizer <: MOI.AbstractOptimizer
+    # Fields go here
+end

Optimizer objects for C solvers

Warning

This section is important if you wrap a solver written in C.

Wrapping a solver written in C will require the use of pointers, and for you to manually free the solver's memory when the Optimizer is garbage collected by Julia.

Never pass a pointer directly to a Julia ccall function.

Instead, store the pointer as a field in your Optimizer, and implement Base.cconvert and Base.unsafe_convert. Then you can pass Optimizer to any ccall function that expects the pointer.

In addition, make sure you implement a finalizer for each model you create.

If newsolver_createProblem() is the low-level function that creates the problem pointer in C, and newsolver_freeProblem(::Ptr{Cvoid}) is the low-level function that frees memory associated with the pointer, your Optimizer() function should look like this:

struct Optimizer <: MOI.AbstractOptimizer
+    ptr::Ptr{Cvoid}
+
+    function Optimizer()
+        ptr = newsolver_createProblem()
+        model = Optimizer(ptr)
+        finalizer(model) do m
+            newsolver_freeProblem(m)
+            return
+        end
+        return model
+    end
+end
+
+Base.cconvert(::Type{Ptr{Cvoid}}, model::Optimizer) = model
+Base.unsafe_convert(::Type{Ptr{Cvoid}}, model::Optimizer) = model.ptr

Implement methods for Optimizer

All Optimizers must implement the following methods:

Other methods, detailed below, are optional or depend on how you implement the interface.

Tip

For this and all future methods, read the docstrings to understand what each method does, what it expects as input, and what it produces as output. If it isn't clear, let us know and we will improve the docstrings. It is also very helpful to look at an existing wrapper for a similar solver.

You should also implement Base.show(::IO, ::Optimizer) to print a nice string when someone prints your model. For example

function Base.show(io::IO, model::Optimizer)
+    return print(io, "NewSolver with the pointer $(model.ptr)")
+end

Implement attributes

MathOptInterface uses attributes to manage different aspects of the problem.

For each attribute

  • get gets the current value of the attribute
  • set sets a new value of the attribute. Not all attributes can be set. For example, the user can't modify the SolverName.
  • supports returns a Bool indicating whether the solver supports the attribute.
Info

Use attribute_value_type to check the value expected by a given attribute. You should make sure that your get function correctly infers to this type (or a subtype of it).

Each column in the table indicates whether you need to implement the particular method for each attribute.

Attributegetsetsupports
SolverNameYesNoNo
SolverVersionYesNoNo
RawSolverYesNoNo
NameYesYesYes
SilentYesYesYes
TimeLimitSecYesYesYes
ObjectiveLimitYesYesYes
RawOptimizerAttributeYesYesYes
NumberOfThreadsYesYesYes
AbsoluteGapToleranceYesYesYes
RelativeGapToleranceYesYesYes

For example:

function MOI.get(model::Optimizer, ::MOI.Silent)
+    return # true if MOI.Silent is set
+end
+
+function MOI.set(model::Optimizer, ::MOI.Silent, v::Bool)
+    if v
+        # Set a parameter to turn off printing
+    else
+        # Restore the default printing
+    end
+    return
+end
+
+MOI.supports(::Optimizer, ::MOI.Silent) = true

Define supports_constraint

The next step is to define which constraints and objective functions you plan to support.

For each function-set constraint pair, define supports_constraint:

function MOI.supports_constraint(
+    ::Optimizer,
+    ::Type{MOI.VariableIndex},
+    ::Type{MOI.ZeroOne},
+)
+    return true
+end

To make this easier, you may want to use Unions:

function MOI.supports_constraint(
+    ::Optimizer,
+    ::Type{MOI.VariableIndex},
+    ::Type{<:Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo}},
+)
+    return true
+end
Tip

Only support a constraint if your solver has native support for it.

The big decision: incremental modification?

Now you need to decide whether to support incremental modification or not.

Incremental modification means that the user can add variables and constraints one-by-one without needing to rebuild the entire problem, and they can modify the problem data after an optimize! call. Supporting incremental modification means implementing functions like add_variable and add_constraint.

The alternative is to accept the problem data in a single optimize! or copy_to function call. Because these functions see all of the data at once, it can typically call a more efficient function to load data into the underlying solver.

Good examples of solvers supporting incremental modification are MILP solvers like GLPK.jl and Gurobi.jl. Examples of non-incremental solvers are AmplNLWriter.jl and SCS.jl

It is possible for a solver to implement both approaches, but you should probably start with one for simplicity.

Tip

Only support incremental modification if your solver has native support for it.

In general, supporting incremental modification is more work, and it usually requires some extra book-keeping. However, it provides a more efficient interface to the solver if the problem is going to be resolved multiple times with small modifications. Moreover, once you've implemented incremental modification, it's usually not much extra work to add a copy_to interface. The converse is not true.

Tip

If this is your first time writing an interface, start with the one-shot optimize!.

The non-incremental interface

There are two ways to implement the non-incremental interface. The first uses a two-argument version of optimize!, the second implements copy_to followed by the one-argument version of optimize!.

If your solver does not support modification, and requires all data to solve the problem in a single function call, you should implement the "one-shot" optimize!.

If your solver separates data loading and the actual optimization into separate steps, implement the copy_to interface.

The incremental interface

Warning

Writing this interface is a lot of work. The easiest way is to consult the source code of a similar solver.

To implement the incremental interface, implement the following functions:

Info

Solvers do not have to support AbstractScalarFunction in GreaterThan, LessThan, EqualTo, or Interval with a nonzero constant in the function. Throw ScalarFunctionConstantNotZero if the function constant is not zero.

In addition, you should implement the following model attributes:

Attributegetsetsupports
ListOfModelAttributesSetYesNoNo
ObjectiveFunctionTypeYesNoNo
ObjectiveFunctionYesYesYes
ObjectiveSenseYesYesYes
NameYesYesYes

Variable-related attributes:

Attributegetsetsupports
ListOfVariableAttributesSetYesNoNo
NumberOfVariablesYesNoNo
ListOfVariableIndicesYesNoNo

Constraint-related attributes:

Attributegetsetsupports
ListOfConstraintAttributesSetYesNoNo
NumberOfConstraintsYesNoNo
ListOfConstraintTypesPresentYesNoNo
ConstraintFunctionYesYesNo
ConstraintSetYesYesNo

Modifications

If your solver supports modifying data in-place, implement modify for the following AbstractModifications:

Variables constrained on creation

Some solvers require variables be associated with a set when they are created. This conflicts with the incremental modification approach, since you cannot first add a free variable and then constrain it to the set.

If this is the case, implement:

By default, MathOptInterface assumes solvers support free variables. If your solver does not support free variables, define:

MOI.supports_add_constrained_variables(::Optimizer, ::Type{Reals}) = false

Incremental and copy_to

If you implement the incremental interface, you have the option of also implementing copy_to.

If you don't want to implement copy_to, for example, because the solver has no API for building the problem in a single function call, define the following fallback:

MOI.supports_incremental_interface(::Optimizer) = true
+
+function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
+    return MOI.Utilities.default_copy_to(dest, src)
+end

Names

Regardless of which interface you implement, you have the option of implementing the Name attribute for variables and constraints:

Attributegetsetsupports
VariableNameYesYesYes
ConstraintNameYesYesYes

If you implement names, you must also implement the following three methods:

function MOI.get(model::Optimizer, ::Type{MOI.VariableIndex}, name::String)
+    return # The variable named `name`.
+end
+
+function MOI.get(model::Optimizer, ::Type{MOI.ConstraintIndex}, name::String)
+    return # The constraint any type named `name`.
+end
+
+function MOI.get(
+    model::Optimizer,
+    ::Type{MOI.ConstraintIndex{F,S}},
+    name::String,
+) where {F,S}
+    return # The constraint of type F-in-S named `name`.
+end

These methods have the following rules:

  • If there is no variable or constraint with the name, return nothing
  • If there is a single variable or constraint with that name, return the variable or constraint
  • If there are multiple variables or constraints with the name, throw an error.
Warning

You should not implement ConstraintName for VariableIndex constraints. If you implement ConstraintName for other constraints, you can add the following two methods to disable ConstraintName for VariableIndex constraints.

function MOI.supports(
+    ::Optimizer,
+    ::MOI.ConstraintName,
+    ::Type{<:MOI.ConstraintIndex{MOI.VariableIndex,<:MOI.AbstractScalarSet}},
+)
+    return throw(MOI.VariableIndexConstraintNameError())
+end
+function MOI.set(
+    ::Optimizer,
+    ::MOI.ConstraintName,
+    ::MOI.ConstraintIndex{MOI.VariableIndex,<:MOI.AbstractScalarSet},
+    ::String,
+)
+    return throw(MOI.VariableIndexConstraintNameError())
+end

Solutions

Implement optimize! to solve the model:

All Optimizers must implement the following attributes:

Info

You only need to implement get for solution attributes. Don't implement set or supports.

Note

Solver wrappers should document how the low-level statuses map to the MOI statuses. Statuses like NEARLY_FEASIBLE_POINT and INFEASIBLE_POINT, are designed to be used when the solver explicitly indicates that relaxed tolerances are satisfied or the returned point is infeasible, respectively.

You should also implement the following attributes:

Tip

Attributes like VariablePrimal and ObjectiveValue are indexed by the result count. Use MOI.check_result_index_bounds(model, attr) to throw an error if the attribute is not available.

If your solver returns dual solutions, implement:

For integer solvers, implement:

If applicable, implement:

If your solver uses the Simplex method, implement:

If your solver accepts primal or dual warm-starts, implement:

Other tips

Here are some other points to be aware of when writing your wrapper.

Unsupported constraints at runtime

In some cases, your solver may support a particular type of constraint (for example, quadratic constraints), but only if the data meets some condition (for example, it is convex).

In this case, declare that you support the constraint, and throw AddConstraintNotAllowed.

Dealing with multiple variable bounds

MathOptInterface uses VariableIndex constraints to represent variable bounds. Defining multiple variable bounds on a single variable is not allowed.

Throw LowerBoundAlreadySet or UpperBoundAlreadySet if the user adds a constraint that results in multiple bounds.

Only throw if the constraints conflict. It is okay to add VariableIndex-in-GreaterThan and then VariableIndex-in-LessThan, but not VariableIndex-in-Interval and then VariableIndex-in-LessThan,

Expect duplicate coefficients

Solvers must expect that functions such as ScalarAffineFunction and VectorQuadraticFunction may contain duplicate coefficients.

For example, ScalarAffineFunction([ScalarAffineTerm(x, 1), ScalarAffineTerm(x, 1)], 0.0).

Use Utilities.canonical to return a new function with the duplicate coefficients aggregated together.

Don't modify user-data

All data passed to the solver must be copied immediately to internal data structures. Solvers may not modify any input vectors and must assume that input vectors will not be modified by users in the future.

This applies, for example, to the terms vector in ScalarAffineFunction. Vectors returned to the user, for example, via ObjectiveFunction or ConstraintFunction attributes, must not be modified by the solver afterwards. The in-place version of get! can be used by users to avoid extra copies in this case.

Column Generation

There is no special interface for column generation. If the solver has a special API for setting coefficients in existing constraints when adding a new variable, it is possible to queue modifications and new variables and then call the solver's API once all of the new coefficients are known.

Solver-specific attributes

You don't need to restrict yourself to the attributes defined in the MathOptInterface.jl package.

Solver-specific attributes should be specified by creating an appropriate subtype of AbstractModelAttribute, AbstractOptimizerAttribute, AbstractVariableAttribute, or AbstractConstraintAttribute.

For example, Gurobi.jl adds attributes for multiobjective optimization by defining:

struct NumberOfObjectives <: MOI.AbstractModelAttribute end
+
+function MOI.set(model::Optimizer, ::NumberOfObjectives, n::Integer)
+    # Code to set NumberOfObjectives
+    return
+end
+
+function MOI.get(model::Optimizer, ::NumberOfObjectives)
+    n = # Code to get NumberOfObjectives
+    return n
+end

Then, the user can write:

model = Gurobi.Optimizer()
+MOI.set(model, Gurobi.NumberofObjectives(), 3)
diff --git a/previews/PR3536/moi/tutorials/latency/index.html b/previews/PR3536/moi/tutorials/latency/index.html new file mode 100644 index 00000000000..a331708944e --- /dev/null +++ b/previews/PR3536/moi/tutorials/latency/index.html @@ -0,0 +1,133 @@ + +Latency · JuMP

Latency

MathOptInterface suffers the "time-to-first-solve" problem of start-up latency.

This hurts both the user- and developer-experience of MathOptInterface. In the first case, because simple models have a multi-second delay before solving, and in the latter, because our tests take so long to run.

This page contains some advice on profiling and fixing latency-related problems in the MathOptInterface.jl repository.

Background

Before reading this part of the documentation, you should familiarize yourself with the reasons for latency in Julia and how to fix them.

Causes

There are three main causes of latency in MathOptInterface:

  1. A large number of types
  2. Lack of method ownership
  3. Type-instability in the bridge layer

A large number of types

Julia is very good at specializing method calls based on the input type. Each specialization has a compilation cost, but the benefit of faster run-time performance.

The best-case scenario is for a method to be called a large number of times with a single set of argument types. The worst-case scenario is for a method to be called a single time for a large set of argument types.

Because of MathOptInterface's function-in-set formulation, we fall into the worst-case situation.

This is a fundamental limitation of Julia, so there isn't much we can do about it. However, if we can precompile MathOptInterface, much of the cost can be shifted from start-up latency to the time it takes to precompile a package on installation.

However, there are two things which make MathOptInterface hard to precompile.

Lack of method ownership

Lack of method ownership happens when a call is made using a mix of structs and methods from different modules. Because of this, no single module "owns" the method that is being dispatched, and so it cannot be precompiled.

Tip

This is a slightly simplified explanation. Read the precompilation tutorial for a more in-depth discussion on back-edges.

Unfortunately, the design of MOI means that this is a frequent occurrence: we have a bunch of types in MOI.Utilities that wrap types defined in external packages (for example, the Optimizers), which implement methods of functions defined in MOI (for example, add_variable, add_constraint).

Here's a simple example of method-ownership in practice:

module MyMOI
+struct Wrapper{T}
+    inner::T
+end
+optimize!(x::Wrapper) = optimize!(x.inner)
+end  # MyMOI
+
+module MyOptimizer
+using ..MyMOI
+struct Optimizer end
+MyMOI.optimize!(x::Optimizer) = 1
+end  # MyOptimizer
+
+using SnoopCompile
+model = MyMOI.Wrapper(MyOptimizer.Optimizer())
+
+julia> tinf = @snoopi_deep MyMOI.optimize!(model)
+InferenceTimingNode: 0.008256/0.008543 on InferenceFrameInfo for Core.Compiler.Timings.ROOT() with 1 direct children

The result is that there was one method that required type inference. If we visualize tinf:

using ProfileView
+ProfileView.view(flamegraph(tinf))

we see a flamegraph with a large red-bar indicating that the method MyMOI.optimize(MyMOI.Wrapper{MyOptimizer.Optimizer}) cannot be precompiled.

To fix this, we need to designate a module to "own" that method (that is, create a back-edge). The easiest way to do this is for MyOptimizer to call MyMOI.optimize(MyMOI.Wrapper{MyOptimizer.Optimizer}) during using MyOptimizer. Let's see that in practice:

module MyMOI
+struct Wrapper{T}
+    inner::T
+end
+optimize(x::Wrapper) = optimize(x.inner)
+end  # MyMOI
+
+module MyOptimizer
+using ..MyMOI
+struct Optimizer end
+MyMOI.optimize(x::Optimizer) = 1
+# The syntax of this let-while loop is very particular:
+#  * `let ... end` keeps everything local to avoid polluting the MyOptimizer
+#    namespace
+#  * `while true ... break end` runs the code once, and forces Julia to compile
+#    the inner loop, rather than interpret it.
+let
+    while true
+        model = MyMOI.Wrapper(Optimizer())
+        MyMOI.optimize(model)
+        break
+    end
+end
+end  # MyOptimizer
+
+using SnoopCompile
+model = MyMOI.Wrapper(MyOptimizer.Optimizer())
+
+julia> tinf = @snoopi_deep MyMOI.optimize(model)
+InferenceTimingNode: 0.006822/0.006822 on InferenceFrameInfo for Core.Compiler.Timings.ROOT() with 0 direct children

There are now 0 direct children that required type inference because the method was already stored in MyOptimizer!

Unfortunately, this trick only works if the call-chain is fully inferrable. If there are breaks (due to type instability), then the benefit of doing this is reduced. And unfortunately for us, the design of MathOptInterface has a lot of type instabilities.

Type instability in the bridge layer

Most of MathOptInterface is pretty good at ensuring type-stability. However, a key component is not type stable, and that is the bridging layer.

In particular, the bridging layer defines Bridges.LazyBridgeOptimizer, which has fields like:

struct LazyBridgeOptimizer
+    constraint_bridge_types::Vector{Any}
+    constraint_node::Dict{Tuple{Type,Type},ConstraintNode}
+    constraint_types::Vector{Tuple{Type,Type}}
+end

This is because the LazyBridgeOptimizer needs to be able to deal with any function-in-set type passed to it, and we also allow users to pass additional bridges that they defined in external packages.

So to recap, MathOptInterface suffers package latency because:

  1. there are a large number of types and functions
  2. and these are split between multiple modules, including external packages
  3. and there are type-instabilities like those in the bridging layer.

Resolutions

There are no magic solutions to reduce latency. Issue #1313 tracks progress on reducing latency in MathOptInterface.

A useful script is the following (replace GLPK as needed):

import GLPK
+import MathOptInterface as MOI
+
+function example_diet(optimizer, bridge)
+    category_data = [
+        1800.0 2200.0;
+          91.0    Inf;
+           0.0   65.0;
+           0.0 1779.0
+    ]
+    cost = [2.49, 2.89, 1.50, 1.89, 2.09, 1.99, 2.49, 0.89, 1.59]
+    food_data = [
+        410 24 26 730;
+        420 32 10 1190;
+        560 20 32 1800;
+        380  4 19 270;
+        320 12 10 930;
+        320 15 12 820;
+        320 31 12 1230;
+        100  8 2.5 125;
+        330  8 10 180
+    ]
+    bridge_model = if bridge
+        MOI.instantiate(optimizer; with_bridge_type=Float64)
+    else
+        MOI.instantiate(optimizer)
+    end
+    model = MOI.Utilities.CachingOptimizer(
+        MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()),
+        MOI.Utilities.AUTOMATIC,
+    )
+    MOI.Utilities.reset_optimizer(model, bridge_model)
+    MOI.set(model, MOI.Silent(), true)
+    nutrition = MOI.add_variables(model, size(category_data, 1))
+    for (i, v) in enumerate(nutrition)
+        MOI.add_constraint(model, v, MOI.GreaterThan(category_data[i, 1]))
+        MOI.add_constraint(model, v, MOI.LessThan(category_data[i, 2]))
+    end
+    buy = MOI.add_variables(model, size(food_data, 1))
+    MOI.add_constraint.(model, buy, MOI.GreaterThan(0.0))
+    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
+    f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(cost, buy), 0.0)
+    MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
+    for (j, n) in enumerate(nutrition)
+        f = MOI.ScalarAffineFunction(
+            MOI.ScalarAffineTerm.(food_data[:, j], buy),
+            0.0,
+        )
+        push!(f.terms, MOI.ScalarAffineTerm(-1.0, n))
+        MOI.add_constraint(model, f, MOI.EqualTo(0.0))
+    end
+    MOI.optimize!(model)
+    term_status = MOI.get(model, MOI.TerminationStatus())
+    @assert term_status == MOI.OPTIMAL
+    MOI.add_constraint(
+        model,
+        MOI.ScalarAffineFunction(
+            MOI.ScalarAffineTerm.(1.0, [buy[end-1], buy[end]]),
+            0.0,
+        ),
+        MOI.LessThan(6.0),
+    )
+    MOI.optimize!(model)
+    @assert MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE
+    return
+end
+
+if length(ARGS) > 0
+    bridge = get(ARGS, 2, "") != "--no-bridge"
+    println("Running: $(ARGS[1]) $(get(ARGS, 2, ""))")
+    @time example_diet(GLPK.Optimizer, bridge)
+    @time example_diet(GLPK.Optimizer, bridge)
+    exit(0)
+end

You can create a flame-graph via

using SnoopComile
+tinf = @snoopi_deep example_diet(GLPK.Optimizer, true)
+using ProfileView
+ProfileView.view(flamegraph(tinf))

Here's how things looked in mid-August 2021: flamegraph

There are a few opportunities for improvement (non-red flames, particularly on the right). But the main problem is a large red (non-precompilable due to method ownership) flame.

diff --git a/previews/PR3536/moi/tutorials/manipulating_expressions/index.html b/previews/PR3536/moi/tutorials/manipulating_expressions/index.html new file mode 100644 index 00000000000..cd302a1bacb --- /dev/null +++ b/previews/PR3536/moi/tutorials/manipulating_expressions/index.html @@ -0,0 +1,26 @@ + +Manipulating expressions · JuMP

Manipulating expressions

This guide highlights a syntactically appealing way to build expressions at the MOI level, but also to look at their contents. It may be especially useful when writing models or bridge code.

Creating functions

This section details the ways to create functions with MathOptInterface.

Creating scalar affine functions

The simplest scalar function is simply a variable:

julia> x = MOI.add_variable(model) # Create the variable x
+MOI.VariableIndex(1)

This type of function is extremely simple; to express more complex functions, other types must be used. For instance, a ScalarAffineFunction is a sum of linear terms (a factor times a variable) and a constant. Such an object can be built using the standard constructor:

julia> f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1, x)], 2) # x + 2
+(2) + (1) MOI.VariableIndex(1)

However, you can also use operators to build the same scalar function:

julia> f = x + 2
+(2) + (1) MOI.VariableIndex(1)

Creating scalar quadratic functions

Scalar quadratic functions are stored in ScalarQuadraticFunction objects, in a way that is highly similar to scalar affine functions. You can obtain a quadratic function as a product of affine functions:

julia> 1 * x * x
+(0) + 1.0 MOI.VariableIndex(1)²
+
+julia> f * f  # (x + 2)²
+(4) + (2) MOI.VariableIndex(1) + (2) MOI.VariableIndex(1) + 1.0 MOI.VariableIndex(1)²
+
+julia> f^2  # (x + 2)² too
+(4) + (2) MOI.VariableIndex(1) + (2) MOI.VariableIndex(1) + 1.0 MOI.VariableIndex(1)²

Creating vector functions

A vector function is a function with several values, irrespective of the number of input variables. Similarly to scalar functions, there are three main types of vector functions: VectorOfVariables, VectorAffineFunction, and VectorQuadraticFunction.

The easiest way to create a vector function is to stack several scalar functions using Utilities.vectorize. It takes a vector as input, and the generated vector function (of the most appropriate type) has each dimension corresponding to a dimension of the vector.

julia> g = MOI.Utilities.vectorize([f, 2 * f])
+┌                              ┐
+│(2) + (1) MOI.VariableIndex(1)│
+│(4) + (2) MOI.VariableIndex(1)│
+└                              ┘
Warning

Utilities.vectorize only takes a vector of similar scalar functions: you cannot mix VariableIndex and ScalarAffineFunction, for instance. In practice, it means that Utilities.vectorize([x, f]) does not work; you should rather use Utilities.vectorize([1 * x, f]) instead to only have ScalarAffineFunction objects.

Canonicalizing functions

In more advanced use cases, you might need to ensure that a function is "canonical." Functions are stored as an array of terms, but there is no check that these terms are redundant: a ScalarAffineFunction object might have two terms with the same variable, like x + x + 1. These terms could be merged without changing the semantics of the function: 2x + 1.

Working with these objects might be cumbersome. Canonicalization helps maintain redundancy to zero.

Utilities.is_canonical checks whether a function is already in its canonical form:

julia> MOI.Utilities.is_canonical(f + f) # (x + 2) + (x + 2) is stored as x + x + 4
+false

Utilities.canonical returns the equivalent canonical version of the function:

julia> MOI.Utilities.canonical(f + f) # Returns 2x + 4
+(4) + (2) MOI.VariableIndex(1)

Exploring functions

At some point, you might need to dig into a function, for instance to map it into solver constructs.

Vector functions

Utilities.scalarize returns a vector of scalar functions from a vector function:

julia> MOI.Utilities.scalarize(g) # Returns a vector [f, 2 * f].
+2-element Vector{MathOptInterface.ScalarAffineFunction{Int64}}:
+ (2) + (1) MOI.VariableIndex(1)
+ (4) + (2) MOI.VariableIndex(1)
Note

Utilities.eachscalar returns an iterator on the dimensions, which serves the same purpose as Utilities.scalarize.

output_dimension returns the number of dimensions of the output of a function:

julia> MOI.output_dimension(g)
+2
diff --git a/previews/PR3536/moi/tutorials/mathprogbase/index.html b/previews/PR3536/moi/tutorials/mathprogbase/index.html new file mode 100644 index 00000000000..56c8eb604df --- /dev/null +++ b/previews/PR3536/moi/tutorials/mathprogbase/index.html @@ -0,0 +1,58 @@ + +Transitioning from MathProgBase · JuMP

Transitioning from MathProgBase

MathOptInterface is a replacement for MathProgBase.jl. However, it is not a direct replacement.

Transitioning a solver interface

MathOptInterface is more extensive than MathProgBase which may make its implementation seem daunting at first. There are however numerous utilities in MathOptInterface that the simplify implementation process.

For more information, read Implementing a solver interface.

Transitioning the high-level functions

MathOptInterface doesn't provide replacements for the high-level interfaces in MathProgBase. We recommend you use JuMP as a modeling interface instead.

Tip

If you haven't used JuMP before, start with the tutorial Getting started with JuMP

linprog

Here is one way of transitioning from linprog:

using JuMP
+
+function linprog(c, A, sense, b, l, u, solver)
+    N = length(c)
+    model = Model(solver)
+    @variable(model, l[i] <= x[i=1:N] <= u[i])
+    @objective(model, Min, c' * x)
+    eq_rows, ge_rows, le_rows = sense .== '=', sense .== '>', sense .== '<'
+    @constraint(model, A[eq_rows, :] * x .== b[eq_rows])
+    @constraint(model, A[ge_rows, :] * x .>= b[ge_rows])
+    @constraint(model, A[le_rows, :] * x .<= b[le_rows])
+    optimize!(model)
+    return (
+        status = termination_status(model),
+        objval = objective_value(model),
+        sol = value.(x)
+    )
+end

mixintprog

Here is one way of transitioning from mixintprog:

using JuMP
+
+function mixintprog(c, A, rowlb, rowub, vartypes, lb, ub, solver)
+    N = length(c)
+    model = Model(solver)
+    @variable(model, lb[i] <= x[i=1:N] <= ub[i])
+    for i in 1:N
+        if vartypes[i] == :Bin
+            set_binary(x[i])
+        elseif vartypes[i] == :Int
+            set_integer(x[i])
+        end
+    end
+    @objective(model, Min, c' * x)
+    @constraint(model, rowlb .<= A * x .<= rowub)
+    optimize!(model)
+    return (
+        status = termination_status(model),
+        objval = objective_value(model),
+        sol = value.(x)
+    )
+end

quadprog

Here is one way of transitioning from quadprog:

using JuMP
+
+function quadprog(c, Q, A, rowlb, rowub, lb, ub, solver)
+    N = length(c)
+    model = Model(solver)
+    @variable(model, lb[i] <= x[i=1:N] <= ub[i])
+    @objective(model, Min, c' * x + 0.5 * x' * Q * x)
+    @constraint(model, rowlb .<= A * x .<= rowub)
+    optimize!(model)
+    return (
+        status = termination_status(model),
+        objval = objective_value(model),
+        sol = value.(x)
+    )
+end
diff --git a/previews/PR3536/packages/Alpine/index.html b/previews/PR3536/packages/Alpine/index.html new file mode 100644 index 00000000000..b92ce8ce5bb --- /dev/null +++ b/previews/PR3536/packages/Alpine/index.html @@ -0,0 +1,49 @@ + +lanl-ansi/Alpine.jl · JuMP

Alpine, a global solver for non-convex MINLPs

CI codecov Documentation version

ALPINE (glob(AL) o(P)timization for mixed-(I)nteger programs with (N)onlinear (E)quations), is a novel global optimization solver that uses an adaptive, piecewise convexification scheme and constraint programming methods to solve non-convex Mixed-Integer Non-Linear Programs (MINLPs) efficiently. MINLPs are typically "hard" optimization problems which appear in numerous applications (see MINLPLib.jl).

Alpine is entirely built upon JuMP and MathOptInterface in Julia, which provides incredible flexibility for usage and further development.

Alpine globally solves a given MINLP by:

  • Analyzing the problem's expressions (objective & constraints) and applies appropriate convex relaxations and polyhedral outer-approximations

  • Performing sequential optimization-based bound tightening (OBBT) and an iterative MIP-based adaptive partitioning scheme via piecewise polyhedral relaxations with a guarantee of global convergence

Installation

Install Alpine using the Julia package manager:

import Pkg
+Pkg.add("Alpine")

Use with JuMP

Use Alpine with JuMP as follows:

using JuMP, Alpine, Ipopt, HiGHS
+ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0)
+highs = optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false)
+model = Model(
+    optimizer_with_attributes(
+        Juniper.Optimizer,
+        "nlp_solver" => ipopt,
+        "mip_solver" => highs,
+    ),
+)

Documentation

For more details, see the online documentation.

Support problem types

Alpine can currently handle MINLPs with polynomials in constraints and/or in the objective. Currently, there is no support for exponential cones and Positive Semi-Definite (PSD) cones in MINLPs. Alpine is also a good fit for subsets of the MINLP family, for example, Mixed-Integer Quadratically Constrained Quadratic Programs (MIQCQPs), Non-Linear Programs (NLPs), etc.

For more details, check out this video on Alpine.jl at JuMP-dev 2018.

Underlying solvers

Though the MIP-based bounding algorithm implemented in Alpine is quite involved, most of the computational bottleneck arises in the underlying MIP solvers. Since every iteration of Alpine solves an MIP sub-problem, which is typically a convex MILP/MIQCQP, Alpine's run time heavily depends on the run-time of these solvers. For the best performance of Alpine, we recommend using the commercial solver Gurobi, which is available free for academic purposes. However, due to the flexibility offered by JuMP, the following MIP and NLP solvers are supported in Alpine:

SolverJulia Package
CPLEXCPLEX.jl
CbcCbc.jl
GurobiGurobi.jl
IpoptIpopt.jl
BonminBonmin.jl
Artelys KNITROKNITRO.jl
XpressXpress.jl
HiGHSHiGHS.jl

Bug reports and support

Please report any issues via the GitHub issue tracker. All types of issues are welcome and encouraged; this includes bug reports, documentation typos, feature requests, etc.

Challenging Problems

We are seeking out hard benchmark instances for MINLPs. Please get in touch either by opening an issue or privately if you would like to share any hard instances.

Citing Alpine

If you find Alpine useful in your work, we kindly request that you cite the following papers (PDF, PDF)

@article{alpine_JOGO2019,
+  title = {An adaptive, multivariate partitioning algorithm for global optimization of nonconvex programs},
+  author = {Nagarajan, Harsha and Lu, Mowen and Wang, Site and Bent, Russell and Sundar, Kaarthik},
+  journal = {Journal of Global Optimization},
+  year = {2019},
+  issn = {1573-2916},
+  doi = {10.1007/s10898-018-00734-1},
+}
+
+@inproceedings{alpine_CP2016,
+  title = {Tightening {McCormick} relaxations for nonlinear programs via dynamic multivariate partitioning},
+  author = {Nagarajan, Harsha and Lu, Mowen and Yamangil, Emre and Bent, Russell},
+  booktitle = {International Conference on Principles and Practice of Constraint Programming},
+  pages = {369--387},
+  year = {2016},
+  organization = {Springer},
+  doi = {10.1007/978-3-319-44953-1_24},
+}

If you find the underlying piecewise polyhedral formulations implemented in Alpine useful in your work, we kindly request that you cite the following papers (link-1, link-2):

@article{alpine_ORL2021,
+  title = {Piecewise polyhedral formulations for a multilinear term},
+  author = {Sundar, Kaarthik and Nagarajan, Harsha and Linderoth, Jeff and Wang, Site and Bent, Russell},
+  journal = {Operations Research Letters},
+  volume = {49},
+  number = {1},
+  pages = {144--149},
+  year = {2021},
+  publisher = {Elsevier}
+}
+
+@article{alpine_OptOnline2022,
+  title={Piecewise Polyhedral Relaxations of Multilinear Optimization},
+  author={Kim, Jongeun and Richard, Jean-Philippe P. and Tawarmalani, Mohit},
+  eprinttype={Optimization Online},
+  date={2022}
+}
diff --git a/previews/PR3536/packages/AmplNLWriter/index.html b/previews/PR3536/packages/AmplNLWriter/index.html new file mode 100644 index 00000000000..96d8ad0a107 --- /dev/null +++ b/previews/PR3536/packages/AmplNLWriter/index.html @@ -0,0 +1,15 @@ + +jump-dev/AmplNLWriter.jl · JuMP

AmplNLWriter.jl

Build Status MINLPTests codecov

AmplNLWriter.jl is an interface between MathOptInterface.jl and AMPL-enabled solvers.

Affiliation

This wrapper is maintained by the JuMP community and has no official connection with the AMPL modeling language or AMPL Optimization Inc.

Installation

Install AmplNLWriter using Pkg.add:

import Pkg
+Pkg.add("AmplNLWriter")

Use with JuMP

AmplNLWriter requires an AMPL compatible solver binary to function.

Pass a string pointing to any AMPL-compatible solver binary as the first positional argument to AmplNLWriter.

For example, if the bonmin executable is on the system path, use:

using JuMP, AmplNLWriter
+model = Model(() -> AmplNLWriter.Optimizer("bonmin"))

If the solver is not on the system path, pass the full path to the solver:

using JuMP, AmplNLWriter
+model = Model(() -> AmplNLWriter.Optimizer("/Users/Oscar/ampl.macos64/bonmin"))

Precompiled binaries

To simplify the process of installing solver binaries, a number of Julia packages provide precompiled binaries that are compatible with AmplNLWriter. These are generally the name of the solver, followed by _jll. For example, bomin is provided by the Bonmin_jll package.

To call Bonmin via AmplNLWriter.jl, install the Bonmin_jll package, then run:

using JuMP, AmplNLWriter, Bonmin_jll
+model = Model(() -> AmplNLWriter.Optimizer(Bonmin_jll.amplexe))

Supported packages include:

SolverJulia PackageExecutable
BonminBonmin_jll.jlBomin_jll.amplexe
CouenneCouenne_jll.jlCouenne_jll.amplexe
IpoptIpopt_jll.jlIpopt_jll.amplexe
SHOTSHOT_jll.jlSHOT_jll.amplexe
KNITROKNITRO.jlKNITRO.amplexe

MathOptInterface API

The AmplNLWriter optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Note that some solver executables may not support the full list of constraint types. For example, Ipopt_jll does not support MOI.Integer or MOI.ZeroOne constraints.

Options

A list of available options for each solver can be found here:

Set an option using set_attribute. For example, to set the "bonmin.nlp_log_level" option to 0 in Bonmin, use:

using JuMP
+import AmplNLWriter
+import Bonmin_jll
+model = Model(() -> AmplNLWriter.Optimizer(Bonmin_jll.amplexe))
+set_attribute(model, "bonmin.nlp_log_level", 0)

opt files

Some options need to be specified via an .opt file.

This file must be located in the current working directory whenever the model is solved.

The .opt file must be named after the name of the solver, for example, bonmin.opt, and each line must contain an option name and the desired value, separated by a space.

For example, to set the absolute and relative tolerances in Couenne to 1 and 0.05 respectively, the couenne.opt file should contain:

allowable_gap 1
+allowable_fraction_gap 0.05
diff --git a/previews/PR3536/packages/BARON/index.html b/previews/PR3536/packages/BARON/index.html new file mode 100644 index 00000000000..4247765c279 --- /dev/null +++ b/previews/PR3536/packages/BARON/index.html @@ -0,0 +1,9 @@ + +jump-dev/BARON.jl · JuMP

BARON.jl

Build Status codecov

BARON.jl is a wrapper for BARON by The Optimization Firm.

Affiliation

This wrapper is maintained by the JuMP community and is not officially supported by The Optimization Firm.

License

BARON.jl is licensed under the MIT License.

The underlying solver is a closed-source commercial product for which you must obtain a license from The Optimization Firm, although a small trial version is available for free.

Installation

First, download a copy of the BARON solver and unpack the executable in a location of your choosing.

Once installed, set the BARON_EXEC environment variable pointing to the BARON executable (full path, including file name as it differs across platforms), and run Pkg.add("BARON"). For example:

ENV["BARON_EXEC"] = "/path/to/baron.exe"
+using Pkg
+Pkg.add("BARON")

Use with JuMP

using JuMP, BARON
+model = Model(BARON.Optimizer)

MathOptInterface API

The BARON optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

diff --git a/previews/PR3536/packages/BilevelJuMP/index.html b/previews/PR3536/packages/BilevelJuMP/index.html new file mode 100644 index 00000000000..288eaf7a588 --- /dev/null +++ b/previews/PR3536/packages/BilevelJuMP/index.html @@ -0,0 +1,37 @@ + +joaquimg/BilevelJuMP.jl · JuMP

BilevelJuMP.jl

Build Status Codecov branch Docs dev Docs stable

BilevelJuMP.jl is a JuMP extension for modeling and solving bilevel optimization problems.

License

BilevelJuMP.jl is licensed under the MIT license.

Documentation

You can find the documentation at https://joaquimg.github.io/BilevelJuMP.jl/stable/.

Help

If you need help, please open a GitHub issue.

Example

Install

import Pkg
+Pkg.add("BilevelJuMP")
+Pkg.add("HiGHS")

Run

using BilevelJuMP, HiGHS
+
+model = BilevelModel(
+    HiGHS.Optimizer,
+    mode = BilevelJuMP.FortunyAmatMcCarlMode(primal_big_M = 100, dual_big_M = 100)
+)
+
+@variable(Lower(model), x)
+@variable(Upper(model), y)
+
+@objective(Upper(model), Min, 3x + y)
+@constraints(Upper(model), begin
+    x <= 5
+    y <= 8
+    y >= 0
+end)
+
+@objective(Lower(model), Min, -x)
+@constraints(Lower(model), begin
+     x +  y <= 8
+    4x +  y >= 8
+    2x +  y <= 13
+    2x - 7y <= 0
+end)
+
+optimize!(model)
+
+objective_value(model) # = 3 * (3.5 * 8/15) + 8/15 # = 6.13...
+value(x) # = 3.5 * 8/15 # = 1.86...
+value(y) # = 8/15 # = 0.53...
diff --git a/previews/PR3536/packages/CDCS/index.html b/previews/PR3536/packages/CDCS/index.html new file mode 100644 index 00000000000..f02d391257d --- /dev/null +++ b/previews/PR3536/packages/CDCS/index.html @@ -0,0 +1,30 @@ + +oxfordcontrol/CDCS.jl · JuMP

CDCS.jl

CDCS.jl is a wrapper for the CDCS solver.

The wrapper has two components:

  • an exported cdcs function that is a thin wrapper on top of the cdcs MATLAB function
  • an interface to MathOptInterface

License

CDCS.jl is licensed under the MIT license.

The underlying solver oxfordcontrol/CDCS is licensed under the LGPL v3 license.

In addition, CDCS requires an installation of MATLAB, which is a closed-source commercial product for which you must obtain a license.

Installation

First, make sure that you satisfy the requirements of the MATLAB.jl Julia package and that the CDCS software is installed in your MATLAB™ installation.

Then, install CDCS.jl using Pkg.add:

import Pkg
+Pkg.add("CDCS")

Use with JuMP

To use CDCS with JuMP, do:

using JuMP, CDCS
+model = Model(CDCS.Optimizer)
+set_attribute(model, "verbose", 0)

Troubleshooting

If you get the error:

Undefined function or variable 'cdcs'.
+
+Error using save
+Variable 'jx_cdcs_arg_out_1' not found.
+
+Linear Programming example: Error During Test at /home/blegat/.julia/dev/CDCS/test/lp.jl:5
+  Got exception outside of a @test
+  MATLAB.MEngineError("failed to get variable jx_cdcs_arg_out_1 from MATLAB session")
+  Stacktrace:
+    [1] get_mvariable(session::MATLAB.MSession, name::Symbol)
+      @ MATLAB ~/.julia/packages/MATLAB/SVjnA/src/engine.jl:164
+    [2] mxcall(::MATLAB.MSession, ::Symbol, ::Int64, ::Matrix{Float64}, ::Vararg{Any})
+      @ MATLAB ~/.julia/packages/MATLAB/SVjnA/src/engine.jl:297
+    [3] mxcall
+      @ ~/.julia/packages/MATLAB/SVjnA/src/engine.jl:317 [inlined]
+    [4] cdcs(A::Matrix{Float64}, b::Vector{Float64}, c::Vector{Float64}, K::CDCS.Cone; kws::Base.Pairs{Symbol, Int64, Tuple{Symbol}, NamedTuple{(:verbose,), Tuple{Int64}}})

The error means that we could not find the cdcs function with one output argument using the MATLAB C API.

This most likely means that you did not add CDCS to the MATLAB's path (that is, the toolbox/local/pathdef.m file).

If modifying toolbox/local/pathdef.m does not work, the following should work where /path/to/CDCS/ is the directory where the CDCS folder is located:

julia> using MATLAB
+
+julia> cd("/path/to/CDCS/") do
+           mat"cdcsInstall"
+       end
+
+julia> mat"savepath"
diff --git a/previews/PR3536/packages/CDDLib/index.html b/previews/PR3536/packages/CDDLib/index.html new file mode 100644 index 00000000000..7c3d46e58f2 --- /dev/null +++ b/previews/PR3536/packages/CDDLib/index.html @@ -0,0 +1,9 @@ + +JuliaPolyhedra/CDDLib.jl · JuMP

CDDLib

CDDLib.jl is a wrapper for cddlib.

CDDLib.jl can be used with C API of cddlib, the higher level interface of Polyhedra.jl, or as a linear programming solver with JuMP or MathOptInterface.

Problem description

As written in the README of cddlib:

The C-library cddlib is a C implementation of the Double Description Method of Motzkin et al. for generating all vertices (that is, extreme points) and extreme rays of a general convex polyhedron in R^d given by a system of linear inequalities:

P = { x=(x1, ..., xd)^T :  b - A  x  >= 0 }

where A is a given m x d real matrix, b is a given m-vector and 0 is the m-vector of all zeros.

The program can be used for the reverse operation (that is, convex hull computation). This means that one can move back and forth between an inequality representation and a generator (that is, vertex and ray) representation of a polyhedron with cdd. Also, cdd can solve a linear programming problem, that is, a problem of maximizing and minimizing a linear function over P.

License

CDDLib.jl is licensed under the GPL v2 license.

The underlying solver, cddlib/cddlib is also licensed under the GPL v2 license.

Installation

Install CDDLib.jl using the Julia package manager:

import Pkg
+Pkg.add("CDDLib")

Building the package will download binaries of cddlib that are provided by cddlib_jll.jl.

Use with JuMP

Use CDDLib.Optimizer{Float64} to use CDDLib.jl with JuMP:

using JuMP, CDDLib
+model = Model(CDDLib.Optimizer{Float64})

When using CDDLib.jl with MathOptInterface, you can pass a different number type:

using MathOptInterface, CDDLib
+model = CDDLib.Optimizer{Rational{BigInt}}()

Debugging

CDDLib.jl uses two global Boolean variables to enable debugging outputs: debug and log.

You can query the value of debug and log with get_debug and get_log, and set their values with set_debug and set_log.

diff --git a/previews/PR3536/packages/COPT/index.html b/previews/PR3536/packages/COPT/index.html new file mode 100644 index 00000000000..e5512e28d87 --- /dev/null +++ b/previews/PR3536/packages/COPT/index.html @@ -0,0 +1,42 @@ + +COPT-Public/COPT.jl · JuMP

COPT.jl

COPT.jl is a wrapper for the COPT (Cardinal Optimizer), a mathematical optimization solver for large-scale optimization problems.

COPT includes high-performance solvers for LP, MIP, SOCP, convex QP/QCP and SDP.

License

COPT.jl is licensed under the MIT license.

The underlying solver is a closed-source commercial product for which you must obtain a license.

Note

When COPT is upgraded to a newer version, you may see an error message such as ERROR: COPT error 4: Unable to create COPT environment, which indicates that you will need to reapply and upgrade your COPT license files as well.

Installation

Install COPT using the Julia package manager

import Pkg
+Pkg.add("COPT")

When there is no local version of COPT installed, installing COPT.jl will automatically download the necessary solver binaries.

Without a license, you can solve small models for non-commercial purpose. We strongly recommend that you apply for a license by following the link above.

Note

MacOS Apple M1/ARM: on MacOS with Apple M1 chips, Intel based programs can run via Rosetta. When you installed the COPT binaries manually, then please make sure that the COPT build matches the Julia build. We recommend the Intel based COPT and Julia build, as the Apple M1/ARM build of Julia is experimental.

Use with JuMP

To use COPT with JuMP, use COPT.Optimizer:

using JuMP
+using COPT
+model = Model(COPT.Optimizer)
+@variable(model, x >= 0)
+@variable(model, 0 <= y <= 3)
+@objective(model, Min, 12x + 20y)
+@constraint(model, c1, 6x + 8y >= 100)
+@constraint(model, c2, 7x + 12y >= 120)
+print(model)
+optimize!(model)
+@show termination_status(model)
+@show primal_status(model)
+@show dual_status(model)
+@show objective_value(model)
+@show value(x)
+@show value(y)
+@show shadow_price(c1)
+@show shadow_price(c2)

To use the semidefinite programming solver in COPT with JuMP, use COPT.ConeOptimizer:

using JuMP
+using COPT
+using LinearAlgebra
+model = Model(COPT.ConeOptimizer)
+C = [1.0 -1.0; -1.0 2.0]
+@variable(model, X[1:2, 1:2], PSD)
+@variable(model, z[1:2] >= 0)
+@objective(model, Min, C ⋅ X)
+@constraint(model, c1, X[1, 1] - z[1] == 1)
+@constraint(model, c2, X[2, 2] - z[2] == 1)
+optimize!(model)
+@show termination_status(model)
+@show primal_status(model)
+@show dual_status(model)
+@show objective_value(model)
+@show value.(X)
+@show value.(z)
+@show shadow_price(c1)
+@show shadow_price(c2)
diff --git a/previews/PR3536/packages/COSMO/index.html b/previews/PR3536/packages/COSMO/index.html new file mode 100644 index 00000000000..119201d306f --- /dev/null +++ b/previews/PR3536/packages/COSMO/index.html @@ -0,0 +1,37 @@ + +oxfordcontrol/COSMO.jl · JuMP
+ +

+ +

+ + + + + +

+ Features • + Installation • + News • + Citing • + Contributing +

This is a Julia implementation of the Conic operator splitting method (COSMO) solver. It can solve large convex conic optimization problems of the following form:

+ +

with decision variables x ϵ R^n, s ϵ R^m and data matrices P=P'>=0, q ϵ R^n, A ϵ R^(m×n), and b ϵ R^m. The convex set K is a composition of convex sets and cones.

For more information take a look at the COSMO.jl Documentation (stable | dev).

Features

  • Versatile: COSMO solves linear programs, quadratic programs, second-order cone programs, semidefinite programs and problems involving exponential and power cones
  • Quad SDPs: Positive semidefinite programs with quadratic objective functions are natively supported
  • Safeguarded acceleration: robust and faster convergence to higher precision using COSMOAccelerators
  • Infeasibility detection: Infeasible problems are detected without a homogeneous self-dual embedding of the problem
  • JuMP / Convex.jl support: We provide an interface to MathOptInterface (MOI), which allows you to describe your problem in JuMP and Convex.jl.
  • Warm starting: COSMO supports warm starting of the decision variables
  • Custom sets and linear solver: Customize COSMO's components by defining your own convex constraint sets and by choosing from a number of direct and indirect linear system solvers, for example, QDLDL, Pardiso, Conjugate Gradient and MINRES
  • Arbitrary precision types: You can solve problems with any floating point precision.
  • Open Source: Our code is free to use and distributed under the Apache 2.0 Licence
  • Chordal decomposition: COSMO tries to decompose large structured PSD constraints using chordal decomposition techniques. This often results in a significant speedup compared to the original problem.
  • Smart clique merging: After an initial decomposition of a structured SDP, COSMO recombines overlapping cliques/blocks to speed up the algorithm.
+ +

Installation

  • COSMO can be added via the Julia package manager (type ]): pkg> add COSMO

Citing

If you find COSMO useful in your project, we kindly request that you cite the following paper:

@Article{Garstka_2021,
+  author  = {Michael Garstka and Mark Cannon and Paul Goulart},
+  journal = {Journal of Optimization Theory and Applications},
+  title   = {{COSMO}: A Conic Operator Splitting Method for Convex Conic Problems},
+  volume  = {190},
+  number  = {3},
+  pages   = {779--810},
+  year    = {2021},
+  publisher = {Springer},
+  doi     = {10.1007/s10957-021-01896-x},
+  url     = {https://doi.org/10.1007/s10957-021-01896-x}
+}

The article is available under Open Access here.

Contributing

  • Contributions are always welcome. Our style guide can be found here.
  • Current issues, tasks and future ideas are listed in Issues. Please report any issues or bugs that you encounter.
  • As an open source project we are also interested in any projects and applications that use COSMO. Please let us know by opening a GitHub issue.

Python - Interface

COSMO can also be called from Python. Take a look at: cosmo-python

Licence 🔍

This project is licensed under the Apache License - see the LICENSE.md file for details.

diff --git a/previews/PR3536/packages/CPLEX/index.html b/previews/PR3536/packages/CPLEX/index.html new file mode 100644 index 00000000000..f853c1411f6 --- /dev/null +++ b/previews/PR3536/packages/CPLEX/index.html @@ -0,0 +1,164 @@ + +jump-dev/CPLEX.jl · JuMP

CPLEX.jl

Build Status codecov

CPLEX.jl is a wrapper for the IBM® ILOG® CPLEX® Optimization Studio.

CPLEX.jl has two components:

The C API can be accessed via CPLEX.CPXxx functions, where the names and arguments are identical to the C API. See the CPLEX documentation for details.

Affiliation

This wrapper is maintained by the JuMP community and is not officially supported by IBM. However, we thank IBM for providing us with a CPLEX license to test CPLEX.jl on GitHub. If you are a commercial customer interested in official support for CPLEX in Julia, let them know.

License

CPLEX.jl is licensed under the MIT License.

The underlying solver is a closed-source commercial product for which you must purchase a license.

Free CPLEX licenses are available for academics and students.

Installation

CPLEX.jl requires CPLEX version 12.10, 20.1, or 22.1.

First, obtain a license of CPLEX and install CPLEX solver, following the instructions on IBM's website.

Once installed, set the CPLEX_STUDIO_BINARIES environment variable as appropriate and run Pkg.add("CPLEX"). For example:

# On Windows, this might be:
+ENV["CPLEX_STUDIO_BINARIES"] = "C:\\Program Files\\CPLEX_Studio1210\\cplex\\bin\\x86-64_win\\"
+# On OSX, this might be:
+ENV["CPLEX_STUDIO_BINARIES"] = "/Applications/CPLEX_Studio1210/cplex/bin/x86-64_osx/"
+# On Unix, this might be:
+ENV["CPLEX_STUDIO_BINARIES"] = "/opt/CPLEX_Studio1210/cplex/bin/x86-64_linux/"
+
+import Pkg
+Pkg.add("CPLEX")
Note

The exact path may differ. Check which folder you installed CPLEX in, and update the path accordingly.

Use with JuMP

Use CPLEX.jl with JuMP as follows:

using JuMP, CPLEX
+model = Model(CPLEX.Optimizer)
+set_attribute(model, "CPX_PARAM_EPINT", 1e-8)

MathOptInterface API

The CPLEX optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

Options match those of the C API in the CPLEX documentation.

Callbacks

CPLEX.jl provides a solver-specific callback to CPLEX:

using JuMP, CPLEX, Test
+
+model = direct_model(CPLEX.Optimizer())
+set_silent(model)
+
+# This is very, very important!!! Only use callbacks in single-threaded mode.
+MOI.set(model, MOI.NumberOfThreads(), 1)
+
+@variable(model, 0 <= x <= 2.5, Int)
+@variable(model, 0 <= y <= 2.5, Int)
+@objective(model, Max, y)
+cb_calls = Clong[]
+function my_callback_function(cb_data::CPLEX.CallbackContext, context_id::Clong)
+    # You can reference variables outside the function as normal
+    push!(cb_calls, context_id)
+    # You can select where the callback is run
+    if context_id != CPX_CALLBACKCONTEXT_CANDIDATE
+        return
+    end
+    ispoint_p = Ref{Cint}()
+    ret = CPXcallbackcandidateispoint(cb_data, ispoint_p)
+    if ret != 0 || ispoint_p[] == 0
+        return  # No candidate point available or error
+    end
+    # You can query CALLBACKINFO items
+    valueP = Ref{Cdouble}()
+    ret = CPXcallbackgetinfodbl(cb_data, CPXCALLBACKINFO_BEST_BND, valueP)
+    @info "Best bound is currently: $(valueP[])"
+    # As well as any other C API
+    x_p = Vector{Cdouble}(undef, 2)
+    obj_p = Ref{Cdouble}()
+    ret = CPXcallbackgetincumbent(cb_data, x_p, 0, 1, obj_p)
+    if ret == 0
+        @info "Objective incumbent is: $(obj_p[])"
+        @info "Incumbent solution is: $(x_p)"
+        # Use CPLEX.column to map between variable references and the 1-based
+        # column.
+        x_col = CPLEX.column(cb_data, index(x))
+        @info "x = $(x_p[x_col])"
+    else
+        # Unable to query incumbent.
+    end
+
+    # Before querying `callback_value`, you must call:
+    CPLEX.load_callback_variable_primal(cb_data, context_id)
+    x_val = callback_value(cb_data, x)
+    y_val = callback_value(cb_data, y)
+    # You can submit solver-independent MathOptInterface attributes such as
+    # lazy constraints, user-cuts, and heuristic solutions.
+    if y_val - x_val > 1 + 1e-6
+        con = @build_constraint(y - x <= 1)
+        MOI.submit(model, MOI.LazyConstraint(cb_data), con)
+    elseif y_val + x_val > 3 + 1e-6
+        con = @build_constraint(y + x <= 3)
+        MOI.submit(model, MOI.LazyConstraint(cb_data), con)
+    end
+end
+MOI.set(model, CPLEX.CallbackFunction(), my_callback_function)
+optimize!(model)
+@test termination_status(model) == MOI.OPTIMAL
+@test primal_status(model) == MOI.FEASIBLE_POINT
+@test value(x) == 1
+@test value(y) == 2

Annotations for automatic Benders' decomposition

Here is an example of using the annotation feature for automatic Benders' decomposition:

using JuMP, CPLEX
+
+function add_annotation(
+    model::JuMP.Model,
+    variable_classification::Dict;
+    all_variables::Bool = true,
+)
+    num_variables = sum(length(it) for it in values(variable_classification))
+    if all_variables
+        @assert num_variables == JuMP.num_variables(model)
+    end
+    indices, annotations = CPXINT[], CPXLONG[]
+    for (key, value) in variable_classification
+        for variable_ref in value
+            push!(indices, variable_ref.index.value - 1)
+            push!(annotations, CPX_BENDERS_MASTERVALUE + key)
+        end
+    end
+    cplex = backend(model)
+    index_p = Ref{CPXINT}()
+    CPXnewlongannotation(
+        cplex.env,
+        cplex.lp,
+        CPX_BENDERS_ANNOTATION,
+        CPX_BENDERS_MASTERVALUE,
+    )
+    CPXgetlongannotationindex(
+        cplex.env,
+        cplex.lp,
+        CPX_BENDERS_ANNOTATION,
+        index_p,
+    )
+    CPXsetlongannotations(
+        cplex.env,
+        cplex.lp,
+        index_p[],
+        CPX_ANNOTATIONOBJ_COL,
+        length(indices),
+        indices,
+        annotations,
+    )
+    return
+end
+
+# Problem
+
+function illustrate_full_annotation()
+    c_1, c_2 = [1, 4], [2, 3]
+    dim_x, dim_y = length(c_1), length(c_2)
+    b = [-2; -3]
+    A_1, A_2 = [1 -3; -1 -3], [1 -2; -1 -1]
+    model = JuMP.direct_model(CPLEX.Optimizer())
+    set_optimizer_attribute(model, "CPXPARAM_Benders_Strategy", 1)
+    @variable(model, x[1:dim_x] >= 0, Bin)
+    @variable(model, y[1:dim_y] >= 0)
+    variable_classification = Dict(0 => [x[1], x[2]], 1 => [y[1], y[2]])
+    @constraint(model, A_2 * y + A_1 * x .<= b)
+    @objective(model, Min, c_1' * x + c_2' * y)
+    add_annotation(model, variable_classification)
+    optimize!(model)
+    x_optimal = value.(x)
+    y_optimal = value.(y)
+    println("x: $(x_optimal), y: $(y_optimal)")
+end
+
+function illustrate_partial_annotation()
+    c_1, c_2 = [1, 4], [2, 3]
+    dim_x, dim_y = length(c_1), length(c_2)
+    b = [-2; -3]
+    A_1, A_2 = [1 -3; -1 -3], [1 -2; -1 -1]
+    model = JuMP.direct_model(CPLEX.Optimizer())
+    # Note that the "CPXPARAM_Benders_Strategy" has to be set to 2 if partial
+    # annotation is provided. If "CPXPARAM_Benders_Strategy" is set to 1, then
+    # the following error will be thrown:
+    # `CPLEX Error  2002: Invalid Benders decomposition.`
+    set_optimizer_attribute(model, "CPXPARAM_Benders_Strategy", 2)
+    @variable(model, x[1:dim_x] >= 0, Bin)
+    @variable(model, y[1:dim_y] >= 0)
+    variable_classification = Dict(0 => [x[1]], 1 => [y[1], y[2]])
+    @constraint(model, A_2 * y + A_1 * x .<= b)
+    @objective(model, Min, c_1' * x + c_2' * y)
+    add_annotation(model, variable_classification; all_variables = false)
+    optimize!(model)
+    x_optimal = value.(x)
+    y_optimal = value.(y)
+    println("x: $(x_optimal), y: $(y_optimal)")
+end
diff --git a/previews/PR3536/packages/CSDP/index.html b/previews/PR3536/packages/CSDP/index.html new file mode 100644 index 00000000000..c3b685b8e2f --- /dev/null +++ b/previews/PR3536/packages/CSDP/index.html @@ -0,0 +1,13 @@ + +jump-dev/CSDP.jl · JuMP

CSDP.jl

Build Status codecov

CSDP.jl is a wrapper for the COIN-OR SemiDefinite Programming solver.

The wrapper has two components:

Affiliation

This wrapper is maintained by the JuMP community and is not a COIN-OR project.

The original algorithm is described by B. Borchers (1999). CSDP, A C Library for Semidefinite Programming. Optimization Methods and Software. 11(1), 613-623. [preprint]

License

CSDP.jl is licensed under the MIT License.

The underlying solver, coin-or/Csdp, is licensed under the Eclipse public license.

Installation

Install CSDP using Pkg.add:

import Pkg
+Pkg.add("CSDP")

In addition to installing the CSDP.jl package, this will also download and install the CSDP binaries. You do not need to install CSDP separately.

Use with JuMP

To use CSDP with JuMP, use CSDP.Optimizer:

using JuMP, CSDP
+model = Model(CSDP.Optimizer)
+set_attribute(model, "maxiter", 1000)

MathOptInterface API

The CSDP optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

The CSDP options are listed in the table below.

NameDefault ValueExplanation
axtol1.0e-8Tolerance for primal feasibility
atytol1.0e-8Tolerance for dual feasibility
objtol1.0e-8Tolerance for relative duality gap
pinftol1.0e8Tolerance for determining primal infeasibility
dinftol1.0e8Tolerance for determining dual infeasibility
maxiter100Limit for the total number of iterations
minstepfrac0.90The minstepfrac and maxstepfrac parameters determine how close to the edge of the feasible region CSDP will step
maxstepfrac0.97The minstepfrac and maxstepfrac parameters determine how close to the edge of the feasible region CSDP will step
minstepp1.0e-8If the primal step is shorter than minstepp then CSDP declares a line search failure
minstepd1.0e-8If the dual step is shorter than minstepd then CSDP declares a line search failure
usexzgap1If usexzgap is 0 then CSDP will use the objective duality gap d - p instead of the XY duality gap ⟨Z, X⟩
tweakgap0If tweakgap is set to 1, and usexzgap is set to 0, then CSDP will attempt to "fix" negative duality gaps
affine0If affine is set to 1, then CSDP will take only primal-dual affine steps and not make use of the barrier term. This can be useful for some problems that do not have feasible solutions that are strictly in the interior of the cone of semidefinite matrices
perturbobj1The perturbobj parameter determines whether the objective function will be perturbed to help deal with problems that have unbounded optimal solution sets. If perturbobj is 0, then the objective will not be perturbed. If perturbobj is 1, then the objective function will be perturbed by a default amount. Larger values of perturbobj (for example, 100) increase the size of the perturbation. This can be helpful in solving some difficult problems.
fastmode0The fastmode parameter determines whether or not CSDP will skip certain time consuming operations that slightly improve the accuracy of the solutions. If fastmode is set to 1, then CSDP may be somewhat faster, but also somewhat less accurate
printlevel1The printlevel parameter determines how much debugging information is output. Use a printlevel of 0 for no output and a printlevel of 1 for normal output. Higher values of printlevel will generate more debugging output

Problem representation

The primal is represented internally by CSDP as follows:

max ⟨C, X⟩
+      A(X) = a
+         X ⪰ 0

where A(X) = [⟨A_1, X⟩, ..., ⟨A_m, X⟩]. The corresponding dual is:

min ⟨a, y⟩
+     A'(y) - C = Z
+             Z ⪰ 0

where A'(y) = y_1A_1 + ... + y_mA_m

Termination criteria

CSDP will terminate successfully (or partially) in the following cases:

  • If CSDP finds X, Z ⪰ 0 such that the following 3 tolerances are satisfied:
    • primal feasibility tolerance: ||A(x) - a||_2 / (1 + ||a||_2) < axtol
    • dual feasibility tolerance: ||A'(y) - C - Z||_F / (1 + ||C||_F) < atytol
    • relative duality gap tolerance: gap / (1 + |⟨a, y⟩| + |⟨C, X⟩|) < objtol
      • objective duality gap: if usexygap is 0, gap = ⟨a, y⟩ - ⟨C, X⟩
      • XY duality gap: if usexygap is 1, gap = ⟨Z, X⟩
    then it returns 0.
  • If CSDP finds y and Z ⪰ 0 such that -⟨a, y⟩ / ||A'(y) - Z||_F > pinftol, it returns 1 with y such that ⟨a, y⟩ = -1.
  • If CSDP finds X ⪰ 0 such that ⟨C, X⟩ / ||A(X)||_2 > dinftol, it returns 2 with X such that ⟨C, X⟩ = 1.
  • If CSDP finds X, Z ⪰ 0 such that the following 3 tolerances are satisfied with 1000*axtol, 1000*atytol and 1000*objtol but at least one of them is not satisfied with axtol, atytol and objtol and cannot make progress, then it returns 3.

In addition, if the printlevel option is at least 1, the following will be printed:

  • If the return code is 1, CSDP will print ⟨a, y⟩ and ||A'(y) - Z||_F
  • If the return code is 2, CSDP will print ⟨C, X⟩ and ||A(X)||_F
  • Otherwise, CSDP will print
    • the primal/dual objective value,
    • the relative primal/dual infeasibility,
    • the objective duality gap ⟨a, y⟩ - ⟨C, X⟩ and objective relative duality gap (⟨a, y⟩ - ⟨C, X⟩) / (1 + |⟨a, y⟩| + |⟨C, X⟩|),
    • the XY duality gap ⟨Z, X⟩ and XY relative duality gap ⟨Z, X⟩ / (1 + |⟨a, y⟩| + |⟨C, X⟩|)
    • and the DIMACS error measures.

In theory, for feasible primal and dual solutions, ⟨a, y⟩ - ⟨C, X⟩ = ⟨Z, X⟩, so the objective and XY duality gap should be equivalent. However, in practice, there are sometimes solution which satisfy primal and dual feasibility tolerances but have objective duality gap which are not close to XY duality gap. In some cases, the objective duality gap may even become negative (hence the tweakgap option). This is the reason usexygap is 1 by default.

CSDP considers that X ⪰ 0 (resp. Z ⪰ 0) is satisfied when the Cholesky factorizations can be computed. In practice, this is somewhat more conservative than simply requiring all eigenvalues to be nonnegative.

Status

The table below shows how the different CSDP statuses are converted to the MathOptInterface statuses.

CSDP codeStateDescriptionMOI status
0SuccessSDP solvedMOI.OPTIMAL
1SuccessThe problem is primal infeasible, and we have a certificateMOI.INFEASIBLE
2SuccessThe problem is dual infeasible, and we have a certificateMOI.DUAL_INFEASIBLE
3Partial SuccessA solution has been found, but full accuracy was not achievedMOI.ALMOST_OPTIMAL
4FailureMaximum iterations reachedMOI.ITERATION_LIMIT
5FailureStuck at edge of primal feasibilityMOI.SLOW_PROGRESS
6FailureStuck at edge of dual infeasibilityMOI.SLOW_PROGRESS
7FailureLack of progressMOI.SLOW_PROGRESS
8FailureX, Z, or O was singularMOI.NUMERICAL_ERROR
9FailureDetected NaN or Inf valuesMOI.NUMERICAL_ERROR
diff --git a/previews/PR3536/packages/Cbc/index.html b/previews/PR3536/packages/Cbc/index.html new file mode 100644 index 00000000000..6bc9cc7a93e --- /dev/null +++ b/previews/PR3536/packages/Cbc/index.html @@ -0,0 +1,12 @@ + +jump-dev/Cbc.jl · JuMP

Cbc.jl

Build Status codecov

Cbc.jl is a wrapper for the COIN-OR Branch and Cut (Cbc) solver.

The wrapper has two components:

Affiliation

This wrapper is maintained by the JuMP community and is not a COIN-OR project.

License

Cbc.jl is licensed under the MIT License.

The underlying solver, coin-or/Cbc, is licensed under the Eclipse public license.

Installation

Install Cbc using Pkg.add:

import Pkg
+Pkg.add("Cbc")

In addition to installing the Cbc.jl package, this will also download and install the Cbc binaries. You do not need to install Cbc separately.

To use a custom binary, read the Custom solver binaries section of the JuMP documentation.

Use with JuMP

To use Cbc with JuMP, use Cbc.Optimizer:

using JuMP, Cbc
+model = Model(Cbc.Optimizer)
+set_attribute(model, "logLevel", 1)

MathOptInterface API

The COIN Branch-and-Cut (Cbc) optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

List of supported optimizer attributes:

List of supported variable attributes:

List of supported constraint attributes:

Options

Options are, unfortunately, not well documented.

The following options are likely to be the most useful:

ParameterExampleExplanation
seconds60.0Solution timeout limit
logLevel2Set to 0 to disable solution output
maxSolutions1Terminate after this many feasible solutions have been found
maxNodes1Terminate after this many branch-and-bound nodes have been evaluated
allowableGap0.05Terminate after optimality gap is less than this value (on an absolute scale)
ratioGap0.05Terminate after optimality gap is smaller than this relative fraction
threads1Set the number of threads to use for parallel branch & bound

The complete list of parameters can be found by running the cbc executable and typing ? at the prompt.

Start the cbc executable from Julia as follows:

using Cbc_jll
+Cbc_jll.cbc() do exe
+    run(`$(exe)`)
+end
diff --git a/previews/PR3536/packages/Clarabel/index.html b/previews/PR3536/packages/Clarabel/index.html new file mode 100644 index 00000000000..d07b317a64c --- /dev/null +++ b/previews/PR3536/packages/Clarabel/index.html @@ -0,0 +1,26 @@ + +oxfordcontrol/Clarabel.jl · JuMP

+ + +
+Interior Point Conic Optimization for Julia +

+ + + + + +

+ Features • + Installation • + License • + Documentation +

Clarabel.jl is a Julia implementation of an interior point numerical solver for convex optimization problems using a novel homogeneous embedding. Clarabel.jl solves the following problem:

\[\begin{array}{r} +\text{minimize} & \frac{1}{2}x^T P x + q^T x\\\\[2ex] +\text{subject to} & Ax + s = b \\\\[1ex] + & s \in \mathcal{K} +\end{array}\]

with decision variables $x \in \mathbb{R}^n$, $s \in \mathbb{R}^m$ and data matrices $P=P^\top \succeq 0$, $q \in \mathbb{R}^n$, $A \in \mathbb{R}^{m \times n}$, and $b \in \mathbb{R}^m$. The convex set $\mathcal{K}$ is a composition of convex cones.

For more information see the Clarabel Documentation (stable | dev).

Clarabel is also available in a Rust / Python implementation. See here.

Features

  • Versatile: Clarabel.jl solves linear programs (LPs), quadratic programs (QPs), second-order cone programs (SOCPs) and semidefinite programs (SDPs). It also solves problems with exponential and power cone constraints.
  • Quadratic objectives: Unlike interior point solvers based on the standard homogeneous self-dual embedding (HSDE), Clarabel.jl handles quadratic objectives without requiring any epigraphical reformulation of the objective. It can therefore be significantly faster than other HSDE-based solvers for problems with quadratic objective functions.
  • Infeasibility detection: Infeasible problems are detected using a homogeneous embedding technique.
  • JuMP / Convex.jl support: We provide an interface to MathOptInterface (MOI), which allows you to describe your problem in JuMP and Convex.jl.
  • Arbitrary precision types: You can solve problems with any floating point precision, for example, Float32 or Julia's BigFloat type, using either the native interface, or via MathOptInterface / Convex.jl.
  • Open Source: Our code is available on GitHub and distributed under the Apache 2.0 License

Installation

  • Clarabel.jl can be added via the Julia package manager (type ]): pkg> add Clarabel

License 🔍

This project is licensed under the Apache License 2.0 - see the LICENSE.md file for details.

diff --git a/previews/PR3536/packages/Clp/index.html b/previews/PR3536/packages/Clp/index.html new file mode 100644 index 00000000000..5bb34c06f6a --- /dev/null +++ b/previews/PR3536/packages/Clp/index.html @@ -0,0 +1,10 @@ + +jump-dev/Clp.jl · JuMP

Clp.jl

Build Status codecov

Clp.jl is a wrapper for the COIN-OR Linear Programming solver.

The wrapper has two components:

Affiliation

This wrapper is maintained by the JuMP community and is not a COIN-OR project.

License

Clp.jl is licensed under the MIT License.

The underlying solver, coin-or/Clp, is licensed under the Eclipse public license.

Installation

Install Clp using Pkg.add:

import Pkg
+Pkg.add("Clp")

In addition to installing the Clp.jl package, this will also download and install the Clp binaries. You do not need to install Clp separately.

To use a custom binary, read the Custom solver binaries section of the JuMP documentation.

Use with JuMP

To use Clp with JuMP, use Clp.Optimizer:

using JuMP, Clp
+model = Model(Clp.Optimizer)
+set_attribute(model, "LogLevel", 1)
+set_attribute(model, "Algorithm", 4)

MathOptInterface API

The Clp optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

Options are, unfortunately, not well documented.

The following options are likely to be the most useful:

ParameterExampleExplanation
PrimalTolerance1e-7Primal feasibility tolerance
DualTolerance1e-7Dual feasibility tolerance
DualObjectiveLimit1e308When using dual simplex (where the objective is monotonically changing), terminate when the objective exceeds this limit
MaximumIterations2147483647Terminate after performing this number of simplex iterations
MaximumSeconds-1.0Terminate after this many seconds have passed. A negative value means no time limit
LogLevel1Set to 1, 2, 3, or 4 for increasing output. Set to 0 to disable output
PresolveType0Set to 1 to disable presolve
SolveType5Solution method: dual simplex (0), primal simplex (1), sprint (2), barrier with crossover (3), barrier without crossover (4), automatic (5)
InfeasibleReturn0Set to 1 to return as soon as the problem is found to be infeasible (by default, an infeasibility proof is computed as well)
Scaling30 -off, 1 equilibrium, 2 geometric, 3 auto, 4 dynamic(later)
Perturbation100switch on perturbation (50), automatic (100), don't try perturbing (102)

C API

The C API can be accessed via Clp.Clp_XXX functions, where the names and arguments are identical to the C API.

diff --git a/previews/PR3536/packages/DAQP/index.html b/previews/PR3536/packages/DAQP/index.html new file mode 100644 index 00000000000..3b0fea553bc --- /dev/null +++ b/previews/PR3536/packages/DAQP/index.html @@ -0,0 +1,8 @@ + +darnstrom/DAQP.jl · JuMP

DAQP.jl

DAQP.jl is a Julia wrapper for the Quadratic Programming solver DAQP.

License

DAQP.jl is licensed under the MIT license.

The underlying solver, darnstrom/daqp is licensed under the MIT license.

Installation

Install DAQP.jl using the Julia package manager:

import Pkg
+Pkg.add("DAQP")

Use with JuMP

To use DAQP with JuMP, do:

using JuMP, DAQP
+model = Model(DAQP.Optimizer)

Documentation

General information about the solver is available at https://darnstrom.github.io/daqp/, and specifics for the Julia interface are available at https://darnstrom.github.io/daqp/start/julia.

diff --git a/previews/PR3536/packages/DiffOpt/index.html b/previews/PR3536/packages/DiffOpt/index.html new file mode 100644 index 00000000000..c300539892c --- /dev/null +++ b/previews/PR3536/packages/DiffOpt/index.html @@ -0,0 +1,23 @@ + +jump-dev/DiffOpt.jl · JuMP

DiffOpt.jl

stable docs development docs Build Status Coverage

DiffOpt.jl is a package for differentiating convex optimization programs with respect to the program parameters. DiffOpt currently supports linear, quadratic, and conic programs.

License

DiffOpt.jl is licensed under the MIT License.

Installation

Install DiffOpt using Pkg.add:

import Pkg
+Pkg.add("DiffOpt")

Documentation

The documentation for DiffOpt.jl includes a detailed description of the theory behind the package, along with examples, tutorials, and an API reference.

Use with JuMP

Use DiffOpt with JuMP by following this brief example:

using JuMP, DiffOpt, HiGHS
+# Create a model using the wrapper
+model = Model(() -> DiffOpt.diff_optimizer(HiGHS.Optimizer))
+# Define your model and solve it
+@variable(model, x)
+@constraint(model, cons, x >= 3)
+@objective(model, Min, 2x)
+optimize!(model)
+# Choose the problem parameters to differentiate with respect to, and set their
+# perturbations.
+MOI.set(model, DiffOpt.ReverseVariablePrimal(), x, 1.0)
+# Differentiate the model
+DiffOpt.reverse_differentiate!(model)
+# fetch the gradients
+grad_exp = MOI.get(model, DiffOpt.ReverseConstraintFunction(), cons)  # -3 x - 1
+constant(grad_exp)        # -1
+coefficient(grad_exp, x)  # -3

GSOC2020

DiffOpt began as a NumFOCUS sponsored Google Summer of Code (2020) project

diff --git a/previews/PR3536/packages/Dualization/index.html b/previews/PR3536/packages/Dualization/index.html new file mode 100644 index 00000000000..47ffb22de1c --- /dev/null +++ b/previews/PR3536/packages/Dualization/index.html @@ -0,0 +1,13 @@ + +jump-dev/Dualization.jl · JuMP

Dualization.jl

Build Status codecov DOI

Dualization.jl is an extension package for MathOptInterface.jl that formulates the dual of conic optimization problems.

Dualization.jl has two main features:

  • The Dualization.dualize function that computes the dual formulation of either a MathOptInterface.jl or a JuMP model.
  • The Dualization.dual_optimizer function that creates a MathOptInterface-compatible optimizer that solves the dual of the problem instead of the primal.

License

Dualization.jl is licensed under the MIT License.

Installation

Install Dualization using Pkg.add:

import Pkg
+Pkg.add("Dualization")

Use with JuMP

To compute the dual formulation of a JuMP model, use dualize:

using JuMP, Dualization
+model = Model()
+# ... build model ...
+dual_model = dualize(model)

To solve the dual formulation of a JuMP model, create a dual_optimizer:

using JuMP, Dualization, SCS
+model = Model(dual_optimizer(SCS.Optimizer))
+# ... build model ...
+optimize!(model)  # Solves the dual instead of the primal

Documentation

The documentation for Dualization.jl includes a detailed description of the dual reformulation, along with examples and an API reference.

diff --git a/previews/PR3536/packages/ECOS/index.html b/previews/PR3536/packages/ECOS/index.html new file mode 100644 index 00000000000..183f1e9951a --- /dev/null +++ b/previews/PR3536/packages/ECOS/index.html @@ -0,0 +1,9 @@ + +jump-dev/ECOS.jl · JuMP

ECOS.jl

Build Status codecov

ECOS.jl is a wrapper for the ECOS solver.

The wrapper has two components:

Affiliation

This wrapper is maintained by the JuMP community and is not a product of Embotech AG.

License

ECOS.jl is licensed under the MIT License.

The underlying solver, embotech/ecos, is licensed under the GPL v3 license.

Installation

Install ECOS.jl using Pkg.add:

import Pkg
+Pkg.add("ECOS")

In addition to installing the ECOS.jl package, this will also download and install the ECOS binaries. You do not need to install ECOS separately.

To use a custom binary, read the Custom solver binaries section of the JuMP documentation.

Use with JuMP

To use ECOS with JuMP, use ECOS.Optimizer:

using JuMP, ECOS
+model = Model(ECOS.Optimizer)
+set_attribute(model, "maxit", 100)

MathOptInterface API

The ECOS optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

The following options are supported:

ParameterExplanation
gammascaling the final step length
deltaregularization parameter
epsregularization threshold
feastolprimal/dual infeasibility tolerance
abstolabsolute tolerance on duality gap
reltolrelative tolerance on duality gap
feastol_inaccprimal/dual infeasibility relaxed tolerance
abstol_inaccabsolute relaxed tolerance on duality gap
reltol_inaccrelative relaxed tolerance on duality gap
nitrefnumber of iterative refinement steps
maxitmaximum number of iterations
verboseverbosity bool for PRINTLEVEL < 3
diff --git a/previews/PR3536/packages/GAMS/index.html b/previews/PR3536/packages/GAMS/index.html new file mode 100644 index 00000000000..55e8a6f9520 --- /dev/null +++ b/previews/PR3536/packages/GAMS/index.html @@ -0,0 +1,25 @@ + +GAMS-dev/GAMS.jl · JuMP

GAMS.jl

GAMS.jl provides a MathOptInterface Optimizer to solve JuMP models using GAMS.

GAMS comes with dozens of supported solvers. Among them are: ALPHAECP, ANTIGONE, BARON, CBC, CONOPT, CPLEX, DICOPT, GUROBI, IPOPT, KNITRO, LINDO, LINDOGLOBAL, MINOS, MOSEK, NLPEC, PATH, QUADMINOS, SBB, SHOT, SCIP, SNOPT, SOPLEX, XPRESS. Find a complete list here.

GAMS.jl supports the following JuMP features:

  • linear, quadratic and nonlinear (convex and non-convex) objective and constraints
  • continuous, binary, integer, semi-continuous and semi-integer variables
  • SOS1 and SOS2 sets
  • complementarity constraints

Installation

  1. Download GAMS and obtain a GAMS license. Please note that GAMS also offers a free community license.
  2. (optional) Add the GAMS system directory to the PATH variable in order to find GAMS automatically.
  3. Install GAMS.jl using the Julia package manager:
    using Pkg
    +Pkg.add("GAMS")

Usage

Using GAMS as optimizer for your JuMP model:

using GAMS, JuMP
+model = Model(GAMS.Optimizer)

GAMS System

If the GAMS system directory has been added to the PATH variable (you can check this with print(ENV["PATH"])), GAMS.jl will find it automatically. Otherwise, or if you like to switch between systems, the system directory can be specified by (one of the following):

set_optimizer_attribute(model, "SysDir", "<gams_system_dir>")
+set_optimizer_attribute(model, GAMS.SysDir(), "<gams_system_dir>")

Analogously, you can specify a working directory with "WorkDir" or GAMS.WorkDir(). If no working directory has been set, GAMS.jl will create a temporary one.

If you want to use the same GAMS workspace (same system and working directory) for multiple models, you can create a GAMSWorkspace first with either of the following

ws = GAMS.GAMSWorkspace()
+ws = GAMS.GAMSWorkspace("<gams_system_dir>")
+ws = GAMS.GAMSWorkspace("<gams_system_dir>", "<gams_working_dir>")

and then pass it to your models:

model = Model(() -> GAMS.Optimizer(ws))

GAMS Options

GAMS command line options can be specified by

set_optimizer_attribute(model, "<option>", "<solver_name>")
+set_optimizer_attribute(model, GAMS.<option>(), "<solver_name>")

where <option> is either HoldFixed, IterLim, License, LogOption, NodLim, OptCA, OptCR, ResLim, Solver, Threads, Trace, TraceOpt as well as LP, MIP, RMIP, NLP, DNLP, CNS, MINLP, RMINLP, QCP, MIQCP, RMIQCP, MCP or MPEC. Note that GAMS.ResLim() is equivalent to MOI.TimeLimitSec() and GAMS.Threads() to MOI.NumberOfThreads(). Options LimCol, LimRow, SolPrint and SolveLink cannot be changed and are set to 0, 0, 0 and 5, respectively.

Model Type

GAMS.jl will automatically choose a GAMS model type for you. Choosing a different model type:

set_optimizer_attribute(model, GAMS.ModelType(), "<model_type>")

GAMS Solver Options

Specifying GAMS solver options:

set_optimizer_attribute(model, "<solver_option_name>", <option_value>)

Note that passing a solver option is only valid when explicitly choosing a GAMS solver and not using the default.

GAMS Names vs. JuMP Names

GAMS uses generated variable and constraint names although it is possible to pass the JuMP names to the GAMS optimizer, because GAMS is more restrictive when it comes to variable and constraint naming. Use the attributes GeneratedVariableName, GeneratedConstraintName, OriginalVariableName, OriginalConstraintName to query a GAMS symbol name from a JuMP symbol and vice versa. This can help for debugging, for example in case of GAMS compilation errors. For example:

using GAMS
+
+model = direct_model(GAMS.Optimizer())
+
+@variable(model, x[1:2,1:3] >= 0)
+@constraint(model, c[i = 1:2], sum(x[i,j] for j = 1:3) <= 10)
+
+MOI.get(model, GAMS.GeneratedVariableName(), x[2,2]) # returns x4
+MOI.get(model, GAMS.OriginalVariableName("x6"))      # returns x[2,3]
+MOI.get(model, GAMS.OriginalVariableName("x10"))     # returns nothing
+
+MOI.get(model, GAMS.GeneratedConstraintName(), c[2]) # returns eq2
+MOI.get(model, GAMS.OriginalConstraintName("eq1"))   # returns c[1]
+MOI.get(model, GAMS.OriginalConstraintName("eq10"))  # returns nothing

Note that JuMP direct-mode is used.

diff --git a/previews/PR3536/packages/GLPK/index.html b/previews/PR3536/packages/GLPK/index.html new file mode 100644 index 00000000000..911cfb4905a --- /dev/null +++ b/previews/PR3536/packages/GLPK/index.html @@ -0,0 +1,39 @@ + +jump-dev/GLPK.jl · JuMP

GLPK.jl

Build Status codecov

GLPK.jl is a wrapper for the GNU Linear Programming Kit library.

The wrapper has two components:

Affiliation

This wrapper is maintained by the JuMP community and is not an GNU project.

License

GLPK.jl is licensed under the GPL v3 license.

Installation

Install GLPK using Pkg.add:

import Pkg
+Pkg.add("GLPK")

In addition to installing the GLPK.jl package, this will also download and install the GLPK binaries. You do not need to install GLPK separately.

To use a custom binary, read the Custom solver binaries section of the JuMP documentation.

Use with JuMP

To use GLPK with JuMP, use GLPK.Optimizer:

using JuMP, GLPK
+model = Model(GLPK.Optimizer)
+set_attribute(model, "tm_lim", 60 * 1_000)
+set_attribute(model, "msg_lev", GLPK.GLP_MSG_OFF)

If the model is primal or dual infeasible, GLPK will attempt to find a certificate of infeasibility. This can be expensive, particularly if you do not intend to use the certificate. If this is the case, use:

model = Model(() -> GLPK.Optimizer(; want_infeasibility_certificates = false))

MathOptInterface API

The GLPK optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

Options for GLPK are comprehensively documented in the PDF documentation, but they are hard to find.

  • Options when solving a linear program are defined in Section 2.8.1
  • Options when solving a mixed-integer program are defined in Section 2.10.5

However, the following options are likely to be the most useful:

ParameterExampleExplanation
msg_levGLPK.GLP_MSG_ALLMessage level for terminal output
presolveGLPK.GLP_ONTurn presolve on or off
tol_int1e-5Absolute tolerance for integer feasibility
tol_obj1e-7Relative objective tolerance for mixed-integer programs

Callbacks

Here is an example using GLPK's solver-specific callbacks.

using JuMP, GLPK, Test
+
+model = Model(GLPK.Optimizer)
+@variable(model, 0 <= x <= 2.5, Int)
+@variable(model, 0 <= y <= 2.5, Int)
+@objective(model, Max, y)
+reasons = UInt8[]
+function my_callback_function(cb_data)
+    reason = GLPK.glp_ios_reason(cb_data.tree)
+    push!(reasons, reason)
+    if reason != GLPK.GLP_IROWGEN
+        return
+    end
+    x_val = callback_value(cb_data, x)
+    y_val = callback_value(cb_data, y)
+    if y_val - x_val > 1 + 1e-6
+        con = @build_constraint(y - x <= 1)
+        MOI.submit(model, MOI.LazyConstraint(cb_data), con)
+    elseif y_val + x_val > 3 + 1e-6
+        con = @build_constraint(y - x <= 1)
+        MOI.submit(model, MOI.LazyConstraint(cb_data), con)
+    end
+end
+MOI.set(model, GLPK.CallbackFunction(), my_callback_function)
+optimize!(model)
+@test termination_status(model) == MOI.OPTIMAL
+@test primal_status(model) == MOI.FEASIBLE_POINT
+@test value(x) == 1
+@test value(y) == 2
+@show reasons

C API

The C API can be accessed via GLPK.glp_XXX functions, where the names and arguments are identical to the C API. See the /tests folder for inspiration.

diff --git a/previews/PR3536/packages/Gurobi/index.html b/previews/PR3536/packages/Gurobi/index.html new file mode 100644 index 00000000000..ab513a0309d --- /dev/null +++ b/previews/PR3536/packages/Gurobi/index.html @@ -0,0 +1,97 @@ + +jump-dev/Gurobi.jl · JuMP

Gurobi.jl

Build Status codecov

Gurobi.jl is a wrapper for the Gurobi Optimizer.

It has two components:

Affiliation

This wrapper is maintained by the JuMP community and is not officially supported by Gurobi. However, we thank Gurobi for providing us with a license to test Gurobi.jl on GitHub. If you are a commercial customer interested in official support for Gurobi in Julia, let them know.

License

Gurobi.jl is licensed under the MIT License.

The underlying solver is a closed-source commercial product for which you must obtain a license.

Free Gurobi licenses are available for academics and students.

Installation

First, obtain a license of Gurobi and install Gurobi solver.

Then, set the GUROBI_HOME environment variable as appropriate and run Pkg.add("Gurobi"):

# On Windows, this might be
+ENV["GUROBI_HOME"] = "C:\\Program Files\\gurobi1000\\win64"
+# ... or perhaps ...
+ENV["GUROBI_HOME"] = "C:\\gurobi1000\\win64"
+# On Mac, this might be
+ENV["GUROBI_HOME"] = "/Library/gurobi1000/mac64"
+
+import Pkg
+Pkg.add("Gurobi")

Note: your path may differ. Check which folder you installed Gurobi in, and update the path accordingly.

By default, building Gurobi.jl will fail if the Gurobi library is not found. This may not be desirable in certain cases, for example when part of a package's test suite uses Gurobi as an optional test dependency, but Gurobi cannot be installed on a CI server running the test suite. To support this use case, the GUROBI_JL_SKIP_LIB_CHECK environment variable may be set (to any value) to make Gurobi.jl installable (but not usable).

Use with JuMP

To use Gurobi with JuMP, use Gurobi.Optimizer:

using JuMP, Gurobi
+model = Model(Gurobi.Optimizer)
+set_attribute(model, "TimeLimit", 100)
+set_attribute(model, "Presolve", 0)

MathOptInterface API

The Gurobi optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

See the Gurobi Documentation for a list and description of allowable parameters.

C API

The C API can be accessed via Gurobi.GRBxx functions, where the names and arguments are identical to the C API.

See the Gurobi documentation for details.

Reusing the same Gurobi environment for multiple solves

When using this package via other packages such as JuMP.jl, the default behavior is to obtain a new Gurobi license token every time a model is created. If you are using Gurobi in a setting where the number of concurrent Gurobi uses is limited (for example, "Single-Use" or "Floating-Use" licenses), you might instead prefer to obtain a single license token that is shared by all models that your program solves.

You can do this by passing a Gurobi.Env() object as the first parameter to Gurobi.Optimizer. For example:

using JuMP, Gurobi
+const GRB_ENV = Gurobi.Env()
+
+model_1 = Model(() -> Gurobi.Optimizer(GRB_ENV))
+
+# The solvers can have different options too
+model_2 = direct_model(Gurobi.Optimizer(GRB_ENV))
+set_attribute(model_2, "OutputFlag", 0)

Accessing Gurobi-specific attributes

Get and set Gurobi-specific variable, constraint, and model attributes as follows:

using JuMP, Gurobi
+model = direct_model(Gurobi.Optimizer())
+@variable(model, x >= 0)
+@constraint(model, c, 2x >= 1)
+@objective(model, Min, x)
+MOI.set(model, Gurobi.ConstraintAttribute("Lazy"), c, 2)
+optimize!(model)
+MOI.get(model, Gurobi.VariableAttribute("LB"), x)  # Returns 0.0
+MOI.get(model, Gurobi.ModelAttribute("NumConstrs")) # Returns 1

A complete list of supported Gurobi attributes can be found in their online documentation.

Callbacks

Here is an example using Gurobi's solver-specific callbacks.

using JuMP, Gurobi, Test
+
+model = direct_model(Gurobi.Optimizer())
+@variable(model, 0 <= x <= 2.5, Int)
+@variable(model, 0 <= y <= 2.5, Int)
+@objective(model, Max, y)
+cb_calls = Cint[]
+function my_callback_function(cb_data, cb_where::Cint)
+    # You can reference variables outside the function as normal
+    push!(cb_calls, cb_where)
+    # You can select where the callback is run
+    if cb_where != GRB_CB_MIPSOL && cb_where != GRB_CB_MIPNODE
+        return
+    end
+    # You can query a callback attribute using GRBcbget
+    if cb_where == GRB_CB_MIPNODE
+        resultP = Ref{Cint}()
+        GRBcbget(cb_data, cb_where, GRB_CB_MIPNODE_STATUS, resultP)
+        if resultP[] != GRB_OPTIMAL
+            return  # Solution is something other than optimal.
+        end
+    end
+    # Before querying `callback_value`, you must call:
+    Gurobi.load_callback_variable_primal(cb_data, cb_where)
+    x_val = callback_value(cb_data, x)
+    y_val = callback_value(cb_data, y)
+    # You can submit solver-independent MathOptInterface attributes such as
+    # lazy constraints, user-cuts, and heuristic solutions.
+    if y_val - x_val > 1 + 1e-6
+        con = @build_constraint(y - x <= 1)
+        MOI.submit(model, MOI.LazyConstraint(cb_data), con)
+    elseif y_val + x_val > 3 + 1e-6
+        con = @build_constraint(y + x <= 3)
+        MOI.submit(model, MOI.LazyConstraint(cb_data), con)
+    end
+    if rand() < 0.1
+        # You can terminate the callback as follows:
+        GRBterminate(backend(model))
+    end
+    return
+end
+# You _must_ set this parameter if using lazy constraints.
+MOI.set(model, MOI.RawOptimizerAttribute("LazyConstraints"), 1)
+MOI.set(model, Gurobi.CallbackFunction(), my_callback_function)
+optimize!(model)
+@test termination_status(model) == MOI.OPTIMAL
+@test primal_status(model) == MOI.FEASIBLE_POINT
+@test value(x) == 1
+@test value(y) == 2

See the Gurobi documentation for other information that can be queried with GRBcbget.

Common Performance Pitfall with JuMP

Gurobi's API works differently than most solvers. Any changes to the model are not applied immediately, but instead go sit in a internal buffer (making any modifications appear to be instantaneous) waiting for a call to GRBupdatemodel (where the work is done).

This leads to a common performance pitfall that has the following message as its main symptom:

Warning: excessive time spent in model updates. Consider calling update less frequently.

This often means the JuMP program was structured in such a way that Gurobi.jl ends up calling GRBupdatemodel in each iteration of a loop.

Usually, it is possible (and easy) to restructure the JuMP program in a way it stays ssolver-agnostic and has a close-to-ideal performance with Gurobi.

To guide such restructuring it is good to keep in mind the following bits of information:

  1. GRBupdatemodel is only called if changes were done since last GRBupdatemodel (that is, if the internal buffer is not empty).
  2. GRBupdatemodel is called when JuMP.optimize! is called, but this often is not the source of the problem.
  3. GRBupdatemodel may be called when any model attribute is queried, even if that specific attribute was not changed. This often the source of the problem.

The worst-case scenario is, therefore, a loop of modify-query-modify-query, even if what is being modified and what is being queried are two completely distinct things.

As an example, instead of:

model = Model(Gurobi.Optimizer)
+@variable(model, x[1:100] >= 0)
+for i in 1:100
+    set_upper_bound(x[i], i)
+    # `GRBupdatemodel` called on each iteration of this loop.
+    println(lower_bound(x[i]))
+end

do

model = Model(Gurobi.Optimizer)
+@variable(model, x[1:100] >= 0)
+# All modifications are done before any queries.
+for i in 1:100
+    set_upper_bound(x[i], i)
+end
+for i in 1:100
+    # Only the first `lower_bound` query may trigger an `GRBupdatemodel`.
+    println(lower_bound(x[i]))
+end

Common errors

Using Gurobi v9.0 and you got an error like Q not PSD?

You need to set the NonConvex parameter:

model = Model(Gurobi.Optimizer)
+set_optimizer_attribute(model, "NonConvex", 2)

Gurobi Error 1009: Version number is XX.X, license is for version XX.X

Make sure that your license is correct for your Gurobi version. See the Gurobi documentation for details.

Once you are sure that the license and Gurobi versions match, re-install Gurobi.jl by running:

import Pkg
+Pkg.build("Gurobi")
diff --git a/previews/PR3536/packages/HiGHS/index.html b/previews/PR3536/packages/HiGHS/index.html new file mode 100644 index 00000000000..625b421719f --- /dev/null +++ b/previews/PR3536/packages/HiGHS/index.html @@ -0,0 +1,10 @@ + +jump-dev/HiGHS.jl · JuMP

HiGHS.jl

Build Status codecov

HiGHS.jl is a wrapper for the HiGHS solver.

It has two components:

Affiliation

This wrapper is maintained by the JuMP community and is not an official project of the HiGHS developers.

License

HiGHS.jl is licensed under the MIT License.

The underlying solver, ERGO-Code/HiGHS, is licensed under the MIT license.

Installation

Install HiGHS as follows:

import Pkg
+Pkg.add("HiGHS")

In addition to installing the HiGHS.jl package, this will also download and install the HiGHS binaries. You do not need to install HiGHS separately.

To use a custom binary, read the Custom solver binaries section of the JuMP documentation.

Use with JuMP

To use HiGHS with JuMP, use HiGHS.Optimizer:

using JuMP, HiGHS
+model = Model(HiGHS.Optimizer)
+set_attribute(model, "presolve", "on")
+set_attribute(model, "time_limit", 60.0)

MathOptInterface API

The HiGHS optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

See the HiGHS documentation for a full list of the available options.

C API

The C API can be accessed via HiGHS.Highs_xxx functions, where the names and arguments are identical to the C API.

diff --git a/previews/PR3536/packages/Hypatia/index.html b/previews/PR3536/packages/Hypatia/index.html new file mode 100644 index 00000000000..1f3b48d62f2 --- /dev/null +++ b/previews/PR3536/packages/Hypatia/index.html @@ -0,0 +1,45 @@ + +chriscoey/Hypatia.jl · JuMP
Hypatia logo

Build Status codecov

Hypatia is a highly customizable open source interior point solver for generic conic optimization problems, written in Julia.

For more information on Hypatia, please see:

and preprints of our papers:

and corresponding raw results CSV files generated by our run scripts in the benchmarks folder.

License

Hypatia is licensed under the MIT License (see LICENSE).

Installation

To use Hypatia, install Julia, then at the Julia REPL, type:

using Hypatia
+using Pkg
+Pkg.add("Hypatia")

Hypatia is an experimental solver and a work in progress, and may not run with older releases of Julia. Default options/parameters are not well-tuned, so we encourage you to experiment with these.

Usage

Hypatia can be accessed through a low-level native Julia interface or through open-source modeling tools such as JuMP and Convex.jl. The native interface is more expressive, allowing Hypatia to solve conic models expressed with generic real floating point types and structured matrices or linear operators, for example. However, it is typically sufficient and more convenient to use JuMP.

Using JuMP, we can model a simple D-optimal experiment design problem and call Hypatia:

using LinearAlgebra
+using JuMP
+using Hypatia
+
+model = Model(() -> Hypatia.Optimizer(verbose = false))
+@variable(model, x[1:3] >= 0)
+@constraint(model, sum(x) == 5)
+@variable(model, hypo)
+@objective(model, Max, hypo)
+V = rand(2, 3)
+Q = V * diagm(x) * V'
+aff = vcat(hypo, [Q[i, j] for i in 1:2 for j in 1:i]...)
+@constraint(model, aff in MOI.RootDetConeTriangle(2))
+
+# solve and query solution
+optimize!(model)
+termination_status(model)
+objective_value(model)
+value.(x)

See our D-optimal design example for more information and references.

Many more examples using the native interface or JuMP can be found in the examples folder.

Contributing

Comments, questions, suggestions, and improvements/extensions to the code or documentation are welcomed. Please reach out on Discourse, or submit an issue or contribute a PR on our GitHub. If contributing code, try to maintain consistent style and add docstrings or comments for clarity. New examples are welcomed and should be implemented similarly to the existing examples.

Acknowledgements

This work has been partially funded by the National Science Foundation under grant OAC-1835443 and the Office of Naval Research under grant N00014-18-1-2079.

Citing Hypatia

If you find Hypatia solver useful, please cite our solver paper:

@article{coey2022solving,
+    title={Solving natural conic formulations with {H}ypatia.jl},
+    author={Chris Coey and Lea Kapelevich and Juan Pablo Vielma},
+    year={2022},
+    journal={INFORMS Journal on Computing},
+    publisher={INFORMS},
+    volume={34},
+    number={5},
+    pages={2686--2699},
+    doi={https://doi.org/10.1287/ijoc.2022.1202}
+}

If you find aspects of Hypatia's IPM implementation useful, please cite our algorithm paper:

@article{coey2022performance,
+    title={Performance enhancements for a generic conic interior point algorithm},
+    author={Chris Coey and Lea Kapelevich and Juan Pablo Vielma},
+    year={2023},
+    journal={Mathematical Programming Computation},
+    publisher={Springer},
+    volume={15},
+    pages={53--101},
+    doi={https://doi.org/10.1007/s12532-022-00226-0}
+}
diff --git a/previews/PR3536/packages/InfiniteOpt/index.html b/previews/PR3536/packages/InfiniteOpt/index.html new file mode 100644 index 00000000000..5b3bf6a6dfa --- /dev/null +++ b/previews/PR3536/packages/InfiniteOpt/index.html @@ -0,0 +1,15 @@ + +infiniteopt/InfiniteOpt.jl · JuMP

Logo

InfiniteOpt.jl is a JuMP extension for expressing and solving infinite-dimensional optimization problems. Such areas include stochastic programming, dynamic programming, space-time optimization, and more. InfiniteOpt serves as an easy-to-use modeling interface for these advanced problem types that can be used by those with little background in these areas. It also it contains a wealth of capabilities making it a powerful and convenient tool for advanced users.

Current VersionDocumentationBuild StatusCitation
Build Status codecov.ioDOI

InfiniteOpt builds upon JuMP to add support for many complex modeling objects which include:

  • Infinite parameters (for example, time, space, and/or uncertainty)
  • Finite parameters (similar to ParameterJuMP)
  • Infinite variables (decision functions) (for example, $y(t, x)$)
  • Derivatives (for example, $\frac{\partial y(t, x)}{\partial t}$)
  • Measures (for example, $\int_{t \in T}y(t, x)dt$ and $\mathbb{E}[y(\xi)]$)
  • First class nonlinear modeling

The unifying modeling abstraction behind InfiniteOpt captures a wide spectrum of disciplines which include dynamic, PDE, stochastic, and semi-infinite optimization. Moreover, we facilitate transferring techniques between these to synthesize new optimization paradigms.

License

InfiniteOpt is licensed under the MIT "Expat" license.

Installation

InfiniteOpt.jl is a registered Julia package and can be installed by entering the following in the REPL.

julia> import Pkg; Pkg.add("InfiniteOpt")

Documentation

Please visit our documentation pages to learn more. These pages are quite extensive and feature overviews, guides, manuals, tutorials, examples, and more.

Questions

For additional help please visit and post in our discussion forum.

Citing

DOI DOI

If you use InfiniteOpt.jl in your research, we would greatly appreciate your citing it.

@article{pulsipher2022unifying,
+      title = {A unifying modeling abstraction for infinite-dimensional optimization},
+      journal = {Computers & Chemical Engineering},
+      volume = {156},
+      year = {2022},
+      issn = {0098-1354},
+      doi = {https://doi.org/10.1016/j.compchemeng.2021.107567},
+      url = {https://www.sciencedirect.com/science/article/pii/S0098135421003458},
+      author = {Joshua L. Pulsipher and Weiqi Zhang and Tyler J. Hongisto and Victor M. Zavala},
+}

A pre-print version is freely available though arXiv.

diff --git a/previews/PR3536/packages/Ipopt/index.html b/previews/PR3536/packages/Ipopt/index.html new file mode 100644 index 00000000000..647dbc6ac63 --- /dev/null +++ b/previews/PR3536/packages/Ipopt/index.html @@ -0,0 +1,118 @@ + +jump-dev/Ipopt.jl · JuMP

Ipopt.jl

Build Status codecov

Ipopt.jl is a wrapper for the Ipopt solver.

Affiliation

This wrapper is maintained by the JuMP community and is not a COIN-OR project.

License

Ipopt.jl is licensed under the MIT License.

The underlying solver, coin-or/Ipopt, is licensed under the Eclipse public license.

Installation

Install Ipopt.jl using the Julia package manager:

import Pkg
+Pkg.add("Ipopt")

In addition to installing the Ipopt.jl package, this will also download and install the Ipopt binaries. You do not need to install Ipopt separately.

To use a custom binary, read the Custom solver binaries section of the JuMP documentation.

For details on using a different linear solver, see the Linear Solvers section below. You do not need a custom binary to change the linear solver.

Use with JuMP

You can use Ipopt with JuMP as follows:

using JuMP, Ipopt
+model = Model(Ipopt.Optimizer)
+set_attribute(model, "max_cpu_time", 60.0)
+set_attribute(model, "print_level", 0)

MathOptInterface API

The Ipopt optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

Supported options are listed in the Ipopt documentation.

Solver-specific callbacks

Ipopt provides a callback that can be used to log the status of the optimization during a solve. It can also be used to terminate the optimization by returning false. Here is an example:

using JuMP, Ipopt, Test
+model = Model(Ipopt.Optimizer)
+set_silent(model)
+@variable(model, x >= 1)
+@objective(model, Min, x + 0.5)
+x_vals = Float64[]
+function my_callback(
+   alg_mod::Cint,
+   iter_count::Cint,
+   obj_value::Float64,
+   inf_pr::Float64,
+   inf_du::Float64,
+   mu::Float64,
+   d_norm::Float64,
+   regularization_size::Float64,
+   alpha_du::Float64,
+   alpha_pr::Float64,
+   ls_trials::Cint,
+)
+   push!(x_vals, callback_value(model, x))
+   @test isapprox(obj_value, 1.0 * x_vals[end] + 0.5, atol = 1e-1)
+   # return `true` to keep going, or `false` to terminate the optimization.
+   return iter_count < 1
+end
+MOI.set(model, Ipopt.CallbackFunction(), my_callback)
+optimize!(model)
+@test MOI.get(model, MOI.TerminationStatus()) == MOI.INTERRUPTED
+@test length(x_vals) == 2

See the Ipopt documentation for an explanation of the arguments to the callback. They are identical to the output contained in the logging table printed to the screen.

To access the current solution and primal, dual, and complementarity violations of each iteration, use Ipopt.GetIpoptCurrentViolations and Ipopt.GetIpoptCurrentIterate. The two functions are identical to the ones in the Ipopt C interface.

C API

Ipopt.jl wraps the Ipopt C interface with minimal modifications.

A complete example is available in the test/C_wrapper.jl file.

For simplicity, the five callbacks required by Ipopt are slightly different to the C interface. They are as follows:

"""
+   eval_f(x::Vector{Float64})::Float64
+
+Returns the objective value `f(x)`.
+"""
+function eval_f end
+
+"""
+   eval_grad_f(x::Vector{Float64}, grad_f::Vector{Float64})::Nothing
+
+Fills `grad_f` in-place with the gradient of the objective function evaluated at
+`x`.
+"""
+function eval_grad_f end
+
+"""
+   eval_g(x::Vector{Float64}, g::Vector{Float64})::Nothing
+
+Fills `g` in-place with the value of the constraints evaluated at `x`.
+"""
+function eval_g end
+
+"""
+   eval_jac_g(
+      x::Vector{Float64},
+      rows::Vector{Cint},
+      cols::Vector{Cint},
+      values::Union{Nothing,Vector{Float64}},
+   )::Nothing
+
+Compute the Jacobian matrix.
+
+* If `values === nothing`
+   - Fill `rows` and `cols` with the 1-indexed sparsity structure
+* Otherwise:
+   - Fill `values` with the elements of the Jacobian matrix according to the
+     sparsity structure.
+
+!!! warning
+    If `values === nothing`, `x` is an undefined object. Accessing any elements
+    in it will cause Julia to segfault.
+"""
+function eval_jac_g end
+
+"""
+   eval_h(
+      x::Vector{Float64},
+      rows::Vector{Cint},
+      cols::Vector{Cint},
+      obj_factor::Float64,
+      lambda::Float64,
+      values::Union{Nothing,Vector{Float64}},
+   )::Nothing
+
+Compute the Hessian-of-the-Lagrangian matrix.
+
+* If `values === nothing`
+   - Fill `rows` and `cols` with the 1-indexed sparsity structure
+* Otherwise:
+   - Fill `values` with the Hessian matrix according to the sparsity structure.
+
+!!! warning
+    If `values === nothing`, `x` is an undefined object. Accessing any elements
+    in it will cause Julia to segfault.
+"""
+function eval_h end

INVALID_MODEL error

If you get a termination status MOI.INVALID_MODEL, it is probably because you have some undefined value in your model, for example, a division by zero. Fix this by removing the division, or by imposing variable bounds so that you cut off the undefined region.

Instead of

model = Model(Ipopt.Optimizer)
+@variable(model, x)
+@NLobjective(model, 1 / x)

do

model = Model(Ipopt.Optimizer)
+@variable(model, x >= 0.0001)
+@NLobjective(model, 1 / x)

Linear Solvers

To improve performance, Ipopt supports a number of linear solvers.

HSL

Obtain a license and download HSL_jll.jl from https://licences.stfc.ac.uk/product/julia-hsl.

There are two versions available: LBT and OpenBLAS. LBT is the recommended option for Julia ≥ v1.9.

Install this download into your current environment using:

import Pkg
+Pkg.develop(path = "/full/path/to/HSL_jll.jl")

Then, use a linear solver in HSL by setting the hsllib and linear_solver attributes:

using JuMP, Ipopt
+import HSL_jll
+model = Model(Ipopt.Optimizer)
+set_attribute(model, "hsllib", HSL_jll.libhsl_path)
+set_attribute(model, "linear_solver", "ma86")

macOS users

Due to the security policy of macOS, Mac users may need to delete the quarantine attribute of the ZIP archive before extracting. For example:

xattr -d com.apple.quarantine lbt_HSL_jll.jl-2023.5.26.zip
+xattr -d com.apple.quarantine openblas_HSL_jll.jl-2023.5.26.zip

Pardiso

Download Pardiso from https://www.pardiso-project.org. Save the shared library somewhere, and record the filename.

Then, use Pardiso by setting the pardisolib and linear_solver attributes:

using JuMP, Ipopt
+model = Model(Ipopt.Optimizer)
+set_attribute(model, "pardisolib", "/full/path/to/libpardiso")
+set_attribute(model, "linear_solver", "pardiso")

SPRAL

If you use Ipopt.jl with Julia ≥ v1.9, the linear solver SPRAL is available. You can use it by setting the linear_solver attribute:

using JuMP, Ipopt
+model = Model(Ipopt.Optimizer)
+set_attribute(model, "linear_solver", "spral")

Note that the following environment variables must be set before starting Julia:

export OMP_CANCELLATION=TRUE
+export OMP_PROC_BIND=TRUE
diff --git a/previews/PR3536/packages/Juniper/index.html b/previews/PR3536/packages/Juniper/index.html new file mode 100644 index 00000000000..de88f6beef2 --- /dev/null +++ b/previews/PR3536/packages/Juniper/index.html @@ -0,0 +1,36 @@ + +lanl-ansi/Juniper.jl · JuMP

Juniper

CI codecov Documentation

Juniper (Jump Nonlinear Integer Program solver) is a solver for mixed-integer nonlinear programs.

It is a heuristic which is not guaranteed to find the global optimum. If you need the global optimum, check out Alpine.

Installation

Install Juniper using the Julia package manager:

import Pkg
+Pkg.add("JuMP")

Use with JuMP

Use Juniper with JuMP as follows:

using JuMP, Juniper, Ipopt
+ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level"=>0)
+optimizer = optimizer_with_attributes(Juniper.Optimizer, "nl_solver"=>ipopt)
+model = Model(optimizer)
+v = [10, 20, 12, 23, 42]
+w = [12, 45, 12, 22, 21]
+@variable(model, x[1:5], Bin)
+@objective(model, Max, v' * x)
+@constraint(model, sum(w[i]*x[i]^2 for i in 1:5) <= 45)
+optimize!(model)
+println(termination_status(model))
+println(objective_value(model))
+println(value.(x))

The nl_solver is used by Juniper to solve continuous nonlinear sub-problems while Juniper searches for acceptable assignments to the discrete variables. A common choice is Ipopt, but any optimizer that supports the continuous relaxation of the model may be used.

To solve problems with more complex nonlinear functions, use the @NLconstraint and @NLobjective JuMP macros.

Documentation

The online documentation is available at https://lanl-ansi.github.io/Juniper.jl/stable/.

Feasibility pump

If Juniper has difficulty finding feasible solutions on your model, try adding a solver that supports integer variables (for example, HiGHS) to run a feasibility pump:

using JuMP, Juniper, Ipopt, HiGHS
+ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0)
+highs = optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false)
+model = Model(
+    optimizer_with_attributes(
+        Juniper.Optimizer,
+        "nl_solver" => ipopt,
+        "mip_solver" => highs,
+    ),
+)

The feasibility pump is used at the start of Juniper to find a feasible solution before the branch and bound part starts. For some classes of problems this can be a highly effective pre-processor.

Citing Juniper

If you find Juniper useful in your work, we kindly request that you cite the following paper or technical report:

@inproceedings{juniper,
+     Author = {Ole Kröger and Carleton Coffrin and Hassan Hijazi and Harsha Nagarajan},
+     Title = {Juniper: An Open-Source Nonlinear Branch-and-Bound Solver in Julia},
+     booktitle="Integration of Constraint Programming, Artificial Intelligence, and Operations Research",
+     pages="377--386",
+     year="2018",
+     publisher="Springer International Publishing",
+     isbn="978-3-319-93031-2"
+}
diff --git a/previews/PR3536/packages/KNITRO/index.html b/previews/PR3536/packages/KNITRO/index.html new file mode 100644 index 00000000000..5a6c3612508 --- /dev/null +++ b/previews/PR3536/packages/KNITRO/index.html @@ -0,0 +1,13 @@ + +jump-dev/KNITRO.jl · JuMP

KNITRO.jl

KNITRO.jl is a wrapper for the Artelys Knitro solver.

It has two components:

Affiliation

This wrapper is maintained by the JuMP community with help from Artelys.

Contact Artelys support if you encounter any problem with this interface or the solver.

License

KNITRO.jl is licensed under the MIT License.

The underlying solver is a closed-source commercial product for which you must purchase a license.

Installation

First, obtain a license and install a copy of KNITRO from Artelys.

Then, install KNITRO.jl using the Julia package manager:

import Pkg
+Pkg.add("KNITRO")

If you are having trouble installing KNITRO.jl, here are several things to try:

  • Make sure that you have defined your global variables correctly, for example with export KNITRODIR="/path/to/knitro-vXXX-$OS-64" and export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$KNITRODIR/lib". You can check that KNITRO.jl sees your library with using KNITRO; KNITRO.has_knitro().
  • If KNITRO.has_knitro() returns false but you are confident that your paths are correct, try running Pkg.build("KNITRO") and restarting Julia. In at least one user's experience, installing and using KNITRO in a temporary Julia environment (activated with ] activate --temp) does not work and the need to manually build is likely the reason why.

Use with JuMP

To use KNITRO with JuMP, use KNITRO.Optimizer:

using JuMP, KNITRO
+model = Model(KNITRO.Optimizer)
+set_attribute(model, "outlev", 1)
+set_attribute(model, "algorithm", 4)

Use with AMPL

To use KNITRO with AmplNLWriter.jl, use KNITRO.amplexe:

using JuMP
+import AmplNLWriter
+import KNITRO
+model = Model(() -> AmplNLWriter.Optimizer(KNITRO.amplexe, ["outlev=3"]))

Use with other packages

A variety of packages extend KNITRO.jl to support other optimization modeling systems. These include:

MathOptInterface API

The Knitro optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

A list of available options is provided in the KNITRO reference manual.

Low-level wrapper

KNITRO.jl implements most of Knitro's functionalities. If you aim at using part of Knitro's API that are not implemented in the MathOptInterface/JuMP ecosystem, you can refer to the low-level API, which wraps Knitro's C API (whose templates are specified in the file knitro.h).

Extensive examples using the C wrapper can be found in examples/.

Multi-threading

Due to limitations in the interaction between Julia and C, KNITRO.jl disables multi-threading if the problem is nonlinear. This will override any options such as par_numthreads that you may have set. Read GitHub issue #93 for more details.

diff --git a/previews/PR3536/packages/Loraine/index.html b/previews/PR3536/packages/Loraine/index.html new file mode 100644 index 00000000000..7b780d0ffdc --- /dev/null +++ b/previews/PR3536/packages/Loraine/index.html @@ -0,0 +1,32 @@ + +kocvara/Loraine.jl · JuMP

Loraine.jl

Sweet Lor(r)aine, let the party carry on[1]...

Loraine.jl is a Julia implementation of an interior point method algorithm for linear semidefinite optimization problems.

The special feature of Loraine is the iterative solver for linear systems. This is to be used for problems with (very) low rank solution matrix.

Standard (non-low-rank) problems and linear programs can be solved using the direct solver; then the user gets a standard IP method akin SDPT3.

There is also a MATLAB version of the code at kocvara/Loraine.m.

License and Original Contributors

Loraine is licensed under the MIT License.

Loraine was developed by Soodeh Habibi and Michal Kočvara, University of Birmingham, and Michael Stingl, University of Erlangen, for H2020 ITN POEMA.

The JuMP interface was provided by Benoît Legat. His help is greatly acknowledged.

Installation

Install Loraine using Pkg.add:

import Pkg
+Pkg.add("Loraine")

Use with JuMP

To use Loraine with JuMP, use Loraine.Optimizer:

using JuMP, Loraine
+model = Model(Loraine.Optimizer)
+set_attribute(model, "maxit", 100)

To solve an SDP problem stored in SDPA format, do

using JuMP, Loraine
+model = read_from_file("examples/data/theta1.dat-s")
+set_optimizer(model, Loraine.Optimizer)
+optimize!(model)

For more examples, the folder examples includes a few examples of how to use Loraine via JuMP; in particular, solve_sdpa.jl reads an SDP in the SDPA input format and solves it by Loraine. A few sample problems can be found in folder examples/data.

Rank-one data

If the solution does not have low rank, it is recommended to use a direct solver kit = 0. However, if you know that your data matrices are all rank-one, use the option datarank = -1 to get a significant reduction in the complexity (and CPU time). Examples of such problems are maxG11 and thetaG11 from the SDPLIB collection.

Options

The list of options:

kit             # kit = 0 for direct solver; kit = 1 for CG [0]
+tol_cg          # tolerance for CG solver [1.0e-2]
+tol_cg_up       # tolerance update [0.5]
+tol_cg_min      # minimal tolerance for CG solver [1.0e-6]
+eDIMACS         # epsilon for DIMACS error stopping criterion [1.0e-5]
+preconditioner  # 0...no; 1...H_alpha; 2...H_beta; 4...hybrid [1]
+erank           # estimated rank [1]
+aamat           # 0..A^TA; 1..diag(A^TA); 2..identity [2]
+verb            # 2..full output; 1..short output; 0..no output [1]
+datarank        # 0..full rank matrices expected [0]
+                # -1..rank-1 matrices expected, converted to vectors, if possible
+                # (TBD) 1..vectors expected for low-rank data matrices
+initpoint       # 0..Loraine heuristics, 1..SDPT3-like heuristics [0]
+timing          # 1..yes, 0..no
+maxit           # maximal number of global iterations [200]

Citing

If you find Loraine useful, please cite the following paper:

@article{loraine2023,
+  title={Loraine-An interior-point solver for low-rank semidefinite programming},
+  author={Habibi, Soodeh and Ko{\v{c}}vara, Michal and Stingl, Michael},
+  www={https://hal.science/hal-04076509/}
+  note={Preprint hal-04076509}
+  year={2023}
+}
  • 1https://www.youtube.com/watch?v=0D2wNf1lVrI
diff --git a/previews/PR3536/packages/MadNLP/index.html b/previews/PR3536/packages/MadNLP/index.html new file mode 100644 index 00000000000..2af925922d5 --- /dev/null +++ b/previews/PR3536/packages/MadNLP/index.html @@ -0,0 +1,48 @@ + +MadNLP/MadNLP.jl · JuMP
DocumentationBuild StatusCoverageDOI
docbuildcodecovDOI

MadNLP is a nonlinear programming (NLP) solver, purely implemented in Julia. MadNLP implements a filter line-search algorithm, as that used in Ipopt. MadNLP seeks to streamline the development of modeling and algorithmic paradigms in order to exploit structures and to make efficient use of high-performance computers.

License

MadNLP is available under the MIT license.

Installation

pkg> add MadNLP

Optionally, various extension packages can be installed together:

pkg> add MadNLPHSL, MadNLPPardiso, MadNLPMumps, MadNLPGPU, MadNLPGraph, MadNLPKrylov

These packages are stored in the lib subdirectory within the main MadNLP repository. Some extension packages may require additional dependencies or specific hardware. For the instructions for the build procedure, see the following links:

Usage

Interfaces

MadNLP is interfaced with modeling packages:

Users can pass various options to MadNLP also through the modeling packages. The interface-specific syntax are shown below. To see the list of MadNLP solver options, check the OPTIONS.md file.

JuMP interface

using MadNLP, JuMP
+model = Model(()->MadNLP.Optimizer(print_level=MadNLP.INFO, max_iter=100))
+@variable(model, x, start = 0.0)
+@variable(model, y, start = 0.0)
+@NLobjective(model, Min, (1 - x)^2 + 100 * (y - x^2)^2)
+optimize!(model)

NLPModels interface

using MadNLP, CUTEst
+model = CUTEstModel("PRIMALC1")
+madnlp(model, print_level=MadNLP.WARN, max_wall_time=3600)

Plasmo interface (requires extension MadNLPGraph)

using MadNLP, MadNLPGraph, Plasmo
+graph = OptiGraph()
+@optinode(graph,n1)
+@optinode(graph,n2)
+@variable(n1,0 <= x <= 2)
+@variable(n1,0 <= y <= 3)
+@constraint(n1,x+y <= 4)
+@objective(n1,Min,x)
+@variable(n2,x)
+@NLnodeconstraint(n2,exp(x) >= 2)
+@linkconstraint(graph,n1[:x] == n2[:x])
+MadNLP.optimize!(graph; print_level=MadNLP.DEBUG, max_iter=100)

Linear Solvers

MadNLP is interfaced with non-Julia sparse/dense linear solvers:

Each linear solver in MadNLP is a Julia type, and the linear_solver option should be specified by the actual type. Note that the linear solvers are always exported to Main.

Built-in Solvers: Umfpack, PardisoMKL, LapackCPU

using MadNLP, JuMP
+# ...
+model = Model(()->MadNLP.Optimizer(linear_solver=UmfpackSolver)) # default
+model = Model(()->MadNLP.Optimizer(linear_solver=LapackCPUSolver))

HSL (requires extension MadNLPHSL)

using MadNLP, MadNLPHSL, JuMP
+# ...
+model = Model(()->MadNLP.Optimizer(linear_solver=Ma27Solver))
+model = Model(()->MadNLP.Optimizer(linear_solver=Ma57Solver))
+model = Model(()->MadNLP.Optimizer(linear_solver=Ma77Solver))
+model = Model(()->MadNLP.Optimizer(linear_solver=Ma86Solver))
+model = Model(()->MadNLP.Optimizer(linear_solver=Ma97Solver))

Mumps (requires extension MadNLPMumps)

using MadNLP, MadNLPMumps, JuMP
+# ...
+model = Model(()->MadNLP.Optimizer(linear_solver=MumpsSolver))

Pardiso (requires extension MadNLPPardiso)

using MadNLP, MadNLPPardiso, JuMP
+# ...
+model = Model(()->MadNLP.Optimizer(linear_solver=PardisoSolver))
+model = Model(()->MadNLP.Optimizer(linear_solver=PardisoMKLSolver))

LapackGPU (requires extension MadNLPGPU)

using MadNLP, MadNLPGPU, JuMP
+# ...
+model = Model(()->MadNLP.Optimizer(linear_solver=LapackGPUSolver))

Schur and Schwarz (requires extension MadNLPGraph)

using MadNLP, MadNLPGraph, JuMP
+# ...
+model = Model(()->MadNLP.Optimizer(linear_solver=MadNLPSchwarz))
+model = Model(()->MadNLP.Optimizer(linear_solver=MadNLPSchur))

The solvers in MadNLPGraph (Schur and Schwarz) use multi-thread parallelism; thus, Julia session should be started with -t flag.

julia -t 16 # to use 16 threads

Citing MadNLP.jl

If you use MadNLP.jl in your research, we would greatly appreciate your citing it.

@article{shin2020graph,
+  title={Graph-Based Modeling and Decomposition of Energy Infrastructures},
+  author={Shin, Sungho and Coffrin, Carleton and Sundar, Kaarthik and Zavala, Victor M},
+  journal={arXiv preprint arXiv:2010.02404},
+  year={2020}
+}

Bug reports and support

Please report issues and feature requests via the GitHub issue tracker.

diff --git a/previews/PR3536/packages/MiniZinc/index.html b/previews/PR3536/packages/MiniZinc/index.html new file mode 100644 index 00000000000..f135e3c43cc --- /dev/null +++ b/previews/PR3536/packages/MiniZinc/index.html @@ -0,0 +1,57 @@ + +jump-dev/MiniZinc.jl · JuMP

MiniZinc.jl

MiniZinc.jl is a wrapper for the MiniZinc constraint modeling language.

It provides a way to write MathOptInterface models to .mzn files, and a way to interact with libminizinc.

Affiliation

This wrapper is maintained by the JuMP community and is not part of the MiniZinc project.

License

MiniZinc.jl is licensed under the MIT License.

The underlying project, MiniZinc/libminizinc, is licensed under the MPL 2.0 license.

Install

Install MiniZinc.jl using the Julia package manager:

import Pkg
+Pkg.add("MiniZinc")

Windows

On Linux and macOS, this package automatically installs libminizinc. However, we're still working out problems with the install on Windows. To use MiniZinc.jl, you'll need to manually install a copy of libminizinc from minizinc.org or compile one yourself from MiniZinc/libminizinc.

To teach MiniZinc.jl where to look for libminizinc, set the JULIA_LIBMINIZINC_DIR environment variable. For example:

ENV["JULIA_LIBMINIZINC_DIR"] = "C:\\Program Files\\MiniZinc"

Use with MathOptInterface

MiniZinc.jl supports the constraint programming sets defined in MathOptInterface, as well as (in)equality constraints.

The following example solves the following constraint program:

xᵢ ∈ {1, 2, 3} ∀i=1,2,3
+zⱼ ∈ {0, 1}    ∀j=1,2
+z₁ <-> x₁ != x₂
+z₂ <-> x₂ != x₃
+z₁ + z₂ = 1
julia> import MiniZinc
+
+julia> const MOI = MiniZinc.MOI
+MathOptInterface
+
+julia> function main()
+           model = MOI.Utilities.CachingOptimizer(
+               MiniZinc.Model{Int}(),
+               MiniZinc.Optimizer{Int}("chuffed"),
+           )
+           # xᵢ ∈ {1, 2, 3} ∀i=1,2,3
+           x = MOI.add_variables(model, 3)
+           MOI.add_constraint.(model, x, MOI.Interval(1, 3))
+           MOI.add_constraint.(model, x, MOI.Integer())
+           # zⱼ ∈ {0, 1}    ∀j=1,2
+           z = MOI.add_variables(model, 2)
+           MOI.add_constraint.(model, z, MOI.ZeroOne())
+           # z₁ <-> x₁ != x₂
+           MOI.add_constraint(
+               model,
+               MOI.VectorOfVariables([z[1], x[1], x[2]]),
+               MOI.Reified(MOI.AllDifferent(2)),
+           )
+           # z₂ <-> x₂ != x₃
+           MOI.add_constraint(
+               model,
+               MOI.VectorOfVariables([z[2], x[2], x[3]]),
+               MOI.Reified(MOI.AllDifferent(2)),
+           )
+           # z₁ + z₂ = 1
+           MOI.add_constraint(model, 1 * z[1] + x[2], MOI.EqualTo(1))
+           MOI.optimize!(model)
+           x_star = MOI.get(model, MOI.VariablePrimal(), x)
+           z_star = MOI.get(model, MOI.VariablePrimal(), z)
+           return x_star, z_star
+       end
+main (generic function with 1 method)
+
+julia> main()
+([1, 1, 3], [0, 1])

Use with JuMP

You can also call MiniZinc from JuMP, using any solver that libminizinc supports. By default, MiniZinc.jl is compiled with "highs":

using JuMP
+import MiniZinc
+model = Model(() -> MiniZinc.Optimizer{Float64}("highs"))
+@variable(model, 1 <= x[1:3] <= 3, Int)
+@constraint(model, x in MOI.AllDifferent(3))
+@objective(model, Max, sum(i * x[i] for i in 1:3))
+optimize!(model)
+@show value.(x)

MathOptInterface API

The MiniZinc Optimizer{T} supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

Set options using MOI.RawOptimizerAttribute in MOI or set_attribute in JuMP.

MiniZinc.jl supports the following options:

  • model_filename::String = "": the location at which to write out the .mzn file during optimization. This option can be helpful during debugging. If left empty, a temporary file will be used instead.
diff --git a/previews/PR3536/packages/MosekTools/index.html b/previews/PR3536/packages/MosekTools/index.html new file mode 100644 index 00000000000..c980546f87b --- /dev/null +++ b/previews/PR3536/packages/MosekTools/index.html @@ -0,0 +1,10 @@ + +jump-dev/MosekTools.jl · JuMP

MosekTools.jl

MosekTools.jl is the MathOptInterface.jl implementation for the MOSEK solver.

The low-level solver API for MOSEK is found in the package Mosek.jl.

Affiliation

MosekTools.jl is maintained by the JuMP community and is not officially supported by MOSEK. However, Mosek.jl is an officially supported product of MOSEK.

License

MosekTools.jl is licensed under the MIT License.

The underlying solver is a closed-source commercial product for which you must obtain a license.

Installation

The latest release of this package and the master branch are to be used with the latest release of Mosek.jl (which uses MOSEK v10).

To use MOSEK v9 (resp. v8), use the v0.12.x (resp. v0.7.x) releases of this package, and the mosekv9 (resp. mosekv8) branch and v1.2.x (resp. v0.9.x) releases of Mosek.jl.

See the following table for a summary:

MOSEKMosek.jlMosekTools.jl releaseMosekTools.jl branch
v10v10v0.13master
v9v0.12v0.12mosekv9
v8v0.9v0.7mosekv8

Use with JuMP

using JuMP
+using MosekTools
+model = Model(Mosek.Optimizer)
+set_attribute(model, "QUIET", true)
+set_attribute(model, "INTPNT_CO_TOL_DFEAS", 1e-7)

Options

The parameter QUIET is a special parameter that when set to true disables all Mosek printing output.

All other parameters can be found in the Mosek documentation.

Note that the prefix MSK_IPAR_ (for integer parameters), MSK_DPAR_ (for floating point parameters) or MSK_SPAR_ (for string parameters) are optional. If they are not given, they are inferred from the type of the value. For example, in the example above, as 1e-7 is a floating point number, the parameters name used is MSK_DPAR_INTPNT_CO_TOL_DFEAS.

diff --git a/previews/PR3536/packages/MultiObjectiveAlgorithms/index.html b/previews/PR3536/packages/MultiObjectiveAlgorithms/index.html new file mode 100644 index 00000000000..4f2df3c34c7 --- /dev/null +++ b/previews/PR3536/packages/MultiObjectiveAlgorithms/index.html @@ -0,0 +1,12 @@ + +jump-dev/MultiObjectiveAlgorithms.jl · JuMP
An image of the Moa bird. Licensed into the Public Domain by https://freesvg.org/moa

MultiObjectiveAlgorithms.jl

Build Status codecov

MultiObjectiveAlgorithms.jl (MOA) is a collection of algorithms for multi-objective optimization.

License

MultiObjectiveAlgorithms.jl is licensed under the MPL 2.0 License.

Installation

Install MOA using Pkg.add:

import Pkg
+Pkg.add("MultiObjectiveAlgorithms")

Use with JuMP

Use MultiObjectiveAlgorithms with JuMP as follows:

using JuMP
+import HiGHS
+import MultiObjectiveAlgorithms as MOA
+model = JuMP.Model(() -> MOA.Optimizer(HiGHS.Optimizer))
+set_attribute(model, MOA.Algorithm(), MOA.Dichotomy())
+set_attribute(model, MOA.SolutionLimit(), 4)

Replace HiGHS.Optimizer with an optimizer capable of solving a single-objective instance of your optimization problem.

You may set additional optimizer attributes, the supported attributes depend on the choice of solution algorithm.

Algorithm

Set the algorithm using the MOA.Algorithm() attribute.

The value must be one of the algorithms supported by MOA:

  • MOA.Chalmet()
  • MOA.Dichotomy()
  • MOA.DominguezRios()
  • MOA.EpsilonConstraint()
  • MOA.Hierarchical()
  • MOA.KirlikSayin()
  • MOA.Lexicographic() [default]
  • MOA.TambyVanderpooten()

Consult their docstrings for details.

Other optimizer attributes

There are a number of optimizer attributes supported by the algorithms in MOA.

Each algorithm supports only a subset of the attributes. Consult the algorithm's docstring for details on which attributes it supports, and how it uses them in the solution process.

  • MOA.EpsilonConstraintStep()
  • MOA.LexicographicAllPermutations()
  • MOA.ObjectiveAbsoluteTolerance(index::Int)
  • MOA.ObjectivePriority(index::Int)
  • MOA.ObjectiveRelativeTolerance(index::Int)
  • MOA.ObjectiveWeight(index::Int)
  • MOA.SolutionLimit()
  • MOI.TimeLimitSec()
diff --git a/previews/PR3536/packages/NEOSServer/index.html b/previews/PR3536/packages/NEOSServer/index.html new file mode 100644 index 00000000000..a6d6f1343d9 --- /dev/null +++ b/previews/PR3536/packages/NEOSServer/index.html @@ -0,0 +1,31 @@ + +odow/NEOSServer.jl · JuMP

NEOSServer.jl

Build Status codecov

NEOSServer.jl is a wrapper for the NEOS Server, a free internet-based service for solving numerical optimization problems.

See here for the full list of solvers and input formats that NEOS supports.

License

NEOSServer.jl is licensed under the MIT License.

However, use of the NEOS Server requires you to comply with NEOS Server terms of use. In particular, the commercial solvers are to be used solely for academic, non-commercial research purposes.

Installation

Install NEOSServer.jl using the package manager:

import Pkg
+Pkg.add("NEOSServer")

The NEOS API

This package contains an interface for the NEOS XML-RPC API.

The following example shows how you can interact with the API. Wrapped XML-RPC functions begin with neos_ and are exported.

using NEOSServer
+
+# Create a server. You must supply a valid email:
+server = NEOSServer.Server("me@mydomain.com")
+
+# Print the NEOS welcome message:
+println(neos_welcome(server))
+
+# Get an XML template:
+xml_string = neos_getSolverTemplate(server, "milp", "Cbc", "AMPL")
+
+# Modify template with problem data...
+
+# Submit the XML job to NEOS:
+job = neos_submitJob(server, xml_string)
+
+# Get the status of the Job from NEOS:
+status = neos_getJobStatus(server, job)
+
+# Get the final results:
+results = neos_getFinalResults(server, job)

Use with JuMP

Use NEOSServer.jl with JuMP as follows:

using JuMP, NEOSServer
+
+model = Model() do
+    NEOSServer.Optimizer(email="me@mydomain.com", solver="Ipopt")
+end

Note: NEOSServer.Optimizer is limited to the following solvers:

  • "CPLEX"
  • "FICO-Xpress"
  • "Ipopt"
  • "Knitro"
  • "MOSEK"
  • "OCTERACT"
  • "SNOPT"

NEOS Limits

NEOS currently limits jobs to an 8 hour time limit, 3 GB of memory, and a 16 MB submission file. If your model exceeds these limits, NEOSServer.jl may be unable to return useful information to the user.

diff --git a/previews/PR3536/packages/NLopt/index.html b/previews/PR3536/packages/NLopt/index.html new file mode 100644 index 00000000000..742ff3b41a9 --- /dev/null +++ b/previews/PR3536/packages/NLopt/index.html @@ -0,0 +1,9 @@ + +JuliaOpt/NLopt.jl · JuMP

NLopt.jl

Build Status codecov

NLopt.jl is a wrapper for the NLopt library.

License

NLopt.jl is licensed under the MIT License.

The underlying solver, stevengj/nlopt, is licensed under the LGPL v3.0 license.

Installation

Install NLopt.jl using the Julia package manager:

import Pkg
+Pkg.add("NLopt")

In addition to installing the NLopt.jl package, this will also download and install the NLopt binaries. You do not need to install NLopt separately.

Use with JuMP

You can use NLopt with JuMP as follows:

using JuMP, NLopt
+model = Model(NLopt.Optimizer)
+set_attribute(model, "algorithm", :LD_MMA)

Options

The algorithm attribute is required. The value must be one of the supported NLopt algorithms.

Documentation

For more details, see the NLopt.jl README or the NLopt documentation.

diff --git a/previews/PR3536/packages/OSQP/index.html b/previews/PR3536/packages/OSQP/index.html new file mode 100644 index 00000000000..8bddbee7219 --- /dev/null +++ b/previews/PR3536/packages/OSQP/index.html @@ -0,0 +1,9 @@ + +osqp/OSQP.jl · JuMP

OSQP.jl

Build Status codecov.io

OSQP.jl is a Julia wrapper for OSQP: the Operator Splitting QP Solver.

License

OSQP.jl is licensed under the Apache-2.0 license.

The upstream solver, osqp/osqp is also licensed under the Apache-2.0 license.

Installation

Install OSQP.jl using the Julia package manager

import Pkg
+Pkg.add("OSQP")

Problem class

The OSQP (Operator Splitting Quadratic Program) solver is a numerical optimization package for solving problems in the form

minimize        0.5 x' P x + q' x
+
+subject to      l <= A x <= u

where x in R^n is the optimization variable. The objective function is defined by a positive semidefinite matrix P in S^n_+ and vector q in R^n. The linear constraints are defined by matrix A in R^{m x n} and vectors l in R^m U {-inf}^m, u in R^m U {+inf}^m.

Documentation

Detailed documentation is available at https://osqp.org/.

diff --git a/previews/PR3536/packages/PATHSolver/index.html b/previews/PR3536/packages/PATHSolver/index.html new file mode 100644 index 00000000000..1749646f041 --- /dev/null +++ b/previews/PR3536/packages/PATHSolver/index.html @@ -0,0 +1,168 @@ + +chkwon/PATHSolver.jl · JuMP

PATHSolver.jl

Build Status codecov

PATHSolver.jl is a wrapper for the PATH solver.

The wrapper has two components:

You can solve any complementarity problem using the wrapper around the C API, although you must manually provide the callback functions, including the Jacobian.

The MathOptInterface wrapper is more limited, supporting only linear complementarity problems, but it enables PATHSolver to be used with JuMP.

Affiliation

This wrapper is maintained by the JuMP community and is not an official wrapper of PATH. However, we are in close contact with the PATH developers, and they have given us permission to re-distribute the PATH binaries for automatic installation.

License

PATHSolver.jl is licensed under the MIT License.

The underlying solver, path is closed source and requires a license.

Without a license, the PATH Solver can solve problem instances up to with up to 300 variables and 2000 non-zeros. For larger problems, this web page provides a temporary license that is valid for a year.

You can either store the license in the PATH_LICENSE_STRING environment variable, or you can use the PATHSolver.c_api_License_SetString function immediately after importing the PATHSolver package:

import PATHSolver
+PATHSolver.c_api_License_SetString("<LICENSE STRING>")

where <LICENSE STRING> is replaced by the current license string.

Installation

Install PATHSolver.jl as follows:

import Pkg
+Pkg.add("PATHSolver")

By default, PATHSolver.jl will download a copy of the underlying PATH solver. To use a different version of PATH, see the Manual Installation section below.

Use with JuMP

julia> using JuMP, PATHSolver
+
+julia> M = [
+           0  0 -1 -1
+           0  0  1 -2
+           1 -1  2 -2
+           1  2 -2  4
+       ]
+4×4 Array{Int64,2}:
+ 0   0  -1  -1
+ 0   0   1  -2
+ 1  -1   2  -2
+ 1   2  -2   4
+
+julia> q = [2, 2, -2, -6]
+4-element Array{Int64,1}:
+  2
+  2
+ -2
+ -6
+
+julia> model = Model(PATHSolver.Optimizer)
+A JuMP Model
+Feasibility problem with:
+Variables: 0
+Model mode: AUTOMATIC
+CachingOptimizer state: EMPTY_OPTIMIZER
+Solver name: Path 5.0.00
+
+julia> set_optimizer_attribute(model, "output", "no")
+
+julia> @variable(model, x[1:4] >= 0)
+4-element Array{VariableRef,1}:
+ x[1]
+ x[2]
+ x[3]
+ x[4]
+
+julia> @constraint(model, M * x .+ q ⟂ x)
+[-x[3] - x[4] + 2, x[3] - 2 x[4] + 2, x[1] - x[2] + 2 x[3] - 2 x[4] - 2, x[1] + 2 x[2] - 2 x[3] + 4 x[4] - 6, x[1], x[2], x[3], x[4]] ∈ MOI.Complements(4)
+
+julia> optimize!(model)
+Reading options file /var/folders/bg/dzq_hhvx1dxgy6gb5510pxj80000gn/T/tmpiSsCRO
+Read of options file complete.
+
+Path 5.0.00 (Mon Aug 19 10:57:18 2019)
+Written by Todd Munson, Steven Dirkse, Youngdae Kim, and Michael Ferris
+
+julia> value.(x)
+4-element Array{Float64,1}:
+ 2.8
+ 0.0
+ 0.7999999999999998
+ 1.2
+
+julia> termination_status(model)
+LOCALLY_SOLVED::TerminationStatusCode = 4

Note that options are set using JuMP.set_optimizer_attribute.

The list of options supported by PATH can be found here: https://pages.cs.wisc.edu/~ferris/path/options.pdf

MathOptInterface API

The Path 5.0.03 optimizer supports the following constraints and attributes.

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Use with the C API

PATHSolver.jl wraps the PATH C API using PATHSolver.c_api_XXX for the C method XXX. However, using the C API directly from Julia can be challenging, particularly with respect to avoiding issues with Julia's garbage collector.

Instead, we recommend that you use the PATHSolver.solve_mcp function, which wrappers the C API into a single call. See the docstring of PATHSolver.solve_mcp for a detailed description of the arguments.

Here is the same example using PATHSolver.solve_mcp. Note that you must manually construct the sparse Jacobian callback.

julia> import PATHSolver
+
+julia> M = [
+           0  0 -1 -1
+           0  0  1 -2
+           1 -1  2 -2
+           1  2 -2  4
+       ]
+4×4 Matrix{Int64}:
+ 0   0  -1  -1
+ 0   0   1  -2
+ 1  -1   2  -2
+ 1   2  -2   4
+
+julia> q = [2, 2, -2, -6]
+4-element Vector{Int64}:
+  2
+  2
+ -2
+ -6
+
+julia> function F(n::Cint, x::Vector{Cdouble}, f::Vector{Cdouble})
+           @assert n == length(x) == length(f)
+           f .= M * x .+ q
+           return Cint(0)
+       end
+F (generic function with 1 method)
+
+julia> function J(
+           n::Cint,
+           nnz::Cint,
+           x::Vector{Cdouble},
+           col::Vector{Cint},
+           len::Vector{Cint},
+           row::Vector{Cint},
+           data::Vector{Cdouble},
+       )
+           @assert n == length(x) == length(col) == length(len) == 4
+           @assert nnz == length(row) == length(data)
+           i = 1
+           for c in 1:n
+               col[c], len[c] = i, 0
+               for r in 1:n
+                   if !iszero(M[r, c])
+                       row[i], data[i] = r, M[r, c]
+                       len[c] += 1
+                       i += 1
+                   end
+               end
+           end
+           return Cint(0)
+       end
+J (generic function with 1 method)
+
+julia> status, z, info = PATHSolver.solve_mcp(
+           F,
+           J,
+           fill(0.0, 4),  # Lower bounds
+           fill(Inf, 4),  # Upper bounds
+           fill(0.0, 4);  # Starting point
+           nnz = 12,      # Number of nonzeros in the Jacobian
+           output = "yes",
+       )
+Reading options file /var/folders/bg/dzq_hhvx1dxgy6gb5510pxj80000gn/T/jl_iftYBS
+ > output yes
+Read of options file complete.
+
+Path 5.0.03 (Fri Jun 26 09:58:07 2020)
+Written by Todd Munson, Steven Dirkse, Youngdae Kim, and Michael Ferris
+
+Crash Log
+major  func  diff  size  residual    step       prox   (label)
+    0     0             1.2649e+01             0.0e+00 (f[    4])
+    1     2     4     2 1.0535e+01  8.0e-01    0.0e+00 (f[    1])
+    2     3     2     4 8.4815e-01  1.0e+00    0.0e+00 (f[    4])
+    3     4     0     3 4.4409e-16  1.0e+00    0.0e+00 (f[    3])
+pn_search terminated: no basis change.
+
+Major Iteration Log
+major minor  func  grad  residual    step  type prox    inorm  (label)
+    0     0     5     4 4.4409e-16           I 0.0e+00 4.4e-16 (f[    3])
+
+Major Iterations. . . . 0
+Minor Iterations. . . . 0
+Restarts. . . . . . . . 0
+Crash Iterations. . . . 3
+Gradient Steps. . . . . 0
+Function Evaluations. . 5
+Gradient Evaluations. . 4
+Basis Time. . . . . . . 0.000016
+Total Time. . . . . . . 0.044383
+Residual. . . . . . . . 4.440892e-16
+(PATHSolver.MCP_Solved, [2.8, 0.0, 0.8, 1.2], PATHSolver.Information(4.4408920985006247e-16, 0.0, 0.0, 0.044383, 1.6e-5, 0.0, 0, 0, 3, 5, 4, 0, 0, 0, 0, false, false, false, true, false, false, false))
+
+julia> status
+MCP_Solved::MCP_Termination = 1
+
+julia> z
+4-element Vector{Float64}:
+ 2.8
+ 0.0
+ 0.8
+ 1.2

Thread safety

PATH is not thread-safe and there are no known work-arounds. Do not run it in parallel using Threads.@threads. See issue #62 for more details.

Factorization methods

By default, PATHSolver.jl will download the LUSOL shared library. To use LUSOL, set the following options:

model = Model(PATHSolver.Optimizer)
+set_optimizer_attribute(model, "factorization_method", "blu_lusol")
+set_optimizer_attribute(model, "factorization_library_name", PATHSolver.LUSOL_LIBRARY_PATH)

To use factorization_method umfpack you will need the umfpack shared library that is available directly from the developers of that code for academic use.

Manual installation

By default PATHSolver.jl will download a copy of the libpath library. If you already have one installed and want to use that, set the PATH_JL_LOCATION environment variable to point to the libpath50.xx library.

diff --git a/previews/PR3536/packages/Pajarito/index.html b/previews/PR3536/packages/Pajarito/index.html new file mode 100644 index 00000000000..e666c0780f3 --- /dev/null +++ b/previews/PR3536/packages/Pajarito/index.html @@ -0,0 +1,30 @@ + +jump-dev/Pajarito.jl · JuMP

Pajarito.jl

Build Status codecov

Pajarito is a mixed-integer convex programming (MICP) solver package written in Julia.

MICP problems are convex except for restrictions that some variables take binary or integer values.

Pajarito solves MICP problems in conic form, by constructing sequential polyhedral outer approximations of the conic feasible set.

The underlying algorithm has theoretical finite-time convergence under reasonable assumptions.

Pajarito accesses state-of-the-art MILP solvers and continuous conic solvers through MathOptInterface.

Pavito

For algorithms that use a derivative-based nonlinear programming (NLP) solver (for example, Ipopt) instead of a conic solver, use Pavito.

Pavito is a convex mixed-integer nonlinear programming (convex MINLP) solver. Because Pavito relies on gradient cuts, it can fail near points of non-differentiability. Pajarito may be more robust than Pavito on non-smooth problems.

License

Pajarito.jl is licensed under the MPL 2.0 license.

Installation

Install Pajarito using Pkg.add:

import Pkg
+Pkg.add("Pajarito")

MIP and continuous solvers

The algorithm implemented by Pajarito itself is relatively simple, and most of the hard work is performed by the MIP outer approximation (OA) solver and the continuous conic solver.

Therefore, in addition to installing Pajarito, you must also install a mixed-integer linear programming solver and a continuous conic solver.

The performance of Pajarito depends on these two types of solvers.

The OA solver (typically a mixed-integer linear solver) is specified by the oa_solver option. You must first load the Julia package that provides this solver, for example, using Gurobi. The continuous conic solver is specified by the conic_solver option.

See JuMP's list of supported solvers.

Use with JuMP

To use Pajarito with JuMP, use:

using JuMP, Pajarito, HiGHS, Hypatia
+model = Model(
+    optimizer_with_attributes(
+        Pajarito.Optimizer,
+        "oa_solver" => optimizer_with_attributes(
+            HiGHS.Optimizer,
+            MOI.Silent() => true,
+            "mip_feasibility_tolerance" => 1e-8,
+            "mip_rel_gap" => 1e-6,
+        ),
+        "conic_solver" =>
+            optimizer_with_attributes(Hypatia.Optimizer, MOI.Silent() => true),
+    )
+)
+set_attribute(model, "time_limit", 60)

Options

We list Pajarito's options below.

  • verbose::Bool toggles printing
  • tol_feas::Float64 is the feasibility tolerance for conic constraints
  • tol_rel_gap::Float64 is the relative optimality gap tolerance
  • tol_abs_gap::Float64 is the absolute optimality gap tolerance
  • time_limit::Float64 sets the time limit (in seconds)
  • iteration_limit::Int sets the iteration limit (for the iterative method)
  • use_iterative_method::Union{Nothing,Bool} toggles the iterative algorithm; if nothing is specified, Pajarito defaults to the OA-solver-driven (single tree) algorithm if lazy callbacks are supported by the OA solver
  • use_extended_form::Bool toggles the use of extended formulations for the second-order cone
  • solve_relaxation::Bool toggles solution of the continuous conic relaxation
  • solve_subproblems::Bool toggles solution of the continuous conic subproblems
  • use_init_fixed_oa::Bool toggles initial fixed OA cuts
  • oa_solver::Union{Nothing,MOI.OptimizerWithAttributes} is the OA solver
  • conic_solver::Union{Nothing,MOI.OptimizerWithAttributes} is the conic solver

Pajarito may require tuning of parameters to improve convergence.

For example, it often helps to tighten the OA solver's integrality tolerance. OA solver and conic solver options must be specified directly to those solvers.

Note: if solve_subproblems is true, Pajarito usually returns a solution constructed from one of the conic solver's feasible solutions; since the conic solver is not subject to the same feasibility tolerances as the OA solver, Pajarito's solution will not necessarily satisfy tol_feas.

Cone interface

Pajarito has a generic cone interface (see the cones folder that allows the user to add support for new convex cones.

To illustrate, in the experimental package PajaritoExtras we have extended Pajarito by adding support for several cones recognized by Hypatia.jl (a continuous conic solver with its own generic cone interface).

The examples folder of PajaritoExtras also contains many applied mixed-integer convex problems that are solved using Pajarito.

Bug reports and support

Please report any issues via the GitHub issue tracker.

All types of issues are welcome and encouraged; this includes bug reports, documentation typos, and feature requests. The Optimization (Mathematical) category on Discourse is appropriate for general discussion.

References

If you find Pajarito useful in your work, we kindly request that you cite the following paper (arXiv preprint), which is recommended reading for advanced users:

@article{CoeyLubinVielma2020,
+    title={Outer approximation with conic certificates for mixed-integer convex problems},
+    author={Coey, Chris and Lubin, Miles and Vielma, Juan Pablo},
+    journal={Mathematical Programming Computation},
+    volume={12},
+    number={2},
+    pages={249--293},
+    year={2020},
+    publisher={Springer}
+}

Note this paper describes a legacy MathProgBase version of Pajarito, which is available on the mathprogbase branch of this repository. Starting with version v0.8.0, Pajarito supports MathOptInterface instead of MathProgBase.

diff --git a/previews/PR3536/packages/ParametricOptInterface/index.html b/previews/PR3536/packages/ParametricOptInterface/index.html new file mode 100644 index 00000000000..e0b2166e721 --- /dev/null +++ b/previews/PR3536/packages/ParametricOptInterface/index.html @@ -0,0 +1,16 @@ + +jump-dev/ParametricOptInterface.jl · JuMP

ParametricOptInterface.jl

stable docs development docs Build Status Coverage

ParametricOptInterface.jl is a package that adds parameters to models in JuMP and MathOptInterface.

License

ParametricOptInterface.jl is licensed under the MIT License.

Installation

Install ParametricOptInterface using Pkg.add:

import Pkg
+Pkg.add("ParametricOptInterface")

Documentation

The documentation for ParametricOptInterface.jl includes a detailed description of the theory behind the package, along with examples, tutorials, and an API reference.

Use with JuMP

Use ParametricOptInterface with JuMP by following this brief example:

using JuMP, HiGHS
+import ParametricOptInterface as POI
+model = Model(() -> POI.Optimizer(HiGHS.Optimizer()))
+@variable(model, x)
+@variable(model, p in POI.Parameter(1.0))
+@constraint(model, cons, x + p >= 3)
+@objective(model, Min, 2x)
+optimize!(model)
+MOI.set(model, POI.ParameterValue(), p, 2.0)
+optimize!(model)

GSOC2020

ParametricOptInterface began as a NumFOCUS sponsored Google Summer of Code (2020) project.

diff --git a/previews/PR3536/packages/Pavito/index.html b/previews/PR3536/packages/Pavito/index.html new file mode 100644 index 00000000000..68202cf903c --- /dev/null +++ b/previews/PR3536/packages/Pavito/index.html @@ -0,0 +1,16 @@ + +jump-dev/Pavito.jl · JuMP

Pavito.jl

Build Status Coverage

Pavito.jl is a mixed-integer convex programming (MICP) solver package written in Julia.

MICP problems are convex, except for restrictions that some variables take binary or integer values.

Pavito solves MICP problems by constructing sequential polyhedral outer-approximations of the convex feasible set, similar to Bonmin.

Pavito accesses state-of-the-art MILP solvers and continuous, derivative-based nonlinear programming (NLP) solvers through MathOptInterface.

For algorithms that use a conic solver instead of an NLP solver, use Pajarito. Pajarito is a robust mixed-integer conic solver that can handle such established problem classes as mixed-integer second-order cone programming (MISOCP) and mixed-integer semidefinite programming (MISDP).

License

Pavito.jl is licensed under the MPL 2.0 license.

Installation

Install Pavito using Pkg.add:

import Pkg
+Pkg.add("Pavito")

Use with JuMP

To use Pavito with JuMP, use Pavito.Optimizer:

using JuMP, Pavito
+import GLPK, Ipopt
+model = Model(
+    optimizer_with_attributes(
+        Pavito.Optimizer,
+        "mip_solver" => optimizer_with_attributes(GLPK.Optimizer),
+        "cont_solver" =>
+            optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0),
+    ),
+)

The algorithm implemented by Pavito itself is relatively simple; most of the hard work is performed by the MILP solver passed as mip_solver and the NLP solver passed as cont_solver.

The performance of Pavito depends on these two types of solvers.

For better performance, you should use a commercial MILP solver such as CPLEX or Gurobi.

Options

The following optimizer attributes can set to a Pavito.Optimizer to modify its behavior:

  • log_level::Int Verbosity flag: 0 for quiet, higher for basic solve info
  • timeout::Float64 Time limit for algorithm (in seconds)
  • rel_gap::Float64 Relative optimality gap termination condition
  • mip_solver_drives::Bool Let MILP solver manage convergence ("branch and cut")
  • mip_solver::MOI.OptimizerWithAttributes MILP solver
  • cont_solver::MOI.OptimizerWithAttributes Continuous NLP solver

Pavito is not yet numerically robust and may require tuning of parameters to improve convergence.

If the default parameters don't work for you, please let us know by opening an issue.

For improved Pavito performance, MILP solver integrality tolerance and feasibility tolerances should typically be tightened, for example to 1e-8.

Bug reports and support

Please report any issues via the GitHub issue tracker. All types of issues are welcome and encouraged; this includes bug reports, documentation typos, feature requests, etc. The Optimization (Mathematical) category on Discourse is appropriate for general discussion.

diff --git a/previews/PR3536/packages/Plasmo/index.html b/previews/PR3536/packages/Plasmo/index.html new file mode 100644 index 00000000000..eb884f0fc49 --- /dev/null +++ b/previews/PR3536/packages/Plasmo/index.html @@ -0,0 +1,49 @@ + +plasmo-dev/Plasmo.jl · JuMP

CI codecov DOI

Plasmo.jl

Plasmo.jl (Platform for Scalable Modeling and Optimization) is a graph-based algebraic modeling framework that adopts a modular style to create mathematical optimization problems and manage distributed and hierarchical structures. The package has been developed as a JuMP extension and consequently supports most JuMP syntax and functions.

Overview

The core data structure in Plasmo.jl is the OptiGraph. The optigraph contains a set of optinodes which represent self-contained optimization problems and optiedges that represent coupling between optinodes (which produces an underlying hypergraph structure of optinodes and optiedges). Optigraphs can further be embedded within other optigraphs to create nested hierarchical graph structures. The graph structures obtained using Plasmo.jl can be used for simple model and data management, but they can also be used to perform graph partitioning or develop interfaces to structured optimization solvers.

License

Plasmo is licensed under the MPL 2.0 license.

Installation

Install Plasmo using Pkg.add:

import Pkg
+Pkg.add("Plasmo")

Documentation

The latest documentation is available through GitHub Pages. Additional examples can be found in the examples folder.

Simple Example

using Plasmo
+using Ipopt
+
+#create an optigraph
+graph = OptiGraph()
+
+#add nodes to an optigraph
+@optinode(graph, n1)
+@optinode(graph, n2)
+
+#add variables, constraints, and objective functions to nodes
+@variable(n1, 0 <= x <= 2)
+@variable(n1, 0 <= y <= 3)
+@constraint(n1, x+y <= 4)
+@objective(n1, Min, x)
+
+@variable(n2,x)
+@NLconstraint(n2, exp(x) >= 2)
+
+#add a linkconstraint to couple nodes
+@linkconstraint(graph, n1[:x] == n2[:x])
+
+#optimize with Ipopt
+set_optimizer(graph, Ipopt.Optimizer)
+optimize!(graph)
+
+#Print solution values
+println("n1[:x] = ", value(n1[:x]))
+println("n2[:x] = ", value(n2[:x]))

Acknowledgments

This code is based on work supported by the following funding agencies:

  • U.S. Department of Energy (DOE), Office of Science, under Contract No. DE-AC02-06CH11357
  • DOE Office of Electricity Delivery and Energy Reliability’s Advanced Grid Research and Development program at Argonne National Laboratory
  • National Science Foundation under award NSF-EECS-1609183 and under award CBET-1748516

The primary developer is Jordan Jalving (@jalving) with support from the following contributors.

  • Victor Zavala (University of Wisconsin-Madison)
  • Yankai Cao (University of British Columbia)
  • Kibaek Kim (Argonne National Laboratory)
  • Sungho Shin (University of Wisconsin-Madison)

Citing Plasmo.jl

If you find Plasmo.jl useful for your work, you may cite the manuscript as:

@article{JalvingShinZavala2022,
+  title={A Graph-Based Modeling Abstraction for Optimization: Concepts and Implementation in Plasmo.jl},
+  author={Jordan Jalving and Sungho Shin and Victor M. Zavala},
+  journal={Mathematical Programming Computation},
+  year={2022},
+  volume={14},
+  pages={699 - 747}
+}

There is also a freely available pre-print:

@misc{JalvingShinZavala2020,
+title = {A Graph-Based Modeling Abstraction for Optimization: Concepts and Implementation in Plasmo.jl},
+author = {Jordan Jalving and Sungho Shin and Victor M. Zavala},
+year = {2020},
+eprint = {2006.05378},
+archivePrefix = {arXiv},
+primaryClass = {math.OC}
+}
diff --git a/previews/PR3536/packages/PolyJuMP/index.html b/previews/PR3536/packages/PolyJuMP/index.html new file mode 100644 index 00000000000..c0e0dbb02d1 --- /dev/null +++ b/previews/PR3536/packages/PolyJuMP/index.html @@ -0,0 +1,7 @@ + +jump-dev/PolyJuMP.jl · JuMP
diff --git a/previews/PR3536/packages/ProxSDP/index.html b/previews/PR3536/packages/ProxSDP/index.html new file mode 100644 index 00000000000..005cb3ecad9 --- /dev/null +++ b/previews/PR3536/packages/ProxSDP/index.html @@ -0,0 +1,59 @@ + +mariohsouto/ProxSDP.jl · JuMP
logo

ProxSDP is an open-source semidefinite programming (SDP) solver based on the paper "Exploiting Low-Rank Structure in Semidefinite Programming by Approximate Operator Splitting".

The main advantage of ProxSDP over other state-of-the-art solvers is the ability to exploit the low-rank structure inherent to several SDP problems.

Overview of problems ProxSDP can solve

License

ProxSDP is licensed under the MIT License.

Installation

Install ProxSDP using Julia package manager:

import Pkg
+Pkg.add("ProxSDP")

Documentation

The online documentation is available at https://mariohsouto.github.io/ProxSDP.jl/latest/.

Usage

Consider the semidefinite programming relaxation of the max-cut problem, as in:

max   0.25 * W•X
+s.t.  diag(X) = 1,
+      X ≽ 0,

JuMP

This problem can be solved by the following code using ProxSDP and JuMP.

# Load packages
+using ProxSDP, JuMP, LinearAlgebra
+# Number of vertices
+n = 4
+# Graph weights
+W = [
+    18.0  -5.0  -7.0  -6.0
+    -5.0   6.0   0.0  -1.0
+    -7.0   0.0   8.0  -1.0
+    -6.0  -1.0  -1.0   8.0
+]
+# Build Max-Cut SDP relaxation via JuMP
+model = Model(ProxSDP.Optimizer)
+set_optimizer_attribute(model, "log_verbose", true)
+set_optimizer_attribute(model, "tol_gap", 1e-4)
+set_optimizer_attribute(model, "tol_feasibility", 1e-4)
+@variable(model, X[1:n, 1:n], PSD)
+@objective(model, Max, 0.25 * LinearAlgebra.dot(W, X))
+@constraint(model, LinearAlgebra.diag(X) .== 1)
+# Solve optimization problem with ProxSDP
+optimize!(model)
+# Retrieve solution
+Xsol = value.(X)

Convex.jl

Another alternative is to use ProxSDP via Convex.jl:

# Load packages
+using Convex, ProxSDP
+# Number of vertices
+n = 4
+# Graph weights
+W = [
+    18.0  -5.0  -7.0  -6.0
+    -5.0   6.0   0.0  -1.0
+    -7.0   0.0   8.0  -1.0
+    -6.0  -1.0  -1.0   8.0
+]
+# Define optimization problem
+X = Semidefinite(n)
+problem = maximize(0.25 * dot(W, X), diag(X) == 1)
+# Solve optimization problem with ProxSDP
+solve!(problem, ProxSDP.Optimizer(log_verbose=true, tol_gap=1e-4, tol_feasibility=1e-4))
+# Get the objective value
+problem.optval
+# Retrieve solution
+evaluate(X)

Citing this package

The published version of the paper can be found here and the arXiv version here.

We kindly request that you cite the paper as:

@article{souto2020exploiting,
+  author = {Mario Souto and Joaquim D. Garcia and \'Alvaro Veiga},
+  title = {Exploiting low-rank structure in semidefinite programming by approximate operator splitting},
+  journal = {Optimization},
+  pages = {1-28},
+  year  = {2020},
+  publisher = {Taylor & Francis},
+  doi = {10.1080/02331934.2020.1823387},
+  URL = {https://doi.org/10.1080/02331934.2020.1823387}
+}

The preprint version of the paper can be found here.

Disclaimer

  • ProxSDP is a research software, therefore it should not be used in production.
  • Please open an issue if you find any problems, developers will try to fix and find alternatives.
  • There is no continuous development for 32-bit systems, the package should work, but might reach some issues.
  • ProxSDP assumes primal and dual feasibility.

ROAD MAP

  • Support for exponential and power cones
  • Warm start
diff --git a/previews/PR3536/packages/SCIP/index.html b/previews/PR3536/packages/SCIP/index.html new file mode 100644 index 00000000000..0e8dee66037 --- /dev/null +++ b/previews/PR3536/packages/SCIP/index.html @@ -0,0 +1,13 @@ + +scipopt/SCIP.jl · JuMP

SCIP.jl

Build Status codecov Genie Downloads

SCIP.jl is a Julia interface to the SCIP solver.

Affiliation

This wrapper is maintained by the SCIP project with the help of the JuMP community.

License

SCIP.jl is licensed under the MIT License.

The underlying solver, scipopt/scip, is licensed under the Apache 2.0 license.

Installation

Install SCIP.jl using Pkg.add:

import Pkg
+Pkg.add("SCIP")

On MacOS and Linux, installing the SCIP Julia package will work out of the box and install the SCIP_jll.jl and SCIP_PaPILO_jll.jl dependencies.

On Windows, a separate installation of SCIP is still mandatory, as detailed in the "Custom installation" section below.

Custom installations

If you use an older Julia version, Windows, or you want a custom SCIP installation, you must manually install the SCIP binaries.

Once installed, set the SCIPOPTDIR environment variable to point to the installation path, that is, depending on your operating system, $SCIPOPTDIR/lib/libscip.so, $SCIPOPTDIR/lib/libscip.dylib, or $SCIPOPTDIR/bin/scip.dll must exist.

Then, install SCIP.jl using Pkg.add and Pkg.build:

ENV["SCIPOPTDIR"] = "/Users/Oscar/code/SCIP"
+import Pkg
+Pkg.add("SCIP")
+Pkg.build("SCIP")

Use with JuMP

Use SCIP with JuMP as follows:

using JuMP, SCIP
+model = Model(SCIP.Optimizer)
+set_attribute(model, "display/verblevel", 0)
+set_attribute(model, "limits/gap", 0.05)

Options

See the SCIP documentation for a list of supported options.

MathOptInterface API

The SCIP optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Design considerations

Wrapping the public API

All of the public API methods are wrapped and available within the SCIP package. This includes the scip_*.h and pub_*.h headers that are collected in scip.h, as well as all default constraint handlers (cons_*.h.)

The wrapped functions do not transform any data structures and work on the raw pointers (for example, SCIP* in C, Ptr{SCIP_} in Julia). Convenience wrapper functions based on Julia types are added as needed.

Memory management

Programming with SCIP requires dealing with variable and constraint objects that use reference counting for memory management.

The SCIP.Optimizer wrapper type collects lists of SCIP_VAR* and SCIP_CONS* under the hood, and it releases all references when it is garbage collected itself (via finalize).

When adding a variable (add_variable) or a constraint (add_linear_constraint), an integer index is returned. This index can be used to retrieve the SCIP_VAR* or SCIP_CONS* pointer via get_var and get_cons respectively.

Supported nonlinear operators

Supported operators in nonlinear expressions are as follows:

  • +
  • -
  • *
  • /
  • ^
  • sqrt
  • exp
  • log
  • abs
  • cos
  • sin
diff --git a/previews/PR3536/packages/SCS/index.html b/previews/PR3536/packages/SCS/index.html new file mode 100644 index 00000000000..a9097d1383a --- /dev/null +++ b/previews/PR3536/packages/SCS/index.html @@ -0,0 +1,71 @@ + +jump-dev/SCS.jl · JuMP

SCS.jl

Build Status codecov

SCS.jl is a wrapper for the SCS splitting cone solver.

SCS can solve linear programs, second-order cone programs, semidefinite programs, exponential cone programs, and power cone programs.

Affiliation

This wrapper is maintained by the JuMP community and is not a project of the SCS developers.

License

SCS.jl is licensed under the MIT License.

The underlying solver, cvxgrp/scs, is licensed under the MIT License.

Installation

Install SCS.jl using the Julia package manager:

import Pkg
+Pkg.add("SCS")

In addition to installing the SCS.jl package, this will also download and install the SCS binaries. (You do not need to install SCS separately.)

To use a custom binary, read the Custom solver binaries section of the JuMP documentation.

Use with Convex.jl

This example shows how we can model a simple knapsack problem with Convex and use SCS to solve it.

using Convex, SCS
+items  = [:Gold, :Silver, :Bronze]
+values = [5.0, 3.0, 1.0]
+weights = [2.0, 1.5, 0.3]
+
+# Define a variable of size 3, each index representing an item
+x = Variable(3)
+p = maximize(x' * values, 0 <= x, x <= 1, x' * weights <= 3)
+solve!(p, SCS.Optimizer)
+println([items x.value])
+# [:Gold 0.9999971880377178
+#  :Silver 0.46667637765641057
+#  :Bronze 0.9999998036351865]

Use with JuMP

This example shows how we can model a simple knapsack problem with JuMP and use SCS to solve it.

using JuMP, SCS
+items  = [:Gold, :Silver, :Bronze]
+values = Dict(:Gold => 5.0,  :Silver => 3.0,  :Bronze => 1.0)
+weight = Dict(:Gold => 2.0,  :Silver => 1.5,  :Bronze => 0.3)
+
+model = Model(SCS.Optimizer)
+@variable(model, 0 <= take[items] <= 1)  # Define a variable for each item
+@objective(model, Max, sum(values[item] * take[item] for item in items))
+@constraint(model, sum(weight[item] * take[item] for item in items) <= 3)
+optimize!(model)
+println(value.(take))
+# 1-dimensional DenseAxisArray{Float64,1,...} with index sets:
+#     Dimension 1, Symbol[:Gold, :Silver, :Bronze]
+# And data, a 3-element Array{Float64,1}:
+#  1.0000002002226671
+#  0.4666659513182934
+#  1.0000007732744878

MathOptInterface API

The SCS optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

All SCS solver options can be set through Convex.jl or MathOptInterface.jl.

For example:

model = Model(optimizer_with_attributes(SCS.Optimizer, "max_iters" => 10))
+
+# via MathOptInterface:
+optimizer = SCS.Optimizer()
+MOI.set(optimizer, MOI.RawOptimizerAttribute("max_iters"), 10)
+MOI.set(optimizer, MOI.RawOptimizerAttribute("verbose"), 0)

Common options are:

  • max_iters: the maximum number of iterations to take
  • verbose: turn printing on (1) or off (0)

See the glbopts.h header for other options.

Linear solvers

SCS uses a linear solver internally, see this section of SCS documentation. SCS.jl ships with

  • SCS.DirectSolver (sparse direct, the default) and
  • SCS.LinearSolver (sparse indirect, by conjugate gradient)

enabled.

The find currently available linear solvers one can inspect SCS.available_solvers:

julia> using SCS
+
+julia> SCS.available_solvers
+2-element Vector{DataType}:
+ SCS.DirectSolver
+ SCS.IndirectSolver

To select the linear solver of choice:

SCS with MKL Pardiso linear solver

To enable the MKL Pardiso (direct sparse) solver one needs to load MKL_jll before SCS:

julia> import Pkg
+
+julia> Pkg.add(Pkg.PackageSpec(name = "MKL_jll", version = "2022.2"))
+
+julia> using MKL_jll    # This must be called before `using SCS`.
+
+julia> using SCS
+
+julia> SCS.available_solvers
+3-element Vector{DataType}:
+ SCS.DirectSolver
+ SCS.IndirectSolver
+ SCS.MKLDirectSolver

The MKLDirectSolver is available on Linux x86_64 platform only.

SCS with Sparse GPU indirect solver (CUDA only)

To enable the indirect linear solver on GPU one needs to load CUDA_jll before SCS:

julia> import Pkg
+
+julia> Pkg.add(Pkg.PackageSpec(name = "CUDA_jll", version = "10.1"))
+
+julia> using CUDA_jll  # This must be called before `using SCS`.
+
+julia> using SCS
+
+julia> SCS.available_solvers
+3-element Array{DataType,1}:
+ SCS.DirectSolver
+ SCS.IndirectSolver
+ SCS.GpuIndirectSolver

The GpuIndirectSolver is available on Linux x86_64 platform only.

Low-level wrapper

SCS.jl provides a low-level interface to solve a problem directly, without interfacing through MathOptInterface.

This is an advanced interface with a risk of incorrect usage. For new users, we recommend that you use the JuMP or Convex interfaces instead.

SCS solves a problem of the form:

minimize        1/2 * x' * P * x + c' * x
+subject to      A * x + s = b
+                s in K

where K is a product cone of:

  • zero cone
  • positive orthant { x | x ≥ 0 }
  • box cone { (t,x) | t*l ≤ x ≤ t*u}
  • second-order cone (SOC) { (t,x) | ||x||_2 ≤ t }
  • semi-definite cone (SDC) { X | X is psd }
  • exponential cone { (x,y,z) | y e^(x/y) ≤ z, y>0 }
  • power cone { (x,y,z) | x^a * y^(1-a) ≥ |z|, x ≥ 0, y ≥ 0 }
  • dual power cone { (u,v,w) | (u/a)^a * (v/(1-a))^(1-a) ≥ |w|, u ≥ 0, v ≥ 0 }.

To solve this problem with SCS, call SCS.scs_solve; see the docstring for details.

diff --git a/previews/PR3536/packages/SDDP/index.html b/previews/PR3536/packages/SDDP/index.html new file mode 100644 index 00000000000..ad676efd662 --- /dev/null +++ b/previews/PR3536/packages/SDDP/index.html @@ -0,0 +1,6 @@ + +odow/SDDP.jl · JuMP
diff --git a/previews/PR3536/packages/SDPA/index.html b/previews/PR3536/packages/SDPA/index.html new file mode 100644 index 00000000000..67a3d5dd36f --- /dev/null +++ b/previews/PR3536/packages/SDPA/index.html @@ -0,0 +1,15 @@ + +jump-dev/SDPA.jl · JuMP

SDPA.jl

Build Status codecov

SDPA.jl is a wrapper for the SDPA semidefinite programming solver in double precision floating point arithmetic.

Affiliation

This wrapper is maintained by the JuMP community and is not a product of the SDPA developers.

License

SDPA.jl is licensed under the MIT License.

The underlying solver, SDPA is licensed under the GPL v2 license.

Installation

Install SDPA using Pkg.add:

import Pkg
+Pkg.add("SDPA")

In addition to installing the SDPA.jl package, this will also download and install the SDPA binaries. (You do not need to install SDPA separately.)

If you see an error similar to:

INFO: Precompiling module GZip.
+ERROR: LoadError: LoadError: error compiling anonymous: could not load library "libz"

please see GZip.jl#54 or Flux.jl#343. In particular, in Ubuntu this issue may be resolved by running

sudo apt-get install zlib1g-dev

See SDPAFamily for the other solvers, SDPA-GMP, SDPA-DD, and SDPA-QD of the family.

Use with JuMP

using JuMP, SDPA
+model = Model(SDPA.Optimizer)
+set_attribute(model, "Mode", SDPA.PARAMETER_DEFAULT)

MathOptInterface API

The SDPA optimizer supports the following constraints and attributes.

List of supported objective functions:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

SDPA has three modes that give default value to all ten parameters.

The following table gives the default values for each parameter and mode.

ParameterPARAMETER_DEFAULTPARAMETER_UNSTABLE_BUT_FASTPARAMETER_STABLE_BUT_SLOW
MaxIteration1001001000
EpsilonStar1.0e-71.0e-71.0e-7
LambdaStar1.0e+21.0e+21.0e+4
OmegaStar2.02.02.0
LowerBound1.0e+51.0e+51.0e+5
UpperBound1.0e+51.0e+51.0e+5
BetaStar0.10.010.1
BetaBar0.20.020.3
GammaStar0.90.950.8
EpsilonDash1.0e-71.0e-71.0e-7

By default, we put SDPA in the SDPA.PARAMETER_DEFAULT mode.

Change the mode using the "Mode" option:

using JuMP, SDPA
+model = Model(SDPA.Optimizer)
+set_attribute(model, "Mode", SDPA.PARAMETER_STABLE_BUT_SLOW)

Note that the parameters are set in the order they are given, so you can set a mode and then modify parameters from this mode.

using JuMP, SDPA
+model = Model(SDPA.Optimizer)
+set_attribute(model, "Mode", SDPA.PARAMETER_STABLE_BUT_SLOW)
+set_attribute(model, "MaxIteration", 100)

The choice of parameter mode has a large impact on the performance and stability of SDPA, and not necessarily in the way implied by the names of the modes; for example, PARAMETER_UNSTABLE_BUT_FAST can be more stable than the other modes for some problems. You should try each mode to see how it performs on your specific problem. See SDPA.jl#17 for more details.

diff --git a/previews/PR3536/packages/SDPNAL/index.html b/previews/PR3536/packages/SDPNAL/index.html new file mode 100644 index 00000000000..204a1f77a6f --- /dev/null +++ b/previews/PR3536/packages/SDPNAL/index.html @@ -0,0 +1,21 @@ + +jump-dev/SDPNAL.jl · JuMP

SDPNAL.jl

SDPNAL.jl is wrapper for the SDPNALplus solver.

The wrapper has two components:

  • an exported sdpnalplus function that is a thin wrapper on top of the sdpnalplus MATLAB function
  • an interface to MathOptInterface

Affiliation

This wrapper is maintained by the JuMP community and is not an official wrapper of SDPNALplus.

License

SDPNAL.jl is licensed under the MIT License.

The underlying solver, SDPNALplus is licensed under the Creative Commons Attribution-ShareAlike 4.0 International Public License.

In addition, SDPNAL requires an installation of MATLAB, which is a closed-source commercial product for which you must obtain a license.

Use with JuMP

To use SDPNAL with JuMP, do:

using JuMP, SDPNAL
+model = Model(SDPNAL.Optimizer)
+set_attribute(model, "printlevel", 0)

Note that, contrary to implementation of other solver-independent interfaces, using SDPNAL from JuMP or MOI fully exploits the particular structures of the SDPNAL interface and does not create superfluous slack variables and equality constraints as discussed in the SDPNAL guide:

A new interface is necessary to facilitate the modeling of an SDP problem for SDPNAL+ because of latter’s flexibility to directly accept inequality constraints of the form “l ≤ B(X) ≤ u”, and bound constraints of the form “L ≤ X ≤ U”. The flexibility can significantly simplify the generation of the data in the SDPNAL+ format as compared to what need to be done in CVX or YALMIP to reformulate them as equality constraints through introducing extra variables. In addition, the final number of equality constraints present in the data input to SDPNAL+ can also be substantially fewer than those present in CVX or YALMIP. It is important to note here that the number of equality constraints present in the generated problem data can greatly affect the computational efficiency of the solvers, especially for interior-point based solvers.

Installation

First, make sure that you satisfy the requirements of the MATLAB.jl Julia package, and that the SDPNALplus software is installed in your MATLAB™ installation.

Then, install SDPNAL.jl using Pkg.add:

import Pkg
+Pkg.add("SDPNAL")

There is a startup.m file at the root of the SDPNAL folder. This adds all subdirectories recursively when MATLAB starts. However, the interface directory contains a .git subdirectory which contains a very large number of files. Because of this, MATLAB crashes if SDPNAL is in its path because the startup.m requests MATLAB to try to parse all the files in the .git folder. To resolve this problem, delete the startup.m file and .git folder, and add the subdirectories manually your toolbox/local/pathdef.m file as follows:

function p = pathdef
+
+% (...)
+
+p = [...
+%%% BEGIN ENTRIES %%%
+'/path/to/SDPNALv1.0:', ...
+'/path/to/SDPNALv1.0/interface:', ...
+'/path/to/SDPNALv1.0/mexfun:', ...
+'/path/to/SDPNALv1.0/solver:', ...
+'/path/to/SDPNALv1.0/solver_main_default:', ...
+'/path/to/SDPNALv1.0/util:', ...
+% (...)

If you have SDPT3 in addition to SDPNAL in the MATLAB path (that is, the toolbox/local/pathdef.m file) then you might have issues because both solvers define a validate function, and this might make SDPNAL call SDPT3's validate function instead of SDPT3's validate function.

diff --git a/previews/PR3536/packages/SDPT3/index.html b/previews/PR3536/packages/SDPT3/index.html new file mode 100644 index 00000000000..d805ff2209f --- /dev/null +++ b/previews/PR3536/packages/SDPT3/index.html @@ -0,0 +1,32 @@ + +jump-dev/SDPT3.jl · JuMP

SDPT3.jl

SDPT3.jl is wrapper for the SDPT3 solver.

The wrapper has two components:

  • an exported sdpt3 function that is a thin wrapper on top of the sdpt3 MATLAB function
  • an interface to MathOptInterface

Affiliation

This wrapper is maintained by the JuMP community and is not an official wrapper of SDPT3.

License

SDPT3.jl is licensed under the MIT License.

The underlying solver, SDPT3 is licensed under the GPL v2 License.

In addition, SDPT3 requires an installation of MATLAB, which is a closed-source commercial product for which you must obtain a license.

Use with JuMP

To use SDPT3 with JuMP, do:

using JuMP, SDPT3
+model = Model(SDPT3.Optimizer)
+set_attribute(model, "printlevel", 0)

Installation

First, make sure that you satisfy the requirements of the MATLAB.jl Julia package, and that the SeDuMi software is installed in your MATLAB™ installation.

Then, install SDPT3.jl using Pkg.add:

import Pkg
+Pkg.add("SDPT3")

SDPT3 not in PATH

If you get the error:

Error using save
+Variable 'jx_sdpt3_arg_out_1' not found.
+
+ERROR: LoadError: MATLAB.MEngineError("failed to get variable jx_sdpt3_arg_out_1 from MATLAB session")
+Stacktrace:
+[...]

The error means that we could not find the sdpt3 function with one output argument using the MATLAB C API. This most likely means that you did not add SDPT3 to the MATLAB's path (that is, the toolbox/local/pathdef.m file).

If modifying toolbox/local/pathdef.m does not work, the following should work, where /path/to/sdpt3/ is the directory where the sdpt3 folder is located:

julia> using MATLAB
+
+julia> cd("/path/to/sdpt3/") do
+           MATLAB.mat"install_sdpt3"
+       end
+
+julia> MATLAB.mat"savepath"

An alternative fix is suggested in the following issue.

Error in validate

If you get the error:

Brace indexing is not supported for variables of this type.
+
+Error in validate
+
+Error in sdpt3 (line 171)
+   [blk,At,C,b,blkdim,numblk,parbarrier] = validate(blk,At,C,b,par,parbarrier);
+
+Error using save
+Variable 'jx_sdpt3_arg_out_1' not found.

It might mean that you have added SDPNAL in addition to SDPT3 in the MATLAB's path (that is, the toolbox/local/pathdef.m file). Because SDPNAL also defines a validate function, this can make sdpt3 call SDPNAL's validate function instead of SDPT3's validate function, which causes the issue.

One way to fix this from the Julia REPL is to reset the search path to the factory-installed state using restoredefaultpath:

julia> using MATLAB
+
+julia> MATLAB.restoredefaultpath()
+
+julia> MATLAB.mat"savepath"
diff --git a/previews/PR3536/packages/SeDuMi/index.html b/previews/PR3536/packages/SeDuMi/index.html new file mode 100644 index 00000000000..d5e27c73627 --- /dev/null +++ b/previews/PR3536/packages/SeDuMi/index.html @@ -0,0 +1,20 @@ + +jump-dev/SeDuMi.jl · JuMP

SeDuMi.jl

SeDuMi.jl is wrapper for the SeDuMi solver.

The wrapper has two components:

  • an exported sedumi function that is a thin wrapper on top of the sedumi MATLAB function
  • an interface to MathOptInterface

Affiliation

This wrapper is maintained by the JuMP community and is not an official wrapper of SeDuMi.

License

SeDuMi.jl is licensed under the MIT License.

The underlying solver, sqlp/sedumi is licensed under the GPL v2 license.

In addition, SeDuMi requires an installation of MATLAB, which is a closed-source commercial product for which you must obtain a license.

Use with JuMP

To use SeDuMi with JuMP, do:

using JuMP, SeDuMi
+model = Model(SeDuMi.Optimizer)
+set_attribute(model, "fid", 0)

Installation

First, make sure that you satisfy the requirements of the MATLAB.jl Julia package, and that the SeDuMi software is installed in your MATLAB™ installation.

Then, install SeDuMi.jl using Pkg.add:

import Pkg
+Pkg.add("SeDuMi")

If you get the error:

Undefined function or variable 'sedumi'.
+
+Error using save
+Variable 'jx_sedumi_arg_out_1' not found.
+
+ERROR: LoadError: MATLAB.MEngineError("failed to get variable jx_sedumi_arg_out_1 from MATLAB session")

The error means that we couldn't find the sedumi function with one output argument using the MATLAB C API.

This most likely means that you did not add SeDuMi to the MATLAB's path, that is, the toolbox/local/pathdef.m file.

If modifying toolbox/local/pathdef.m does not work, the following should work, where /path/to/sedumi/ is the directory where the sedumi folder is located:

julia> import MATLAB
+
+julia> cd("/path/to/sedumi/") do
+           MATLAB.mat"install_sedumi"
+       end
+
+julia> MATLAB.mat"savepath"
diff --git a/previews/PR3536/packages/SumOfSquares/index.html b/previews/PR3536/packages/SumOfSquares/index.html new file mode 100644 index 00000000000..2e2dabc0b46 --- /dev/null +++ b/previews/PR3536/packages/SumOfSquares/index.html @@ -0,0 +1,7 @@ + +jump-dev/SumOfSquares.jl · JuMP

SumOfSquares.jl

Build Status codecov

SumOfSquares.jl is a JuMP extension that, when used in conjunction with MultivariatePolynomial and PolyJuMP, implements a sum of squares reformulation for polynomial optimization.

License

SumOfSquares.jl is licensed under the MIT license.

Installation

Install SumOfSquares using Pkg.add:

import Pkg
+Pkg.add("SumOfSquares")

Documentation

See https://jump.dev/SumOfSquares.jl/stable for the most recently tagged version of the documentation.

See https://jump.dev/SumOfSquares.jl/dev for the in-development version of the documentation.

Presentations

Some presentations on, or using, SumOfSquares (see blegat/SumOfSquaresSlides for the source code of the presentations):

Citing

See CITATION.bib.

diff --git a/previews/PR3536/packages/Tulip/index.html b/previews/PR3536/packages/Tulip/index.html new file mode 100644 index 00000000000..bdfbad836b3 --- /dev/null +++ b/previews/PR3536/packages/Tulip/index.html @@ -0,0 +1,31 @@ + +ds4dm/Tulip.jl · JuMP

Tulip

DOI

Tulip is an open-source interior-point solver for linear optimization, written in pure Julia. It implements the homogeneous primal-dual interior-point algorithm with multiple centrality corrections, and therefore handles unbounded and infeasible problems. Tulip’s main feature is that its algorithmic framework is disentangled from linear algebra implementations. This allows to seamlessly integrate specialized routines for structured problems.

License

Tulip is licensed under the MPL 2.0 license.

Installation

Install Tulip using the Julia package manager:

import Pkg
+Pkg.add("Tulip")

Usage

The recommended way of using Tulip is through JuMP or MathOptInterface (MOI).

The low-level interface is still under development and is likely change in the future. The MOI interface is more stable.

Using with JuMP

Tulip follows the syntax convention PackageName.Optimizer:

using JuMP
+import Tulip
+model = Model(Tulip.Optimizer)

Linear objectives, linear constraints and lower/upper bounds on variables are supported.

Using with MOI

The type Tulip.Optimizer is parametrized by the model's arithmetic, for example, Float64 or BigFloat. This allows to solve problem in higher numerical precision. See the documentation for more details.

import MathOptInterface as MOI
+import Tulip
+model = Tulip.Optimizer{Float64}()   # Create a model in Float64 precision
+model = Tulip.Optimizer()            # Defaults to the above call
+model = Tulip.Optimizer{BigFloat}()  # Create a model in BigFloat precision

Solver parameters

See the documentation for a full list of parameters.

To set parameters in JuMP, use:

using JuMP, Tulip
+model = Model(Tulip.Optimizer)
+set_attribute(model, "IPM_IterationsLimit", 200)

To set parameters in MathOptInterface, use:

using Tulip
+import MathOptInterface as MOI
+model = Tulip.Optimizer{Float64}()
+MOI.set(model, MOI.RawOptimizerAttribute("IPM_IterationsLimit"), 200)

To set parameters in the Tulip API, use:

using Tulip
+model = Tulip.Model{Float64}()
+Tulip.set_parameter(model, "IPM_IterationsLimit", 200)

Command-line executable

See app building instructions.

Citing Tulip.jl

If you use Tulip in your work, we kindly ask that you cite the following reference (preprint available here).

@Article{Tulip.jl,
+  author   = {Tanneau, Mathieu and Anjos, Miguel F. and Lodi, Andrea},
+  journal  = {Mathematical Programming Computation},
+  title    = {Design and implementation of a modular interior-point solver for linear optimization},
+  year     = {2021},
+  issn     = {1867-2957},
+  month    = feb,
+  doi      = {10.1007/s12532-020-00200-8},
+  language = {en},
+  url      = {https://doi.org/10.1007/s12532-020-00200-8},
+  urldate  = {2021-03-07},
+}
diff --git a/previews/PR3536/packages/Xpress/index.html b/previews/PR3536/packages/Xpress/index.html new file mode 100644 index 00000000000..21e08c7c959 --- /dev/null +++ b/previews/PR3536/packages/Xpress/index.html @@ -0,0 +1,11 @@ + +jump-dev/Xpress.jl · JuMP

Xpress.jl

Build Status codecov

Xpress.jl is a wrapper for the FICO Xpress Solver.

It has two components:

Affiliation

The Xpress wrapper for Julia is community driven and not officially supported by FICO Xpress. If you are a commercial customer interested in official support for Julia from FICO Xpress, let them know.

License

Xpress.jl is licensed under the MIT License.

The underlying solver is a closed-source commercial product for which you must purchase a license.

Installation

First, obtain a license of Xpress and install Xpress solver, following the instructions on the FICO website.

Then, install this package using:

import Pkg
+Pkg.add("Xpress")

If you encounter an error, make sure that the XPRESSDIR environmental variable is set to the path of the Xpress directory. This should be part of a standard installation. The Xpress library will be searched for in XPRESSDIR/lib on Unix platforms and XPRESSDIR/bin on Windows.

For example, on macOS, you may need:

ENV["XPRESSDIR"] = "/Applications/FICO Xpress/xpressmp/"
+import Pkg
+Pkg.add("Xpress")

By default, building Xpress.jl will fail if the Xpress library is not found. This may not be desirable in certain cases, for example when part of a package's test suite uses Xpress as an optional test dependency, but Xpress cannot be installed on a CI server running the test suite. To support this use case, the XPRESS_JL_SKIP_LIB_CHECK environment variable may be set (to any value) to make Xpress.jl installable (but not usable).

Use with JuMP

To use Xpress with JuMP, use:

using JuMP, Xpress
+model = Model(Xpress.Optimizer)
+set_optimizer(model, "PRESOLVE", 0)

Options

For other parameters use Xpress Optimizer manual or type julia -e "using Xpress; println(keys(Xpress.XPRS_ATTRIBUTES))".

If logfile is set to "", the log file is disabled and output is printed to the console (there might be issues with console output on windows (it is manually implemented with callbacks)). If logfile is set to a file's path, output is printed to that file. By default, logfile = "" (console).

Callbacks

Solver specific and solver independent callbacks are working in MathOptInterface and, consequently, in JuMP. However, the current implementation should be considered experimental.

Environment variables

  • XPRESS_JL_SKIP_LIB_CHECK: Used to skip build lib check as previously described.
  • XPRESS_JL_NO_INFO: Disable license info log.
  • XPRESS_JL_NO_DEPS_ERROR: Disable error when do deps.jl file is found.
  • XPRESS_JL_NO_AUTO_INIT: Disable automatic run of Xpress.initialize().

Specially useful for explicitly loading the dynamic library.

Skipping Xpress.postsolve

In older versions of Xpress, the command XPRSpostsolve throws an error in infeasible models. In these older versions the post solve should not be executed. To do this, one can use the MOI.RawOptimizerAttribute("MOI_POST_SOLVE") to skip this routine.

C API

The C API can be accessed via Xpress.Lib.XPRSxx functions, where the names and arguments are identical to the C API.

See the Xpress documentation for details.

Documentation

For more information, consult the FICO optimizer manual.

diff --git a/previews/PR3536/packages/solvers/index.html b/previews/PR3536/packages/solvers/index.html new file mode 100644 index 00000000000..8e91c04b22b --- /dev/null +++ b/previews/PR3536/packages/solvers/index.html @@ -0,0 +1,6 @@ + +Introduction · JuMP

Introduction

This section of the documentation contains brief documentation for some of the solvers that JuMP supports. The list of solvers is not exhaustive, but instead is intended to help you discover commonly used solvers.

Affiliation

Packages beginning with jump-dev/ are developed and maintained by the JuMP developers. In many cases, these packages wrap external solvers that are not developed by the JuMP developers and, while the Julia packages are all open-source, in some cases the solvers themselves are closed source commercial products.

Packages that do not begin with jump-dev/ are developed independently. The developers of these packages requested or consented to the inclusion of their README contents in the JuMP documentation for the benefit of users.

Adding new solvers

Written a solver? Add it to this section of the JuMP documentation by making a pull request to the docs/packages.toml file.

diff --git a/previews/PR3536/release_notes/index.html b/previews/PR3536/release_notes/index.html new file mode 100644 index 00000000000..50116f2eabe --- /dev/null +++ b/previews/PR3536/release_notes/index.html @@ -0,0 +1,15 @@ + +Release notes · JuMP

Release notes

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Version 1.15.1 (September 24, 2023)

Fixed

Other

Version 1.15.0 (September 15, 2023)

This is a large minor release because it adds an entirely new data structure and API path for working with nonlinear programs. The previous nonlinear interface remains unchanged and is documented at Nonlinear Modeling (Legacy). The new interface is a treated as a non-breaking feature addition and is documented at Nonlinear Modeling.

Breaking

Although the new nonlinear interface is a feature addition, there are two changes which might be breaking for a very small number of users.

  • The syntax inside JuMP macros is parsed using a different code path, even for linear and quadratic expressions. We made this change to unify how we parse linear, quadratic, and nonlinear expressions. In all cases, the new code returns equivalent expressions, but because of the different order of operations, there are three changes to be aware of when updating:
    • The printed form of the expression may change, for example from x * y to y * x. This can cause tests which test the String representation of a model to fail.
    • Some coefficients may change slightly due to floating point round-off error.
    • Particularly when working with a JuMP extension, you may encounter a MethodError due to a missing or ambiguous method. These errors are due to previously existing bugs that were not triggered by the previous parsing code. If you encounter such an error, please open a GitHub issue.
  • The methods for Base.:^(x::VariableRef, n::Integer) and Base.:^(x::AffExpr, n::Integer) have changed. Previously, these methods supported only n = 0, 1, 2 and they always returned a QuadExpr, even for the case when n = 0 or n = 1. Now:
    • x^0 returns one(T), where T is the value_type of the model (defaults to Float64)
    • x^1 returns x
    • x^2 returns a QuadExpr
    • x^n where !(0 <= n <= 2) returns a NonlinearExpr.
    We made this change to support nonlinear expressions and to align the mathematical definition of the operation with their return type. (Previously, users were surprised that x^1 returned a QuadExpr.) As a consequence of this change, the methods are now not type-stable. This means that the compiler cannot prove that x^2 returns a QuadExpr. If benchmarking shows that this is a performance problem, you can use the type-stable x * x instead of x^2.

Added

Fixed

Other

Version 1.14.1 (September 2, 2023)

Fixed

  • Fix links in Documentation (#3478)

Version 1.14.0 (August 27, 2023)

Added

Fixed

  • Fixed model_convert for BridgeableConstraint (#3437)
  • Fixed printing models with integer coefficients larger than typemax(Int) (#3447)
  • Fixed support for constant left-hand side functions in a complementarity constraint (#3452)

Other

  • Updated packages used in documentation (#3444) (#3455)
  • Fixed docstring tests (#3445)
  • Fixed printing change for MathOptInterface (#3446)
  • Fixed typos in documentation (#3448) (#3457)
  • Added SCIP to callback documentation (#3449)

Version 1.13.0 (July 27, 2023)

Added

Fixed

Other

  • Added Loraine.jl to the installation table (#3426)
  • Removed Penopt.jl from packages.toml (#3428)
  • Improved problem statement in cannery example of tutorial (#3430)
  • Minor cleanups in Containers.DenseAxisArray implementation (#3429)
  • Changed nested_problems.jl: outer/inner to upper/lower (#3433)
  • Removed second SDP relaxation in OPF tutorial (#3432)

Version 1.12.0 (June 19, 2023)

Added

Fixed

Other

Version 1.11.1 (May 19, 2023)

Fixed

  • Fixed a poor error message when sum(::DenseAxisArray; dims) was called (#3338)
  • Fixed support for dependent sets in the @variable macro (#3344)
  • Fixed a performance bug in constraints with sparse symmetric matrices (#3349)

Other

  • Improved the printing of complex numbers (#3332)
  • When printing, sets which contain constants ending in .0 now print as integers. This follows the behavior of constants in functions (#3341)
  • Added InfiniteOpt to the extensions documentation (#3343)
  • Added more documentation for the exponential cone (#3345) (#3347)
  • Added checklists for developers (#3346) (#3355)
  • Fixed test support upcoming Julia nightly (#3351)
  • Fixed extension-tests.yml action (#3353)
  • Add more solvers to the documentation (#3359) (#3361) (#3362)

Version 1.11.0 (May 3, 2023)

Added

Fixed

  • Fixed tests for MOI v1.14.0 release (#3312)
  • Fixed indexing containers when an axis is Vector{Any} that contains a Vector{Any} element (#3280)
  • Fixed getindex(::AbstractJuMPScalar) which is called for an expression like x[] (#3314)
  • Fixed bug in set_string_names_on_creation with a vector of variables (#3322)
  • Fixed bug in memoize function in nonlinear documentation (#3337)

Other

  • Fixed typos in the documentation (#3317) (#3318) (#3328)
  • Added a test for the order of setting start values (#3315)
  • Added READMEs of solvers and extensions to the docs (#3309) (#3320) (#3327) (#3329) (#3333)
  • Style improvements to src/variables.jl (#3324)
  • Clarify that column generation does not find global optimum (#3325)
  • Add a GitHub actions workflow for testing extensions prior to release (#3331)
  • Document the release process for JuMP (#3334)
  • Fix links to discourse and chatroom (#3335)

Version 1.10.0 (April 3, 2023)

Added

Fixed

  • Fixed [compat] bound for MathOptInterface in Project.toml (#3272)

Other

Version 1.9.0 (March 7, 2023)

Added

Fixed

  • The matrix returned by a variable in HermitianPSDCone is now a LinearAlgebra.Hermitian matrix. This is potentially breaking if you have written code to assume the return is a Matrix. (#3245) (#3246)
  • Fixed missing support for Base.isreal of expressions (#3252)

Other

Version 1.8.2 (February 27, 2023)

Fixed

  • Fixed dot product between complex JuMP expression and number (#3244)

Other

  • Polish simple SDP examples (#3232)

Version 1.8.1 (February 23, 2023)

Fixed

  • Fixed support for init in nonlinear generator expressions (#3226)

Other

  • Use and document import MathOptInterface as MOI (#3222)
  • Removed references in documentation to multiobjective optimization being unsupported (#3223)
  • Added tutorial on multi-objective portfolio optimization (#3227)
  • Refactored some of the conic tutorials (#3229)
  • Fixed typos in the documentation (#3230)
  • Added tutorial on parallelism (#3231)

Version 1.8.0 (February 16, 2023)

Added

  • Added --> syntax support for indicator constraints. The old syntax of => remains supported (#3207)
  • Added <--> syntax for reified constraints. For now, few solvers support reified constraints (#3206)
  • Added fix_discrete_variables. This is most useful for computing the dual of a mixed-integer program (#3208)
  • Added support for vector-valued objectives. For details, see the Multi-objective knapsack tutorial (#3176)

Fixed

  • Fixed a bug in lp_sensitivity_report by switching to an explicit LU factorization of the basis matrix (#3182)
  • Fixed a bug that prevented [; kwarg] arguments in macros (#3220)

Other

Version 1.7.0 (January 25, 2023)

Added

Other

  • Large refactoring of the tests (#3166) (#3167) (#3168) (#3169) (#3170) (#3171)
  • Remove unreachable code due to VERSION checks (#3172)
  • Document how to test JuMP extensions (#3174)
  • Fix method ambiguities in Containers (#3173)
  • Improve error message that is thrown when = is used instead of == in the @constraint macro (#3178)
  • Improve the error message when Bool is used instead of Bin in the @variable macro (#3180)
  • Update versions of the documentation (#3185)
  • Tidy the import of packages and remove unnecessary prefixes (#3186) (#3187)
  • Refactor src/JuMP.jl by moving methods into more relevant files (#3188)
  • Fix docstring of Model not appearing in the documentation (#3198)

Version 1.6.0 (January 1, 2023)

Added

Fixed

  • Fixed promotion of complex expressions (#3150) (#3164)

Other

  • Added Benders tutorial with in-place resolves (#3145)
  • Added more Tips and tricks for linear programs (#3144) (#3163)
  • Clarified documentation that start can depend on the indices of a variable container (#3148)
  • Replace instances of length and size by the recommended eachindex and axes (#3149)
  • Added a warning explaining why the model is dirty when accessing solution results from a modified model (#3156)
  • Clarify documentation that PSD ensures a symmetric matrix (#3159)
  • Maintenance of the JuMP test suite (#3146) (#3158) (#3162)

Version 1.5.0 (December 8, 2022)

Added

Fixed

Other

Version 1.4.0 (October 29, 2022)

Added

Fixed

  • Fixed a bug in copy_to(dest::Model, src::MOI.ModelLike) when src has nonlinear components (#3101)
  • Fixed the printing of (-1.0 + 0.0im) coefficients in complex expressions (#3112)
  • Fixed a parsing bug in nonlinear expressions with generator statements that contain multiple for statements (#3116)

Other

  • Converted the multi-commodity flow tutorial to use an SQLite database (#3098)
  • Fixed a number of typos in the documentation (#3103) (#3107) (#3018)
  • Improved various style aspects of the PDF documentation (#3095) (#3098) (#3102)

Version 1.3.1 (September 28, 2022)

Fixed

  • Fixed a performance issue in relax_integrality (#3087)
  • Fixed the type stability of operators with Complex arguments (#3072)
  • Fixed a bug which added additional +() terms to some nonlinear expressions (#3091)
  • Fixed potential method ambiguities with AffExpr and QuadExpr objects (#3092)

Other

Version 1.3.0 (September 5, 2022)

Added

  • Support slicing in SparseAxisArray (#3031)

Fixed

  • Fixed a bug introduced in v1.2.0 that prevented DenseAxisArrays with Vector keys (#3064)

Other

Version 1.2.1 (August 22, 2022)

Fixed

  • Fixed a bug when parsing two-sided nonlinear constraints (#3045)

Version 1.2.0 (August 16, 2022)

Breaking

This is a large minor release because it significantly refactors the internal code for handling nonlinear programs to use the MathOptInterface.Nonlinear submodule that was introduced in MathOptInterface v1.3.0. As a consequence, the internal datastructure in model.nlp_data has been removed, as has the JuMP._Derivatives submodule. Despite the changes, the public API for nonlinear programming has not changed, and any code that uses only the public API and that worked with v1.1.1 will continue to work with v1.2.0.

Added

  • Added all_constraints(model; include_variable_in_set_constraints) which simplifies returning a list of all constraint indices in the model.
  • Added the ability to delete nonlinear constraints via delete(::Model, ::NonlinearConstraintRef).
  • Added the ability to provide an explicit Hessian for a multivariate user-defined function.
  • Added support for querying the primal value of a nonlinear constraint via value(::NonlinearConstraintRef)

Fixed

  • Fixed a bug in Containers.DenseAxisArray so that it now supports indexing with keys that hash to the same value, even if they are different types, for example, Int32 and Int64.
  • Fixed a bug printing the model when the solver does not support MOI.Name.

Other

  • Added a constraint programming formulation to the Sudoku tutorial.
  • Added newly supported solvers Pajarito, Clarabel, and COPT to the installation table.
  • Fixed a variety of other miscellaneous issues in the documentation.

Version 1.1.1 (June 14, 2022)

Other

  • Fixed problem displaying LaTeX in the documentation
  • Minor updates to the style guide
  • Updated to MOI v1.4.0 in the documentation

Version 1.1.0 (May 25, 2022)

Added

  • Added num_constraints(::Model; count_variable_in_set_constraints) to simplify the process of counting the number of constraints in a model
  • Added VariableRef(::ConstraintRef) for querying the variable associated with a bound or integrality constraint.
  • Added set_normalized_coefficients for modifying the variable coefficients of a vector-valued constraint.
  • Added set_string_names_on_creation to disable creating String names for variables and constraints. This can improve performance.

Fixed

  • Fixed a bug passing nothing to the start keyword of @variable

Other

  • New tutorials:
    • Sensitivity analysis of a linear program
    • Serving web apps
  • Minimal ellipse SDP tutorial refactored and improved
  • Docs updated to the latest version of each package
  • Lots of minor fixes and improvements to the documentation

Version 1.0.0 (March 24, 2022)

Read more about this release, along with an acknowledgement of all the contributors in our JuMP 1.0.0 is released blog post.

Breaking

  • The previously deprecated functions (v0.23.0, v0.23.1) have been removed. Deprecation was to improve consistency of function names:
    • num_nl_constraints (see num_nonlinear_constraints)
    • all_nl_constraints (see all_nonlinear_constraints)
    • add_NL_expression (see add_nonlinear_expression)
    • set_NL_objective (see set_nonlinear_objective)
    • add_NL_constraint (see add_nonlinear_constraint)
    • nl_expr_string (see nonlinear_expr_string)
    • nl_constraint_string (see nonlinear_constraint_string)
    • SymMatrixSpace (see SymmetricMatrixSpace)
  • The unintentionally exported variable JuMP.op_hint has been renamed to the unexported JuMP._OP_HINT

Fixed

  • Fixed a bug writing .nl files
  • Fixed a bug broadcasting SparseAxisArrays

Version 0.23.2 (March 14, 2022)

Added

  • Added relative_gap to solution_summary
  • register now throws an informative error if the function is not differentiable using ForwardDiff. In some cases, the check in register will encounter a false negative, and the informative error will be thrown at run-time. This usually happens when the function is non-differentiable in a subset of the domain.

Fixed

  • Fixed a scoping issue when extending the container keyword of containers

Other

  • Docs updated to the latest version of each package

Version 0.23.1 (March 2, 2022)

Deprecated

  • nl_expr_string and nl_constraint_string have been renamed to nonlinear_expr_string and nonlinear_constraint_string. The old methods still exist with deprecation warnings. This change should impact very few users because to call them you must rely on private internals of the nonlinear API. Users are encouraged to use sprint(show, x) instead, where x is the nonlinear expression or constraint of interest.

Added

  • Added support for Base.abs2(x) where x is a variable or affine expression. This is mainly useful for complex-valued constraints.

Fixed

  • Fixed addition of complex and real affine expressions
  • Fixed arithmetic for Complex-valued quadratic expressions
  • Fixed variable bounds passed as Rational{Int}(Inf)
  • Fixed printing of the coefficient (0 + 1im)
  • Fixed a bug when solution_summary is called prior to optimize!

Version 0.23.0 (February 25, 2022)

JuMP v0.23.0 is a breaking release. It is also a release-candidate for JuMP v1.0.0. That is, if no issues are found with the v0.23.0 release, then it will be re-tagged as v1.0.0.

Breaking

  • Julia 1.6 is now the minimum supported version
  • MathOptInterface has been updated to v1.0.0
  • All previously deprecated functionality has been removed
  • PrintMode, REPLMode and IJuliaMode have been removed in favor of the MIME types MIME"text/plain" and MIME"text/latex". Replace instances of ::Type{REPLMode} with ::MIME"text/plain", REPLMode with MIME("text/plain"), ::Type{IJuliaMode} with ::MIME"text/latex", and IJuliaMode with MIME("text/latex").
  • Functions containing the nl_ acronym have been renamed to the more explicit nonlinear_. For example, num_nl_constraints is now num_nonlinear_constraints and set_NL_objective is now set_nonlinear_objective. Calls to the old functions throw an error explaining the new name.
  • SymMatrixSpace has been renamed to SymmetricMatrixSpace

Added

  • Added nonlinear_dual_start_value and set_nonlinear_dual_start_value
  • Added preliminary support for Complex coefficient types

Fixed

  • Fixed a bug in solution_summary

Other

  • MILP examples have been migrated from GLPK to HiGHS
  • Fixed various typos
  • Improved section on setting constraint start values

Troubleshooting problems when updating

If you experience problems when updating, you are likely using previously deprecated functionality. (By default, Julia does not warn when you use deprecated features.)

To find the deprecated features you are using, start Julia with --depwarn=yes:

$ julia --depwarn=yes

Then install JuMP v0.22.3:

julia> using Pkg
+julia> pkg"add JuMP@0.22.3"

And then run your code. Apply any suggestions, or search the release notes below for advice on updating a specific deprecated feature.

Version 0.22.3 (February 10, 2022)

Fixed

  • Fixed a reproducibility issue in the TSP tutorial
  • Fixed a reproducibility issue in the max_cut_sdp tutorial
  • Fixed a bug broadcasting an empty SparseAxisArray

Other

  • Added a warning and improved documentation for the modify-then-query case
  • Fixed a typo in the docstring of RotatedSecondOrderCone
  • Added Aqua.jl as a check for code health
  • Added introductions to each section of the tutorials
  • Improved the column generation and Benders decomposition tutorials
  • Updated documentation to MOI v0.10.8
  • Updated JuliaFormatter to v0.22.2

Version 0.22.2 (January 10, 2022)

Added

  • The function all_nl_constraints now returns all nonlinear constraints in a model
  • start_value and set_start_value can now be used to get and set the primal start for constraint references
  • Plural macros now return a tuple containing the elements that were defined instead of nothing
  • Anonymous variables are now printed as _[i] where i is the index of the variable instead of noname. Calling name(x) still returns "" so this is non-breaking.

Fixed

  • Fixed handling of min and max in nonlinear expressions
  • CartesianIndex is no longer allowed as a key for DenseAxisArrays.

Other

  • Improved the performance of GenericAffExpr
  • Added a tutorial on the Travelling Salesperson Problem
  • Added a tutorial on querying the Hessian of a nonlinear program
  • Added documentation on using custom solver binaries.

Version 0.22.1 (November 29, 2021)

Added

  • Export OptimizationSense enum, with instances: MIN_SENSE, MAX_SENSE, and FEASIBILITY_SENSE
  • Add Base.isempty(::Model) to match Base.empty(::Model)

Fixed

  • Fix bug in container with tuples as indices
  • Fix bug in set_time_limit_sec

Other

  • Add tutorial "Design patterns for larger models"
  • Remove release notes section from PDF
  • General edits of the documentation and error messages

Version 0.22.0 (November 10, 2021)

JuMP v0.22 is a breaking release

Breaking

JuMP 0.22 contains a number of breaking changes. However, these should be invisible for the majority of users. You will mostly encounter these breaking changes if you: wrote a JuMP extension, accessed backend(model), or called @SDconstraint.

The breaking changes are as follows:

  • MathOptInterface has been updated to v0.10.4. For users who have interacted with the MOI backend, this contains a large number of breaking changes. Read the MathOptInterface release notes for more details.
  • The bridge_constraints keyword argument to Model and set_optimizer has been renamed add_bridges to reflect that more thing were bridged than just constraints.
  • The backend(model) field now contains a concrete instance of a MOI.Utilities.CachingOptimizer instead of one with an abstractly typed optimizer field. In most cases, this will lead to improved performance. However, calling set_optimizer after backend invalidates the old backend. For example:
    model = Model()
    +b = backend(model)
    +set_optimizer(model, GLPK.Optimizer)
    +@variable(model, x)
    +# b is not updated with `x`! Get a new b by calling `backend` again.
    +new_b = backend(model)
  • All usages of @SDconstraint are deprecated. The new syntax is @constraint(model, X >= Y, PSDCone()).
  • Creating a DenseAxisArray with a Number as an axis will now display a warning. This catches a common error in which users write @variable(model, x[length(S)]) instead of @variable(model, x[1:length(S)]).
  • The caching_mode argument to Model, for example, Model(caching_mode = MOIU.MANUAL) mode has been removed. For more control over the optimizer, use direct_model instead.
  • The previously deprecated lp_objective_perturbation_range and lp_rhs_perturbation_range functions have been removed. Use lp_sensitivity_report instead.
  • The .m fields of NonlinearExpression and NonlinearParameter have been renamed to .model.
  • Infinite variable bounds are now ignored. Thus, @variable(model, x <= Inf) will show has_upper_bound(x) == false. Previously, these bounds were passed through to the solvers which caused numerical issues for solvers expecting finite bounds.
  • The variable_type and constraint_type functions were removed. This should only affect users who previously wrote JuMP extensions. The functions can be deleted without consequence.
  • The internal functions moi_mode, moi_bridge_constraints, moi_add_constraint, and moi_add_to_function_constant are no longer exported.
  • The un-used method Containers.generate_container has been deleted.
  • The Containers API has been refactored, and _build_ref_sets is now public as Containers.build_ref_sets.
  • The parse_constraint_ methods for extending @constraint at parse time have been refactored in a breaking way. Consult the Extensions documentation for more details and examples.

Added

  • The TerminationStatusCode and ResultStatusCode enums are now exported by JuMP. Prefer termination_status(model) == OPTIMAL instead of == MOI.OPTIMAL, although the MOI. prefix way still works.
  • Copy a x::DenseAxisArray to an Array by calling Array(x).
  • NonlinearExpression is now a subtype of AbstractJuMPScalar
  • Constraints such as @constraint(model, x + 1 in MOI.Integer()) are now supported.
  • primal_feasibility_report now accepts a function as the first argument.
  • Scalar variables @variable(model, x[1:2] in MOI.Integer()) creates two variables, both of which are constrained to be in the set MOI.Integer.
  • Conic constraints can now be specified as inequalities under a different partial ordering. So @constraint(model, x - y in MOI.Nonnegatives()) can now be written as @constraint(model, x >= y, MOI.Nonnegatives()).
  • Names are now set for vectorized constraints.

Fixed

  • Fixed a performance issue when show was called on a SparseAxisArray with a large number of elements.
  • Fixed a bug displaying barrier and simplex iterations in solution_summary.
  • Fixed a bug by implementing hash for DenseAxisArray and SparseAxisArray.
  • Names are now only set if the solver supports them. Previously, this prevented solvers such as Ipopt from being used with direct_model.
  • MutableArithmetics.Zero is converted into a 0.0 before being returned to the user. Previously, some calls to @expression would return the undocumented MutableArithmetics.Zero() object. One example is summing over an empty set @expression(model, sum(x[i] for i in 1:0)). You will now get 0.0 instead.
  • AffExpr and QuadExpr can now be used with == 0 instead of iszero. This fixes a number of issues relating to Julia standard libraries such as LinearAlgebra and SparseArrays.
  • Fixed a bug when registering a user-defined function with splatting.

Other

  • The documentation is now available as a PDF.
  • The documentation now includes a full copy of the MathOptInterface documentation to make it easy to link concepts between the docs. (The MathOptInterface documentation has also been significantly improved.)
  • The documentation contains a large number of improvements and clarifications on a range of topics. Thanks to @sshin23, @DilumAluthge, and @jlwether.
  • The documentation is now built with Julia 1.6 instead of 1.0.
  • Various error messages have been improved to be more readable.

Version 0.21.10 (September 4, 2021)

Added

  • Added add_NL_expression
  • add_NL_xxx functions now support AffExpr and QuadExpr as terms

Fixed

  • Fixed a bug in solution_summary
  • Fixed a bug in relax_integrality

Other

  • Improved error message in lp_sensitivity_report

Version 0.21.9 (August 1, 2021)

Added

  • Containers now support arbitrary container types by passing the type to the container keyword and overloading Containers.container.
  • is_valid now supports nonlinear constraints
  • Added unsafe_backend for querying the inner-most optimizer of a JuMP model.
  • Nonlinear parameters now support the plural @NLparameters macro.
  • Containers (for example, DenseAxisArray) can now be used in vector-valued constraints.

Other

  • Various improvements to the documentation.

Version 0.21.8 (May 8, 2021)

Added

  • The @constraint macro is now extendable in the same way as @variable.
  • AffExpr and QuadExpr can now be used in nonlinear macros.

Fixed

  • Fixed a bug in lp_sensitivity_report.
  • Fixed an inference issue when creating empty SparseAxisArrays.

Version 0.21.7 (April 12, 2021)

Added

  • Added primal_feasibility_report, which can be used to check whether a primal point satisfies primal feasibility.
  • Added coefficient, which returns the coefficient associated with a variable in affine and quadratic expressions.
  • Added copy_conflict, which returns the IIS of an infeasible model.
  • Added solution_summary, which returns (and prints) a struct containing a summary of the solution.
  • Allow AbstractVector in vector constraints instead of just Vector.
  • Added latex_formulation(model) which returns an object representing the latex formulation of a model. Use print(latex_formulation(model)) to print the formulation as a string.
  • User-defined functions in nonlinear expressions are now automatically registered to aid quick model prototyping. However, a warning is printed to encourage the manual registration.
  • DenseAxisArray's now support broadcasting over multiple arrays.
  • Container indices can now be iterators of Base.SizeUnknown.

Fixed

  • Fixed bug in rad2deg and deg2rad in nonlinear expressions.
  • Fixed a MethodError bug in Containers when forcing container type.
  • Allow partial slicing of a DenseAxisArray, resolving an issue from 2014.
  • Fixed a bug printing variable names in IJulia.
  • Ending an IJulia cell with model now prints a summary of the model (like in the REPL) not the latex formulation. Use print(model) to print the latex formulation.
  • Fixed a bug when copying models containing nested arrays.

Other

  • Tutorials are now part of the documentation, and more refactoring has taken place.
  • Added JuliaFormatter added as a code formatter.
  • Added some precompilation statements to reduce initial latency.
  • Various improvements to error messages to make them more helpful.
  • Improved performance of value(::NonlinearExpression).
  • Improved performance of fix(::VariableRef).

Version 0.21.6 (January 29, 2021)

Added

  • Added support for skew symmetric variables via @variable(model, X[1:2, 1:2] in SkewSymmetricMatrixSpace()).
  • lp_sensitivity_report has been added which significantly improves the performance of querying the sensitivity summary of an LP. lp_objective_perturbation_range and lp_rhs_perturbation_range are deprecated.
  • Dual warm-starts are now supported with set_dual_start_value and dual_start_value.
  • (\in<tab>) can now be used in macros instead of = or in.
  • Use haskey(model::Model, key::Symbol) to check if a name key is registered in a model.
  • Added unregister(model::Model, key::Symbol) to unregister a name key from model.
  • Added callback_node_status for use in callbacks.
  • Added print_bridge_graph to visualize the bridging graph generated by MathOptInterface.
  • Improved error message for containers with duplicate indices.

Fixed

  • Various fixes to pass tests on Julia 1.6.
  • Fixed a bug in the printing of nonlinear expressions in IJulia.
  • Fixed a bug when nonlinear expressions are passed to user-defined functions.
  • Some internal functions that were previously exported are now no longer exported.
  • Fixed a bug when relaxing a fixed binary variable.
  • Fixed a StackOverflowError that occurred when SparseAxisArrays had a large number of elements.
  • Removed an unnecessary type assertion in list_of_constraint_types.
  • Fixed a bug when copying models with registered expressions.

Other

  • The documentation has been significantly overhauled. It now has distinct sections for the manual, API reference, and examples. The existing examples in /examples have now been moved to /docs/src/examples and rewritten using Literate.jl, and they are now included in the documentation.
  • JuliaFormatter has been applied to most of the codebase. This will continue to roll out over time, as we fix upstream issues in the formatter, and will eventually become compulsory.
  • The root cause of a large number of method invalidations has been resolved.
  • We switched continuous integration from Travis and Appveyor to GitHub Actions.

Version 0.21.5 (September 18, 2020)

Fixed

  • Fixed deprecation warnings
  • Throw DimensionMismatch for incompatibly sized functions and sets
  • Unify treatment of keys(x) on JuMP containers

Version 0.21.4 (September 14, 2020)

Added

  • Add debug info when adding unsupported constraints
  • Add relax_integrality for solving continuous relaxation
  • Allow querying constraint conflicts

Fixed

  • Dispatch on Real for MOI.submit
  • Implement copy for CustomSet in tests
  • Don't export private macros
  • Fix invalid assertion in nonlinear
  • Error if constraint has NaN right-hand side
  • Improve speed of tests
  • Lots of work modularizing files in /test
  • Improve line numbers in macro error messages
  • Print nonlinear subexpressions
  • Various documentation updates
  • Dependency updates:
    • Datastructures 0.18
    • MathOptFormat v0.5
    • Prep for MathOptInterface 0.9.15

Version 0.21.3 (June 18, 2020)

  • Added Special Order Sets (SOS1 and SOS2) to JuMP with default weights to ease the creation of such constraints (#2212).
  • Added functions simplex_iterations, barrier_iterations and node_count (#2201).
  • Added function reduced_cost (#2205).
  • Implemented callback_value for affine and quadratic expressions (#2231).
  • Support MutableArithmetics.Zero in objective and constraints (#2219).
  • Documentation improvements:
    • Mention tutorials in the docs (#2223).
    • Update COIN-OR links (#2242).
    • Explicit link to the documentation of MOI.FileFormats (#2253).
    • Typo fixes (#2261).
  • Containers improvements:
    • Fix Base.map for DenseAxisArray (#2235).
    • Throw BoundsError if number of indices is incorrect for DenseAxisArray and SparseAxisArray (#2240).
  • Extensibility improvements:
    • Implement a set_objective method fallback that redirects to set_objective_sense and set_objective_function (#2247).
    • Add parse_constraint method with arbitrary number of arguments (#2051).
    • Add parse_constraint_expr and parse_constraint_head (#2228).

Version 0.21.2 (April 2, 2020)

  • Added relative_gap() to access MOI.RelativeGap() attribute (#2199).
  • Documentation fixes:
    • Added link to source for docstrings in the documentation (#2207).
    • Added docstring for @variables macro (#2216).
    • Typo fixes (#2177, #2184, #2182).
  • Implementation of methods for Base functions:
    • Implemented Base.empty! for JuMP.Model (#2198).
    • Implemented Base.conj for JuMP scalar types (#2209).

Fixed

  • Fixed sum of expression with scalar product in macro (#2178).
  • Fixed writing of nonlinear models to MathOptFormat (#2181).
  • Fixed construction of empty SparseAxisArray (#2179).
  • Fixed constraint with zero function (#2188).

Version 0.21.1 (Feb 18, 2020)

  • Improved the clarity of the with_optimizer deprecation warning.

Version 0.21.0 (Feb 16, 2020)

Breaking

  • Deprecated with_optimizer (#2090, #2084, #2141). You can replace with_optimizer by either nothing, optimizer_with_attributes or a closure:

    • replace with_optimizer(Ipopt.Optimizer) by Ipopt.Optimizer.
    • replace with_optimizer(Ipopt.Optimizer, max_cpu_time=60.0) by optimizer_with_attributes(Ipopt.Optimizer, "max_cpu_time" => 60.0).
    • replace with_optimizer(Gurobi.Optimizer, env) by () -> Gurobi.Optimizer(env).
    • replace with_optimizer(Gurobi.Optimizer, env, Presolve=0) by optimizer_with_attributes(() -> Gurobi.Optimizer(env), "Presolve" => 0).

    alternatively to optimizer_with_attributes, you can also set the attributes separately with set_optimizer_attribute.

  • Renamed set_parameter and set_parameters to set_optimizer_attribute and set_optimizer_attributes (#2150).

  • Broadcast should now be explicit inside macros. @SDconstraint(model, x >= 1) and @constraint(model, x + 1 in SecondOrderCone()) now throw an error instead of broadcasting 1 along the dimension of x (#2107).

  • @SDconstraint(model, x >= 0) is now equivalent to @constraint(model, x in PSDCone()) instead of @constraint(model, (x .- 0) in PSDCone()) (#2107).

  • The macros now create the containers with map instead of for loops, as a consequence, containers created by @expression can now have any element type and containers of constraint references now have concrete element types when possible. This fixes a long-standing issue where @expression could only be used to generate a collection of linear expressions. Now it works for quadratic expressions as well (#2070).

  • Calling deepcopy(::AbstractModel) now throws an error.

  • The constraint name is now printed in the model string (#2108).

Added

  • Added support for solver-independent and solver-specific callbacks (#2101).
  • Added write_to_file and read_from_file, supported formats are CBF, LP, MathOptFormat, MPS and SDPA (#2114).
  • Added support for complementarity constraints (#2132).
  • Added support for indicator constraints (#2092).
  • Added support for querying multiple solutions with the result keyword (#2100).
  • Added support for constraining variables on creation (#2128).
  • Added method delete that deletes a vector of variables at once if it is supported by the underlying solver (#2135).
  • The arithmetic between JuMP expression has be refactored into the MutableArithmetics package (#2107).
  • Improved error on complex values in NLP (#1978).
  • Added an example of column generation (#2010).

Fixed

  • Incorrect coefficients generated when using Symmetric variables (#2102)

Version 0.20.1 (Oct 18, 2019)

  • Add sections on @variables and @constraints in the documentation (#2062).
  • Fixed product of sparse matrices for Julia v1.3 (#2063).
  • Added set_objective_coefficient to modify the coefficient of a linear term of the objective function (#2008).
  • Added set_time_limit_sec, unset_time_limit_sec and time_limit_sec to set and query the time limit for the solver in seconds (#2053).

Version 0.20.0 (Aug 24, 2019)

  • Documentation updates.
  • Numerous bug fixes.
  • Better error messages (#1977, #1978, #1997, #2017).
  • Performance improvements (#1947, #2032).
  • Added LP sensitivity summary functions lp_objective_perturbation_range and lp_rhs_perturbation_range (#1917).
  • Added functions dual_objective_value, raw_status and set_parameter.
  • Added function set_objective_coefficient to modify the coefficient of a linear term of the objective (#2008).
  • Added functions set_normalized_rhs, normalized_rhs, and add_to_function_constant to modify and get the constant part of a constraint (#1935, #1960).
  • Added functions set_normalized_coefficient and normalized_coefficient to modify and get the coefficient of a linear term of a constraint (#1935, #1960).
  • Numerous other improvements in MOI 0.9, see the NEWS.md file of MOI for more details.

Version 0.19.2 (June 8, 2019)

  • Fix a bug in derivatives that could arise in models with nested nonlinear subexpressions.

Version 0.19.1 (May 12, 2019)

  • Usability and performance improvements.
  • Bug fixes.

Version 0.19.0 (February 15, 2019)

JuMP 0.19 contains significant breaking changes.

Breaking

  • JuMP's abstraction layer for communicating with solvers changed from MathProgBase (MPB) to MathOptInterface (MOI). MOI addresses many longstanding design issues. (See @mlubin's slides from JuMP-dev 2018.) JuMP 0.19 is compatible only with solvers that have been updated for MOI. See the installation guide for a list of solvers that have and have not yet been updated.

  • Most solvers have been renamed to PackageName.Optimizer. For example, GurobiSolver() is now Gurobi.Optimizer.

  • Solvers are no longer added to a model via Model(solver = XXX(kwargs...)). Instead use Model(with_optimizer(XXX, kwargs...)). For example, Model(with_optimizer(Gurobi.Optimizer, OutputFlag=0)).

  • JuMP containers (for example, the objects returned by @variable) have been redesigned. Containers.SparseAxisArray replaces JuMPDict, JuMPArray was rewritten (inspired by AxisArrays) and renamed Containers.DenseAxisArray, and you can now request a container type with the container= keyword to the macros. See the corresponding documentation for more details.

  • The statuses returned by solvers have changed. See the possible status values here. The MOI statuses are much richer than the MPB statuses and can be used to distinguish between previously indistinguishable cases (for example, did the solver have a feasible solution when it stopped because of the time limit?).

  • Starting values are separate from result values. Use value to query the value of a variable in a solution. Use start_value and set_start_value to get and set an initial starting point provided to the solver. The solutions from previous solves are no longer automatically set as the starting points for the next solve.

  • The data structures for affine and quadratic expressions AffExpr and QuadExpr have changed. Internally, terms are stored in dictionaries instead of lists. Duplicate coefficients can no longer exist. Accessors and iteration methods have changed.

  • JuMPNLPEvaluator no longer includes the linear and quadratic parts of the model in the evaluation calls. These are now handled separately to allow NLP solvers that support various types of constraints.

  • JuMP solver-independent callbacks have been replaced by solver-specific callbacks. See your favorite solver for more details. (See the note below: No solver-specific callbacks are implemented yet.)

  • The norm() syntax is no longer recognized inside macros. Use the SecondOrderCone() set instead.

  • JuMP no longer performs automatic transformation between special quadratic forms and second-order cone constraints. Support for these constraint classes depends on the solver.

  • The symbols :Min and :Max are no longer used as optimization senses. Instead, JuMP uses the OptimizationSense enum from MathOptInterface. @objective(model, Max, ...), @objective(model, Min, ...), @NLobjective(model, Max, ...), and @objective(model, Min, ...) remain valid, but @objective(m, :Max, ...) is no longer accepted.

  • The sign conventions for duals has changed in some cases for consistency with conic duality (see the documentation). The shadow_price helper method returns duals with signs that match conventional LP interpretations of dual values as sensitivities of the objective value to relaxations of constraints.

  • @constraintref is no longer defined. Instead, create the appropriate container to hold constraint references manually. For example,

    constraints = Dict() # Optionally, specify types for improved performance.
    +for i in 1:N
    +  constraints[i] = @constraint(model, ...)
    +end
  • The lowerbound, upperbound, and basename keyword arguments to the @variable macro have been renamed to lower_bound, upper_bound, and base_name, for consistency with JuMP's new style recommendations.

  • We rely on broadcasting syntax to apply accessors to collections of variables, for example, value.(x) instead of getvalue(x) for collections. (Use value(x) when x is a scalar object.)

Added

  • Splatting (like f(x...)) is recognized in restricted settings in nonlinear expressions.

  • Support for deleting constraints and variables.

  • The documentation has been completely rewritten using docstrings and Documenter.

  • Support for modeling mixed conic and quadratic models (for example, conic models with quadratic objectives and bi-linear matrix inequalities).

  • Significantly improved support for modeling new types of constraints and for extending JuMP's macros.

  • Support for providing dual warm starts.

  • Improved support for accessing solver-specific attributes (for example, the irreducible inconsistent subsystem).

  • Explicit control of whether symmetry-enforcing constraints are added to PSD constraints.

  • Support for modeling exponential cones.

  • Significant improvements in internal code quality and testing.

  • Style and naming guidelines.

  • Direct mode and manual mode provide explicit control over when copies of a model are stored or regenerated. See the corresponding documentation.

Regressions

There are known regressions from JuMP 0.18 that will be addressed in a future release (0.19.x or later):

  • Performance regressions in model generation (issue). Please file an issue anyway if you notice a significant performance regression. We have plans to address a number of performance issues, but we might not be aware of all of them.

  • Fast incremental NLP solves are not yet reimplemented (issue).

  • We do not yet have an implementation of solver-specific callbacks.

  • The column generation syntax in @variable has been removed (that is, the objective, coefficients, and inconstraints keyword arguments). Support for column generation will be re-introduced in a future release.

  • The ability to solve the continuous relaxation (that is, via solve(model; relaxation = true)) is not yet reimplemented (issue).

Version 0.18.5 (December 1, 2018)

  • Support views in some derivative evaluation functions.
  • Improved compatibility with PackageCompiler.

Version 0.18.4 (October 8, 2018)

  • Fix a bug in model printing on Julia 0.7 and 1.0.

Version 0.18.3 (October 1, 2018)

  • Add support for Julia v1.0 (Thanks @ExpandingMan)
  • Fix matrix expressions with quadratic functions (#1508)

Version 0.18.2 (June 10, 2018)

  • Fix a bug in second-order derivatives when expressions are present (#1319)
  • Fix a bug in @constraintref (#1330)

Version 0.18.1 (April 9, 2018)

  • Fix for nested tuple destructuring (#1193)
  • Preserve internal model when relaxation=true (#1209)
  • Minor bug fixes and updates for example

Version 0.18.0 (July 27, 2017)

  • Drop support for Julia 0.5.
  • Update for ForwardDiff 0.5.
  • Minor bug fixes.

Version 0.17.1 (June 9, 2017)

  • Use of constructconstraint! in @SDconstraint.
  • Minor bug fixes.

Version 0.17.0 (May 27, 2017)

  • Breaking change: Mixing quadratic and conic constraints is no longer supported.
  • Breaking change: The getvariable and getconstraint functions are replaced by indexing on the corresponding symbol. For instance, to access the variable with name x, one should now write m[:x] instead of getvariable(m, :x). As a consequence, creating a variable and constraint with the same name now triggers a warning, and accessing one of them afterwards throws an error. This change is breaking only in the latter case.
  • Addition of the getobjectivebound function that mirrors the functionality of the MathProgBase getobjbound function except that it takes into account transformations performed by JuMP.
  • Minor bug fixes.

The following changes are primarily of interest to developers of JuMP extensions:

  • The new syntax @constraint(model, expr in Cone) creates the constraint ensuring that expr is inside Cone. The Cone argument is passed to constructconstraint! which enables the call to the dispatched to an extension.
  • The @variable macro now calls constructvariable! instead of directly calling the Variable constructor. Extra arguments and keyword arguments passed to @variable are passed to constructvariable! which enables the call to be dispatched to an extension.
  • Refactor the internal function conicdata (used build the MathProgBase conic model) into smaller sub-functions to make these parts reusable by extensions.

Version 0.16.2 (March 28, 2017)

  • Minor bug fixes and printing tweaks
  • Address deprecation warnings for Julia 0.6

Version 0.16.1 (March 7, 2017)

  • Better support for AbstractArray in JuMP (Thanks @tkoolen)
  • Minor bug fixes

Version 0.16.0 (February 23, 2017)

  • Breaking change: JuMP no longer has a mechanism for selecting solvers by default (the previous mechanism was flawed and incompatible with Julia 0.6). Not specifying a solver before calling solve() will result in an error.
  • Breaking change: User-defined functions are no longer global. The first argument to JuMP.register is now a JuMP Model object within whose scope the function will be registered. Calling JuMP.register without a Model now produces an error.
  • Breaking change: Use the new JuMP.fix method to fix a variable to a value or to update the value to which a variable is fixed. Calling setvalue on a fixed variable now results in an error in order to avoid silent behavior changes. (Thanks @joaquimg)
  • Nonlinear expressions now print out similarly to linear/quadratic expressions (useful for debugging!)
  • New category keyword to @variable. Used for specifying categories of anonymous variables.
  • Compatibility with Julia 0.6-dev.
  • Minor fixes and improvements (Thanks @cossio, @ccoffrin, @blegat)

Version 0.15.1 (January 31, 2017)

  • Bugfix for @LinearConstraints and friends

Version 0.15.0 (December 22, 2016)

  • Julia 0.5.0 is the minimum required version for this release.
  • Document support for BARON solver
  • Enable info callbacks in more states than before, for example, for recording solutions. New when argument to addinfocallback (#814, thanks @yeesian)
  • Improved support for anonymous variables. This includes new warnings for potentially confusing use of the traditional non-anonymous syntax:
    • When multiple variables in a model are given the same name
    • When non-symbols are used as names, for example, @variable(m, x[1][1:N])
  • Improvements in iterating over JuMP containers (#836, thanks @IssamT)
  • Support for writing variable names in .lp file output (Thanks @leethargo)
  • Support for querying duals to SDP problems (Thanks @blegat)
  • The comprehension syntax with curly braces sum{}, prod{}, and norm2{} has been deprecated in favor of Julia's native comprehension syntax sum(), prod() and norm() as previously announced. (For early adopters of the new syntax, norm2() was renamed to norm() without deprecation.)
  • Unit tests rewritten to use Base.Test instead of FactCheck
  • Improved support for operations with matrices of JuMP types (Thanks @ExpandingMan)
  • The syntax to halt a solver from inside a callback has changed from throw(CallbackAbort()) to return JuMP.StopTheSolver
  • Minor bug fixes

Version 0.14.2 (December 12, 2016)

  • Allow singleton anonymous variables (includes bugfix)

Version 0.14.1 (September 12, 2016)

  • More consistent handling of states in informational callbacks, includes a new when parameter to addinfocallback for specifying in which state an informational callback should be called.

Version 0.14.0 (August 7, 2016)

  • Compatibility with Julia 0.5 and ForwardDiff 0.2
  • Support for "anonymous" variables, constraints, expressions, and parameters, for example, x = @variable(m, [1:N]) instead of @variable(m, x[1:N])
  • Support for retrieving constraints from a model by name via getconstraint
  • @NLconstraint now returns constraint references (as expected).
  • Support for vectorized expressions within lazy constraints
  • On Julia 0.5, parse new comprehension syntax sum(x[i] for i in 1:N if isodd(i)) instead of sum{ x[i], i in 1:N; isodd(i) }. The old syntax with curly braces will be deprecated in JuMP 0.15.
  • Now possible to provide nonlinear expressions as "raw" Julia Expr objects instead of using JuMP's nonlinear macros. This input format is useful for programmatically generated expressions.
  • s/Mathematical Programming/Mathematical Optimization/
  • Support for local cuts (Thanks to @madanim, Mehdi Madani)
  • Document Xpress interface developed by @joaquimg, Joaquim Dias Garcia
  • Minor bug and deprecation fixes (Thanks @odow, @jrevels)

Version 0.13.2 (May 16, 2016)

  • Compatibility update for MathProgBase

Version 0.13.1 (May 3, 2016)

  • Fix broken deprecation for registerNLfunction.

Version 0.13.0 (April 29, 2016)

  • Most exported methods and macros have been renamed to avoid camelCase. See the list of changes here. There is a 1-1 mapping from the old names to the new, and it is safe to simply replace the names to update existing models.
  • Specify variable lower/upper bounds in @variable using the lowerbound and upperbound keyword arguments.
  • Change name printed for variable using the basename keyword argument to @variable.
  • New @variables macro allows multi-line declaration of groups of variables.
  • A number of solver methods previously available only through MathProgBase are now exposed directly in JuMP. The fix was recorded live.
  • Compatibility fixes with Julia 0.5.
  • The "end" indexing syntax is no longer supported within JuMPArrays which do not use 1-based indexing until upstream issues are resolved, see here.

Version 0.12.2 (March 9, 2016)

  • Small fixes for nonlinear optimization

Version 0.12.1 (March 1, 2016)

  • Fix a regression in slicing for JuMPArrays (when not using 1-based indexing)

Version 0.12.0 (February 27, 2016)

  • The automatic differentiation functionality has been completely rewritten with a number of user-facing changes:
    • @defExpr and @defNLExpr now take the model as the first argument. The previous one-argument version of @defExpr is deprecated; all expressions should be named. For example, replace @defExpr(2x+y) with @defExpr(jump_model, my_expr, 2x+y).
    • JuMP no longer uses Julia's variable binding rules for efficiently re-solving a sequence of nonlinear models. Instead, we have introduced nonlinear parameters. This is a breaking change, so we have added a warning message when we detect models that may depend on the old behavior.
    • Support for user-defined functions integrated within nonlinear JuMP expressions.
  • Replaced iteration over AffExpr with Number-like scalar iteration; previous iteration behavior is now available via linearterms(::AffExpr).
  • Stopping the solver via throw(CallbackAbort()) from a callback no longer triggers an exception. Instead, solve() returns UserLimit status.
  • getDual() now works for conic problems (Thanks @emreyamangil.)

Version 0.11.3 (February 4, 2016)

  • Bug-fix for problems with quadratic objectives and semidefinite constraints

Version 0.11.2 (January 14, 2016)

  • Compatibility update for Mosek

Version 0.11.1 (December 1, 2015)

  • Remove usage of @compat in tests.
  • Fix updating quadratic objectives for nonlinear models.

Version 0.11.0 (November 30, 2015)

  • Julia 0.4.0 is the minimum required version for this release.
  • Fix for scoping semantics of index variables in sum{}. Index variables no longer leak into the surrounding scope.
  • Addition of the solve(m::Model, relaxation=true) keyword argument to solve the standard continuous relaxation of model m
  • The getConstraintBounds() method allows access to the lower and upper bounds of all constraints in a (nonlinear) model.
  • Update for breaking changes in MathProgBase

Version 0.10.3 (November 20, 2015)

  • Fix a rare error when parsing quadratic expressions
  • Fix Variable() constructor with default arguments
  • Detect unrecognized keywords in solve()

Version 0.10.2 (September 28, 2015)

  • Fix for deprecation warnings

Version 0.10.1 (September 3, 2015)

  • Fixes for ambiguity warnings.
  • Fix for breaking change in precompilation syntax in Julia 0.4-pre

Version 0.10.0 (August 31, 2015)

  • Support (on Julia 0.4 and later) for conditions in indexing @defVar and @addConstraint constructs, for example, @defVar(m, x[i=1:5,j=1:5; i+j >= 3])
  • Support for vectorized operations on Variables and expressions. See the documentation for details.
  • New getVar() method to access variables in a model by name
  • Support for semidefinite programming.
  • Dual solutions are now available for general nonlinear problems. You may call getDual on a reference object for a nonlinear constraint, and getDual on a variable object for Lagrange multipliers from active bounds.
  • Introduce warnings for two common performance traps: too many calls to getValue() on a collection of variables and use of the + operator in a loop to sum expressions.
  • Second-order cone constraints can be written directly with the norm() and norm2{} syntax.
  • Implement MathProgBase interface for querying Hessian-vector products.
  • Iteration over JuMPContainers is deprecated; instead, use the keys and values functions, and zip(keys(d),values(d)) for the old behavior.
  • @defVar returns Array{Variable,N} when each of N index sets are of the form 1:nᵢ.
  • Module precompilation: on Julia 0.4 and later, using JuMP is now much faster.

Version 0.9.3 (August 11, 2015)

  • Fixes for FactCheck testing on julia v0.4.

Version 0.9.2 (June 27, 2015)

  • Fix bug in @addConstraints.

Version 0.9.1 (April 25, 2015)

  • Fix for Julia 0.4-dev.
  • Small infrastructure improvements for extensions.

Version 0.9.0 (April 18, 2015)

  • Comparison operators for constructing constraints (for example, 2x >= 1) have been deprecated. Instead, construct the constraints explicitly in the @addConstraint macro to add them to the model, or in the @LinearConstraint macro to create a stand-alone linear constraint instance.
  • getValue() method implemented to compute the value of a nonlinear subexpression
  • JuMP is now released under the Mozilla Public License version 2.0 (was previously LGPL). MPL is a copyleft license which is less restrictive than LGPL, especially for embedding JuMP within other applications.
  • A number of performance improvements in ReverseDiffSparse for computing derivatives.
  • MathProgBase.getsolvetime(m) now returns the solution time reported by the solver, if available. (Thanks @odow, Oscar Dowson)
  • Formatting fix for LP format output. (Thanks @sbebo, Leonardo Taccari).

Version 0.8.0 (February 17, 2015)

  • Nonlinear subexpressions now supported with the @defNLExpr macro.
  • SCS supported for solving second-order conic problems.
  • setXXXCallback family deprecated in favor of addXXXCallback.
  • Multiple callbacks of the same type can be registered.
  • Added support for informational callbacks via addInfoCallback.
  • A CallbackAbort exception can be thrown from callback to safely exit optimization.

Version 0.7.4 (February 4, 2015)

  • Reduced costs and linear constraint duals are now accessible when quadratic constraints are present.
  • Two-sided nonlinear constraints are supported.
  • Methods for accessing the number of variables and constraints in a model are renamed.
  • New default procedure for setting initial values in nonlinear optimization: project zero onto the variable bounds.
  • Small bug fixes.

Version 0.7.3 (January 14, 2015)

  • Fix a method ambiguity conflict with Compose.jl (cosmetic fix)

Version 0.7.2 (January 9, 2015)

  • Fix a bug in sum(::JuMPDict)
  • Added the setCategory function to change a variables category (for example, continuous or binary)

after construction, and getCategory to retrieve the variable category.

Version 0.7.1 (January 2, 2015)

  • Fix a bug in parsing linear expressions in macros. Affects only Julia 0.4 and later.

Version 0.7.0 (December 29, 2014)

Linear/quadratic/conic programming

  • Breaking change: The syntax for column-wise model generation has been changed to use keyword arguments in @defVar.
  • On Julia 0.4 and later, variables and coefficients may be multiplied in any order within macros. That is, variable*coefficient is now valid syntax.
  • ECOS supported for solving second-order conic problems.

Nonlinear programming

  • Support for skipping model generation when solving a sequence of nonlinear models with changing data.
  • Fix a memory leak when solving a sequence of nonlinear models.
  • The @addNLConstraint macro now supports the three-argument version to define sets of nonlinear constraints.
  • KNITRO supported as a nonlinear solver.
  • Speed improvements for model generation.
  • The @addNLConstraints macro supports adding multiple (groups of) constraints at once. Syntax is similar to @addConstraints.
  • Discrete variables allowed in nonlinear problems for solvers which support them (currently only KNITRO).

General

  • Starting values for variables may now be specified with @defVar(m, x, start=value).
  • The setSolver function allows users to change the solver subsequent to model creation.
  • Support for "fixed" variables via the @defVar(m, x == 1) syntax.
  • Unit tests rewritten to use FactCheck.jl, improved testing across solvers.

Version 0.6.3 (October 19, 2014)

  • Fix a bug in multiplying two AffExpr objects.

Version 0.6.2 (October 11, 2014)

  • Further improvements and bug fixes for printing.
  • Fixed a bug in @defExpr.
  • Support for accessing expression graphs through the MathProgBase NLP interface.

Version 0.6.1 (September 19, 2014)

  • Improvements and bug fixes for printing.

Version 0.6.0 (September 9, 2014)

  • Julia 0.3.0 is the minimum required version for this release.
  • buildInternalModel(m::Model) added to build solver-level model in memory without optimizing.
  • Deprecate load_model_only keyword argument to solve.
  • Add groups of constraints with @addConstraints macro.
  • Unicode operators now supported, including for sum, for prod, and /
  • Quadratic constraints supported in @addConstraint macro.
  • Quadratic objectives supported in @setObjective macro.
  • MathProgBase solver-independent interface replaces Ipopt-specific interface for nonlinear problems
    • Breaking change: IpoptOptions no longer supported to specify solver options, use m = Model(solver=IpoptSolver(options...)) instead.
  • New solver interfaces: ECOS, NLopt, and nonlinear support for MOSEK
  • New option to control whether the lazy constraint callback is executed at each node in the B&B tree or just when feasible solutions are found
  • Add support for semicontinuous and semi-integer variables for those solvers that support them.
  • Add support for index dependencies (for example, triangular indexing) in @defVar, @addConstraint, and @defExpr (for example, @defVar(m, x[i=1:10,j=i:10])).
    • This required some changes to the internal structure of JuMP containers, which may break code that explicitly stored JuMPDict objects.

Version 0.5.8 (September 24, 2014)

  • Fix a bug with specifying solvers (affects Julia 0.2 only)

Version 0.5.7 (September 5, 2014)

  • Fix a bug in printing models

Version 0.5.6 (September 2, 2014)

  • Add support for semicontinuous and semi-integer variables for those solvers that support them.
    • Breaking change: Syntax for Variable() constructor has changed (use of this interface remains discouraged)
  • Update for breaking changes in MathProgBase

Version 0.5.5 (July 6, 2014)

  • Fix bug with problem modification: adding variables that did not appear in existing constraints or objective.

Version 0.5.4 (June 19, 2014)

  • Update for breaking change in MathProgBase which reduces loading times for using JuMP
  • Fix error when MIPs not solved to optimality

Version 0.5.3 (May 21, 2014)

  • Update for breaking change in ReverseDiffSparse

Version 0.5.2 (May 9, 2014)

  • Fix compatibility with Julia 0.3 prerelease

Version 0.5.1 (May 5, 2014)

  • Fix a bug in coefficient handling inside lazy constraints and user cuts

Version 0.5.0 (May 2, 2014)

  • Support for nonlinear optimization with exact, sparse second-order derivatives automatically computed. Ipopt is currently the only solver supported.
  • getValue for AffExpr and QuadExpr
  • Breaking change: getSolverModel replaced by getInternalModel, which returns the internal MathProgBase-level model
  • Groups of constraints can be specified with @addConstraint (see documentation for details). This is not a breaking change.
  • dot(::JuMPDict{Variable},::JuMPDict{Variable}) now returns the corresponding quadratic expression.

Version 0.4.1 (March 24, 2014)

  • Fix bug where change in objective sense was ignored when re-solving a model.
  • Fix issue with handling zero coefficients in AffExpr.

Version 0.4.0 (March 10, 2014)

  • Support for SOS1 and SOS2 constraints.
  • Solver-independent callback for user heuristics.
  • dot and sum implemented for JuMPDict objects. Now you can say @addConstraint(m, dot(a,x) <= b).
  • Developers: support for extensions to JuMP. See definition of Model in src/JuMP.jl for more details.
  • Option to construct the low-level model before optimizing.

Version 0.3.2 (February 17, 2014)

  • Improved model printing
    • Preliminary support for IJulia output

Version 0.3.1 (January 30, 2014)

  • Documentation updates
  • Support for MOSEK
  • CPLEXLink renamed to CPLEX

Version 0.3.0 (January 21, 2014)

  • Unbounded/infeasibility rays: getValue() will return the corresponding components of an unbounded ray when a model is unbounded, if supported by the selected solver. getDual() will return an infeasibility ray (Farkas proof) if a model is infeasible and the selected solver supports this feature.
  • Solver-independent callbacks for user generated cuts.
  • Use new interface for solver-independent QCQP.
  • setlazycallback renamed to setLazyCallback for consistency.

Version 0.2.0 (December 15, 2013)

Breaking

  • Objective sense is specified in setObjective instead of in the Model constructor.
  • lpsolver and mipsolver merged into single solver option.

Added

  • Problem modification with efficient LP restarts and MIP warm-starts.
  • Relatedly, column-wise modeling now supported.
  • Solver-independent callbacks supported. Currently we support only a "lazy constraint" callback, which works with Gurobi, CPLEX, and GLPK. More callbacks coming soon.

Version 0.1.2 (November 16, 2013)

  • Bug fixes for printing, improved error messages.
  • Allow AffExpr to be used in macros; for example, ex = y + z; @addConstraint(m, x + 2*ex <= 3)

Version 0.1.1 (October 23, 2013)

  • Update for solver specification API changes in MathProgBase.

Version 0.1.0 (October 3, 2013)

  • Initial public release.
diff --git a/previews/PR3536/search_index.js b/previews/PR3536/search_index.js new file mode 100644 index 00000000000..05fa48cdab9 --- /dev/null +++ b/previews/PR3536/search_index.js @@ -0,0 +1,3 @@ +var documenterSearchIndex = {"docs": +[{"location":"moi/reference/nonlinear/","page":"Nonlinear programming","title":"Nonlinear programming","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/reference/nonlinear.md\"","category":"page"},{"location":"moi/reference/nonlinear/","page":"Nonlinear programming","title":"Nonlinear programming","text":"CurrentModule = MathOptInterface\nDocTestSetup = quote\n import MathOptInterface as MOI\nend\nDocTestFilters = [r\"MathOptInterface|MOI\"]","category":"page"},{"location":"moi/reference/nonlinear/#Nonlinear-programming","page":"Nonlinear programming","title":"Nonlinear programming","text":"","category":"section"},{"location":"moi/reference/nonlinear/#Types","page":"Nonlinear programming","title":"Types","text":"","category":"section"},{"location":"moi/reference/nonlinear/","page":"Nonlinear programming","title":"Nonlinear programming","text":"AbstractNLPEvaluator\nNLPBoundsPair\nNLPBlockData","category":"page"},{"location":"moi/reference/nonlinear/#MathOptInterface.AbstractNLPEvaluator","page":"Nonlinear programming","title":"MathOptInterface.AbstractNLPEvaluator","text":"AbstractNLPEvaluator\n\nAbstract supertype for the callback object that is used to query function values, derivatives, and expression graphs.\n\nIt is used in NLPBlockData.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/nonlinear/#MathOptInterface.NLPBoundsPair","page":"Nonlinear programming","title":"MathOptInterface.NLPBoundsPair","text":"NLPBoundsPair(lower::Float64, upper::Float64)\n\nA struct holding a pair of lower and upper bounds.\n\n-Inf and Inf can be used to indicate no lower or upper bound, respectively.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/nonlinear/#MathOptInterface.NLPBlockData","page":"Nonlinear programming","title":"MathOptInterface.NLPBlockData","text":"struct NLPBlockData\n constraint_bounds::Vector{NLPBoundsPair}\n evaluator::AbstractNLPEvaluator\n has_objective::Bool\nend\n\nA struct encoding a set of nonlinear constraints of the form lb le g(x) le ub and, if has_objective == true, a nonlinear objective function f(x).\n\nNonlinear objectives override any objective set by using the ObjectiveFunction attribute.\n\nThe evaluator is a callback object that is used to query function values, derivatives, and expression graphs. If has_objective == false, then it is an error to query properties of the objective function, and in Hessian-of-the-Lagrangian queries, σ must be set to zero.\n\nnote: Note\nThroughout the evaluator, all variables are ordered according to ListOfVariableIndices. Hence, MOI copies of nonlinear problems must not re-order variables.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/nonlinear/#Attributes","page":"Nonlinear programming","title":"Attributes","text":"","category":"section"},{"location":"moi/reference/nonlinear/","page":"Nonlinear programming","title":"Nonlinear programming","text":"NLPBlock\nNLPBlockDual\nNLPBlockDualStart","category":"page"},{"location":"moi/reference/nonlinear/#MathOptInterface.NLPBlock","page":"Nonlinear programming","title":"MathOptInterface.NLPBlock","text":"NLPBlock()\n\nAn AbstractModelAttribute that stores an NLPBlockData, representing a set of nonlinear constraints, and optionally a nonlinear objective.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/nonlinear/#MathOptInterface.NLPBlockDual","page":"Nonlinear programming","title":"MathOptInterface.NLPBlockDual","text":"NLPBlockDual(result_index::Int = 1)\n\nAn AbstractModelAttribute for the Lagrange multipliers on the constraints from the NLPBlock in result result_index.\n\nIf result_index is omitted, it is 1 by default.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/nonlinear/#MathOptInterface.NLPBlockDualStart","page":"Nonlinear programming","title":"MathOptInterface.NLPBlockDualStart","text":"NLPBlockDualStart()\n\nAn AbstractModelAttribute for the initial assignment of the Lagrange multipliers on the constraints from the NLPBlock that the solver may use to warm-start the solve.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/nonlinear/#Functions","page":"Nonlinear programming","title":"Functions","text":"","category":"section"},{"location":"moi/reference/nonlinear/","page":"Nonlinear programming","title":"Nonlinear programming","text":"initialize\nfeatures_available\neval_objective\neval_constraint\neval_objective_gradient\njacobian_structure\neval_constraint_gradient\nconstraint_gradient_structure\neval_constraint_jacobian\neval_constraint_jacobian_product\neval_constraint_jacobian_transpose_product\nhessian_lagrangian_structure\nhessian_objective_structure\nhessian_constraint_structure\neval_hessian_objective\neval_hessian_constraint\neval_hessian_lagrangian\neval_hessian_lagrangian_product\nobjective_expr\nconstraint_expr","category":"page"},{"location":"moi/reference/nonlinear/#MathOptInterface.initialize","page":"Nonlinear programming","title":"MathOptInterface.initialize","text":"initialize(\n d::AbstractNLPEvaluator,\n requested_features::Vector{Symbol},\n)::Nothing\n\nInitialize d with the set of features in requested_features. Check features_available before calling initialize to see what features are supported by d.\n\nwarning: Warning\nThis method must be called before any other methods.\n\nFeatures\n\nThe following features are defined:\n\n:Grad: enables eval_objective_gradient\n:Jac: enables eval_constraint_jacobian\n:JacVec: enables eval_constraint_jacobian_product and eval_constraint_jacobian_transpose_product\n:Hess: enables eval_hessian_lagrangian\n:HessVec: enables eval_hessian_lagrangian_product\n:ExprGraph: enables objective_expr and constraint_expr.\n\nIn all cases, including when requested_features is empty, eval_objective and eval_constraint are supported.\n\nExamples\n\nMOI.initialize(d, Symbol[])\nMOI.initialize(d, [:ExprGraph])\nMOI.initialize(d, MOI.features_available(d))\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.features_available","page":"Nonlinear programming","title":"MathOptInterface.features_available","text":"features_available(d::AbstractNLPEvaluator)::Vector{Symbol}\n\nReturns the subset of features available for this problem instance.\n\nSee initialize for the list of defined features.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_objective","page":"Nonlinear programming","title":"MathOptInterface.eval_objective","text":"eval_objective(d::AbstractNLPEvaluator, x::AbstractVector{T})::T where {T}\n\nEvaluate the objective f(x), returning a scalar value.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_constraint","page":"Nonlinear programming","title":"MathOptInterface.eval_constraint","text":"eval_constraint(d::AbstractNLPEvaluator,\n g::AbstractVector{T},\n x::AbstractVector{T},\n)::Nothing where {T}\n\nGiven a set of vector-valued constraints l le g(x) le u, evaluate the constraint function g(x), storing the result in the vector g.\n\nImplementation notes\n\nWhen implementing this method, you must not assume that g is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_objective_gradient","page":"Nonlinear programming","title":"MathOptInterface.eval_objective_gradient","text":"eval_objective_gradient(\n d::AbstractNLPEvaluator,\n grad::AbstractVector{T},\n x::AbstractVector{T},\n)::Nothing where {T}\n\nEvaluate the gradient of the objective function grad = nabla f(x) as a dense vector, storing the result in the vector grad.\n\nImplementation notes\n\nWhen implementing this method, you must not assume that grad is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.jacobian_structure","page":"Nonlinear programming","title":"MathOptInterface.jacobian_structure","text":"jacobian_structure(d::AbstractNLPEvaluator)::Vector{Tuple{Int64,Int64}}\n\nReturns a vector of tuples, (row, column), where each indicates the position of a structurally nonzero element in the Jacobian matrix: J_g(x) = left beginarrayc nabla g_1(x) nabla g_2(x) vdots nabla g_m(x) endarrayright where g_i is the itextth component of the nonlinear constraints g(x).\n\nThe indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.\n\nThe sparsity structure is assumed to be independent of the point x.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_constraint_gradient","page":"Nonlinear programming","title":"MathOptInterface.eval_constraint_gradient","text":"eval_constraint_gradient(\n d::AbstractNLPEvaluator,\n ∇g::AbstractVector{T},\n x::AbstractVector{T},\n i::Int,\n)::Nothing where {T}\n\nEvaluate the gradient of constraint i, nabla g_i(x), and store the non-zero values in ∇g, corresponding to the structure returned by constraint_gradient_structure.\n\nImplementation notes\n\nWhen implementing this method, you must not assume that ∇g is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.constraint_gradient_structure","page":"Nonlinear programming","title":"MathOptInterface.constraint_gradient_structure","text":"constraint_gradient_structure(d::AbstractNLPEvaluator, i::Int)::Vector{Int64}\n\nReturns a vector of indices, where each element indicates the position of a structurally nonzero element in the gradient of constraint nabla g_i(x).\n\nThe indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.\n\nThe sparsity structure is assumed to be independent of the point x.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_constraint_jacobian","page":"Nonlinear programming","title":"MathOptInterface.eval_constraint_jacobian","text":"eval_constraint_jacobian(d::AbstractNLPEvaluator,\n J::AbstractVector{T},\n x::AbstractVector{T},\n)::Nothing where {T}\n\nEvaluates the sparse Jacobian matrix J_g(x) = left beginarrayc nabla g_1(x) nabla g_2(x) vdots nabla g_m(x) endarrayright.\n\nThe result is stored in the vector J in the same order as the indices returned by jacobian_structure.\n\nImplementation notes\n\nWhen implementing this method, you must not assume that J is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_constraint_jacobian_product","page":"Nonlinear programming","title":"MathOptInterface.eval_constraint_jacobian_product","text":"eval_constraint_jacobian_product(\n d::AbstractNLPEvaluator,\n y::AbstractVector{T},\n x::AbstractVector{T},\n w::AbstractVector{T},\n)::Nothing where {T}\n\nComputes the Jacobian-vector product y = J_g(x)w, storing the result in the vector y.\n\nThe vectors have dimensions such that length(w) == length(x), and length(y) is the number of nonlinear constraints.\n\nImplementation notes\n\nWhen implementing this method, you must not assume that y is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_constraint_jacobian_transpose_product","page":"Nonlinear programming","title":"MathOptInterface.eval_constraint_jacobian_transpose_product","text":"eval_constraint_jacobian_transpose_product(\n d::AbstractNLPEvaluator,\n y::AbstractVector{T},\n x::AbstractVector{T},\n w::AbstractVector{T},\n)::Nothing where {T}\n\nComputes the Jacobian-transpose-vector product y = J_g(x)^Tw, storing the result in the vector y.\n\nThe vectors have dimensions such that length(y) == length(x), and length(w) is the number of nonlinear constraints.\n\nImplementation notes\n\nWhen implementing this method, you must not assume that y is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.hessian_lagrangian_structure","page":"Nonlinear programming","title":"MathOptInterface.hessian_lagrangian_structure","text":"hessian_lagrangian_structure(\n d::AbstractNLPEvaluator,\n)::Vector{Tuple{Int64,Int64}}\n\nReturns a vector of tuples, (row, column), where each indicates the position of a structurally nonzero element in the Hessian-of-the-Lagrangian matrix: nabla^2 f(x) + sum_i=1^m nabla^2 g_i(x).\n\nThe indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.\n\nAny mix of lower and upper-triangular indices is valid. Elements (i,j) and (j,i), if both present, should be treated as duplicates.\n\nThe sparsity structure is assumed to be independent of the point x.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.hessian_objective_structure","page":"Nonlinear programming","title":"MathOptInterface.hessian_objective_structure","text":"hessian_objective_structure(\n d::AbstractNLPEvaluator,\n)::Vector{Tuple{Int64,Int64}}\n\nReturns a vector of tuples, (row, column), where each indicates the position of a structurally nonzero element in the Hessian matrix: nabla^2 f(x).\n\nThe indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.\n\nAny mix of lower and upper-triangular indices is valid. Elements (i,j) and (j,i), if both present, should be treated as duplicates.\n\nThe sparsity structure is assumed to be independent of the point x.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.hessian_constraint_structure","page":"Nonlinear programming","title":"MathOptInterface.hessian_constraint_structure","text":"hessian_constraint_structure(\n d::AbstractNLPEvaluator,\n i::Int64,\n)::Vector{Tuple{Int64,Int64}}\n\nReturns a vector of tuples, (row, column), where each indicates the position of a structurally nonzero element in the Hessian matrix: nabla^2 g_i(x).\n\nThe indices are not required to be sorted and can contain duplicates, in which case the solver should combine the corresponding elements by adding them together.\n\nAny mix of lower and upper-triangular indices is valid. Elements (i,j) and (j,i), if both present, should be treated as duplicates.\n\nThe sparsity structure is assumed to be independent of the point x.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_hessian_objective","page":"Nonlinear programming","title":"MathOptInterface.eval_hessian_objective","text":"eval_hessian_objective(\n d::AbstractNLPEvaluator,\n H::AbstractVector{T},\n x::AbstractVector{T},\n)::Nothing where {T}\n\nThis function computes the sparse Hessian matrix: nabla^2 f(x), storing the result in the vector H in the same order as the indices returned by hessian_objective_structure.\n\nImplementation notes\n\nWhen implementing this method, you must not assume that H is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_hessian_constraint","page":"Nonlinear programming","title":"MathOptInterface.eval_hessian_constraint","text":"eval_hessian_constraint(\n d::AbstractNLPEvaluator,\n H::AbstractVector{T},\n x::AbstractVector{T},\n i::Int64,\n)::Nothing where {T}\n\nThis function computes the sparse Hessian matrix: nabla^2 g_i(x), storing the result in the vector H in the same order as the indices returned by hessian_constraint_structure.\n\nImplementation notes\n\nWhen implementing this method, you must not assume that H is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_hessian_lagrangian","page":"Nonlinear programming","title":"MathOptInterface.eval_hessian_lagrangian","text":"eval_hessian_lagrangian(\n d::AbstractNLPEvaluator,\n H::AbstractVector{T},\n x::AbstractVector{T},\n σ::T,\n μ::AbstractVector{T},\n)::Nothing where {T}\n\nGiven scalar weight σ and vector of constraint weights μ, this function computes the sparse Hessian-of-the-Lagrangian matrix: sigmanabla^2 f(x) + sum_i=1^m mu_i nabla^2 g_i(x), storing the result in the vector H in the same order as the indices returned by hessian_lagrangian_structure.\n\nImplementation notes\n\nWhen implementing this method, you must not assume that H is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.eval_hessian_lagrangian_product","page":"Nonlinear programming","title":"MathOptInterface.eval_hessian_lagrangian_product","text":"eval_hessian_lagrangian_product(\n d::AbstractNLPEvaluator,\n h::AbstractVector{T},\n x::AbstractVector{T},\n v::AbstractVector{T},\n σ::T,\n μ::AbstractVector{T},\n)::Nothing where {T}\n\nGiven scalar weight σ and vector of constraint weights μ, computes the Hessian-of-the-Lagrangian-vector product h = left(sigmanabla^2 f(x) + sum_i=1^m mu_i nabla^2 g_i(x)right)v, storing the result in the vector h.\n\nThe vectors have dimensions such that length(h) == length(x) == length(v).\n\nImplementation notes\n\nWhen implementing this method, you must not assume that h is Vector{Float64}, but you may assume that it supports setindex! and length. For example, it may be the view of a vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.objective_expr","page":"Nonlinear programming","title":"MathOptInterface.objective_expr","text":"objective_expr(d::AbstractNLPEvaluator)::Expr\n\nReturns a Julia Expr object representing the expression graph of the objective function.\n\nFormat\n\nThe expression has a number of limitations, compared with arbitrary Julia expressions:\n\nAll sums and products are flattened out as simple Expr(:+, ...) and Expr(:*, ...) objects.\nAll decision variables must be of the form Expr(:ref, :x, MOI.VariableIndex(i)), where i is the ith variable in ListOfVariableIndices.\nThere are currently no restrictions on recognized functions; typically these will be built-in Julia functions like ^, exp, log, cos, tan, sqrt, etc., but modeling interfaces may choose to extend these basic functions, or error if they encounter unsupported functions.\n\nExamples\n\nThe expression x_1+sin(x_2exp(x_3)) is represented as\n\n:(x[MOI.VariableIndex(1)] + sin(x[MOI.VariableIndex(2)] / exp(x[MOI.VariableIndex[3]])))\n\nor equivalently\n\nExpr(\n :call,\n :+,\n Expr(:ref, :x, MOI.VariableIndex(1)),\n Expr(\n :call,\n :/,\n Expr(:call, :sin, Expr(:ref, :x, MOI.VariableIndex(2))),\n Expr(:call, :exp, Expr(:ref, :x, MOI.VariableIndex(3))),\n ),\n)\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/nonlinear/#MathOptInterface.constraint_expr","page":"Nonlinear programming","title":"MathOptInterface.constraint_expr","text":"constraint_expr(d::AbstractNLPEvaluator, i::Integer)::Expr\n\nReturns a Julia Expr object representing the expression graph for the itextth nonlinear constraint.\n\nFormat\n\nThe format is the same as objective_expr, with an additional comparison operator indicating the sense of and bounds on the constraint.\n\nFor single-sided comparisons, the body of the constraint must be on the left-hand side, and the right-hand side must be a constant.\n\nFor double-sided comparisons (that is, l le f(x) le u), the body of the constraint must be in the middle, and the left- and right-hand sides must be constants.\n\nThe bounds on the constraints must match the NLPBoundsPairs passed to NLPBlockData.\n\nExamples\n\n:(x[MOI.VariableIndex(1)]^2 <= 1.0)\n:(x[MOI.VariableIndex(1)]^2 >= 2.0)\n:(x[MOI.VariableIndex(1)]^2 == 3.0)\n:(4.0 <= x[MOI.VariableIndex(1)]^2 <= 5.0)\n\n\n\n\n\n","category":"function"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"EditURL = \"https://github.com/jump-dev/Cbc.jl/blob/v1.2.0/README.md\"","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"(Image: )","category":"page"},{"location":"packages/Cbc/#Cbc.jl","page":"jump-dev/Cbc.jl","title":"Cbc.jl","text":"","category":"section"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"(Image: Build Status) (Image: codecov)","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"Cbc.jl is a wrapper for the COIN-OR Branch and Cut (Cbc) solver.","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"The wrapper has two components:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"a thin wrapper around the complete C API\nan interface to MathOptInterface","category":"page"},{"location":"packages/Cbc/#Affiliation","page":"jump-dev/Cbc.jl","title":"Affiliation","text":"","category":"section"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"This wrapper is maintained by the JuMP community and is not a COIN-OR project.","category":"page"},{"location":"packages/Cbc/#License","page":"jump-dev/Cbc.jl","title":"License","text":"","category":"section"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"Cbc.jl is licensed under the MIT License.","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"The underlying solver, coin-or/Cbc, is licensed under the Eclipse public license.","category":"page"},{"location":"packages/Cbc/#Installation","page":"jump-dev/Cbc.jl","title":"Installation","text":"","category":"section"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"Install Cbc using Pkg.add:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"import Pkg\nPkg.add(\"Cbc\")","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"In addition to installing the Cbc.jl package, this will also download and install the Cbc binaries. You do not need to install Cbc separately.","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"To use a custom binary, read the Custom solver binaries section of the JuMP documentation.","category":"page"},{"location":"packages/Cbc/#Use-with-JuMP","page":"jump-dev/Cbc.jl","title":"Use with JuMP","text":"","category":"section"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"To use Cbc with JuMP, use Cbc.Optimizer:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"using JuMP, Cbc\nmodel = Model(Cbc.Optimizer)\nset_attribute(model, \"logLevel\", 1)","category":"page"},{"location":"packages/Cbc/#MathOptInterface-API","page":"jump-dev/Cbc.jl","title":"MathOptInterface API","text":"","category":"section"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"The COIN Branch-and-Cut (Cbc) optimizer supports the following constraints and attributes.","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"List of supported objective functions:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"List of supported variable types:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"MOI.Reals","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"List of supported constraint types:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"MOI.ScalarAffineFunction{Float64} in MOI.EqualTo{Float64}\nMOI.ScalarAffineFunction{Float64} in MOI.GreaterThan{Float64}\nMOI.ScalarAffineFunction{Float64} in MOI.Interval{Float64}\nMOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}\nMOI.VariableIndex in MOI.EqualTo{Float64}\nMOI.VariableIndex in MOI.GreaterThan{Float64}\nMOI.VariableIndex in MOI.Integer\nMOI.VariableIndex in MOI.Interval{Float64}\nMOI.VariableIndex in MOI.LessThan{Float64}\nMOI.VariableIndex in MOI.ZeroOne\nMOI.VectorOfVariables in MOI.SOS1{Float64}\nMOI.VectorOfVariables in MOI.SOS2{Float64}","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"List of supported model attributes:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"Cbc.Status\nCbc.SecondaryStatus\nMOI.DualStatus\nMOI.NodeCount\nMOI.NumberOfVariables\nMOI.ObjectiveBound\nMOI.ObjectiveSense\nMOI.ObjectiveValue\nMOI.PrimalStatus\nMOI.RelativeGap\nMOI.ResultCount\nMOI.SolveTimeSec\nMOI.TerminationStatus","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"List of supported optimizer attributes:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"Cbc.SetVariableNames\nMOI.AbsoluteGapTolerance\nMOI.NumberOfThreads\nMOI.RawOptimizerAttribute\nMOI.RelativeGapTolerance\nMOI.Silent\nMOI.SolverName\nMOI.SolverVersion\nMOI.TimeLimitSec","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"List of supported variable attributes:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"MOI.VariablePrimal\nMOI.VariablePrimalStart\nMOI.VariableName","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"List of supported constraint attributes:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"MOI.ConstraintPrimal","category":"page"},{"location":"packages/Cbc/#Options","page":"jump-dev/Cbc.jl","title":"Options","text":"","category":"section"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"Options are, unfortunately, not well documented.","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"The following options are likely to be the most useful:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"Parameter Example Explanation\nseconds 60.0 Solution timeout limit\nlogLevel 2 Set to 0 to disable solution output\nmaxSolutions 1 Terminate after this many feasible solutions have been found\nmaxNodes 1 Terminate after this many branch-and-bound nodes have been evaluated\nallowableGap 0.05 Terminate after optimality gap is less than this value (on an absolute scale)\nratioGap 0.05 Terminate after optimality gap is smaller than this relative fraction\nthreads 1 Set the number of threads to use for parallel branch & bound","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"The complete list of parameters can be found by running the cbc executable and typing ? at the prompt.","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"Start the cbc executable from Julia as follows:","category":"page"},{"location":"packages/Cbc/","page":"jump-dev/Cbc.jl","title":"jump-dev/Cbc.jl","text":"using Cbc_jll\nCbc_jll.cbc() do exe\n run(`$(exe)`)\nend","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"EditURL = \"geographic_clustering.jl\"","category":"page"},{"location":"tutorials/linear/geographic_clustering/#Geographical-clustering","page":"Geographical clustering","title":"Geographical clustering","text":"","category":"section"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"This tutorial was originally contributed by Matthew Helm and Mathieu Tanneau.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"The goal of this exercise is to cluster n cities into k groups, minimizing the total pairwise distance between cities and ensuring that the variance in the total populations of each group is relatively small.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"This tutorial uses the following packages:","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"using JuMP\nimport DataFrames\nimport HiGHS\nimport LinearAlgebra","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"For this example, we'll use the 20 most populous cities in the United States.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"cities = DataFrames.DataFrame(\n Union{String,Float64}[\n \"New York, NY\" 8.405 40.7127 -74.0059\n \"Los Angeles, CA\" 3.884 34.0522 -118.2436\n \"Chicago, IL\" 2.718 41.8781 -87.6297\n \"Houston, TX\" 2.195 29.7604 -95.3698\n \"Philadelphia, PA\" 1.553 39.9525 -75.1652\n \"Phoenix, AZ\" 1.513 33.4483 -112.0740\n \"San Antonio, TX\" 1.409 29.4241 -98.4936\n \"San Diego, CA\" 1.355 32.7157 -117.1610\n \"Dallas, TX\" 1.257 32.7766 -96.7969\n \"San Jose, CA\" 0.998 37.3382 -121.8863\n \"Austin, TX\" 0.885 30.2671 -97.7430\n \"Indianapolis, IN\" 0.843 39.7684 -86.1580\n \"Jacksonville, FL\" 0.842 30.3321 -81.6556\n \"San Francisco, CA\" 0.837 37.7749 -122.4194\n \"Columbus, OH\" 0.822 39.9611 -82.9987\n \"Charlotte, NC\" 0.792 35.2270 -80.8431\n \"Fort Worth, TX\" 0.792 32.7554 -97.3307\n \"Detroit, MI\" 0.688 42.3314 -83.0457\n \"El Paso, TX\" 0.674 31.7775 -106.4424\n \"Memphis, TN\" 0.653 35.1495 -90.0489\n ],\n [\"city\", \"population\", \"lat\", \"lon\"],\n)","category":"page"},{"location":"tutorials/linear/geographic_clustering/#Model-Specifics","page":"Geographical clustering","title":"Model Specifics","text":"","category":"section"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"We will cluster these 20 cities into 3 different groups and we will assume that the ideal or target population P for a group is simply the total population of the 20 cities divided by 3:","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"n = size(cities, 1)\nk = 3\nP = sum(cities.population) / k","category":"page"},{"location":"tutorials/linear/geographic_clustering/#Obtaining-the-distances-between-each-city","page":"Geographical clustering","title":"Obtaining the distances between each city","text":"","category":"section"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"Let's compute the pairwise Haversine distance between each of the cities in our data set and store the result in a variable we'll call dm:","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"\"\"\"\n haversine(lat1, long1, lat2, long2, r = 6372.8)\n\nCompute the haversine distance between two points on a sphere of radius `r`,\nwhere the points are given by the latitude/longitude pairs `lat1/long1` and\n`lat2/long2` (in degrees).\n\"\"\"\nfunction haversine(lat1, long1, lat2, long2, r = 6372.8)\n lat1, long1 = deg2rad(lat1), deg2rad(long1)\n lat2, long2 = deg2rad(lat2), deg2rad(long2)\n hav(a, b) = sin((b - a) / 2)^2\n inner_term = hav(lat1, lat2) + cos(lat1) * cos(lat2) * hav(long1, long2)\n d = 2 * r * asin(sqrt(inner_term))\n # Round distance to nearest kilometer.\n return round(Int, d)\nend","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"Our distance matrix is symmetric so we'll convert it to a LowerTriangular matrix so that we can better interpret the objective value of our model:","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"dm = LinearAlgebra.LowerTriangular([\n haversine(cities.lat[i], cities.lon[i], cities.lat[j], cities.lon[j])\n for i in 1:n, j in 1:n\n])","category":"page"},{"location":"tutorials/linear/geographic_clustering/#Build-the-model","page":"Geographical clustering","title":"Build the model","text":"","category":"section"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"Now that we have the basics taken care of, we can set up our model, create decision variables, add constraints, and then solve.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"First, we'll set up a model that leverages the Cbc solver. Next, we'll set up a binary variable x_ik that takes the value 1 if city i is in group k and 0 otherwise. Each city must be in a group, so we'll add the constraint sum_k x_ik = 1 for every i.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"model = Model(HiGHS.Optimizer)\nset_silent(model)\n@variable(model, x[1:n, 1:k], Bin)\n@constraint(model, [i = 1:n], sum(x[i, :]) == 1);\n# To reduce symmetry, we fix the first city to belong to the first group.\nfix(x[1, 1], 1; force = true)","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"The total population of a group k is Q_k = sum_ix_ikq_i where q_i is simply the i-th value from the population column in our cities DataFrame. Let's add constraints so that alpha leq (Q_k - P) leq beta. We'll set alpha equal to -3 million and beta equal to 3. By adjusting these thresholds you'll find that there is a tradeoff between having relatively even populations between groups and having geographically close cities within each group. In other words, the larger the absolute values of alpha and beta, the closer together the cities in a group will be but the variance between the group populations will be higher.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"@variable(model, -3 <= population_diff[1:k] <= 3)\n@constraint(model, population_diff .== x' * cities.population .- P)","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"Now we need to add one last binary variable z_ij to our model that we'll use to compute the total distance between the cities in our groups, defined as sum_ijd_ijz_ij. Variable z_ij will equal 1 if cities i and j are in the same group, and 0 if they are not in the same group.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"To ensure that z_ij = 1 if and only if cities i and j are in the same group, we add the constraints z_ij geq x_ik + x_jk - 1 for every pair ij and every k:","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"@variable(model, z[i = 1:n, j = 1:i], Bin)\nfor k in 1:k, i in 1:n, j in 1:i\n @constraint(model, z[i, j] >= x[i, k] + x[j, k] - 1)\nend","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"We can now add an objective to our model which will simply be to minimize the dot product of z and our distance matrix, dm.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"@objective(model, Min, sum(dm[i, j] * z[i, j] for i in 1:n, j in 1:i));\nnothing #hide","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"We can then call optimize! and review the results.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"optimize!(model)","category":"page"},{"location":"tutorials/linear/geographic_clustering/#Reviewing-the-Results","page":"Geographical clustering","title":"Reviewing the Results","text":"","category":"section"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"Now that we have results, we can add a column to our cities DataFrame for the group and then loop through our x variable to assign each city to its group. Once we have that, we can look at the total population for each group and also look at the cities in each group to verify that they are grouped by geographic proximity.","category":"page"},{"location":"tutorials/linear/geographic_clustering/","page":"Geographical clustering","title":"Geographical clustering","text":"cities.group = zeros(n)\n\nfor i in 1:n, j in 1:k\n if round(Int, value(x[i, j])) == 1\n cities.group[i] = j\n end\nend\n\nfor group in DataFrames.groupby(cities, :group)\n @show group\n println(\"\")\n @show sum(group.population)\n println(\"\")\nend","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"CurrentModule = JuMP\nDocTestSetup = quote\n using JuMP\nend\nDocTestFilters = [r\"≤|<=\", r\"≥|>=\", r\" == | = \", r\" ∈ | in \", r\"MathOptInterface|MOI\"]","category":"page"},{"location":"manual/objective/#Objectives","page":"Objectives","title":"Objectives","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"This page describes macros and functions related to linear and quadratic objective functions only, unless otherwise indicated. For nonlinear objective functions, see Nonlinear Modeling.","category":"page"},{"location":"manual/objective/#Set-a-linear-objective","page":"Objectives","title":"Set a linear objective","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use the @objective macro to set a linear objective function.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use Min to create a minimization objective:","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Min, 2x + 1)\n2 x + 1","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use Max to create a maximization objective:","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Max, 2x + 1)\n2 x + 1","category":"page"},{"location":"manual/objective/#Set-a-quadratic-objective","page":"Objectives","title":"Set a quadratic objective","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use the @objective macro to set a quadratic objective function.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use ^2 to have a variable squared:","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Min, x^2 + 2x + 1)\nx² + 2 x + 1","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"You can also have bilinear terms between variables:","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> @objective(model, Max, x * y + x + y)\nx*y + x + y","category":"page"},{"location":"manual/objective/#Set-a-nonlinear-objective","page":"Objectives","title":"Set a nonlinear objective","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use the @objective macro to set a nonlinear objective function:","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x <= 1);\n\njulia> @objective(model, Max, log(x))\nlog(x)","category":"page"},{"location":"manual/objective/#Query-the-objective-function","page":"Objectives","title":"Query the objective function","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use objective_function to return the current objective function.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Min, 2x + 1)\n2 x + 1\n\njulia> objective_function(model)\n2 x + 1","category":"page"},{"location":"manual/objective/#Evaluate-the-objective-function-at-a-point","page":"Objectives","title":"Evaluate the objective function at a point","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use value to evaluate an objective function at a point specifying values for variables.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x[1:2]);\n\njulia> @objective(model, Min, 2x[1]^2 + x[1] + 0.5*x[2])\n2 x[1]² + x[1] + 0.5 x[2]\n\njulia> f = objective_function(model)\n2 x[1]² + x[1] + 0.5 x[2]\n\njulia> point = Dict(x[1] => 2.0, x[2] => 1.0);\n\njulia> value(z -> point[z], f)\n10.5","category":"page"},{"location":"manual/objective/#Query-the-objective-sense","page":"Objectives","title":"Query the objective sense","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use objective_sense to return the current objective sense.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Min, 2x + 1)\n2 x + 1\n\njulia> objective_sense(model)\nMIN_SENSE::OptimizationSense = 0","category":"page"},{"location":"manual/objective/#Modify-an-objective","page":"Objectives","title":"Modify an objective","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"To modify an objective, call @objective with the new objective function.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Min, 2x)\n2 x\n\njulia> @objective(model, Max, -2x)\n-2 x","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Alternatively, use set_objective_function.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Min, 2x)\n2 x\n\njulia> new_objective = @expression(model, -2 * x)\n-2 x\n\njulia> set_objective_function(model, new_objective)","category":"page"},{"location":"manual/objective/#Modify-an-objective-coefficient","page":"Objectives","title":"Modify an objective coefficient","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use set_objective_coefficient to modify an objective coefficient.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Min, 2x)\n2 x\n\njulia> set_objective_coefficient(model, x, 3)\n\njulia> objective_function(model)\n3 x","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"info: Info\nThere is no way to modify the coefficient of a quadratic term. Set a new objective instead.","category":"page"},{"location":"manual/objective/#Modify-the-objective-sense","page":"Objectives","title":"Modify the objective sense","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Use set_objective_sense to modify the objective sense.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Min, 2x)\n2 x\n\njulia> objective_sense(model)\nMIN_SENSE::OptimizationSense = 0\n\njulia> set_objective_sense(model, MAX_SENSE);\n\njulia> objective_sense(model)\nMAX_SENSE::OptimizationSense = 1","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Alternatively, call @objective and pass the existing objective function.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @objective(model, Min, 2x)\n2 x\n\njulia> @objective(model, Max, objective_function(model))\n2 x","category":"page"},{"location":"manual/objective/#Set-a-vector-valued-objective","page":"Objectives","title":"Set a vector-valued objective","text":"","category":"section"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Define a multi-objective optimization problem by passing a vector of objectives:","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x[1:2]);\n\njulia> @objective(model, Min, [1 + x[1], 2 * x[2]])\n2-element Vector{AffExpr}:\n x[1] + 1\n 2 x[2]\n\njulia> f = objective_function(model)\n2-element Vector{AffExpr}:\n x[1] + 1\n 2 x[2]","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"tip: Tip\nThe Multi-objective knapsack tutorial provides an example of solving a multi-objective integer program.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"In most cases, multi-objective optimization solvers will return multiple solutions, corresponding to points on the Pareto frontier. See Multiple solutions for information on how to query and work with multiple solutions.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Note that you must set a single objective sense, that is, you cannot have both minimization and maximization objectives. Work around this limitation by choosing Min and negating any objectives you want to maximize:","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x[1:2]);\n\njulia> @expression(model, obj1, 1 + x[1])\nx[1] + 1\n\njulia> @expression(model, obj2, 2 * x[1])\n2 x[1]\n\njulia> @objective(model, Min, [obj1, -obj2])\n2-element Vector{AffExpr}:\n x[1] + 1\n -2 x[1]","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"Defining your objectives as expressions allows flexibility in how you can solve variations of the same problem, with some objectives removed and constrained to be no worse that a fixed value.","category":"page"},{"location":"manual/objective/","page":"Objectives","title":"Objectives","text":"julia> model = Model();\n\njulia> @variable(model, x[1:2]);\n\njulia> @expression(model, obj1, 1 + x[1])\nx[1] + 1\n\njulia> @expression(model, obj2, 2 * x[1])\n2 x[1]\n\njulia> @expression(model, obj3, x[1] + x[2])\nx[1] + x[2]\n\njulia> @objective(model, Min, [obj1, obj2, obj3]) # Three-objective problem\n3-element Vector{AffExpr}:\n x[1] + 1\n 2 x[1]\n x[1] + x[2]\n\njulia> # optimize!(model), look at the solution, talk to stakeholders, then\n # decide you want to solve a new problem where the third objective is\n # removed and constrained to be better than 2.0.\n nothing\n\njulia> @objective(model, Min, [obj1, obj2]) # Two-objective problem\n2-element Vector{AffExpr}:\n x[1] + 1\n 2 x[1]\n\njulia> @constraint(model, obj3 <= 2.0)\nx[1] + x[2] ≤ 2","category":"page"},{"location":"moi/reference/callbacks/","page":"Callbacks","title":"Callbacks","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/reference/callbacks.md\"","category":"page"},{"location":"moi/reference/callbacks/","page":"Callbacks","title":"Callbacks","text":"CurrentModule = MathOptInterface\nDocTestSetup = quote\n import MathOptInterface as MOI\nend\nDocTestFilters = [r\"MathOptInterface|MOI\"]","category":"page"},{"location":"moi/reference/callbacks/#Callbacks","page":"Callbacks","title":"Callbacks","text":"","category":"section"},{"location":"moi/reference/callbacks/","page":"Callbacks","title":"Callbacks","text":"AbstractCallback\nAbstractSubmittable\nsubmit","category":"page"},{"location":"moi/reference/callbacks/#MathOptInterface.AbstractCallback","page":"Callbacks","title":"MathOptInterface.AbstractCallback","text":"abstract type AbstractCallback <: AbstractModelAttribute end\n\nAbstract type for a model attribute representing a callback function. The value set to subtypes of AbstractCallback is a function that may be called during optimize!. As optimize! is in progress, the result attributes (i.e, the attributes attr such that is_set_by_optimize(attr)) may not be accessible from the callback, hence trying to get result attributes might throw a OptimizeInProgress error.\n\nAt most one callback of each type can be registered. If an optimizer already has a function for a callback type, and the user registers a new function, then the old one is replaced.\n\nThe value of the attribute should be a function taking only one argument, commonly called callback_data, that can be used for instance in LazyConstraintCallback, HeuristicCallback and UserCutCallback.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.AbstractSubmittable","page":"Callbacks","title":"MathOptInterface.AbstractSubmittable","text":"AbstractSubmittable\n\nAbstract supertype for objects that can be submitted to the model.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.submit","page":"Callbacks","title":"MathOptInterface.submit","text":"submit(\n optimizer::AbstractOptimizer,\n sub::AbstractSubmittable,\n values...,\n)::Nothing\n\nSubmit values to the submittable sub of the optimizer optimizer.\n\nAn UnsupportedSubmittable error is thrown if model does not support the attribute attr (see supports) and a SubmitNotAllowed error is thrown if it supports the submittable sub but it cannot be submitted.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/callbacks/#Attributes","page":"Callbacks","title":"Attributes","text":"","category":"section"},{"location":"moi/reference/callbacks/","page":"Callbacks","title":"Callbacks","text":"CallbackNodeStatus\nCallbackVariablePrimal\nCallbackNodeStatusCode\nCALLBACK_NODE_STATUS_INTEGER\nCALLBACK_NODE_STATUS_FRACTIONAL\nCALLBACK_NODE_STATUS_UNKNOWN","category":"page"},{"location":"moi/reference/callbacks/#MathOptInterface.CallbackNodeStatus","page":"Callbacks","title":"MathOptInterface.CallbackNodeStatus","text":"CallbackNodeStatus(callback_data)\n\nAn optimizer attribute describing the (in)feasibility of the primal solution available from CallbackVariablePrimal during a callback identified by callback_data.\n\nReturns a CallbackNodeStatusCode Enum.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.CallbackVariablePrimal","page":"Callbacks","title":"MathOptInterface.CallbackVariablePrimal","text":"CallbackVariablePrimal(callback_data)\n\nA variable attribute for the assignment to some primal variable's value during the callback identified by callback_data.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.CallbackNodeStatusCode","page":"Callbacks","title":"MathOptInterface.CallbackNodeStatusCode","text":"CallbackNodeStatusCode\n\nAn Enum of possible return values from calling get with CallbackNodeStatus.\n\nValues\n\nPossible values are:\n\nCALLBACK_NODE_STATUS_INTEGER: the primal solution available from CallbackVariablePrimal is integer feasible.\nCALLBACK_NODE_STATUS_FRACTIONAL: the primal solution available from CallbackVariablePrimal is integer infeasible.\nCALLBACK_NODE_STATUS_UNKNOWN: the primal solution available from CallbackVariablePrimal might be integer feasible or infeasible.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.CALLBACK_NODE_STATUS_INTEGER","page":"Callbacks","title":"MathOptInterface.CALLBACK_NODE_STATUS_INTEGER","text":"CALLBACK_NODE_STATUS_INTEGER::CallbackNodeStatusCode\n\nAn instance of the CallbackNodeStatusCode enum.\n\nCALLBACK_NODE_STATUS_INTEGER: the primal solution available from CallbackVariablePrimal is integer feasible.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/callbacks/#MathOptInterface.CALLBACK_NODE_STATUS_FRACTIONAL","page":"Callbacks","title":"MathOptInterface.CALLBACK_NODE_STATUS_FRACTIONAL","text":"CALLBACK_NODE_STATUS_FRACTIONAL::CallbackNodeStatusCode\n\nAn instance of the CallbackNodeStatusCode enum.\n\nCALLBACK_NODE_STATUS_FRACTIONAL: the primal solution available from CallbackVariablePrimal is integer infeasible.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/callbacks/#MathOptInterface.CALLBACK_NODE_STATUS_UNKNOWN","page":"Callbacks","title":"MathOptInterface.CALLBACK_NODE_STATUS_UNKNOWN","text":"CALLBACK_NODE_STATUS_UNKNOWN::CallbackNodeStatusCode\n\nAn instance of the CallbackNodeStatusCode enum.\n\nCALLBACK_NODE_STATUS_UNKNOWN: the primal solution available from CallbackVariablePrimal might be integer feasible or infeasible.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/callbacks/#Lazy-constraints","page":"Callbacks","title":"Lazy constraints","text":"","category":"section"},{"location":"moi/reference/callbacks/","page":"Callbacks","title":"Callbacks","text":"LazyConstraintCallback\nLazyConstraint","category":"page"},{"location":"moi/reference/callbacks/#MathOptInterface.LazyConstraintCallback","page":"Callbacks","title":"MathOptInterface.LazyConstraintCallback","text":"LazyConstraintCallback() <: AbstractCallback\n\nThe callback can be used to reduce the feasible set given the current primal solution by submitting a LazyConstraint. For instance, it may be called at an incumbent of a mixed-integer problem. Note that there is no guarantee that the callback is called at every feasible primal solution.\n\nThe current primal solution is accessed through CallbackVariablePrimal. Trying to access other result attributes will throw OptimizeInProgress as discussed in AbstractCallback.\n\nExamples\n\nx = MOI.add_variables(optimizer, 8)\nMOI.set(optimizer, MOI.LazyConstraintCallback(), callback_data -> begin\n sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x)\n if # should add a lazy constraint\n func = # computes function\n set = # computes set\n MOI.submit(optimizer, MOI.LazyConstraint(callback_data), func, set)\n end\nend)\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.LazyConstraint","page":"Callbacks","title":"MathOptInterface.LazyConstraint","text":"LazyConstraint(callback_data)\n\nLazy constraint func-in-set submitted as func, set. The optimal solution returned by VariablePrimal will satisfy all lazy constraints that have been submitted.\n\nThis can be submitted only from the LazyConstraintCallback. The field callback_data is a solver-specific callback type that is passed as the argument to the feasible solution callback.\n\nExamples\n\nSuppose x and y are VariableIndexs of optimizer. To add a LazyConstraint for 2x + 3y <= 1, write\n\nfunc = 2.0x + 3.0y\nset = MOI.LessThan(1.0)\nMOI.submit(optimizer, MOI.LazyConstraint(callback_data), func, set)\n\ninside a LazyConstraintCallback of data callback_data.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#User-cuts","page":"Callbacks","title":"User cuts","text":"","category":"section"},{"location":"moi/reference/callbacks/","page":"Callbacks","title":"Callbacks","text":"UserCutCallback\nUserCut","category":"page"},{"location":"moi/reference/callbacks/#MathOptInterface.UserCutCallback","page":"Callbacks","title":"MathOptInterface.UserCutCallback","text":"UserCutCallback() <: AbstractCallback\n\nThe callback can be used to submit UserCut given the current primal solution. For instance, it may be called at fractional (i.e., non-integer) nodes in the branch and bound tree of a mixed-integer problem. Note that there is not guarantee that the callback is called everytime the solver has an infeasible solution.\n\nThe infeasible solution is accessed through CallbackVariablePrimal. Trying to access other result attributes will throw OptimizeInProgress as discussed in AbstractCallback.\n\nExamples\n\nx = MOI.add_variables(optimizer, 8)\nMOI.set(optimizer, MOI.UserCutCallback(), callback_data -> begin\n sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x)\n if # can find a user cut\n func = # computes function\n set = # computes set\n MOI.submit(optimizer, MOI.UserCut(callback_data), func, set)\n end\nend\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.UserCut","page":"Callbacks","title":"MathOptInterface.UserCut","text":"UserCut(callback_data)\n\nConstraint func-to-set suggested to help the solver detect the solution given by CallbackVariablePrimal as infeasible. The cut is submitted as func, set. Typically CallbackVariablePrimal will violate integrality constraints, and a cut would be of the form ScalarAffineFunction-in-LessThan or ScalarAffineFunction-in-GreaterThan. Note that, as opposed to LazyConstraint, the provided constraint cannot modify the feasible set, the constraint should be redundant, e.g., it may be a consequence of affine and integrality constraints.\n\nThis can be submitted only from the UserCutCallback. The field callback_data is a solver-specific callback type that is passed as the argument to the infeasible solution callback.\n\nNote that the solver may silently ignore the provided constraint.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#Heuristic-solutions","page":"Callbacks","title":"Heuristic solutions","text":"","category":"section"},{"location":"moi/reference/callbacks/","page":"Callbacks","title":"Callbacks","text":"HeuristicCallback\nHeuristicSolution\nHeuristicSolutionStatus\nHEURISTIC_SOLUTION_ACCEPTED\nHEURISTIC_SOLUTION_REJECTED\nHEURISTIC_SOLUTION_UNKNOWN","category":"page"},{"location":"moi/reference/callbacks/#MathOptInterface.HeuristicCallback","page":"Callbacks","title":"MathOptInterface.HeuristicCallback","text":"HeuristicCallback() <: AbstractCallback\n\nThe callback can be used to submit HeuristicSolution given the current primal solution. For example, it may be called at fractional (i.e., non-integer) nodes in the branch and bound tree of a mixed-integer problem. Note that there is no guarantee that the callback is called every time the solver has an infeasible solution.\n\nThe current primal solution is accessed through CallbackVariablePrimal. Trying to access other result attributes will throw OptimizeInProgress as discussed in AbstractCallback.\n\nExamples\n\nx = MOI.add_variables(optimizer, 8)\nMOI.set(optimizer, MOI.HeuristicCallback(), callback_data -> begin\n sol = MOI.get(optimizer, MOI.CallbackVariablePrimal(callback_data), x)\n if # can find a heuristic solution\n values = # computes heuristic solution\n MOI.submit(optimizer, MOI.HeuristicSolution(callback_data), x,\n values)\n end\nend\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.HeuristicSolution","page":"Callbacks","title":"MathOptInterface.HeuristicSolution","text":"HeuristicSolution(callback_data)\n\nHeuristically obtained feasible solution. The solution is submitted as variables, values where values[i] gives the value of variables[i], similarly to set. The submit call returns a HeuristicSolutionStatus indicating whether the provided solution was accepted or rejected.\n\nThis can be submitted only from the HeuristicCallback. The field callback_data is a solver-specific callback type that is passed as the argument to the heuristic callback.\n\nSome solvers require a complete solution, others only partial solutions.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.HeuristicSolutionStatus","page":"Callbacks","title":"MathOptInterface.HeuristicSolutionStatus","text":"HeuristicSolutionStatus\n\nAn Enum of possible return values for submit with HeuristicSolution. This informs whether the heuristic solution was accepted or rejected.\n\nValues\n\nPossible values are:\n\nHEURISTIC_SOLUTION_ACCEPTED: The heuristic solution was accepted\nHEURISTIC_SOLUTION_REJECTED: The heuristic solution was rejected\nHEURISTIC_SOLUTION_UNKNOWN: No information available on the acceptance\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/callbacks/#MathOptInterface.HEURISTIC_SOLUTION_ACCEPTED","page":"Callbacks","title":"MathOptInterface.HEURISTIC_SOLUTION_ACCEPTED","text":"HEURISTIC_SOLUTION_ACCEPTED::HeuristicSolutionStatus\n\nAn instance of the HeuristicSolutionStatus enum.\n\nHEURISTIC_SOLUTION_ACCEPTED: The heuristic solution was accepted\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/callbacks/#MathOptInterface.HEURISTIC_SOLUTION_REJECTED","page":"Callbacks","title":"MathOptInterface.HEURISTIC_SOLUTION_REJECTED","text":"HEURISTIC_SOLUTION_REJECTED::HeuristicSolutionStatus\n\nAn instance of the HeuristicSolutionStatus enum.\n\nHEURISTIC_SOLUTION_REJECTED: The heuristic solution was rejected\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/callbacks/#MathOptInterface.HEURISTIC_SOLUTION_UNKNOWN","page":"Callbacks","title":"MathOptInterface.HEURISTIC_SOLUTION_UNKNOWN","text":"HEURISTIC_SOLUTION_UNKNOWN::HeuristicSolutionStatus\n\nAn instance of the HeuristicSolutionStatus enum.\n\nHEURISTIC_SOLUTION_UNKNOWN: No information available on the acceptance\n\n\n\n\n\n","category":"constant"},{"location":"packages/SDDP/","page":"odow/SDDP.jl","title":"odow/SDDP.jl","text":"EditURL = \"https://github.com/odow/SDDP.jl/blob/v1.6.3/README.md\"","category":"page"},{"location":"packages/SDDP/","page":"odow/SDDP.jl","title":"odow/SDDP.jl","text":"\"logo\"","category":"page"},{"location":"packages/SDDP/#SDDP.jl","page":"odow/SDDP.jl","title":"SDDP.jl","text":"","category":"section"},{"location":"packages/SDDP/","page":"odow/SDDP.jl","title":"odow/SDDP.jl","text":"(Image: Build Status) (Image: codecov)","category":"page"},{"location":"packages/SDDP/","page":"odow/SDDP.jl","title":"odow/SDDP.jl","text":"SDDP.jl is a JuMP extension for solving large convex multistage stochastic programming problems using stochastic dual dynamic programming.","category":"page"},{"location":"packages/SDDP/#License","page":"odow/SDDP.jl","title":"License","text":"","category":"section"},{"location":"packages/SDDP/","page":"odow/SDDP.jl","title":"odow/SDDP.jl","text":"SDDP.jl is licensed under the MPL 2.0 license.","category":"page"},{"location":"packages/SDDP/#Documentation","page":"odow/SDDP.jl","title":"Documentation","text":"","category":"section"},{"location":"packages/SDDP/","page":"odow/SDDP.jl","title":"odow/SDDP.jl","text":"You can find the documentation at https://odow.github.io/SDDP.jl/stable/.","category":"page"},{"location":"packages/SDDP/#Help","page":"odow/SDDP.jl","title":"Help","text":"","category":"section"},{"location":"packages/SDDP/","page":"odow/SDDP.jl","title":"odow/SDDP.jl","text":"If you need help, please open a GitHub issue.","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"EditURL = \"https://github.com/plasmo-dev/Plasmo.jl/blob/37f80fe96781dc647b3a5e0ed26b9eb0a994bc4a/README.md\"","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"(Image: CI) (Image: codecov) (Image: ) (Image: DOI)","category":"page"},{"location":"packages/Plasmo/#Plasmo.jl","page":"plasmo-dev/Plasmo.jl","title":"Plasmo.jl","text":"","category":"section"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"Plasmo.jl (Platform for Scalable Modeling and Optimization) is a graph-based algebraic modeling framework that adopts a modular style to create mathematical optimization problems and manage distributed and hierarchical structures. The package has been developed as a JuMP extension and consequently supports most JuMP syntax and functions. ","category":"page"},{"location":"packages/Plasmo/#Overview","page":"plasmo-dev/Plasmo.jl","title":"Overview","text":"","category":"section"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"The core data structure in Plasmo.jl is the OptiGraph. The optigraph contains a set of optinodes which represent self-contained optimization problems and optiedges that represent coupling between optinodes (which produces an underlying hypergraph structure of optinodes and optiedges). Optigraphs can further be embedded within other optigraphs to create nested hierarchical graph structures. The graph structures obtained using Plasmo.jl can be used for simple model and data management, but they can also be used to perform graph partitioning or develop interfaces to structured optimization solvers.","category":"page"},{"location":"packages/Plasmo/#License","page":"plasmo-dev/Plasmo.jl","title":"License","text":"","category":"section"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"Plasmo is licensed under the MPL 2.0 license.","category":"page"},{"location":"packages/Plasmo/#Installation","page":"plasmo-dev/Plasmo.jl","title":"Installation","text":"","category":"section"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"Install Plasmo using Pkg.add:","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"import Pkg\nPkg.add(\"Plasmo\")","category":"page"},{"location":"packages/Plasmo/#Documentation","page":"plasmo-dev/Plasmo.jl","title":"Documentation","text":"","category":"section"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"The latest documentation is available through GitHub Pages. Additional examples can be found in the examples folder.","category":"page"},{"location":"packages/Plasmo/#Simple-Example","page":"plasmo-dev/Plasmo.jl","title":"Simple Example","text":"","category":"section"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"using Plasmo\nusing Ipopt\n\n#create an optigraph\ngraph = OptiGraph()\n\n#add nodes to an optigraph\n@optinode(graph, n1)\n@optinode(graph, n2)\n\n#add variables, constraints, and objective functions to nodes\n@variable(n1, 0 <= x <= 2)\n@variable(n1, 0 <= y <= 3)\n@constraint(n1, x+y <= 4)\n@objective(n1, Min, x)\n\n@variable(n2,x)\n@NLconstraint(n2, exp(x) >= 2)\n\n#add a linkconstraint to couple nodes\n@linkconstraint(graph, n1[:x] == n2[:x])\n\n#optimize with Ipopt\nset_optimizer(graph, Ipopt.Optimizer)\noptimize!(graph)\n\n#Print solution values\nprintln(\"n1[:x] = \", value(n1[:x]))\nprintln(\"n2[:x] = \", value(n2[:x]))","category":"page"},{"location":"packages/Plasmo/#Acknowledgments","page":"plasmo-dev/Plasmo.jl","title":"Acknowledgments","text":"","category":"section"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"This code is based on work supported by the following funding agencies:","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"U.S. Department of Energy (DOE), Office of Science, under Contract No. DE-AC02-06CH11357\nDOE Office of Electricity Delivery and Energy Reliability’s Advanced Grid Research and Development program at Argonne National Laboratory\nNational Science Foundation under award NSF-EECS-1609183 and under award CBET-1748516","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"The primary developer is Jordan Jalving (@jalving) with support from the following contributors. ","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"Victor Zavala (University of Wisconsin-Madison)\nYankai Cao (University of British Columbia)\nKibaek Kim (Argonne National Laboratory)\nSungho Shin (University of Wisconsin-Madison)","category":"page"},{"location":"packages/Plasmo/#Citing-Plasmo.jl","page":"plasmo-dev/Plasmo.jl","title":"Citing Plasmo.jl","text":"","category":"section"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"If you find Plasmo.jl useful for your work, you may cite the manuscript as:","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"@article{JalvingShinZavala2022,\n title={A Graph-Based Modeling Abstraction for Optimization: Concepts and Implementation in Plasmo.jl},\n author={Jordan Jalving and Sungho Shin and Victor M. Zavala},\n journal={Mathematical Programming Computation},\n year={2022},\n volume={14},\n pages={699 - 747}\n}","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"There is also a freely available pre-print:","category":"page"},{"location":"packages/Plasmo/","page":"plasmo-dev/Plasmo.jl","title":"plasmo-dev/Plasmo.jl","text":"@misc{JalvingShinZavala2020,\ntitle = {A Graph-Based Modeling Abstraction for Optimization: Concepts and Implementation in Plasmo.jl},\nauthor = {Jordan Jalving and Sungho Shin and Victor M. Zavala},\nyear = {2020},\neprint = {2006.05378},\narchivePrefix = {arXiv},\nprimaryClass = {math.OC}\n}","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"EditURL = \"https://github.com/jump-dev/MiniZinc.jl/blob/v0.3.2/README.md\"","category":"page"},{"location":"packages/MiniZinc/#MiniZinc.jl","page":"jump-dev/MiniZinc.jl","title":"MiniZinc.jl","text":"","category":"section"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"MiniZinc.jl is a wrapper for the MiniZinc constraint modeling language.","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"It provides a way to write MathOptInterface models to .mzn files, and a way to interact with libminizinc.","category":"page"},{"location":"packages/MiniZinc/#Affiliation","page":"jump-dev/MiniZinc.jl","title":"Affiliation","text":"","category":"section"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"This wrapper is maintained by the JuMP community and is not part of the MiniZinc project.","category":"page"},{"location":"packages/MiniZinc/#License","page":"jump-dev/MiniZinc.jl","title":"License","text":"","category":"section"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"MiniZinc.jl is licensed under the MIT License.","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"The underlying project, MiniZinc/libminizinc, is licensed under the MPL 2.0 license.","category":"page"},{"location":"packages/MiniZinc/#Install","page":"jump-dev/MiniZinc.jl","title":"Install","text":"","category":"section"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"Install MiniZinc.jl using the Julia package manager:","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"import Pkg\nPkg.add(\"MiniZinc\")","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"Windows","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"On Linux and macOS, this package automatically installs libminizinc. However, we're still working out problems with the install on Windows. To use MiniZinc.jl, you'll need to manually install a copy of libminizinc from minizinc.org or compile one yourself from MiniZinc/libminizinc.","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"To teach MiniZinc.jl where to look for libminizinc, set the JULIA_LIBMINIZINC_DIR environment variable. For example:","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"ENV[\"JULIA_LIBMINIZINC_DIR\"] = \"C:\\\\Program Files\\\\MiniZinc\"","category":"page"},{"location":"packages/MiniZinc/#Use-with-MathOptInterface","page":"jump-dev/MiniZinc.jl","title":"Use with MathOptInterface","text":"","category":"section"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"MiniZinc.jl supports the constraint programming sets defined in MathOptInterface, as well as (in)equality constraints.","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"The following example solves the following constraint program:","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"xᵢ ∈ {1, 2, 3} ∀i=1,2,3\nzⱼ ∈ {0, 1} ∀j=1,2\nz₁ <-> x₁ != x₂\nz₂ <-> x₂ != x₃\nz₁ + z₂ = 1","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"julia> import MiniZinc\n\njulia> const MOI = MiniZinc.MOI\nMathOptInterface\n\njulia> function main()\n model = MOI.Utilities.CachingOptimizer(\n MiniZinc.Model{Int}(),\n MiniZinc.Optimizer{Int}(\"chuffed\"),\n )\n # xᵢ ∈ {1, 2, 3} ∀i=1,2,3\n x = MOI.add_variables(model, 3)\n MOI.add_constraint.(model, x, MOI.Interval(1, 3))\n MOI.add_constraint.(model, x, MOI.Integer())\n # zⱼ ∈ {0, 1} ∀j=1,2\n z = MOI.add_variables(model, 2)\n MOI.add_constraint.(model, z, MOI.ZeroOne())\n # z₁ <-> x₁ != x₂\n MOI.add_constraint(\n model,\n MOI.VectorOfVariables([z[1], x[1], x[2]]),\n MOI.Reified(MOI.AllDifferent(2)),\n )\n # z₂ <-> x₂ != x₃\n MOI.add_constraint(\n model,\n MOI.VectorOfVariables([z[2], x[2], x[3]]),\n MOI.Reified(MOI.AllDifferent(2)),\n )\n # z₁ + z₂ = 1\n MOI.add_constraint(model, 1 * z[1] + x[2], MOI.EqualTo(1))\n MOI.optimize!(model)\n x_star = MOI.get(model, MOI.VariablePrimal(), x)\n z_star = MOI.get(model, MOI.VariablePrimal(), z)\n return x_star, z_star\n end\nmain (generic function with 1 method)\n\njulia> main()\n([1, 1, 3], [0, 1])","category":"page"},{"location":"packages/MiniZinc/#Use-with-JuMP","page":"jump-dev/MiniZinc.jl","title":"Use with JuMP","text":"","category":"section"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"You can also call MiniZinc from JuMP, using any solver that libminizinc supports. By default, MiniZinc.jl is compiled with \"highs\":","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"using JuMP\nimport MiniZinc\nmodel = Model(() -> MiniZinc.Optimizer{Float64}(\"highs\"))\n@variable(model, 1 <= x[1:3] <= 3, Int)\n@constraint(model, x in MOI.AllDifferent(3))\n@objective(model, Max, sum(i * x[i] for i in 1:3))\noptimize!(model)\n@show value.(x)","category":"page"},{"location":"packages/MiniZinc/#MathOptInterface-API","page":"jump-dev/MiniZinc.jl","title":"MathOptInterface API","text":"","category":"section"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"The MiniZinc Optimizer{T} supports the following constraints and attributes.","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"List of supported objective functions:","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}\nMOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}\nMOI.ObjectiveFunction{MOI.VariableIndex}","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"List of supported variable types:","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"MOI.Reals","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"List of supported constraint types:","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"MOI.ScalarAffineFunction{T} in MOI.EqualTo{T}\nMOI.ScalarAffineFunction{T} in MOI.GreaterThan{T}\nMOI.ScalarAffineFunction{T} in MOI.Integer\nMOI.ScalarAffineFunction{T} in MOI.Interval{T}\nMOI.ScalarAffineFunction{T} in MOI.LessThan{T}\nMOI.ScalarAffineFunction{T} in MOI.ZeroOne\nMOI.VariableIndex in MOI.EqualTo{T}\nMOI.VariableIndex in MOI.GreaterThan{T}\nMOI.VariableIndex in MOI.Integer\nMOI.VariableIndex in MOI.Interval{T}\nMOI.VariableIndex in MOI.LessThan{T}\nMOI.VariableIndex in MOI.Parameter{T}\nMOI.VariableIndex in MOI.Semicontinuous{T}\nMOI.VariableIndex in MOI.Semiinteger{T}\nMOI.VariableIndex in MOI.ZeroOne\nMOI.VectorOfVariables in MOI.AllDifferent\nMOI.VectorOfVariables in MOI.BinPacking{T}\nMOI.VectorOfVariables in MOI.Circuit\nMOI.VectorOfVariables in MOI.CountAtLeast\nMOI.VectorOfVariables in MOI.CountBelongs\nMOI.VectorOfVariables in MOI.CountDistinct\nMOI.VectorOfVariables in MOI.CountGreaterThan\nMOI.VectorOfVariables in MOI.Cumulative\nMOI.VectorOfVariables in MOI.Path\nMOI.VectorOfVariables in MOI.Table{T}","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"List of supported model attributes:","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"MOI.NLPBlock()\nMOI.Name()\nMOI.ObjectiveSense()","category":"page"},{"location":"packages/MiniZinc/#Options","page":"jump-dev/MiniZinc.jl","title":"Options","text":"","category":"section"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"Set options using MOI.RawOptimizerAttribute in MOI or set_attribute in JuMP.","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"MiniZinc.jl supports the following options:","category":"page"},{"location":"packages/MiniZinc/","page":"jump-dev/MiniZinc.jl","title":"jump-dev/MiniZinc.jl","text":"model_filename::String = \"\": the location at which to write out the .mzn file during optimization. This option can be helpful during debugging. If left empty, a temporary file will be used instead.","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"EditURL = \"transp.jl\"","category":"page"},{"location":"tutorials/linear/transp/#The-transportation-problem","page":"The transportation problem","title":"The transportation problem","text":"","category":"section"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"This tutorial was originally contributed by Louis Luangkesorn.","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"This tutorial is an adaptation of the transportation problem described in AMPL: A Modeling Language for Mathematical Programming, by R. Fourer, D.M. Gay and B.W. Kernighan.","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"The purpose of this tutorial is to demonstrate how to create a JuMP model from an ad-hoc structured text file.","category":"page"},{"location":"tutorials/linear/transp/#Required-packages","page":"The transportation problem","title":"Required packages","text":"","category":"section"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"This tutorial uses the following packages:","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"using JuMP\nimport DelimitedFiles\nimport HiGHS","category":"page"},{"location":"tutorials/linear/transp/#Formulation","page":"The transportation problem","title":"Formulation","text":"","category":"section"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"Suppose that we have a set of factories that produce pogo sticks, and a set of retail stores in which to sell them. Each factory has a maximum number of pogo sticks that it can produce, and each retail store has a demand of pogo sticks that it can sell.","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"In the transportation problem, we want to choose the number of pogo sticks to make and ship from each factory to each retail store that minimizes the total shipping cost.","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"Mathematically, we represent our set of factories by a set of origins i in O and our retail stores by a set of destinations j in D. The maximum supply at each factory is s_i and the demand from each retail store is d_j. The cost of shipping one pogo stick from i to j is c_ij.","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"With a little effort, we can model the transportation problem as the following linear program:","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"beginaligned\nmin sum_i in O j in D c_ij x_ij \nst sum_j in D x_i j le s_i forall i in O \n sum_i in O x_i j = d_j forall j in D \n x_i j ge 0 forall i in O j in D\nendaligned","category":"page"},{"location":"tutorials/linear/transp/#Data","page":"The transportation problem","title":"Data","text":"","category":"section"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"We assume our data is in the form of a text file that has the following form. In practice, we would obtain this text file from the user as input, but for the purpose of this tutorial we're going to create it from Julia.","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"open(joinpath(@__DIR__, \"transp.txt\"), \"w\") do io\n print(\n io,\n \"\"\"\n . FRA DET LAN WIN STL FRE LAF SUPPLY\n GARY 39 14 11 14 16 82 8 1400\n CLEV 27 . 12 . 26 95 17 2600\n PITT 24 14 17 13 28 99 20 2900\n DEMAND 900 1200 600 400 1700 1100 1000 0\n \"\"\",\n )\n return\nend","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"Here the rows are the origins, the columns are the destinations, and the values are the cost of shipping one pogo stick from the origin to the destination. If pogo stick cannot be transported from a source to a destination, then the value is .. The final row and final column are the demand and supply of each location respectively.","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"We didn't account for arcs which do not exist in our formulation, but we can make a small change and fix x_ij = 0 if c_ij = .","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"Our first step is to convert this text format into an appropriate Julia datastructure that we can work with. Since our data is tabular with named rows and columns, one option is JuMP's Containers.DenseAxisArray object:","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"function read_data(filename::String)\n data = DelimitedFiles.readdlm(filename)\n rows, columns = data[2:end, 1], data[1, 2:end]\n return Containers.DenseAxisArray(data[2:end, 2:end], rows, columns)\nend\n\ndata = read_data(joinpath(@__DIR__, \"transp.txt\"))","category":"page"},{"location":"tutorials/linear/transp/#JuMP-formulation","page":"The transportation problem","title":"JuMP formulation","text":"","category":"section"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"Following Design patterns for larger models, we code our JuMP model as a function which takes in an input. In this example, we print the output to stdout:","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"function solve_transportation_problem(data::Containers.DenseAxisArray)\n # Get the set of supplies and demands\n O, D = axes(data)\n # Drop the SUPPLY and DEMAND nodes from our sets\n O, D = setdiff(O, [\"DEMAND\"]), setdiff(D, [\"SUPPLY\"])\n model = Model(HiGHS.Optimizer)\n set_silent(model)\n @variable(model, x[o in O, d in D] >= 0)\n # Remove arcs with \".\" cost by fixing them to 0.0.\n for o in O, d in D\n if data[o, d] == \".\"\n fix(x[o, d], 0.0; force = true)\n end\n end\n @objective(\n model,\n Min,\n sum(data[o, d] * x[o, d] for o in O, d in D if data[o, d] != \".\"),\n )\n @constraint(model, [o in O], sum(x[o, :]) <= data[o, \"SUPPLY\"])\n @constraint(model, [d in D], sum(x[:, d]) == data[\"DEMAND\", d])\n optimize!(model)\n # Pretty print the solution in the format of the input\n print(\" \", join(lpad.(D, 7, ' ')))\n for o in O\n print(\"\\n\", o)\n for d in D\n if isapprox(value(x[o, d]), 0.0; atol = 1e-6)\n print(\" .\")\n else\n print(\" \", lpad(value(x[o, d]), 6, ' '))\n end\n end\n end\n return\nend","category":"page"},{"location":"tutorials/linear/transp/#Solution","page":"The transportation problem","title":"Solution","text":"","category":"section"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"Let's solve and view the solution:","category":"page"},{"location":"tutorials/linear/transp/","page":"The transportation problem","title":"The transportation problem","text":"solve_transportation_problem(data)","category":"page"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/developer/checklists.md\"","category":"page"},{"location":"moi/developer/checklists/#Checklists","page":"Checklists","title":"Checklists","text":"","category":"section"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"The purpose of this page is to collate a series of checklists for commonly performed changes to the source code of MathOptInterface.","category":"page"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"In each case, copy the checklist into the description of the pull request.","category":"page"},{"location":"moi/developer/checklists/#Making-a-release","page":"Checklists","title":"Making a release","text":"","category":"section"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"Use this checklist when making a release of the MathOptInterface repository.","category":"page"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"## Basic\n\n - [ ] `version` field of `Project.toml` has been updated\n - If a breaking change, increment the MAJOR field and reset others to 0\n - If adding new features, increment the MINOR field and reset PATCH to 0\n - If adding bug fixes or documentation changes, increment the PATCH field\n\n## Documentation\n\n - [ ] Add a new entry to `docs/src/changelog.md`, following existing style\n\n## Tests\n\n - [ ] The `solver-tests.yml` GitHub action does not have unexpected failures.\n To run the action, go to:\n https://github.com/jump-dev/MathOptInterface.jl/actions/workflows/solver-tests.yml\n and click \"Run workflow\"","category":"page"},{"location":"moi/developer/checklists/#Adding-a-new-set","page":"Checklists","title":"Adding a new set","text":"","category":"section"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"Use this checklist when adding a new set to the MathOptInterface repository.","category":"page"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"## Basic\n\n - [ ] Add a new `AbstractScalarSet` or `AbstractVectorSet` to `src/sets.jl`\n - [ ] If `isbitstype(S) == false`, implement `Base.copy(set::S)`\n - [ ] If `isbitstype(S) == false`, implement `Base.:(==)(x::S, y::S)`\n - [ ] If an `AbstractVectorSet`, implement `dimension(set::S)`, unless the\n dimension is given by `set.dimension`.\n\n## Utilities\n\n - [ ] If an `AbstractVectorSet`, implement `Utilities.set_dot`,\n unless the dot product between two vectors in the set is equivalent to\n `LinearAlgebra.dot`\n - [ ] If an `AbstractVectorSet`, implement `Utilities.set_with_dimension` in\n `src/Utilities/matrix_of_constraints.jl`\n - [ ] Add the set to the `@model` macro at the bottom of `src/Utilities.model.jl`\n\n## Documentation\n\n - [ ] Add a docstring, which gives the mathematical definition of the set,\n along with an `## Example` block containing a `jldoctest`\n - [ ] Add the docstring to `docs/src/reference/standard_form.md`\n - [ ] Add the set to the relevant table in `docs/src/manual/standard_form.md`\n\n## Tests\n\n - [ ] Define a new `_set(::Type{S})` method in `src/Test/test_basic_constraint.jl`\n and add the name of the set to the list at the bottom of that files\n - [ ] If the set has any checks in its constructor, add tests to `test/sets.jl`\n\n## MathOptFormat\n\n - [ ] Open an issue at `https://github.com/jump-dev/MathOptFormat` to add\n support for the new set {{ replace with link to the issue }}\n\n## Optional\n\n - [ ] Implement `dual_set(::S)` and `dual_set_type(::Type{S})`\n - [ ] Add new tests to the `Test` submodule exercising your new set\n - [ ] Add new bridges to convert your set into more commonly used sets","category":"page"},{"location":"moi/developer/checklists/#Adding-a-new-bridge","page":"Checklists","title":"Adding a new bridge","text":"","category":"section"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"Use this checklist when adding a new bridge to the MathOptInterface repository.","category":"page"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"The steps are mostly the same, but locations depend on whether the bridge is a Constraint, Objective, or Variable bridge. In each case below, replace XXX with the appropriate type of bridge.","category":"page"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"## Basic\n\n - [ ] Create a new file in `src/Bridges/XXX/bridges`\n - [ ] Define the bridge, following existing examples. The name of the bridge\n struct must end in `Bridge`\n - [ ] Check if your bridge can be a subtype of [`MOI.Bridges.Constraint.SetMapBridge`](@ref)\n - [ ] Define a new `const` that is a `SingleBridgeOptimizer` wrapping the\n new bridge. The name of the const must be the name of the bridge, less\n the `Bridge` suffix\n - [ ] `include` the file in `src/Bridges/XXX/bridges/XXX.jl`\n - [ ] If the bridge should be enabled by default, add the bridge to\n `add_all_bridges` at the bottom of `src/Bridges/XXX/XXX.jl`\n\n## Tests\n\n - [ ] Create a new file in the appropriate subdirectory of `tests/Bridges/XXX`\n - [ ] Use `MOI.Bridges.runtests` to test various inputs and outputs of the\n bridge\n - [ ] If, after opening the pull request to add the bridge, some lines are not\n covered by the tests, add additional bridge-specific tests to cover the\n untested lines.\n\n## Documentation\n\n - [ ] Add a docstring which uses the same template as existing bridges.\n - [ ] Add the docstring to `docs/src/submodules/Bridges/list_of_bridges.md`\n\n## Final touch\n\nIf the bridge depends on run-time values of other variables and constraints in\nthe model:\n\n - [ ] Implement `MOI.Utilities.needs_final_touch(::Bridge)`\n - [ ] Implement `MOI.Utilities.final_touch(::Bridge, ::MOI.ModelLike)`\n - [ ] Ensure that `final_touch` can be called multiple times in a row","category":"page"},{"location":"moi/developer/checklists/#Updating-MathOptFormat","page":"Checklists","title":"Updating MathOptFormat","text":"","category":"section"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"Use this checklist when updating the version of MathOptFormat.","category":"page"},{"location":"moi/developer/checklists/","page":"Checklists","title":"Checklists","text":"## Basic\n\n - [ ] The file at `src/FileFormats/MOF/mof.X.Y.schema.json` is updated\n - [ ] The constants `SCHEMA_PATH`, `VERSION`, and `SUPPORTED_VERSIONS` are\n updated in `src/FileFormats/MOF/MOF.jl`\n\n## New sets\n\n - [ ] New sets are added to the `@model` in `src/FileFormats/MOF/MOF.jl`\n - [ ] New sets are added to the `@enum` in `src/FileFormats/MOF/read.jl`\n - [ ] `set_to_moi` is defined for each set in `src/FileFormats/MOF/read.jl`\n - [ ] `head_name` is defined for each set in `src/FileFormats/MOF/write.jl`\n - [ ] A new unit test calling `_test_model_equality` is aded to\n `test/FileFormats/MOF/MOF.jl`\n\n## Tests\n\n - [ ] The version field in `test/FileFormats/MOF/nlp.mof.json` is updated\n\n## Documentation\n\n - [ ] The version fields are updated in `docs/src/submodules/FileFormats/overview.md`","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"DocTestSetup = quote\n using JuMP\nend","category":"page"},{"location":"manual/expressions/#Expressions","page":"Expressions","title":"Expressions","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"JuMP has three types of expressions: affine, quadratic, and nonlinear. These expressions can be inserted into constraints or into the objective. This is particularly useful if an expression is used in multiple places in the model.","category":"page"},{"location":"manual/expressions/#Affine-expressions","page":"Expressions","title":"Affine expressions","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"There are four ways of constructing an affine expression in JuMP: with the @expression macro, with operator overloading, with the AffExpr constructor, and with add_to_expression!.","category":"page"},{"location":"manual/expressions/#Macros","page":"Expressions","title":"Macros","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"The recommended way to create an affine expression is via the @expression macro.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> ex = @expression(model, 2x + y - 1)\n2 x + y - 1","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"This expression can be used in the objective or added to a constraint. For example:","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> @objective(model, Min, 2 * ex - 1)\n4 x + 2 y - 3\n\njulia> objective_function(model)\n4 x + 2 y - 3","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Just like variables and constraints, named expressions can also be created. For example","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x[i = 1:3]);\n\njulia> @expression(model, expr[i = 1:3], i * sum(x[j] for j in i:3));\n\njulia> expr\n3-element Vector{AffExpr}:\n x[1] + x[2] + x[3]\n 2 x[2] + 2 x[3]\n 3 x[3]","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"tip: Tip\nYou can read more about containers in the Containers section.","category":"page"},{"location":"manual/expressions/#Operator-overloading","page":"Expressions","title":"Operator overloading","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Expressions can also be created without macros. However, note that in some cases, this can be much slower that constructing an expression using macros.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> ex = 2x + y - 1\n2 x + y - 1","category":"page"},{"location":"manual/expressions/#Constructors","page":"Expressions","title":"Constructors","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"A third way to create an affine expression is by the AffExpr constructor. The first argument is the constant term, and the remaining arguments are variable-coefficient pairs.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> ex = AffExpr(-1.0, x => 2.0, y => 1.0)\n2 x + y - 1","category":"page"},{"location":"manual/expressions/#add_to_expression!","page":"Expressions","title":"add_to_expression!","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"The fourth way to create an affine expression is by using add_to_expression!. Compared to the operator overloading method, this approach is faster because it avoids constructing temporary objects. The @expression macro uses add_to_expression! behind-the-scenes.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> ex = AffExpr(-1.0)\n-1\n\njulia> add_to_expression!(ex, 2.0, x)\n2 x - 1\n\njulia> add_to_expression!(ex, 1.0, y)\n2 x + y - 1","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"warning: Warning\nRead the section Initializing arrays for some cases to be careful about when using add_to_expression!.","category":"page"},{"location":"manual/expressions/#Removing-zero-terms","page":"Expressions","title":"Removing zero terms","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Use drop_zeros! to remove terms from an affine expression with a 0 coefficient.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @expression(model, ex, x + 1 - x)\n0 x + 1\n\njulia> drop_zeros!(ex)\n\njulia> ex\n1","category":"page"},{"location":"manual/expressions/#Coefficients","page":"Expressions","title":"Coefficients","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Use coefficient to return the coefficient associated with a variable in an affine expression.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> @expression(model, ex, 2x + 1)\n2 x + 1\n\njulia> coefficient(ex, x)\n2.0\n\njulia> coefficient(ex, y)\n0.0","category":"page"},{"location":"manual/expressions/#Quadratic-expressions","page":"Expressions","title":"Quadratic expressions","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Like affine expressions, there are four ways of constructing a quadratic expression in JuMP: macros, operator overloading, constructors, and add_to_expression!.","category":"page"},{"location":"manual/expressions/#Macros-2","page":"Expressions","title":"Macros","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"The @expression macro can be used to create quadratic expressions by including quadratic terms.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> ex = @expression(model, x^2 + 2 * x * y + y^2 + x + y - 1)\nx² + 2 x*y + y² + x + y - 1","category":"page"},{"location":"manual/expressions/#Operator-overloading-2","page":"Expressions","title":"Operator overloading","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Operator overloading can also be used to create quadratic expressions. The same performance warning (discussed in the affine expression section) applies.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> ex = x^2 + 2 * x * y + y^2 + x + y - 1\nx² + 2 x*y + y² + x + y - 1","category":"page"},{"location":"manual/expressions/#Constructors-2","page":"Expressions","title":"Constructors","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Quadratic expressions can also be created using the QuadExpr constructor. The first argument is an affine expression, and the remaining arguments are pairs, where the first term is a JuMP.UnorderedPair and the second term is the coefficient.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> aff_expr = AffExpr(-1.0, x => 1.0, y => 1.0)\nx + y - 1\n\njulia> quad_expr = QuadExpr(\n aff_expr,\n UnorderedPair(x, x) => 1.0,\n UnorderedPair(x, y) => 2.0,\n UnorderedPair(y, y) => 1.0,\n )\nx² + 2 x*y + y² + x + y - 1","category":"page"},{"location":"manual/expressions/#add_to_expression!-2","page":"Expressions","title":"add_to_expression!","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Finally, add_to_expression! can also be used to add quadratic terms.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> ex = QuadExpr(x + y - 1.0)\nx + y - 1\n\njulia> add_to_expression!(ex, 1.0, x, x)\nx² + x + y - 1\n\njulia> add_to_expression!(ex, 2.0, x, y)\nx² + 2 x*y + x + y - 1\n\njulia> add_to_expression!(ex, 1.0, y, y)\nx² + 2 x*y + y² + x + y - 1","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"warning: Warning\nRead the section Initializing arrays for some cases to be careful about when using add_to_expression!.","category":"page"},{"location":"manual/expressions/#Removing-zero-terms-2","page":"Expressions","title":"Removing zero terms","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Use drop_zeros! to remove terms from a quadratic expression with a 0 coefficient.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @expression(model, ex, x^2 + x + 1 - x^2)\n0 x² + x + 1\n\njulia> drop_zeros!(ex)\n\njulia> ex\nx + 1","category":"page"},{"location":"manual/expressions/#Coefficients-2","page":"Expressions","title":"Coefficients","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Use coefficient to return the coefficient associated with a pair of variables in a quadratic expression.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> @expression(model, ex, 2*x*y + 3*x)\n2 x*y + 3 x\n\njulia> coefficient(ex, x, y)\n2.0\n\njulia> coefficient(ex, x, x)\n0.0\n\njulia> coefficient(ex, y, x)\n2.0\n\njulia> coefficient(ex, x)\n3.0","category":"page"},{"location":"manual/expressions/#Nonlinear-expressions","page":"Expressions","title":"Nonlinear expressions","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Nonlinear expressions in JuMP are represented by a NonlinearExpr object. See Nonlinear expressions in detail for more details.","category":"page"},{"location":"manual/expressions/#Initializing-arrays","page":"Expressions","title":"Initializing arrays","text":"","category":"section"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"JuMP implements zero(AffExpr) and one(AffExpr) to support various functions in LinearAlgebra (for example, accessing the off-diagonal of a Diagonal matrix).","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> zero(AffExpr)\n0\n\njulia> one(AffExpr)\n1","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"However, this can result in a subtle bug if you call add_to_expression! or the MutableArithmetics API on an element created by zeros or ones:","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> x = zeros(AffExpr, 2)\n2-element Vector{AffExpr}:\n 0\n 0\n\njulia> add_to_expression!(x[1], 1.1)\n1.1\n\njulia> x\n2-element Vector{AffExpr}:\n 1.1\n 1.1","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Notice how we modified x[1], but we also changed x[2]!","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"This happened because zeros(AffExpr, 2) calls zero(AffExpr) once to obtain a zero element, and then creates an appropriately sized array filled with the same element.","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"This also happens with broadcasting calls containing a conversion of 0 or 1:","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> x = Vector{AffExpr}(undef, 2)\n2-element Vector{AffExpr}:\n #undef\n #undef\n\njulia> x .= 0\n2-element Vector{AffExpr}:\n 0\n 0\n\njulia> add_to_expression!(x[1], 1.1)\n1.1\n\njulia> x\n2-element Vector{AffExpr}:\n 1.1\n 1.1","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"The recommended way to create an array of empty expressions is as follows:","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> x = Vector{AffExpr}(undef, 2)\n2-element Vector{AffExpr}:\n #undef\n #undef\n\njulia> for i in eachindex(x)\n x[i] = AffExpr(0.0)\n end\n\njulia> add_to_expression!(x[1], 1.1)\n1.1\n\njulia> x\n2-element Vector{AffExpr}:\n 1.1\n 0","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Alternatively, use non-mutating operation to avoid updating x[1] in-place:","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"julia> x = zeros(AffExpr, 2)\n2-element Vector{AffExpr}:\n 0\n 0\n\njulia> x[1] += 1.1\n1.1\n\njulia> x\n2-element Vector{AffExpr}:\n 1.1\n 0","category":"page"},{"location":"manual/expressions/","page":"Expressions","title":"Expressions","text":"Note that for large expressions this will be slower due to the allocation of additional temporary objects.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"EditURL = \"factory_schedule.jl\"","category":"page"},{"location":"tutorials/linear/factory_schedule/#The-factory-schedule-example","page":"The factory schedule example","title":"The factory schedule example","text":"","category":"section"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"This tutorial was originally contributed by @Crghilardi.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"This tutorial is a Julia translation of Part 5 from Introduction to Linear Programming with Python.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"The purpose of this tutorial is to demonstrate how to use DataFrames and delimited files, and to structure your code that is robust to infeasibilities and permits running with different datasets.","category":"page"},{"location":"tutorials/linear/factory_schedule/#Required-packages","page":"The factory schedule example","title":"Required packages","text":"","category":"section"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"This tutorial requires the following packages:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"using JuMP\nimport CSV\nimport DataFrames\nimport HiGHS\nimport StatsPlots","category":"page"},{"location":"tutorials/linear/factory_schedule/#Formulation","page":"The factory schedule example","title":"Formulation","text":"","category":"section"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"The Factory Scheduling Problem assumes we are optimizing the production of a good from factories f in F over the course of 12 months m in M.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"If a factory f runs during a month m, a fixed cost of a_f is incurred, the factory must produce x_mf units that is within some minimum and maximum production levels l_f and u_f respectively, and each unit of production incurs a variable cost c_f. Otherwise, the factory can be shut for the month with zero production and no fixed-cost is incurred. We denote the run/not-run decision by z_mf in 0 1, where z_mf is 1 if factory f runs in month m. The factory must produce enough units to satisfy demand d_m.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"With a little effort, we can formulate our problem as the following linear program:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"beginaligned\nmin sumlimits_f in F m in M a_f z_mf + c_f x_mf \ntextst x_mf le u_f z_mf forall f in F m in M \n x_mf ge l_f z_mf forall f in F m in M \n sumlimits_fin F x_mf = d_m forall f in F m in M \n z_mf in 0 1 forall f in F m in M\nendaligned","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"However, this formulation has a problem: if demand is too high, we may be unable to satisfy the demand constraint, and the problem will be infeasible.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"tip: Tip\nWhen modeling, consider ways to formulate your model such that it always has a feasible solution. This greatly simplifies debugging data errors that would otherwise result in an infeasible solution. In practice, most practical decisions have a feasible solution. In our case, we could satisfy demand (at a high cost) by buying replacement items for the buyer, or running the factories in overtime to make up the difference.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"We can improve our model by adding a new variable, delta_m, which represents the quantity of unmet demand in each month m. We penalize delta_m by an arbitrarily large value of $10,000/unit in the objective.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"beginaligned\nmin sumlimits_f in F m in M a_f z_mf + c_f x_mf + sumlimits_m in M10000 delta_m \ntextst x_mf le u_f z_mf forall f in F m in M \n x_mf ge l_f z_mf forall f in F m in M \n sumlimits_fin F x_mf - delta_m = d_m forall f in F m in M \n z_mf in 0 1 forall f in F m in M \n delta_m ge 0 forall m in M\nendaligned","category":"page"},{"location":"tutorials/linear/factory_schedule/#Data","page":"The factory schedule example","title":"Data","text":"","category":"section"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"The JuMP GitHub repository contains two text files with the data we need for this tutorial.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"The first file contains a dataset of our factories, A and B, with their production and cost levels for each month. For the documentation, the file is located at:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"factories_filename = joinpath(@__DIR__, \"factory_schedule_factories.txt\");\nnothing #hide","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"To run locally, download factory_schedule_factories.txt and update factories_filename appropriately.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"The file has the following contents:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"print(read(factories_filename, String))","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"We use the CSV and DataFrames packages to read it into Julia:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"factory_df = CSV.read(\n factories_filename,\n DataFrames.DataFrame;\n delim = ' ',\n ignorerepeated = true,\n)","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"The second file contains the demand data by month:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"demand_filename = joinpath(@__DIR__, \"factory_schedule_demand.txt\");\nnothing #hide","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"To run locally, download factory_schedule_demand.txt and update demand_filename appropriately.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"demand_df = CSV.read(\n demand_filename,\n DataFrames.DataFrame;\n delim = ' ',\n ignorerepeated = true,\n)","category":"page"},{"location":"tutorials/linear/factory_schedule/#Data-validation","page":"The factory schedule example","title":"Data validation","text":"","category":"section"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Before moving on, it's always good practice to validate the data you read from external sources. The more effort you spend here, the fewer issues you will have later. The following function contains a few simple checks, but we could add more. For example, you might want to check that none of the values are too large (or too small), which might indicate a typo or a unit conversion issue (perhaps the variable costs are in $/1000 units instead of $/unit).","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"function valiate_data(\n demand_df::DataFrames.DataFrame,\n factory_df::DataFrames.DataFrame,\n)\n # Minimum production must not exceed maximum production.\n @assert all(factory_df.min_production .<= factory_df.max_production)\n # Demand, minimum production, fixed costs, and variable costs must all be\n # non-negative.\n @assert all(demand_df.demand .>= 0)\n @assert all(factory_df.min_production .>= 0)\n @assert all(factory_df.fixed_cost .>= 0)\n @assert all(factory_df.variable_cost .>= 0)\n return\nend\n\nvaliate_data(demand_df, factory_df)","category":"page"},{"location":"tutorials/linear/factory_schedule/#JuMP-formulation","page":"The factory schedule example","title":"JuMP formulation","text":"","category":"section"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Next, we need to code our JuMP formulation. As shown in Design patterns for larger models, it's always good practice to code your model in a function that accepts well-defined input and returns well-defined output.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"function solve_factory_scheduling(\n demand_df::DataFrames.DataFrame,\n factory_df::DataFrames.DataFrame,\n)\n # Even though we validated the data above, it's good practice to do it here\n # too.\n valiate_data(demand_df, factory_df)\n months, factories = unique(factory_df.month), unique(factory_df.factory)\n model = Model(HiGHS.Optimizer)\n set_silent(model)\n @variable(model, status[months, factories], Bin)\n @variable(model, production[months, factories], Int)\n @variable(model, unmet_demand[months] >= 0)\n # We use `eachrow` to loop through the rows of the dataframe and add the\n # relevant constraints.\n for r in eachrow(factory_df)\n m, f = r.month, r.factory\n @constraint(model, production[m, f] <= r.max_production * status[m, f])\n @constraint(model, production[m, f] >= r.min_production * status[m, f])\n end\n @constraint(\n model,\n [r in eachrow(demand_df)],\n sum(production[r.month, :]) + unmet_demand[r.month] == r.demand,\n )\n @objective(\n model,\n Min,\n 10_000 * sum(unmet_demand) + sum(\n r.fixed_cost * status[r.month, r.factory] +\n r.variable_cost * production[r.month, r.factory] for\n r in eachrow(factory_df)\n )\n )\n optimize!(model)\n schedules = Dict{Symbol,Vector{Float64}}(\n Symbol(f) => value.(production[:, f]) for f in factories\n )\n schedules[:unmet_demand] = value.(unmet_demand)\n return (\n termination_status = termination_status(model),\n cost = objective_value(model),\n # This `select` statement re-orders the columns in the DataFrame.\n schedules = DataFrames.select(\n DataFrames.DataFrame(schedules),\n [:unmet_demand, :A, :B],\n ),\n )\nend","category":"page"},{"location":"tutorials/linear/factory_schedule/#Solution","page":"The factory schedule example","title":"Solution","text":"","category":"section"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Now we can call our solve_factory_scheduling function using the data we read in above.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"solution = solve_factory_scheduling(demand_df, factory_df);\nnothing #hide","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Let's see what solution contains:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"solution.termination_status","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"solution.cost","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"solution.schedules","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"These schedules will be easier to visualize as a graph:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"StatsPlots.groupedbar(\n Matrix(solution.schedules);\n bar_position = :stack,\n labels = [\"unmet demand\" \"A\" \"B\"],\n xlabel = \"Month\",\n ylabel = \"Production\",\n legend = :topleft,\n color = [\"#20326c\" \"#4063d8\" \"#a0b1ec\"],\n)","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Note that we don't have any unmet demand.","category":"page"},{"location":"tutorials/linear/factory_schedule/#What-happens-if-demand-increases?","page":"The factory schedule example","title":"What happens if demand increases?","text":"","category":"section"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Let's run an experiment by increasing the demand by 50% in all time periods:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"demand_df.demand .*= 1.5","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Now we resolve the problem:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"high_demand_solution = solve_factory_scheduling(demand_df, factory_df);\nnothing #hide","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"and visualize the solution:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"StatsPlots.groupedbar(\n Matrix(high_demand_solution.schedules);\n bar_position = :stack,\n labels = [\"unmet demand\" \"A\" \"B\"],\n xlabel = \"Month\",\n ylabel = \"Production\",\n legend = :topleft,\n color = [\"#20326c\" \"#4063d8\" \"#a0b1ec\"],\n)","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Uh oh, we can't satisfy all of the demand.","category":"page"},{"location":"tutorials/linear/factory_schedule/#How-sensitive-is-the-solution-to-changes-in-variable-cost?","page":"The factory schedule example","title":"How sensitive is the solution to changes in variable cost?","text":"","category":"section"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Let's run another experiment, this time seeing how the optimal objective value changes as we vary the variable costs of each factory.","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"First though, let's reset the demand to it's original level:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"demand_df.demand ./= 1.5;\nnothing #hide","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"For our experiment, we're going to scale the variable costs of both factories by a set of values from 0.0 to 1.5:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"scale_factors = 0:0.1:1.5","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"At a high level, we're going to loop over the scale factors for A, then the scale factors for B, rescale the input data, call our solve_factory_scheduling example, and then store the optimal objective value in the following cost matrix:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"cost = zeros(length(scale_factors), length(scale_factors));\nnothing #hide","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Because we're modifying factory_df in-place, we need to store the original variable costs in a new column:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"factory_df[!, :old_variable_cost] = copy(factory_df.variable_cost);\nnothing #hide","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Then, we need a function to scale the :variable_cost column for a particular factory by a value scale:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"function scale_variable_cost(df, factory, scale)\n rows = df.factory .== factory\n df[rows, :variable_cost] .=\n round.(Int, df[rows, :old_variable_cost] .* scale)\n return\nend","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Our experiment is just a nested for-loop, modifying A and B and storing the cost:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"for (j, a) in enumerate(scale_factors)\n scale_variable_cost(factory_df, \"A\", a)\n for (i, b) in enumerate(scale_factors)\n scale_variable_cost(factory_df, \"B\", b)\n cost[i, j] = solve_factory_scheduling(demand_df, factory_df).cost\n end\nend","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"Let's visualize the cost matrix:","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"StatsPlots.contour(\n scale_factors,\n scale_factors,\n cost;\n xlabel = \"Scale of factory A\",\n ylabel = \"Scale of factory B\",\n)","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"What can you infer from the solution?","category":"page"},{"location":"tutorials/linear/factory_schedule/","page":"The factory schedule example","title":"The factory schedule example","text":"info: Info\nThe Power Systems tutorial explains a number of other ways you can structure a problem to perform a parametric analysis of the solution. In particular, you can use in-place modification to reduce the time it takes to build and solve the resulting models.","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"EditURL = \"changelog.md\"","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"CurrentModule = JuMP","category":"page"},{"location":"release_notes/#Release-notes","page":"Release notes","title":"Release notes","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.","category":"page"},{"location":"release_notes/#[Version-1.15.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.15.1)-(September-24,-2023)","page":"Release notes","title":"Version 1.15.1 (September 24, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed support for single argument min and max operators (#3522)\nFixed error message for add_to_expression! when called with a GenericNonlinearExpr (#3506)\nFixed constraint tags with broadcasted constraints (#3515)\nFixed MethodError in MA.scaling (#3518)\nFixed support for arrays of Parameter variables (#3524)","category":"page"},{"location":"release_notes/#Other","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated to Documenter@1 (#3501)\nFixed links to data in tutorials (#3512)\nFixed typo in TSP tutorial (#3516)\nImproved error message for VariableNotOwned errors (#3520)\nFixed various JET errors (#3519)","category":"page"},{"location":"release_notes/#[Version-1.15.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.15.0)-(September-15,-2023)","page":"Release notes","title":"Version 1.15.0 (September 15, 2023)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"This is a large minor release because it adds an entirely new data structure and API path for working with nonlinear programs. The previous nonlinear interface remains unchanged and is documented at Nonlinear Modeling (Legacy). The new interface is a treated as a non-breaking feature addition and is documented at Nonlinear Modeling.","category":"page"},{"location":"release_notes/#Breaking","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Although the new nonlinear interface is a feature addition, there are two changes which might be breaking for a very small number of users.","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The syntax inside JuMP macros is parsed using a different code path, even for linear and quadratic expressions. We made this change to unify how we parse linear, quadratic, and nonlinear expressions. In all cases, the new code returns equivalent expressions, but because of the different order of operations, there are three changes to be aware of when updating:\nThe printed form of the expression may change, for example from x * y to y * x. This can cause tests which test the String representation of a model to fail.\nSome coefficients may change slightly due to floating point round-off error.\nParticularly when working with a JuMP extension, you may encounter a MethodError due to a missing or ambiguous method. These errors are due to previously existing bugs that were not triggered by the previous parsing code. If you encounter such an error, please open a GitHub issue.\nThe methods for Base.:^(x::VariableRef, n::Integer) and Base.:^(x::AffExpr, n::Integer) have changed. Previously, these methods supported only n = 0, 1, 2 and they always returned a QuadExpr, even for the case when n = 0 or n = 1. Now:\nx^0 returns one(T), where T is the value_type of the model (defaults to Float64)\nx^1 returns x\nx^2 returns a QuadExpr\nx^n where !(0 <= n <= 2) returns a NonlinearExpr.\nWe made this change to support nonlinear expressions and to align the mathematical definition of the operation with their return type. (Previously, users were surprised that x^1 returned a QuadExpr.) As a consequence of this change, the methods are now not type-stable. This means that the compiler cannot prove that x^2 returns a QuadExpr. If benchmarking shows that this is a performance problem, you can use the type-stable x * x instead of x^2.","category":"page"},{"location":"release_notes/#Added","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added triangle_vec which simplifies adding MOI.LogDetConeTriangle and MOI.RootDetConeTriangle constraints (#3456)\nAdded the new nonlinear interface. This is a very large change. See the documentation at Nonlinear Modeling and the (long) discussion in JuMP.jl#3106. Related PRs are (#3468) (#3472) (#3475) (#3483) (#3487) (#3488) (#3489) (#3504) (#3509)","category":"page"},{"location":"release_notes/#Fixed-2","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed uses of @nospecialize which cause precompilation failures in Julia v1.6.0 and v1.6.1. (#3464)\nFixed adding a container of Parameter (#3473)\nFixed return type of x^0 and x^1 to no longer return QuadExpr (see note in Breaking section above) (#3474)\nFixed error messages in LowerBoundRef, UpperBoundRef, FixRef, IntegerRef, BinaryRef, ParameterRef and related functions (#3494)\nFixed type inference of empty containers in JuMP macros (#3500)","category":"page"},{"location":"release_notes/#Other-2","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added GAMS to solver documentation (#3357)\nUpdated various tutorials (#3459) (#3460) (#3462) (#3463) (#3465) (#3490) (#3492) (#3503)\nAdded The network multi-commodity flow problem tutorial (#3491)\nAdded Two-stage stochastic programs tutorial (#3466)\nAdded better error messages for unsupported operations in LinearAlgebra (#3476)\nUpdated to the latest version of Documenter (#3484) (#3495) (#3497)\nUpdated GitHub action versions (#3507)","category":"page"},{"location":"release_notes/#[Version-1.14.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.14.1)-(September-2,-2023)","page":"Release notes","title":"Version 1.14.1 (September 2, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-3","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix links in Documentation (#3478)","category":"page"},{"location":"release_notes/#[Version-1.14.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.14.0)-(August-27,-2023)","page":"Release notes","title":"Version 1.14.0 (August 27, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-2","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added DimensionalData.jl extension (#3413)\nAdded syntactic sugar for the MOI.Parameter set (#3443)\nParameter\nParameterRef\nis_parameter\nparameter_value\nset_parameter_value","category":"page"},{"location":"release_notes/#Fixed-4","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed model_convert for BridgeableConstraint (#3437)\nFixed printing models with integer coefficients larger than typemax(Int) (#3447)\nFixed support for constant left-hand side functions in a complementarity constraint (#3452)","category":"page"},{"location":"release_notes/#Other-3","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated packages used in documentation (#3444) (#3455)\nFixed docstring tests (#3445)\nFixed printing change for MathOptInterface (#3446)\nFixed typos in documentation (#3448) (#3457)\nAdded SCIP to callback documentation (#3449)","category":"page"},{"location":"release_notes/#[Version-1.13.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.13.0)-(July-27,-2023)","page":"Release notes","title":"Version 1.13.0 (July 27, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-3","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added support for generic number types (#3377) (#3385)\nAdded fallback for MOI.AbstractSymmetricMatrixSetTriangle and MOI.AbstractSymmetricMatrixSetSquare (#3424)","category":"page"},{"location":"release_notes/#Fixed-5","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed set_start_values with MOI.Bridges.Objective.SlackBridge (#3422)\nFixed flakey doctest in variables.md (#3425)\nFixed names on CITATION.bib (#3423)","category":"page"},{"location":"release_notes/#Other-4","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added Loraine.jl to the installation table (#3426)\nRemoved Penopt.jl from packages.toml (#3428)\nImproved problem statement in cannery example of tutorial (#3430)\nMinor cleanups in Containers.DenseAxisArray implementation (#3429)\nChanged nested_problems.jl: outer/inner to upper/lower (#3433)\nRemoved second SDP relaxation in OPF tutorial (#3432)","category":"page"},{"location":"release_notes/#[Version-1.12.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.12.0)-(June-19,-2023)","page":"Release notes","title":"Version 1.12.0 (June 19, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-4","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added coefficient_type keyword argument to add_bridge and remove_bridge (#3394)","category":"page"},{"location":"release_notes/#Fixed-6","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed error message for matrix in HermitianPSDCone (#3369)\nFixed EditURL for custom documentation pages (#3373)\nFixed return type annotations for MOI.ConstraintPrimal and MOI.ConstraintDual (#3381)\nFixed printing change in Julia nightly (#3391)\nFixed printing of Complex coefficients (#3397)\nFixed printing of constraints in text/latex mode (#3405)\nFixed performance issue in Containers.rowtable (#3410)\nFixed bug when variables added to set of wrong dimension (#3411)","category":"page"},{"location":"release_notes/#Other-5","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added more solver READMEs to the documentation (#3358) (#3360) (#3364) (#3365) (#3366) (#3368) (#3372) (#3374) (#3376) (#3379) (#3387) (#3389)\nAdded StatusSwitchingQP.jl to the installation table (#3354)\nUpdated checklist for adding a new solver (#3370)\nUpdated extension-tests.yml action (#3371) (#3375)\nColor logs in GitHub actions (#3392)\nAdded new tutorials\nOptimal power flow (#3395) (#3412)\nLovász numbers (#3399)\nDualization (#3402)\nUpdated JuMP paper citation (#3400)\nChanged GitHub action to upload LaTeX logs when building documentation (#3403)\nFixed printing of SCS log in documentation (#3406)\nUpdated solver versions (#3407)\nUpdated documentation to use Julia v1.9 (#3398)\nReplaced _value_type with MOI.Utilities.value_type (#3414)\nFixed a typo in docstring (#3415)\nRefactored API documentation (#3386)\nUpdated SCIP license (#3420)","category":"page"},{"location":"release_notes/#[Version-1.11.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.11.1)-(May-19,-2023)","page":"Release notes","title":"Version 1.11.1 (May 19, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-7","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a poor error message when sum(::DenseAxisArray; dims) was called (#3338)\nFixed support for dependent sets in the @variable macro (#3344)\nFixed a performance bug in constraints with sparse symmetric matrices (#3349)","category":"page"},{"location":"release_notes/#Other-6","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improved the printing of complex numbers (#3332)\nWhen printing, sets which contain constants ending in .0 now print as integers. This follows the behavior of constants in functions (#3341)\nAdded InfiniteOpt to the extensions documentation (#3343)\nAdded more documentation for the exponential cone (#3345) (#3347)\nAdded checklists for developers (#3346) (#3355)\nFixed test support upcoming Julia nightly (#3351)\nFixed extension-tests.yml action (#3353)\nAdd more solvers to the documentation (#3359) (#3361) (#3362)","category":"page"},{"location":"release_notes/#[Version-1.11.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.11.0)-(May-3,-2023)","page":"Release notes","title":"Version 1.11.0 (May 3, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-5","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added new methods to print_active_bridges for printing a particular objective, constraint, or variable (#3316)","category":"page"},{"location":"release_notes/#Fixed-8","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed tests for MOI v1.14.0 release (#3312)\nFixed indexing containers when an axis is Vector{Any} that contains a Vector{Any} element (#3280)\nFixed getindex(::AbstractJuMPScalar) which is called for an expression like x[] (#3314)\nFixed bug in set_string_names_on_creation with a vector of variables (#3322)\nFixed bug in memoize function in nonlinear documentation (#3337)","category":"page"},{"location":"release_notes/#Other-7","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed typos in the documentation (#3317) (#3318) (#3328)\nAdded a test for the order of setting start values (#3315)\nAdded READMEs of solvers and extensions to the docs (#3309) (#3320) (#3327) (#3329) (#3333)\nStyle improvements to src/variables.jl (#3324)\nClarify that column generation does not find global optimum (#3325)\nAdd a GitHub actions workflow for testing extensions prior to release (#3331)\nDocument the release process for JuMP (#3334)\nFix links to discourse and chatroom (#3335)","category":"page"},{"location":"release_notes/#[Version-1.10.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.10.0)-(April-3,-2023)","page":"Release notes","title":"Version 1.10.0 (April 3, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-6","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added Nonnegatives, Nonpositives and Zeros, and support vector-valued inequality syntax in the JuMP macros (#3273)\nAdded special support for LinearAlgebra.Symmetric and LinearAlgebra.Hermitian matrices in Zeros constraints (#3281) (#3296)\nAdded HermitianMatrixSpace and the Hermitian tag for generating a matrix of variables that is Hermitian (#3292) (#3293)\nAdded Semicontinuous and Semiinteger (#3302)\nAdded support for keyword indexing of containers (#3237)","category":"page"},{"location":"release_notes/#Fixed-9","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed [compat] bound for MathOptInterface in Project.toml (#3272)","category":"page"},{"location":"release_notes/#Other-8","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Split out the Nested optimization problems tutorial (#3274)\nUpdated doctests to ensure none have hidden state (#3275) (#3276)\nClarified how lazy constraints may revisit points (#3278)\nAdded P-Norm example (#3282)\nClarified docs that macros create new bindings (#3284)\nFixed threading example (#3283)\nAdded plot to The minimum distortion problem (#3288)\nAdded Google style rules for Vale and fixed warnings (#3285)\nAdded citation for the JuMP 1.0 paper (#3294)\nUpdated package versions in the documentation (#3298)\nAdded comment for the order in which start values must be set (#3303)\nImproved error message for unrecognized constraint operators (#3311)","category":"page"},{"location":"release_notes/#[Version-1.9.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.9.0)-(March-7,-2023)","page":"Release notes","title":"Version 1.9.0 (March 7, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-7","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added get_attribute and set_attribute. These replace get_optimizer_attribute and set_optimizer_attribute, although the _optimizer_ functions remain for backward compatibility. (#3219)\nAdded set_start_values for setting all supported start values in a model (#3238)\nAdd remove_bridge and print_active_bridges (#3259)","category":"page"},{"location":"release_notes/#Fixed-10","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The matrix returned by a variable in HermitianPSDCone is now a LinearAlgebra.Hermitian matrix. This is potentially breaking if you have written code to assume the return is a Matrix. (#3245) (#3246)\nFixed missing support for Base.isreal of expressions (#3252)","category":"page"},{"location":"release_notes/#Other-9","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a thread safety issue in the Parallelism tutorial (#3240) (#3243)\nImproved the error message when unsupported operators are used in @NL macros (#3236)\nClarified the documentation to say that matrices in HermitianPSDCone must be LinearAlgebra.Hermitian (#3241)\nMinor style fixes to internal macro code (#3247)\nAdd Quantum state discrimination tutorial (#3250)\nImprove error message when begin...end not passed to plural macros (#3255)\nDocument how to register function with varying number of input arguments (#3258)\nTidy tests by removing unneeded JuMP. prefixes (#3260)\nClarified the introduction to the Complex number support tutorial (#3262)\nFixed typos in the Documentation (#3263) (#3266) (#3268) (#3269)","category":"page"},{"location":"release_notes/#[Version-1.8.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.8.2)-(February-27,-2023)","page":"Release notes","title":"Version 1.8.2 (February 27, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-11","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed dot product between complex JuMP expression and number (#3244)","category":"page"},{"location":"release_notes/#Other-10","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Polish simple SDP examples (#3232)","category":"page"},{"location":"release_notes/#[Version-1.8.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.8.1)-(February-23,-2023)","page":"Release notes","title":"Version 1.8.1 (February 23, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-12","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed support for init in nonlinear generator expressions (#3226)","category":"page"},{"location":"release_notes/#Other-11","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Use and document import MathOptInterface as MOI (#3222)\nRemoved references in documentation to multiobjective optimization being unsupported (#3223)\nAdded tutorial on multi-objective portfolio optimization (#3227)\nRefactored some of the conic tutorials (#3229)\nFixed typos in the documentation (#3230)\nAdded tutorial on parallelism (#3231)","category":"page"},{"location":"release_notes/#[Version-1.8.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.8.0)-(February-16,-2023)","page":"Release notes","title":"Version 1.8.0 (February 16, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-8","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added --> syntax support for indicator constraints. The old syntax of => remains supported (#3207)\nAdded <--> syntax for reified constraints. For now, few solvers support reified constraints (#3206)\nAdded fix_discrete_variables. This is most useful for computing the dual of a mixed-integer program (#3208)\nAdded support for vector-valued objectives. For details, see the Multi-objective knapsack tutorial (#3176)","category":"page"},{"location":"release_notes/#Fixed-13","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug in lp_sensitivity_report by switching to an explicit LU factorization of the basis matrix (#3182)\nFixed a bug that prevented [; kwarg] arguments in macros (#3220)","category":"page"},{"location":"release_notes/#Other-12","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Minor fixes to the documentation (#3200) (#3201) (#3203) (#3210)\nAdded tutorial Constraint programming (#3202)\nAdded more examples to Tips and Tricks\nRemove _distance_to_set in favor of MOI.Utilities.distance_to_set (#3209)\nImprove The diet problem tutorial by adding the variable as a column in the dataframe (#3213)\nImprove The knapsack problem example tutorial (#3216) (#3217)\nAdded the Ellipsoid approximation tutorial (#3218)","category":"page"},{"location":"release_notes/#[Version-1.7.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.7.0)-(January-25,-2023)","page":"Release notes","title":"Version 1.7.0 (January 25, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-9","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added support for view of a Containers.DenseAxisArray (#3152) (#3180)\nAdded support for containers of variables in ComplexPlane (#3184)\nAdded support for minimum and maximum generators in nonlinear expressions (#3189)\nAdded SnoopPrecompile statements that reduce the time-to-first-solve in Julia 1.9 (#3193) (#3195) (#3196) (#3197)","category":"page"},{"location":"release_notes/#Other-13","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Large refactoring of the tests (#3166) (#3167) (#3168) (#3169) (#3170) (#3171)\nRemove unreachable code due to VERSION checks (#3172)\nDocument how to test JuMP extensions (#3174)\nFix method ambiguities in Containers (#3173)\nImprove error message that is thrown when = is used instead of == in the @constraint macro (#3178)\nImprove the error message when Bool is used instead of Bin in the @variable macro (#3180)\nUpdate versions of the documentation (#3185)\nTidy the import of packages and remove unnecessary prefixes (#3186) (#3187)\nRefactor src/JuMP.jl by moving methods into more relevant files (#3188)\nFix docstring of Model not appearing in the documentation (#3198)","category":"page"},{"location":"release_notes/#[Version-1.6.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.6.0)-(January-1,-2023)","page":"Release notes","title":"Version 1.6.0 (January 1, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-10","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added a result keyword argument to solution_summary to allow summarizing models with multiple solutions (#3138)\nAdded relax_with_penalty!, which is a useful tool when debugging infeasible models (#3140)\nAdded has_start_value (#3157)\nAdded support for HermitianPSDCone in constraints (#3154)","category":"page"},{"location":"release_notes/#Fixed-14","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed promotion of complex expressions (#3150) (#3164)","category":"page"},{"location":"release_notes/#Other-14","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added Benders tutorial with in-place resolves (#3145)\nAdded more Tips and tricks for linear programs (#3144) (#3163)\nClarified documentation that start can depend on the indices of a variable container (#3148)\nReplace instances of length and size by the recommended eachindex and axes (#3149)\nAdded a warning explaining why the model is dirty when accessing solution results from a modified model (#3156)\nClarify documentation that PSD ensures a symmetric matrix (#3159)\nMaintenance of the JuMP test suite (#3146) (#3158) (#3162)","category":"page"},{"location":"release_notes/#[Version-1.5.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.5.0)-(December-8,-2022)","page":"Release notes","title":"Version 1.5.0 (December 8, 2022)","text":"","category":"section"},{"location":"release_notes/#Added-11","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Add support for complex-valued variables:\nHermitianPSDCone (#3109)\nComplexPlane and ComplexVariable (#3134)\nAdd support for MOI.OptimizerWithAttributes in set_optimizer_attribute and get_optimizer_attribute (#3129)","category":"page"},{"location":"release_notes/#Fixed-15","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed error message for vectorized interval constraints (#3123)\nFixed passing AbstractString to set_optimizer_attribute (#3127)","category":"page"},{"location":"release_notes/#Other-15","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Update package versions used in docs (#3119) (#3133) (#3139)\nFixed output of diet tutorial (#3120)\nExplain how to use Dates.period in set_time_limit_sec (#3121)\nUpdate to JuliaFormatter v1.0.15 (#3130)\nFixed HTTP server example in web_app.jl (#3131)\nUpdate docs to build with Documenter#master (#3094)\nAdd tests for LinearAlgebra operations (#3132)\nTidy these release notes (#3135)\nAdded documentation for Complex number support (#3141)\nRemoved the \"workforce scheduling\" and \"steelT3\" tutorials (#3143)","category":"page"},{"location":"release_notes/#[Version-1.4.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.4.0)-(October-29,-2022)","page":"Release notes","title":"Version 1.4.0 (October 29, 2022)","text":"","category":"section"},{"location":"release_notes/#Added-12","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added Containers.rowtable which converts a container into a vector of NamedTuples to support the Tables.jl interface. This simplifies converting Containers.DenseAxisArray and Containers.SparseAxisArray objects into tabular forms such as a DataFrame (#3104)\nAdded a new method to Containers.container so that index names are passed to the container (#3088)","category":"page"},{"location":"release_notes/#Fixed-16","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug in copy_to(dest::Model, src::MOI.ModelLike) when src has nonlinear components (#3101)\nFixed the printing of (-1.0 + 0.0im) coefficients in complex expressions (#3112)\nFixed a parsing bug in nonlinear expressions with generator statements that contain multiple for statements (#3116)","category":"page"},{"location":"release_notes/#Other-16","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Converted the multi-commodity flow tutorial to use an SQLite database (#3098)\nFixed a number of typos in the documentation (#3103) (#3107) (#3018)\nImproved various style aspects of the PDF documentation (#3095) (#3098) (#3102)","category":"page"},{"location":"release_notes/#[Version-1.3.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.3.1)-(September-28,-2022)","page":"Release notes","title":"Version 1.3.1 (September 28, 2022)","text":"","category":"section"},{"location":"release_notes/#Fixed-17","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a performance issue in relax_integrality (#3087)\nFixed the type stability of operators with Complex arguments (#3072)\nFixed a bug which added additional +() terms to some nonlinear expressions (#3091)\nFixed potential method ambiguities with AffExpr and QuadExpr objects (#3092)","category":"page"},{"location":"release_notes/#Other-17","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added vale as a linter for the documentation (#3080)\nAdded a tutorial on debugging JuMP models (#3043)\nFixed a number of typos in the documentation (#3079) (#3083)\nMany other small tweaks to the documentation (#3068) (#3073) (#3074) (#3075) (#3076) (#3077) (#3078) (#3081) (#3082) (#3084) (#3085) (#3089)","category":"page"},{"location":"release_notes/#[Version-1.3.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.3.0)-(September-5,-2022)","page":"Release notes","title":"Version 1.3.0 (September 5, 2022)","text":"","category":"section"},{"location":"release_notes/#Added-13","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Support slicing in SparseAxisArray (#3031)","category":"page"},{"location":"release_notes/#Fixed-18","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug introduced in v1.2.0 that prevented DenseAxisArrays with Vector keys (#3064)","category":"page"},{"location":"release_notes/#Other-18","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Released the JuMP logos under the CC BY 4.0 license (#3063)\nMinor tweaks to the documentation (#3054) (#3056) (#3057) (#3060) (#3061) (#3065)\nImproved code coverage of a number of files (#3048) (#3049) (#3050) (#3051) (#3052) (#3053) (#3058) (#3059)","category":"page"},{"location":"release_notes/#[Version-1.2.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.2.1)-(August-22,-2022)","page":"Release notes","title":"Version 1.2.1 (August 22, 2022)","text":"","category":"section"},{"location":"release_notes/#Fixed-19","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug when parsing two-sided nonlinear constraints (#3045)","category":"page"},{"location":"release_notes/#[Version-1.2.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.2.0)-(August-16,-2022)","page":"Release notes","title":"Version 1.2.0 (August 16, 2022)","text":"","category":"section"},{"location":"release_notes/#Breaking-2","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"This is a large minor release because it significantly refactors the internal code for handling nonlinear programs to use the MathOptInterface.Nonlinear submodule that was introduced in MathOptInterface v1.3.0. As a consequence, the internal datastructure in model.nlp_data has been removed, as has the JuMP._Derivatives submodule. Despite the changes, the public API for nonlinear programming has not changed, and any code that uses only the public API and that worked with v1.1.1 will continue to work with v1.2.0.","category":"page"},{"location":"release_notes/#Added-14","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added all_constraints(model; include_variable_in_set_constraints) which simplifies returning a list of all constraint indices in the model.\nAdded the ability to delete nonlinear constraints via delete(::Model, ::NonlinearConstraintRef).\nAdded the ability to provide an explicit Hessian for a multivariate user-defined function.\nAdded support for querying the primal value of a nonlinear constraint via value(::NonlinearConstraintRef)","category":"page"},{"location":"release_notes/#Fixed-20","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug in Containers.DenseAxisArray so that it now supports indexing with keys that hash to the same value, even if they are different types, for example, Int32 and Int64.\nFixed a bug printing the model when the solver does not support MOI.Name.","category":"page"},{"location":"release_notes/#Other-19","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added a constraint programming formulation to the Sudoku tutorial.\nAdded newly supported solvers Pajarito, Clarabel, and COPT to the installation table.\nFixed a variety of other miscellaneous issues in the documentation.","category":"page"},{"location":"release_notes/#[Version-1.1.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.1.1)-(June-14,-2022)","page":"Release notes","title":"Version 1.1.1 (June 14, 2022)","text":"","category":"section"},{"location":"release_notes/#Other-20","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed problem displaying LaTeX in the documentation\nMinor updates to the style guide\nUpdated to MOI v1.4.0 in the documentation","category":"page"},{"location":"release_notes/#[Version-1.1.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.1.0)-(May-25,-2022)","page":"Release notes","title":"Version 1.1.0 (May 25, 2022)","text":"","category":"section"},{"location":"release_notes/#Added-15","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added num_constraints(::Model; count_variable_in_set_constraints) to simplify the process of counting the number of constraints in a model\nAdded VariableRef(::ConstraintRef) for querying the variable associated with a bound or integrality constraint.\nAdded set_normalized_coefficients for modifying the variable coefficients of a vector-valued constraint.\nAdded set_string_names_on_creation to disable creating String names for variables and constraints. This can improve performance.","category":"page"},{"location":"release_notes/#Fixed-21","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug passing nothing to the start keyword of @variable","category":"page"},{"location":"release_notes/#Other-21","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"New tutorials:\nSensitivity analysis of a linear program\nServing web apps\nMinimal ellipse SDP tutorial refactored and improved\nDocs updated to the latest version of each package\nLots of minor fixes and improvements to the documentation","category":"page"},{"location":"release_notes/#[Version-1.0.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v1.0.0)-(March-24,-2022)","page":"Release notes","title":"Version 1.0.0 (March 24, 2022)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Read more about this release, along with an acknowledgement of all the contributors in our JuMP 1.0.0 is released blog post.","category":"page"},{"location":"release_notes/#Breaking-3","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The previously deprecated functions (v0.23.0, v0.23.1) have been removed. Deprecation was to improve consistency of function names:\nnum_nl_constraints (see num_nonlinear_constraints)\nall_nl_constraints (see all_nonlinear_constraints)\nadd_NL_expression (see add_nonlinear_expression)\nset_NL_objective (see set_nonlinear_objective)\nadd_NL_constraint (see add_nonlinear_constraint)\nnl_expr_string (see nonlinear_expr_string)\nnl_constraint_string (see nonlinear_constraint_string)\nSymMatrixSpace (see SymmetricMatrixSpace)\nThe unintentionally exported variable JuMP.op_hint has been renamed to the unexported JuMP._OP_HINT","category":"page"},{"location":"release_notes/#Fixed-22","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug writing .nl files\nFixed a bug broadcasting SparseAxisArrays","category":"page"},{"location":"release_notes/#[Version-0.23.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.23.2)-(March-14,-2022)","page":"Release notes","title":"Version 0.23.2 (March 14, 2022)","text":"","category":"section"},{"location":"release_notes/#Added-16","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added relative_gap to solution_summary\nregister now throws an informative error if the function is not differentiable using ForwardDiff. In some cases, the check in register will encounter a false negative, and the informative error will be thrown at run-time. This usually happens when the function is non-differentiable in a subset of the domain.","category":"page"},{"location":"release_notes/#Fixed-23","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a scoping issue when extending the container keyword of containers","category":"page"},{"location":"release_notes/#Other-22","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Docs updated to the latest version of each package","category":"page"},{"location":"release_notes/#[Version-0.23.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.23.1)-(March-2,-2022)","page":"Release notes","title":"Version 0.23.1 (March 2, 2022)","text":"","category":"section"},{"location":"release_notes/#Deprecated","page":"Release notes","title":"Deprecated","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"nl_expr_string and nl_constraint_string have been renamed to nonlinear_expr_string and nonlinear_constraint_string. The old methods still exist with deprecation warnings. This change should impact very few users because to call them you must rely on private internals of the nonlinear API. Users are encouraged to use sprint(show, x) instead, where x is the nonlinear expression or constraint of interest.","category":"page"},{"location":"release_notes/#Added-17","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added support for Base.abs2(x) where x is a variable or affine expression. This is mainly useful for complex-valued constraints.","category":"page"},{"location":"release_notes/#Fixed-24","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed addition of complex and real affine expressions\nFixed arithmetic for Complex-valued quadratic expressions\nFixed variable bounds passed as Rational{Int}(Inf)\nFixed printing of the coefficient (0 + 1im)\nFixed a bug when solution_summary is called prior to optimize!","category":"page"},{"location":"release_notes/#[Version-0.23.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.23.0)-(February-25,-2022)","page":"Release notes","title":"Version 0.23.0 (February 25, 2022)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"JuMP v0.23.0 is a breaking release. It is also a release-candidate for JuMP v1.0.0. That is, if no issues are found with the v0.23.0 release, then it will be re-tagged as v1.0.0.","category":"page"},{"location":"release_notes/#Breaking-4","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Julia 1.6 is now the minimum supported version\nMathOptInterface has been updated to v1.0.0\nAll previously deprecated functionality has been removed\nPrintMode, REPLMode and IJuliaMode have been removed in favor of the MIME types MIME\"text/plain\" and MIME\"text/latex\". Replace instances of ::Type{REPLMode} with ::MIME\"text/plain\", REPLMode with MIME(\"text/plain\"), ::Type{IJuliaMode} with ::MIME\"text/latex\", and IJuliaMode with MIME(\"text/latex\").\nFunctions containing the nl_ acronym have been renamed to the more explicit nonlinear_. For example, num_nl_constraints is now num_nonlinear_constraints and set_NL_objective is now set_nonlinear_objective. Calls to the old functions throw an error explaining the new name.\nSymMatrixSpace has been renamed to SymmetricMatrixSpace","category":"page"},{"location":"release_notes/#Added-18","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added nonlinear_dual_start_value and set_nonlinear_dual_start_value\nAdded preliminary support for Complex coefficient types","category":"page"},{"location":"release_notes/#Fixed-25","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug in solution_summary","category":"page"},{"location":"release_notes/#Other-23","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"MILP examples have been migrated from GLPK to HiGHS\nFixed various typos\nImproved section on setting constraint start values","category":"page"},{"location":"release_notes/#Troubleshooting-problems-when-updating","page":"Release notes","title":"Troubleshooting problems when updating","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"If you experience problems when updating, you are likely using previously deprecated functionality. (By default, Julia does not warn when you use deprecated features.)","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"To find the deprecated features you are using, start Julia with --depwarn=yes:","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"$ julia --depwarn=yes","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Then install JuMP v0.22.3:","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"julia> using Pkg\njulia> pkg\"add JuMP@0.22.3\"","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"And then run your code. Apply any suggestions, or search the release notes below for advice on updating a specific deprecated feature.","category":"page"},{"location":"release_notes/#[Version-0.22.3](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.22.3)-(February-10,-2022)","page":"Release notes","title":"Version 0.22.3 (February 10, 2022)","text":"","category":"section"},{"location":"release_notes/#Fixed-26","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a reproducibility issue in the TSP tutorial\nFixed a reproducibility issue in the max_cut_sdp tutorial\nFixed a bug broadcasting an empty SparseAxisArray","category":"page"},{"location":"release_notes/#Other-24","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added a warning and improved documentation for the modify-then-query case\nFixed a typo in the docstring of RotatedSecondOrderCone\nAdded Aqua.jl as a check for code health\nAdded introductions to each section of the tutorials\nImproved the column generation and Benders decomposition tutorials\nUpdated documentation to MOI v0.10.8\nUpdated JuliaFormatter to v0.22.2","category":"page"},{"location":"release_notes/#[Version-0.22.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.22.2)-(January-10,-2022)","page":"Release notes","title":"Version 0.22.2 (January 10, 2022)","text":"","category":"section"},{"location":"release_notes/#Added-19","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The function all_nl_constraints now returns all nonlinear constraints in a model\nstart_value and set_start_value can now be used to get and set the primal start for constraint references\nPlural macros now return a tuple containing the elements that were defined instead of nothing\nAnonymous variables are now printed as _[i] where i is the index of the variable instead of noname. Calling name(x) still returns \"\" so this is non-breaking.","category":"page"},{"location":"release_notes/#Fixed-27","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed handling of min and max in nonlinear expressions\nCartesianIndex is no longer allowed as a key for DenseAxisArrays.","category":"page"},{"location":"release_notes/#Other-25","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improved the performance of GenericAffExpr\nAdded a tutorial on the Travelling Salesperson Problem\nAdded a tutorial on querying the Hessian of a nonlinear program\nAdded documentation on using custom solver binaries.","category":"page"},{"location":"release_notes/#[Version-0.22.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.22.1)-(November-29,-2021)","page":"Release notes","title":"Version 0.22.1 (November 29, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-20","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Export OptimizationSense enum, with instances: MIN_SENSE, MAX_SENSE, and FEASIBILITY_SENSE\nAdd Base.isempty(::Model) to match Base.empty(::Model)","category":"page"},{"location":"release_notes/#Fixed-28","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix bug in container with tuples as indices\nFix bug in set_time_limit_sec","category":"page"},{"location":"release_notes/#Other-26","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Add tutorial \"Design patterns for larger models\"\nRemove release notes section from PDF\nGeneral edits of the documentation and error messages","category":"page"},{"location":"release_notes/#[Version-0.22.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.22.0)-(November-10,-2021)","page":"Release notes","title":"Version 0.22.0 (November 10, 2021)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"JuMP v0.22 is a breaking release","category":"page"},{"location":"release_notes/#Breaking-5","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"JuMP 0.22 contains a number of breaking changes. However, these should be invisible for the majority of users. You will mostly encounter these breaking changes if you: wrote a JuMP extension, accessed backend(model), or called @SDconstraint.","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The breaking changes are as follows:","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"MathOptInterface has been updated to v0.10.4. For users who have interacted with the MOI backend, this contains a large number of breaking changes. Read the MathOptInterface release notes for more details.\nThe bridge_constraints keyword argument to Model and set_optimizer has been renamed add_bridges to reflect that more thing were bridged than just constraints.\nThe backend(model) field now contains a concrete instance of a MOI.Utilities.CachingOptimizer instead of one with an abstractly typed optimizer field. In most cases, this will lead to improved performance. However, calling set_optimizer after backend invalidates the old backend. For example:\nmodel = Model()\nb = backend(model)\nset_optimizer(model, GLPK.Optimizer)\n@variable(model, x)\n# b is not updated with `x`! Get a new b by calling `backend` again.\nnew_b = backend(model)\nAll usages of @SDconstraint are deprecated. The new syntax is @constraint(model, X >= Y, PSDCone()).\nCreating a DenseAxisArray with a Number as an axis will now display a warning. This catches a common error in which users write @variable(model, x[length(S)]) instead of @variable(model, x[1:length(S)]).\nThe caching_mode argument to Model, for example, Model(caching_mode = MOIU.MANUAL) mode has been removed. For more control over the optimizer, use direct_model instead.\nThe previously deprecated lp_objective_perturbation_range and lp_rhs_perturbation_range functions have been removed. Use lp_sensitivity_report instead.\nThe .m fields of NonlinearExpression and NonlinearParameter have been renamed to .model.\nInfinite variable bounds are now ignored. Thus, @variable(model, x <= Inf) will show has_upper_bound(x) == false. Previously, these bounds were passed through to the solvers which caused numerical issues for solvers expecting finite bounds.\nThe variable_type and constraint_type functions were removed. This should only affect users who previously wrote JuMP extensions. The functions can be deleted without consequence.\nThe internal functions moi_mode, moi_bridge_constraints, moi_add_constraint, and moi_add_to_function_constant are no longer exported.\nThe un-used method Containers.generate_container has been deleted.\nThe Containers API has been refactored, and _build_ref_sets is now public as Containers.build_ref_sets.\nThe parse_constraint_ methods for extending @constraint at parse time have been refactored in a breaking way. Consult the Extensions documentation for more details and examples.","category":"page"},{"location":"release_notes/#Added-21","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The TerminationStatusCode and ResultStatusCode enums are now exported by JuMP. Prefer termination_status(model) == OPTIMAL instead of == MOI.OPTIMAL, although the MOI. prefix way still works.\nCopy a x::DenseAxisArray to an Array by calling Array(x).\nNonlinearExpression is now a subtype of AbstractJuMPScalar\nConstraints such as @constraint(model, x + 1 in MOI.Integer()) are now supported.\nprimal_feasibility_report now accepts a function as the first argument.\nScalar variables @variable(model, x[1:2] in MOI.Integer()) creates two variables, both of which are constrained to be in the set MOI.Integer.\nConic constraints can now be specified as inequalities under a different partial ordering. So @constraint(model, x - y in MOI.Nonnegatives()) can now be written as @constraint(model, x >= y, MOI.Nonnegatives()).\nNames are now set for vectorized constraints.","category":"page"},{"location":"release_notes/#Fixed-29","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a performance issue when show was called on a SparseAxisArray with a large number of elements.\nFixed a bug displaying barrier and simplex iterations in solution_summary.\nFixed a bug by implementing hash for DenseAxisArray and SparseAxisArray.\nNames are now only set if the solver supports them. Previously, this prevented solvers such as Ipopt from being used with direct_model.\nMutableArithmetics.Zero is converted into a 0.0 before being returned to the user. Previously, some calls to @expression would return the undocumented MutableArithmetics.Zero() object. One example is summing over an empty set @expression(model, sum(x[i] for i in 1:0)). You will now get 0.0 instead.\nAffExpr and QuadExpr can now be used with == 0 instead of iszero. This fixes a number of issues relating to Julia standard libraries such as LinearAlgebra and SparseArrays.\nFixed a bug when registering a user-defined function with splatting.","category":"page"},{"location":"release_notes/#Other-27","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The documentation is now available as a PDF.\nThe documentation now includes a full copy of the MathOptInterface documentation to make it easy to link concepts between the docs. (The MathOptInterface documentation has also been significantly improved.)\nThe documentation contains a large number of improvements and clarifications on a range of topics. Thanks to @sshin23, @DilumAluthge, and @jlwether.\nThe documentation is now built with Julia 1.6 instead of 1.0.\nVarious error messages have been improved to be more readable.","category":"page"},{"location":"release_notes/#[Version-0.21.10](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.10)-(September-4,-2021)","page":"Release notes","title":"Version 0.21.10 (September 4, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-22","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added add_NL_expression\nadd_NL_xxx functions now support AffExpr and QuadExpr as terms","category":"page"},{"location":"release_notes/#Fixed-30","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug in solution_summary\nFixed a bug in relax_integrality","category":"page"},{"location":"release_notes/#Other-28","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improved error message in lp_sensitivity_report","category":"page"},{"location":"release_notes/#[Version-0.21.9](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.9)-(August-1,-2021)","page":"Release notes","title":"Version 0.21.9 (August 1, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-23","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Containers now support arbitrary container types by passing the type to the container keyword and overloading Containers.container.\nis_valid now supports nonlinear constraints\nAdded unsafe_backend for querying the inner-most optimizer of a JuMP model.\nNonlinear parameters now support the plural @NLparameters macro.\nContainers (for example, DenseAxisArray) can now be used in vector-valued constraints.","category":"page"},{"location":"release_notes/#Other-29","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Various improvements to the documentation.","category":"page"},{"location":"release_notes/#[Version-0.21.8](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.8)-(May-8,-2021)","page":"Release notes","title":"Version 0.21.8 (May 8, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-24","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The @constraint macro is now extendable in the same way as @variable.\nAffExpr and QuadExpr can now be used in nonlinear macros.","category":"page"},{"location":"release_notes/#Fixed-31","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug in lp_sensitivity_report.\nFixed an inference issue when creating empty SparseAxisArrays.","category":"page"},{"location":"release_notes/#[Version-0.21.7](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.7)-(April-12,-2021)","page":"Release notes","title":"Version 0.21.7 (April 12, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-25","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added primal_feasibility_report, which can be used to check whether a primal point satisfies primal feasibility.\nAdded coefficient, which returns the coefficient associated with a variable in affine and quadratic expressions.\nAdded copy_conflict, which returns the IIS of an infeasible model.\nAdded solution_summary, which returns (and prints) a struct containing a summary of the solution.\nAllow AbstractVector in vector constraints instead of just Vector.\nAdded latex_formulation(model) which returns an object representing the latex formulation of a model. Use print(latex_formulation(model)) to print the formulation as a string.\nUser-defined functions in nonlinear expressions are now automatically registered to aid quick model prototyping. However, a warning is printed to encourage the manual registration.\nDenseAxisArray's now support broadcasting over multiple arrays.\nContainer indices can now be iterators of Base.SizeUnknown.","category":"page"},{"location":"release_notes/#Fixed-32","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed bug in rad2deg and deg2rad in nonlinear expressions.\nFixed a MethodError bug in Containers when forcing container type.\nAllow partial slicing of a DenseAxisArray, resolving an issue from 2014.\nFixed a bug printing variable names in IJulia.\nEnding an IJulia cell with model now prints a summary of the model (like in the REPL) not the latex formulation. Use print(model) to print the latex formulation.\nFixed a bug when copying models containing nested arrays.","category":"page"},{"location":"release_notes/#Other-30","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Tutorials are now part of the documentation, and more refactoring has taken place.\nAdded JuliaFormatter added as a code formatter.\nAdded some precompilation statements to reduce initial latency.\nVarious improvements to error messages to make them more helpful.\nImproved performance of value(::NonlinearExpression).\nImproved performance of fix(::VariableRef).","category":"page"},{"location":"release_notes/#[Version-0.21.6](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.6)-(January-29,-2021)","page":"Release notes","title":"Version 0.21.6 (January 29, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-26","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added support for skew symmetric variables via @variable(model, X[1:2, 1:2] in SkewSymmetricMatrixSpace()).\nlp_sensitivity_report has been added which significantly improves the performance of querying the sensitivity summary of an LP. lp_objective_perturbation_range and lp_rhs_perturbation_range are deprecated.\nDual warm-starts are now supported with set_dual_start_value and dual_start_value.\n∈ (\\in) can now be used in macros instead of = or in.\nUse haskey(model::Model, key::Symbol) to check if a name key is registered in a model.\nAdded unregister(model::Model, key::Symbol) to unregister a name key from model.\nAdded callback_node_status for use in callbacks.\nAdded print_bridge_graph to visualize the bridging graph generated by MathOptInterface.\nImproved error message for containers with duplicate indices.","category":"page"},{"location":"release_notes/#Fixed-33","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Various fixes to pass tests on Julia 1.6.\nFixed a bug in the printing of nonlinear expressions in IJulia.\nFixed a bug when nonlinear expressions are passed to user-defined functions.\nSome internal functions that were previously exported are now no longer exported.\nFixed a bug when relaxing a fixed binary variable.\nFixed a StackOverflowError that occurred when SparseAxisArrays had a large number of elements.\nRemoved an unnecessary type assertion in list_of_constraint_types.\nFixed a bug when copying models with registered expressions.","category":"page"},{"location":"release_notes/#Other-31","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The documentation has been significantly overhauled. It now has distinct sections for the manual, API reference, and examples. The existing examples in /examples have now been moved to /docs/src/examples and rewritten using Literate.jl, and they are now included in the documentation.\nJuliaFormatter has been applied to most of the codebase. This will continue to roll out over time, as we fix upstream issues in the formatter, and will eventually become compulsory.\nThe root cause of a large number of method invalidations has been resolved.\nWe switched continuous integration from Travis and Appveyor to GitHub Actions.","category":"page"},{"location":"release_notes/#[Version-0.21.5](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.5)-(September-18,-2020)","page":"Release notes","title":"Version 0.21.5 (September 18, 2020)","text":"","category":"section"},{"location":"release_notes/#Fixed-34","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed deprecation warnings\nThrow DimensionMismatch for incompatibly sized functions and sets\nUnify treatment of keys(x) on JuMP containers","category":"page"},{"location":"release_notes/#[Version-0.21.4](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.4)-(September-14,-2020)","page":"Release notes","title":"Version 0.21.4 (September 14, 2020)","text":"","category":"section"},{"location":"release_notes/#Added-27","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Add debug info when adding unsupported constraints\nAdd relax_integrality for solving continuous relaxation\nAllow querying constraint conflicts","category":"page"},{"location":"release_notes/#Fixed-35","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Dispatch on Real for MOI.submit\nImplement copy for CustomSet in tests\nDon't export private macros\nFix invalid assertion in nonlinear\nError if constraint has NaN right-hand side\nImprove speed of tests\nLots of work modularizing files in /test\nImprove line numbers in macro error messages\nPrint nonlinear subexpressions\nVarious documentation updates\nDependency updates:\nDatastructures 0.18\nMathOptFormat v0.5\nPrep for MathOptInterface 0.9.15","category":"page"},{"location":"release_notes/#[Version-0.21.3](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.3)-(June-18,-2020)","page":"Release notes","title":"Version 0.21.3 (June 18, 2020)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added Special Order Sets (SOS1 and SOS2) to JuMP with default weights to ease the creation of such constraints (#2212).\nAdded functions simplex_iterations, barrier_iterations and node_count (#2201).\nAdded function reduced_cost (#2205).\nImplemented callback_value for affine and quadratic expressions (#2231).\nSupport MutableArithmetics.Zero in objective and constraints (#2219).\nDocumentation improvements:\nMention tutorials in the docs (#2223).\nUpdate COIN-OR links (#2242).\nExplicit link to the documentation of MOI.FileFormats (#2253).\nTypo fixes (#2261).\nContainers improvements:\nFix Base.map for DenseAxisArray (#2235).\nThrow BoundsError if number of indices is incorrect for DenseAxisArray and SparseAxisArray (#2240).\nExtensibility improvements:\nImplement a set_objective method fallback that redirects to set_objective_sense and set_objective_function (#2247).\nAdd parse_constraint method with arbitrary number of arguments (#2051).\nAdd parse_constraint_expr and parse_constraint_head (#2228).","category":"page"},{"location":"release_notes/#[Version-0.21.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.2)-(April-2,-2020)","page":"Release notes","title":"Version 0.21.2 (April 2, 2020)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added relative_gap() to access MOI.RelativeGap() attribute (#2199).\nDocumentation fixes:\nAdded link to source for docstrings in the documentation (#2207).\nAdded docstring for @variables macro (#2216).\nTypo fixes (#2177, #2184, #2182).\nImplementation of methods for Base functions:\nImplemented Base.empty! for JuMP.Model (#2198).\nImplemented Base.conj for JuMP scalar types (#2209).","category":"page"},{"location":"release_notes/#Fixed-36","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed sum of expression with scalar product in macro (#2178).\nFixed writing of nonlinear models to MathOptFormat (#2181).\nFixed construction of empty SparseAxisArray (#2179).\nFixed constraint with zero function (#2188).","category":"page"},{"location":"release_notes/#[Version-0.21.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.1)-(Feb-18,-2020)","page":"Release notes","title":"Version 0.21.1 (Feb 18, 2020)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improved the clarity of the with_optimizer deprecation warning.","category":"page"},{"location":"release_notes/#[Version-0.21.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.21.0)-(Feb-16,-2020)","page":"Release notes","title":"Version 0.21.0 (Feb 16, 2020)","text":"","category":"section"},{"location":"release_notes/#Breaking-6","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Deprecated with_optimizer (#2090, #2084, #2141). You can replace with_optimizer by either nothing, optimizer_with_attributes or a closure:\nreplace with_optimizer(Ipopt.Optimizer) by Ipopt.Optimizer.\nreplace with_optimizer(Ipopt.Optimizer, max_cpu_time=60.0) by optimizer_with_attributes(Ipopt.Optimizer, \"max_cpu_time\" => 60.0).\nreplace with_optimizer(Gurobi.Optimizer, env) by () -> Gurobi.Optimizer(env).\nreplace with_optimizer(Gurobi.Optimizer, env, Presolve=0) by optimizer_with_attributes(() -> Gurobi.Optimizer(env), \"Presolve\" => 0).\nalternatively to optimizer_with_attributes, you can also set the attributes separately with set_optimizer_attribute.\nRenamed set_parameter and set_parameters to set_optimizer_attribute and set_optimizer_attributes (#2150).\nBroadcast should now be explicit inside macros. @SDconstraint(model, x >= 1) and @constraint(model, x + 1 in SecondOrderCone()) now throw an error instead of broadcasting 1 along the dimension of x (#2107).\n@SDconstraint(model, x >= 0) is now equivalent to @constraint(model, x in PSDCone()) instead of @constraint(model, (x .- 0) in PSDCone()) (#2107).\nThe macros now create the containers with map instead of for loops, as a consequence, containers created by @expression can now have any element type and containers of constraint references now have concrete element types when possible. This fixes a long-standing issue where @expression could only be used to generate a collection of linear expressions. Now it works for quadratic expressions as well (#2070).\nCalling deepcopy(::AbstractModel) now throws an error.\nThe constraint name is now printed in the model string (#2108).","category":"page"},{"location":"release_notes/#Added-28","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added support for solver-independent and solver-specific callbacks (#2101).\nAdded write_to_file and read_from_file, supported formats are CBF, LP, MathOptFormat, MPS and SDPA (#2114).\nAdded support for complementarity constraints (#2132).\nAdded support for indicator constraints (#2092).\nAdded support for querying multiple solutions with the result keyword (#2100).\nAdded support for constraining variables on creation (#2128).\nAdded method delete that deletes a vector of variables at once if it is supported by the underlying solver (#2135).\nThe arithmetic between JuMP expression has be refactored into the MutableArithmetics package (#2107).\nImproved error on complex values in NLP (#1978).\nAdded an example of column generation (#2010).","category":"page"},{"location":"release_notes/#Fixed-37","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Incorrect coefficients generated when using Symmetric variables (#2102)","category":"page"},{"location":"release_notes/#[Version-0.20.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.20.1)-(Oct-18,-2019)","page":"Release notes","title":"Version 0.20.1 (Oct 18, 2019)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Add sections on @variables and @constraints in the documentation (#2062).\nFixed product of sparse matrices for Julia v1.3 (#2063).\nAdded set_objective_coefficient to modify the coefficient of a linear term of the objective function (#2008).\nAdded set_time_limit_sec, unset_time_limit_sec and time_limit_sec to set and query the time limit for the solver in seconds (#2053).","category":"page"},{"location":"release_notes/#[Version-0.20.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.20.0)-(Aug-24,-2019)","page":"Release notes","title":"Version 0.20.0 (Aug 24, 2019)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation updates.\nNumerous bug fixes.\nBetter error messages (#1977, #1978, #1997, #2017).\nPerformance improvements (#1947, #2032).\nAdded LP sensitivity summary functions lp_objective_perturbation_range and lp_rhs_perturbation_range (#1917).\nAdded functions dual_objective_value, raw_status and set_parameter.\nAdded function set_objective_coefficient to modify the coefficient of a linear term of the objective (#2008).\nAdded functions set_normalized_rhs, normalized_rhs, and add_to_function_constant to modify and get the constant part of a constraint (#1935, #1960).\nAdded functions set_normalized_coefficient and normalized_coefficient to modify and get the coefficient of a linear term of a constraint (#1935, #1960).\nNumerous other improvements in MOI 0.9, see the NEWS.md file of MOI for more details.","category":"page"},{"location":"release_notes/#[Version-0.19.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.19.2)-(June-8,-2019)","page":"Release notes","title":"Version 0.19.2 (June 8, 2019)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a bug in derivatives that could arise in models with nested nonlinear subexpressions.","category":"page"},{"location":"release_notes/#[Version-0.19.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.19.1)-(May-12,-2019)","page":"Release notes","title":"Version 0.19.1 (May 12, 2019)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Usability and performance improvements.\nBug fixes.","category":"page"},{"location":"release_notes/#[Version-0.19.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.19.0)-(February-15,-2019)","page":"Release notes","title":"Version 0.19.0 (February 15, 2019)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"JuMP 0.19 contains significant breaking changes.","category":"page"},{"location":"release_notes/#Breaking-7","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"JuMP's abstraction layer for communicating with solvers changed from MathProgBase (MPB) to MathOptInterface (MOI). MOI addresses many longstanding design issues. (See @mlubin's slides from JuMP-dev 2018.) JuMP 0.19 is compatible only with solvers that have been updated for MOI. See the installation guide for a list of solvers that have and have not yet been updated.\nMost solvers have been renamed to PackageName.Optimizer. For example, GurobiSolver() is now Gurobi.Optimizer.\nSolvers are no longer added to a model via Model(solver = XXX(kwargs...)). Instead use Model(with_optimizer(XXX, kwargs...)). For example, Model(with_optimizer(Gurobi.Optimizer, OutputFlag=0)).\nJuMP containers (for example, the objects returned by @variable) have been redesigned. Containers.SparseAxisArray replaces JuMPDict, JuMPArray was rewritten (inspired by AxisArrays) and renamed Containers.DenseAxisArray, and you can now request a container type with the container= keyword to the macros. See the corresponding documentation for more details.\nThe statuses returned by solvers have changed. See the possible status values here. The MOI statuses are much richer than the MPB statuses and can be used to distinguish between previously indistinguishable cases (for example, did the solver have a feasible solution when it stopped because of the time limit?).\nStarting values are separate from result values. Use value to query the value of a variable in a solution. Use start_value and set_start_value to get and set an initial starting point provided to the solver. The solutions from previous solves are no longer automatically set as the starting points for the next solve.\nThe data structures for affine and quadratic expressions AffExpr and QuadExpr have changed. Internally, terms are stored in dictionaries instead of lists. Duplicate coefficients can no longer exist. Accessors and iteration methods have changed.\nJuMPNLPEvaluator no longer includes the linear and quadratic parts of the model in the evaluation calls. These are now handled separately to allow NLP solvers that support various types of constraints.\nJuMP solver-independent callbacks have been replaced by solver-specific callbacks. See your favorite solver for more details. (See the note below: No solver-specific callbacks are implemented yet.)\nThe norm() syntax is no longer recognized inside macros. Use the SecondOrderCone() set instead.\nJuMP no longer performs automatic transformation between special quadratic forms and second-order cone constraints. Support for these constraint classes depends on the solver.\nThe symbols :Min and :Max are no longer used as optimization senses. Instead, JuMP uses the OptimizationSense enum from MathOptInterface. @objective(model, Max, ...), @objective(model, Min, ...), @NLobjective(model, Max, ...), and @objective(model, Min, ...) remain valid, but @objective(m, :Max, ...) is no longer accepted.\nThe sign conventions for duals has changed in some cases for consistency with conic duality (see the documentation). The shadow_price helper method returns duals with signs that match conventional LP interpretations of dual values as sensitivities of the objective value to relaxations of constraints.\n@constraintref is no longer defined. Instead, create the appropriate container to hold constraint references manually. For example,\nconstraints = Dict() # Optionally, specify types for improved performance.\nfor i in 1:N\n constraints[i] = @constraint(model, ...)\nend\nThe lowerbound, upperbound, and basename keyword arguments to the @variable macro have been renamed to lower_bound, upper_bound, and base_name, for consistency with JuMP's new style recommendations.\nWe rely on broadcasting syntax to apply accessors to collections of variables, for example, value.(x) instead of getvalue(x) for collections. (Use value(x) when x is a scalar object.)","category":"page"},{"location":"release_notes/#Added-29","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Splatting (like f(x...)) is recognized in restricted settings in nonlinear expressions.\nSupport for deleting constraints and variables.\nThe documentation has been completely rewritten using docstrings and Documenter.\nSupport for modeling mixed conic and quadratic models (for example, conic models with quadratic objectives and bi-linear matrix inequalities).\nSignificantly improved support for modeling new types of constraints and for extending JuMP's macros.\nSupport for providing dual warm starts.\nImproved support for accessing solver-specific attributes (for example, the irreducible inconsistent subsystem).\nExplicit control of whether symmetry-enforcing constraints are added to PSD constraints.\nSupport for modeling exponential cones.\nSignificant improvements in internal code quality and testing.\nStyle and naming guidelines.\nDirect mode and manual mode provide explicit control over when copies of a model are stored or regenerated. See the corresponding documentation.","category":"page"},{"location":"release_notes/#Regressions","page":"Release notes","title":"Regressions","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"There are known regressions from JuMP 0.18 that will be addressed in a future release (0.19.x or later):","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Performance regressions in model generation (issue). Please file an issue anyway if you notice a significant performance regression. We have plans to address a number of performance issues, but we might not be aware of all of them.\nFast incremental NLP solves are not yet reimplemented (issue).\nWe do not yet have an implementation of solver-specific callbacks.\nThe column generation syntax in @variable has been removed (that is, the objective, coefficients, and inconstraints keyword arguments). Support for column generation will be re-introduced in a future release.\nThe ability to solve the continuous relaxation (that is, via solve(model; relaxation = true)) is not yet reimplemented (issue).","category":"page"},{"location":"release_notes/#[Version-0.18.5](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.18.5)-(December-1,-2018)","page":"Release notes","title":"Version 0.18.5 (December 1, 2018)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Support views in some derivative evaluation functions.\nImproved compatibility with PackageCompiler.","category":"page"},{"location":"release_notes/#[Version-0.18.4](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.18.4)-(October-8,-2018)","page":"Release notes","title":"Version 0.18.4 (October 8, 2018)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a bug in model printing on Julia 0.7 and 1.0.","category":"page"},{"location":"release_notes/#[Version-0.18.3](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.18.3)-(October-1,-2018)","page":"Release notes","title":"Version 0.18.3 (October 1, 2018)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Add support for Julia v1.0 (Thanks @ExpandingMan)\nFix matrix expressions with quadratic functions (#1508)","category":"page"},{"location":"release_notes/#[Version-0.18.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.18.2)-(June-10,-2018)","page":"Release notes","title":"Version 0.18.2 (June 10, 2018)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a bug in second-order derivatives when expressions are present (#1319)\nFix a bug in @constraintref (#1330)","category":"page"},{"location":"release_notes/#[Version-0.18.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.18.1)-(April-9,-2018)","page":"Release notes","title":"Version 0.18.1 (April 9, 2018)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix for nested tuple destructuring (#1193)\nPreserve internal model when relaxation=true (#1209)\nMinor bug fixes and updates for example","category":"page"},{"location":"release_notes/#[Version-0.18.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.18.0)-(July-27,-2017)","page":"Release notes","title":"Version 0.18.0 (July 27, 2017)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Drop support for Julia 0.5.\nUpdate for ForwardDiff 0.5.\nMinor bug fixes.","category":"page"},{"location":"release_notes/#[Version-0.17.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.17.1)-(June-9,-2017)","page":"Release notes","title":"Version 0.17.1 (June 9, 2017)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Use of constructconstraint! in @SDconstraint.\nMinor bug fixes.","category":"page"},{"location":"release_notes/#[Version-0.17.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.17.0)-(May-27,-2017)","page":"Release notes","title":"Version 0.17.0 (May 27, 2017)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Breaking change: Mixing quadratic and conic constraints is no longer supported.\nBreaking change: The getvariable and getconstraint functions are replaced by indexing on the corresponding symbol. For instance, to access the variable with name x, one should now write m[:x] instead of getvariable(m, :x). As a consequence, creating a variable and constraint with the same name now triggers a warning, and accessing one of them afterwards throws an error. This change is breaking only in the latter case.\nAddition of the getobjectivebound function that mirrors the functionality of the MathProgBase getobjbound function except that it takes into account transformations performed by JuMP.\nMinor bug fixes.","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The following changes are primarily of interest to developers of JuMP extensions:","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The new syntax @constraint(model, expr in Cone) creates the constraint ensuring that expr is inside Cone. The Cone argument is passed to constructconstraint! which enables the call to the dispatched to an extension.\nThe @variable macro now calls constructvariable! instead of directly calling the Variable constructor. Extra arguments and keyword arguments passed to @variable are passed to constructvariable! which enables the call to be dispatched to an extension.\nRefactor the internal function conicdata (used build the MathProgBase conic model) into smaller sub-functions to make these parts reusable by extensions.","category":"page"},{"location":"release_notes/#[Version-0.16.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.16.2)-(March-28,-2017)","page":"Release notes","title":"Version 0.16.2 (March 28, 2017)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Minor bug fixes and printing tweaks\nAddress deprecation warnings for Julia 0.6","category":"page"},{"location":"release_notes/#[Version-0.16.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.16.1)-(March-7,-2017)","page":"Release notes","title":"Version 0.16.1 (March 7, 2017)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Better support for AbstractArray in JuMP (Thanks @tkoolen)\nMinor bug fixes","category":"page"},{"location":"release_notes/#[Version-0.16.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.16.0)-(February-23,-2017)","page":"Release notes","title":"Version 0.16.0 (February 23, 2017)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Breaking change: JuMP no longer has a mechanism for selecting solvers by default (the previous mechanism was flawed and incompatible with Julia 0.6). Not specifying a solver before calling solve() will result in an error.\nBreaking change: User-defined functions are no longer global. The first argument to JuMP.register is now a JuMP Model object within whose scope the function will be registered. Calling JuMP.register without a Model now produces an error.\nBreaking change: Use the new JuMP.fix method to fix a variable to a value or to update the value to which a variable is fixed. Calling setvalue on a fixed variable now results in an error in order to avoid silent behavior changes. (Thanks @joaquimg)\nNonlinear expressions now print out similarly to linear/quadratic expressions (useful for debugging!)\nNew category keyword to @variable. Used for specifying categories of anonymous variables.\nCompatibility with Julia 0.6-dev.\nMinor fixes and improvements (Thanks @cossio, @ccoffrin, @blegat)","category":"page"},{"location":"release_notes/#[Version-0.15.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.15.1)-(January-31,-2017)","page":"Release notes","title":"Version 0.15.1 (January 31, 2017)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Bugfix for @LinearConstraints and friends","category":"page"},{"location":"release_notes/#[Version-0.15.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.15.0)-(December-22,-2016)","page":"Release notes","title":"Version 0.15.0 (December 22, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Julia 0.5.0 is the minimum required version for this release.\nDocument support for BARON solver\nEnable info callbacks in more states than before, for example, for recording solutions. New when argument to addinfocallback (#814, thanks @yeesian)\nImproved support for anonymous variables. This includes new warnings for potentially confusing use of the traditional non-anonymous syntax:\nWhen multiple variables in a model are given the same name\nWhen non-symbols are used as names, for example, @variable(m, x[1][1:N])\nImprovements in iterating over JuMP containers (#836, thanks @IssamT)\nSupport for writing variable names in .lp file output (Thanks @leethargo)\nSupport for querying duals to SDP problems (Thanks @blegat)\nThe comprehension syntax with curly braces sum{}, prod{}, and norm2{} has been deprecated in favor of Julia's native comprehension syntax sum(), prod() and norm() as previously announced. (For early adopters of the new syntax, norm2() was renamed to norm() without deprecation.)\nUnit tests rewritten to use Base.Test instead of FactCheck\nImproved support for operations with matrices of JuMP types (Thanks @ExpandingMan)\nThe syntax to halt a solver from inside a callback has changed from throw(CallbackAbort()) to return JuMP.StopTheSolver\nMinor bug fixes","category":"page"},{"location":"release_notes/#[Version-0.14.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.14.2)-(December-12,-2016)","page":"Release notes","title":"Version 0.14.2 (December 12, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Allow singleton anonymous variables (includes bugfix)","category":"page"},{"location":"release_notes/#[Version-0.14.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.14.1)-(September-12,-2016)","page":"Release notes","title":"Version 0.14.1 (September 12, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"More consistent handling of states in informational callbacks, includes a new when parameter to addinfocallback for specifying in which state an informational callback should be called.","category":"page"},{"location":"release_notes/#[Version-0.14.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.14.0)-(August-7,-2016)","page":"Release notes","title":"Version 0.14.0 (August 7, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Compatibility with Julia 0.5 and ForwardDiff 0.2\nSupport for \"anonymous\" variables, constraints, expressions, and parameters, for example, x = @variable(m, [1:N]) instead of @variable(m, x[1:N])\nSupport for retrieving constraints from a model by name via getconstraint\n@NLconstraint now returns constraint references (as expected).\nSupport for vectorized expressions within lazy constraints\nOn Julia 0.5, parse new comprehension syntax sum(x[i] for i in 1:N if isodd(i)) instead of sum{ x[i], i in 1:N; isodd(i) }. The old syntax with curly braces will be deprecated in JuMP 0.15.\nNow possible to provide nonlinear expressions as \"raw\" Julia Expr objects instead of using JuMP's nonlinear macros. This input format is useful for programmatically generated expressions.\ns/Mathematical Programming/Mathematical Optimization/\nSupport for local cuts (Thanks to @madanim, Mehdi Madani)\nDocument Xpress interface developed by @joaquimg, Joaquim Dias Garcia\nMinor bug and deprecation fixes (Thanks @odow, @jrevels)","category":"page"},{"location":"release_notes/#[Version-0.13.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.13.2)-(May-16,-2016)","page":"Release notes","title":"Version 0.13.2 (May 16, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Compatibility update for MathProgBase","category":"page"},{"location":"release_notes/#[Version-0.13.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.13.1)-(May-3,-2016)","page":"Release notes","title":"Version 0.13.1 (May 3, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix broken deprecation for registerNLfunction.","category":"page"},{"location":"release_notes/#[Version-0.13.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.13.0)-(April-29,-2016)","page":"Release notes","title":"Version 0.13.0 (April 29, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Most exported methods and macros have been renamed to avoid camelCase. See the list of changes here. There is a 1-1 mapping from the old names to the new, and it is safe to simply replace the names to update existing models.\nSpecify variable lower/upper bounds in @variable using the lowerbound and upperbound keyword arguments.\nChange name printed for variable using the basename keyword argument to @variable.\nNew @variables macro allows multi-line declaration of groups of variables.\nA number of solver methods previously available only through MathProgBase are now exposed directly in JuMP. The fix was recorded live.\nCompatibility fixes with Julia 0.5.\nThe \"end\" indexing syntax is no longer supported within JuMPArrays which do not use 1-based indexing until upstream issues are resolved, see here.","category":"page"},{"location":"release_notes/#[Version-0.12.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.12.2)-(March-9,-2016)","page":"Release notes","title":"Version 0.12.2 (March 9, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Small fixes for nonlinear optimization","category":"page"},{"location":"release_notes/#[Version-0.12.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.12.1)-(March-1,-2016)","page":"Release notes","title":"Version 0.12.1 (March 1, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a regression in slicing for JuMPArrays (when not using 1-based indexing)","category":"page"},{"location":"release_notes/#[Version-0.12.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.12.0)-(February-27,-2016)","page":"Release notes","title":"Version 0.12.0 (February 27, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The automatic differentiation functionality has been completely rewritten with a number of user-facing changes:\n@defExpr and @defNLExpr now take the model as the first argument. The previous one-argument version of @defExpr is deprecated; all expressions should be named. For example, replace @defExpr(2x+y) with @defExpr(jump_model, my_expr, 2x+y).\nJuMP no longer uses Julia's variable binding rules for efficiently re-solving a sequence of nonlinear models. Instead, we have introduced nonlinear parameters. This is a breaking change, so we have added a warning message when we detect models that may depend on the old behavior.\nSupport for user-defined functions integrated within nonlinear JuMP expressions.\nReplaced iteration over AffExpr with Number-like scalar iteration; previous iteration behavior is now available via linearterms(::AffExpr).\nStopping the solver via throw(CallbackAbort()) from a callback no longer triggers an exception. Instead, solve() returns UserLimit status.\ngetDual() now works for conic problems (Thanks @emreyamangil.)","category":"page"},{"location":"release_notes/#[Version-0.11.3](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.11.3)-(February-4,-2016)","page":"Release notes","title":"Version 0.11.3 (February 4, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Bug-fix for problems with quadratic objectives and semidefinite constraints","category":"page"},{"location":"release_notes/#[Version-0.11.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.11.2)-(January-14,-2016)","page":"Release notes","title":"Version 0.11.2 (January 14, 2016)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Compatibility update for Mosek","category":"page"},{"location":"release_notes/#[Version-0.11.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.11.1)-(December-1,-2015)","page":"Release notes","title":"Version 0.11.1 (December 1, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Remove usage of @compat in tests.\nFix updating quadratic objectives for nonlinear models.","category":"page"},{"location":"release_notes/#[Version-0.11.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.11.0)-(November-30,-2015)","page":"Release notes","title":"Version 0.11.0 (November 30, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Julia 0.4.0 is the minimum required version for this release.\nFix for scoping semantics of index variables in sum{}. Index variables no longer leak into the surrounding scope.\nAddition of the solve(m::Model, relaxation=true) keyword argument to solve the standard continuous relaxation of model m\nThe getConstraintBounds() method allows access to the lower and upper bounds of all constraints in a (nonlinear) model.\nUpdate for breaking changes in MathProgBase","category":"page"},{"location":"release_notes/#[Version-0.10.3](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.10.3)-(November-20,-2015)","page":"Release notes","title":"Version 0.10.3 (November 20, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a rare error when parsing quadratic expressions\nFix Variable() constructor with default arguments\nDetect unrecognized keywords in solve()","category":"page"},{"location":"release_notes/#[Version-0.10.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.10.2)-(September-28,-2015)","page":"Release notes","title":"Version 0.10.2 (September 28, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix for deprecation warnings","category":"page"},{"location":"release_notes/#[Version-0.10.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.10.1)-(September-3,-2015)","page":"Release notes","title":"Version 0.10.1 (September 3, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixes for ambiguity warnings.\nFix for breaking change in precompilation syntax in Julia 0.4-pre","category":"page"},{"location":"release_notes/#[Version-0.10.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.10.0)-(August-31,-2015)","page":"Release notes","title":"Version 0.10.0 (August 31, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Support (on Julia 0.4 and later) for conditions in indexing @defVar and @addConstraint constructs, for example, @defVar(m, x[i=1:5,j=1:5; i+j >= 3])\nSupport for vectorized operations on Variables and expressions. See the documentation for details.\nNew getVar() method to access variables in a model by name\nSupport for semidefinite programming.\nDual solutions are now available for general nonlinear problems. You may call getDual on a reference object for a nonlinear constraint, and getDual on a variable object for Lagrange multipliers from active bounds.\nIntroduce warnings for two common performance traps: too many calls to getValue() on a collection of variables and use of the + operator in a loop to sum expressions.\nSecond-order cone constraints can be written directly with the norm() and norm2{} syntax.\nImplement MathProgBase interface for querying Hessian-vector products.\nIteration over JuMPContainers is deprecated; instead, use the keys and values functions, and zip(keys(d),values(d)) for the old behavior.\n@defVar returns Array{Variable,N} when each of N index sets are of the form 1:nᵢ.\nModule precompilation: on Julia 0.4 and later, using JuMP is now much faster.","category":"page"},{"location":"release_notes/#[Version-0.9.3](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.9.3)-(August-11,-2015)","page":"Release notes","title":"Version 0.9.3 (August 11, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixes for FactCheck testing on julia v0.4.","category":"page"},{"location":"release_notes/#[Version-0.9.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.9.2)-(June-27,-2015)","page":"Release notes","title":"Version 0.9.2 (June 27, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix bug in @addConstraints.","category":"page"},{"location":"release_notes/#[Version-0.9.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.9.1)-(April-25,-2015)","page":"Release notes","title":"Version 0.9.1 (April 25, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix for Julia 0.4-dev.\nSmall infrastructure improvements for extensions.","category":"page"},{"location":"release_notes/#[Version-0.9.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.9.0)-(April-18,-2015)","page":"Release notes","title":"Version 0.9.0 (April 18, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Comparison operators for constructing constraints (for example, 2x >= 1) have been deprecated. Instead, construct the constraints explicitly in the @addConstraint macro to add them to the model, or in the @LinearConstraint macro to create a stand-alone linear constraint instance.\ngetValue() method implemented to compute the value of a nonlinear subexpression\nJuMP is now released under the Mozilla Public License version 2.0 (was previously LGPL). MPL is a copyleft license which is less restrictive than LGPL, especially for embedding JuMP within other applications.\nA number of performance improvements in ReverseDiffSparse for computing derivatives.\nMathProgBase.getsolvetime(m) now returns the solution time reported by the solver, if available. (Thanks @odow, Oscar Dowson)\nFormatting fix for LP format output. (Thanks @sbebo, Leonardo Taccari).","category":"page"},{"location":"release_notes/#[Version-0.8.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.8.0)-(February-17,-2015)","page":"Release notes","title":"Version 0.8.0 (February 17, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Nonlinear subexpressions now supported with the @defNLExpr macro.\nSCS supported for solving second-order conic problems.\nsetXXXCallback family deprecated in favor of addXXXCallback.\nMultiple callbacks of the same type can be registered.\nAdded support for informational callbacks via addInfoCallback.\nA CallbackAbort exception can be thrown from callback to safely exit optimization.","category":"page"},{"location":"release_notes/#[Version-0.7.4](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.7.4)-(February-4,-2015)","page":"Release notes","title":"Version 0.7.4 (February 4, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Reduced costs and linear constraint duals are now accessible when quadratic constraints are present.\nTwo-sided nonlinear constraints are supported.\nMethods for accessing the number of variables and constraints in a model are renamed.\nNew default procedure for setting initial values in nonlinear optimization: project zero onto the variable bounds.\nSmall bug fixes.","category":"page"},{"location":"release_notes/#[Version-0.7.3](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.7.3)-(January-14,-2015)","page":"Release notes","title":"Version 0.7.3 (January 14, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a method ambiguity conflict with Compose.jl (cosmetic fix)","category":"page"},{"location":"release_notes/#[Version-0.7.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.7.2)-(January-9,-2015)","page":"Release notes","title":"Version 0.7.2 (January 9, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a bug in sum(::JuMPDict)\nAdded the setCategory function to change a variables category (for example, continuous or binary)","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"after construction, and getCategory to retrieve the variable category.","category":"page"},{"location":"release_notes/#[Version-0.7.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.7.1)-(January-2,-2015)","page":"Release notes","title":"Version 0.7.1 (January 2, 2015)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a bug in parsing linear expressions in macros. Affects only Julia 0.4 and later.","category":"page"},{"location":"release_notes/#[Version-0.7.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.7.0)-(December-29,-2014)","page":"Release notes","title":"Version 0.7.0 (December 29, 2014)","text":"","category":"section"},{"location":"release_notes/#Linear/quadratic/conic-programming","page":"Release notes","title":"Linear/quadratic/conic programming","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Breaking change: The syntax for column-wise model generation has been changed to use keyword arguments in @defVar.\nOn Julia 0.4 and later, variables and coefficients may be multiplied in any order within macros. That is, variable*coefficient is now valid syntax.\nECOS supported for solving second-order conic problems.","category":"page"},{"location":"release_notes/#_nonlinear_programming_release_notes","page":"Release notes","title":"Nonlinear programming","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Support for skipping model generation when solving a sequence of nonlinear models with changing data.\nFix a memory leak when solving a sequence of nonlinear models.\nThe @addNLConstraint macro now supports the three-argument version to define sets of nonlinear constraints.\nKNITRO supported as a nonlinear solver.\nSpeed improvements for model generation.\nThe @addNLConstraints macro supports adding multiple (groups of) constraints at once. Syntax is similar to @addConstraints.\nDiscrete variables allowed in nonlinear problems for solvers which support them (currently only KNITRO).","category":"page"},{"location":"release_notes/#General","page":"Release notes","title":"General","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Starting values for variables may now be specified with @defVar(m, x, start=value).\nThe setSolver function allows users to change the solver subsequent to model creation.\nSupport for \"fixed\" variables via the @defVar(m, x == 1) syntax.\nUnit tests rewritten to use FactCheck.jl, improved testing across solvers.","category":"page"},{"location":"release_notes/#[Version-0.6.3](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.6.3)-(October-19,-2014)","page":"Release notes","title":"Version 0.6.3 (October 19, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a bug in multiplying two AffExpr objects.","category":"page"},{"location":"release_notes/#[Version-0.6.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.6.2)-(October-11,-2014)","page":"Release notes","title":"Version 0.6.2 (October 11, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Further improvements and bug fixes for printing.\nFixed a bug in @defExpr.\nSupport for accessing expression graphs through the MathProgBase NLP interface.","category":"page"},{"location":"release_notes/#[Version-0.6.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.6.1)-(September-19,-2014)","page":"Release notes","title":"Version 0.6.1 (September 19, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improvements and bug fixes for printing.","category":"page"},{"location":"release_notes/#[Version-0.6.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.6.0)-(September-9,-2014)","page":"Release notes","title":"Version 0.6.0 (September 9, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Julia 0.3.0 is the minimum required version for this release.\nbuildInternalModel(m::Model) added to build solver-level model in memory without optimizing.\nDeprecate load_model_only keyword argument to solve.\nAdd groups of constraints with @addConstraints macro.\nUnicode operators now supported, including ∑ for sum, ∏ for prod, and ≤/≥\nQuadratic constraints supported in @addConstraint macro.\nQuadratic objectives supported in @setObjective macro.\nMathProgBase solver-independent interface replaces Ipopt-specific interface for nonlinear problems\nBreaking change: IpoptOptions no longer supported to specify solver options, use m = Model(solver=IpoptSolver(options...)) instead.\nNew solver interfaces: ECOS, NLopt, and nonlinear support for MOSEK\nNew option to control whether the lazy constraint callback is executed at each node in the B&B tree or just when feasible solutions are found\nAdd support for semicontinuous and semi-integer variables for those solvers that support them.\nAdd support for index dependencies (for example, triangular indexing) in @defVar, @addConstraint, and @defExpr (for example, @defVar(m, x[i=1:10,j=i:10])).\nThis required some changes to the internal structure of JuMP containers, which may break code that explicitly stored JuMPDict objects.","category":"page"},{"location":"release_notes/#[Version-0.5.8](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.5.8)-(September-24,-2014)","page":"Release notes","title":"Version 0.5.8 (September 24, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a bug with specifying solvers (affects Julia 0.2 only)","category":"page"},{"location":"release_notes/#[Version-0.5.7](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.5.7)-(September-5,-2014)","page":"Release notes","title":"Version 0.5.7 (September 5, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a bug in printing models","category":"page"},{"location":"release_notes/#[Version-0.5.6](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.5.6)-(September-2,-2014)","page":"Release notes","title":"Version 0.5.6 (September 2, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Add support for semicontinuous and semi-integer variables for those solvers that support them.\nBreaking change: Syntax for Variable() constructor has changed (use of this interface remains discouraged)\nUpdate for breaking changes in MathProgBase","category":"page"},{"location":"release_notes/#[Version-0.5.5](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.5.5)-(July-6,-2014)","page":"Release notes","title":"Version 0.5.5 (July 6, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix bug with problem modification: adding variables that did not appear in existing constraints or objective.","category":"page"},{"location":"release_notes/#[Version-0.5.4](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.5.4)-(June-19,-2014)","page":"Release notes","title":"Version 0.5.4 (June 19, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Update for breaking change in MathProgBase which reduces loading times for using JuMP\nFix error when MIPs not solved to optimality","category":"page"},{"location":"release_notes/#[Version-0.5.3](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.5.3)-(May-21,-2014)","page":"Release notes","title":"Version 0.5.3 (May 21, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Update for breaking change in ReverseDiffSparse","category":"page"},{"location":"release_notes/#[Version-0.5.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.5.2)-(May-9,-2014)","page":"Release notes","title":"Version 0.5.2 (May 9, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix compatibility with Julia 0.3 prerelease","category":"page"},{"location":"release_notes/#[Version-0.5.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.5.1)-(May-5,-2014)","page":"Release notes","title":"Version 0.5.1 (May 5, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix a bug in coefficient handling inside lazy constraints and user cuts","category":"page"},{"location":"release_notes/#[Version-0.5.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.5.0)-(May-2,-2014)","page":"Release notes","title":"Version 0.5.0 (May 2, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Support for nonlinear optimization with exact, sparse second-order derivatives automatically computed. Ipopt is currently the only solver supported.\ngetValue for AffExpr and QuadExpr\nBreaking change: getSolverModel replaced by getInternalModel, which returns the internal MathProgBase-level model\nGroups of constraints can be specified with @addConstraint (see documentation for details). This is not a breaking change.\ndot(::JuMPDict{Variable},::JuMPDict{Variable}) now returns the corresponding quadratic expression.","category":"page"},{"location":"release_notes/#[Version-0.4.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.4.1)-(March-24,-2014)","page":"Release notes","title":"Version 0.4.1 (March 24, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix bug where change in objective sense was ignored when re-solving a model.\nFix issue with handling zero coefficients in AffExpr.","category":"page"},{"location":"release_notes/#[Version-0.4.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.4.0)-(March-10,-2014)","page":"Release notes","title":"Version 0.4.0 (March 10, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Support for SOS1 and SOS2 constraints.\nSolver-independent callback for user heuristics.\ndot and sum implemented for JuMPDict objects. Now you can say @addConstraint(m, dot(a,x) <= b).\nDevelopers: support for extensions to JuMP. See definition of Model in src/JuMP.jl for more details.\nOption to construct the low-level model before optimizing.","category":"page"},{"location":"release_notes/#[Version-0.3.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.3.2)-(February-17,-2014)","page":"Release notes","title":"Version 0.3.2 (February 17, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improved model printing\nPreliminary support for IJulia output","category":"page"},{"location":"release_notes/#[Version-0.3.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.3.1)-(January-30,-2014)","page":"Release notes","title":"Version 0.3.1 (January 30, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation updates\nSupport for MOSEK\nCPLEXLink renamed to CPLEX","category":"page"},{"location":"release_notes/#[Version-0.3.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.3.0)-(January-21,-2014)","page":"Release notes","title":"Version 0.3.0 (January 21, 2014)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Unbounded/infeasibility rays: getValue() will return the corresponding components of an unbounded ray when a model is unbounded, if supported by the selected solver. getDual() will return an infeasibility ray (Farkas proof) if a model is infeasible and the selected solver supports this feature.\nSolver-independent callbacks for user generated cuts.\nUse new interface for solver-independent QCQP.\nsetlazycallback renamed to setLazyCallback for consistency.","category":"page"},{"location":"release_notes/#[Version-0.2.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.2.0)-(December-15,-2013)","page":"Release notes","title":"Version 0.2.0 (December 15, 2013)","text":"","category":"section"},{"location":"release_notes/#Breaking-8","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Objective sense is specified in setObjective instead of in the Model constructor.\nlpsolver and mipsolver merged into single solver option.","category":"page"},{"location":"release_notes/#Added-30","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Problem modification with efficient LP restarts and MIP warm-starts.\nRelatedly, column-wise modeling now supported.\nSolver-independent callbacks supported. Currently we support only a \"lazy constraint\" callback, which works with Gurobi, CPLEX, and GLPK. More callbacks coming soon.","category":"page"},{"location":"release_notes/#[Version-0.1.2](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.1.2)-(November-16,-2013)","page":"Release notes","title":"Version 0.1.2 (November 16, 2013)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Bug fixes for printing, improved error messages.\nAllow AffExpr to be used in macros; for example, ex = y + z; @addConstraint(m, x + 2*ex <= 3)","category":"page"},{"location":"release_notes/#[Version-0.1.1](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.1.1)-(October-23,-2013)","page":"Release notes","title":"Version 0.1.1 (October 23, 2013)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Update for solver specification API changes in MathProgBase.","category":"page"},{"location":"release_notes/#[Version-0.1.0](https://github.com/jump-dev/JuMP.jl/releases/tag/v0.1.0)-(October-3,-2013)","page":"Release notes","title":"Version 0.1.0 (October 3, 2013)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Initial public release.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"CurrentModule = JuMP\nDocTestSetup = quote\n using JuMP\n import HiGHS\nend\nDocTestFilters = [r\"≤|<=\", r\"≥|>=\", r\" == | = \", r\" ∈ | in \", r\"MathOptInterface|MOI\"]","category":"page"},{"location":"manual/constraints/#jump_constraints","page":"Constraints","title":"Constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"JuMP is based on the MathOptInterface (MOI) API. Because of this, JuMP uses the following standard form to represent problems:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"beginalign\n min_x in mathbbR^n f_0(x)\n \n textst f_i(x) in mathcalS_i i = 1 ldots m\nendalign","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Each constraint, f_i(x) in mathcalS_i, is composed of a function and a set. For example, instead of calling a^top x le b a less-than-or-equal-to constraint, we say that it is a scalar-affine-in-less-than constraint, where the function a^top x belongs to the less-than set (-infty b. We use the shorthand function-in-set to refer to constraints composed of different types of functions and sets.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"This page explains how to write various types of constraints in JuMP. For nonlinear constraints, see Nonlinear Modeling instead.","category":"page"},{"location":"manual/constraints/#Add-a-constraint","page":"Constraints","title":"Add a constraint","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Add a constraint to a JuMP model using the @constraint macro. The syntax to use depends on the type of constraint you wish to add.","category":"page"},{"location":"manual/constraints/#Add-a-linear-constraint","page":"Constraints","title":"Add a linear constraint","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Create linear constraints using the @constraint macro:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:3]);\n\njulia> @constraint(model, c1, sum(x) <= 1)\nc1 : x[1] + x[2] + x[3] ≤ 1\n\njulia> @constraint(model, c2, x[1] + 2 * x[3] >= 2)\nc2 : x[1] + 2 x[3] ≥ 2\n\njulia> @constraint(model, c3, sum(i * x[i] for i in 1:3) == 3)\nc3 : x[1] + 2 x[2] + 3 x[3] = 3\n\njulia> @constraint(model, c4, 4 <= 2 * x[2] <= 5)\nc4 : 2 x[2] ∈ [4, 5]","category":"page"},{"location":"manual/constraints/#Normalization","page":"Constraints","title":"Normalization","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"JuMP normalizes constraints by moving all of the terms containing variables to the left-hand side and all of the constant terms to the right-hand side. Thus, we get:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, c, 2x + 1 <= 4x + 4)\nc : -2 x ≤ 3","category":"page"},{"location":"manual/constraints/#quad_constraints","page":"Constraints","title":"Add a quadratic constraint","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"In addition to affine functions, JuMP also supports constraints with quadratic terms. For example:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[i=1:2])\n2-element Vector{VariableRef}:\n x[1]\n x[2]\n\njulia> @variable(model, t >= 0)\nt\n\njulia> @constraint(model, my_q, x[1]^2 + x[2]^2 <= t^2)\nmy_q : x[1]² + x[2]² - t² ≤ 0","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"tip: Tip\nBecause solvers can take advantage of the knowledge that a constraint is quadratic, prefer adding quadratic constraints using @constraint, rather than @NLconstraint.","category":"page"},{"location":"manual/constraints/#Vectorized-constraints","page":"Constraints","title":"Vectorized constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"You can also add constraints to JuMP using vectorized linear algebra. For example:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[i=1:2])\n2-element Vector{VariableRef}:\n x[1]\n x[2]\n\njulia> A = [1 2; 3 4]\n2×2 Matrix{Int64}:\n 1 2\n 3 4\n\njulia> b = [5, 6]\n2-element Vector{Int64}:\n 5\n 6\n\njulia> @constraint(model, con_vector, A * x == b)\ncon_vector : [x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Zeros(2)\n\njulia> @constraint(model, con_scalar, A * x .== b)\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:\n con_scalar : x[1] + 2 x[2] = 5\n con_scalar : 3 x[1] + 4 x[2] = 6","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The two constraints, == and .== are similar, but subtly different. The first creates a single constraint that is a MOI.VectorAffineFunction in MOI.Zeros constraint. The second creates a vector of MOI.ScalarAffineFunction in MOI.EqualTo constraints.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Which formulation to choose depends on the solver, and what you want to do with the constraint object con_vector or con_scalar.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"If you are using a conic solver, expect the dual of con_vector to be a Vector{Float64}, and do not intend to delete a row in the constraint, choose the == formulation.\nIf you are using a solver that expects a list of scalar constraints, for example HiGHS, or you wish to delete part of the constraint or access a single row of the constraint, for example, dual(con_scalar[2]), then use the broadcast .==.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"JuMP reformulates both constraints into the other form if needed by the solver, but choosing the right format for a particular solver is more efficient.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"You can also use <=, .<= , >=, and .>= as comparison operators in the constraint.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, A * x <= b)\n[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonpositives(2)\n\njulia> @constraint(model, A * x .<= b)\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n x[1] + 2 x[2] ≤ 5\n 3 x[1] + 4 x[2] ≤ 6\n\njulia> @constraint(model, A * x >= b)\n[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonnegatives(2)\n\njulia> @constraint(model, A * x .>= b)\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.GreaterThan{Float64}}, ScalarShape}}:\n x[1] + 2 x[2] ≥ 5\n 3 x[1] + 4 x[2] ≥ 6","category":"page"},{"location":"manual/constraints/#Vectorized-matrix-constraints","page":"Constraints","title":"Vectorized matrix constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"In most cases, you cannot use the non-broadcasting syntax for general matrices. For example:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, X[1:2, 1:2])\n2×2 Matrix{VariableRef}:\n X[1,1] X[1,2]\n X[2,1] X[2,2]\n\njulia> @constraint(model, X >= 0)\nERROR: At none:1: `@constraint(model, X >= 0)`: Unsupported matrix in vector-valued set. Did you mean to use the broadcasting syntax `.>=` instead? Alternatively, perhaps you are missing a set argument like `@constraint(model, X >= 0, PSDCone())` or `@constraint(model, X >= 0, HermmitianPSDCone())`.\nStacktrace:\n[...]","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Instead, to represent matrix inequalities you must always use the element-wise broadcasting .==, .>=, or .<=, or use the Set inequality syntax.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"There are two exceptions: if the result of the left-hand side minus the right-hand side is a LinearAlgebra.Symmetric matrix or a LinearAlgebra.Hermitian matrix, you may use the non-broadcasting equality syntax:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> using LinearAlgebra\n\njulia> model = Model();\n\njulia> @variable(model, X[1:2, 1:2], Symmetric)\n2×2 Symmetric{VariableRef, Matrix{VariableRef}}:\n X[1,1] X[1,2]\n X[1,2] X[2,2]\n\njulia> @constraint(model, X == LinearAlgebra.I)\n[X[1,1] - 1 X[1,2];\n X[1,2] X[2,2] - 1] ∈ Zeros()","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Despite the model showing the matrix in Zeros, this will add only three rows to the constraint matrix because the symmetric constraints are redundant. In contrast, the broadcasting syntax adds four linear constraints:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, X .== LinearAlgebra.I)\n2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:\n X[1,1] = 1 X[1,2] = 0\n X[1,2] = 0 X[2,2] = 1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The same holds for LinearAlgebra.Hermitian matrices:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> using LinearAlgebra\n\njulia> model = Model();\n\njulia> @variable(model, X[1:2, 1:2] in HermitianPSDCone())\n2×2 Hermitian{GenericAffExpr{ComplexF64, VariableRef}, Matrix{GenericAffExpr{ComplexF64, VariableRef}}}:\n real(X[1,1]) real(X[1,2]) + imag(X[1,2]) im\n real(X[1,2]) - imag(X[1,2]) im real(X[2,2])\n\njulia> @constraint(model, X == LinearAlgebra.I)\n[real(X[1,1]) - 1 real(X[1,2]) + imag(X[1,2]) im;\n real(X[1,2]) - imag(X[1,2]) im real(X[2,2]) - 1] ∈ Zeros()\n\njulia> @constraint(model, X .== LinearAlgebra.I)\n2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{ComplexF64}, MathOptInterface.EqualTo{ComplexF64}}, ScalarShape}}:\n real(X[1,1]) = 1 real(X[1,2]) + imag(X[1,2]) im = 0\n real(X[1,2]) - imag(X[1,2]) im = 0 real(X[2,2]) = 1","category":"page"},{"location":"manual/constraints/#Containers-of-constraints","page":"Constraints","title":"Containers of constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The @constraint macro supports creating collections of constraints. We'll cover some brief syntax here; read the Constraint containers section for more details:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Create arrays of constraints:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:3]);\n\njulia> @constraint(model, c[i=1:3], x[i] <= i^2)\n3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n c[1] : x[1] ≤ 1\n c[2] : x[2] ≤ 4\n c[3] : x[3] ≤ 9\n\njulia> c[2]\nc[2] : x[2] ≤ 4","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Sets can be any Julia type that supports iteration:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:3]);\n\njulia> @constraint(model, c[i=2:3, [\"red\", \"blue\"]], x[i] <= i^2)\n2-dimensional DenseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape},2,...} with index sets:\n Dimension 1, 2:3\n Dimension 2, [\"red\", \"blue\"]\nAnd data, a 2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n c[2,red] : x[2] ≤ 4 c[2,blue] : x[2] ≤ 4\n c[3,red] : x[3] ≤ 9 c[3,blue] : x[3] ≤ 9\n\njulia> c[2, \"red\"]\nc[2,red] : x[2] ≤ 4","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Sets can depend upon previous indices:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:3]);\n\njulia> @constraint(model, c[i=1:3, j=i:3], x[i] <= j)\nJuMP.Containers.SparseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}, 2, Tuple{Int64, Int64}} with 6 entries:\n [1, 1] = c[1,1] : x[1] ≤ 1\n [1, 2] = c[1,2] : x[1] ≤ 2\n [1, 3] = c[1,3] : x[1] ≤ 3\n [2, 2] = c[2,2] : x[2] ≤ 2\n [2, 3] = c[2,3] : x[2] ≤ 3\n [3, 3] = c[3,3] : x[3] ≤ 3","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"and you can filter elements in the sets using the ; syntax:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:9]);\n\njulia> @constraint(model, c[i=1:9; mod(i, 3) == 0], x[i] <= i)\nJuMP.Containers.SparseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}, 1, Tuple{Int64}} with 3 entries:\n [3] = c[3] : x[3] ≤ 3\n [6] = c[6] : x[6] ≤ 6\n [9] = c[9] : x[9] ≤ 9","category":"page"},{"location":"manual/constraints/#Registered-constraints","page":"Constraints","title":"Registered constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"When you create constraints, JuMP registers them inside the model using their corresponding symbol. Get a registered name using model[:key]:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model()\nA JuMP Model\nFeasibility problem with:\nVariables: 0\nModel mode: AUTOMATIC\nCachingOptimizer state: NO_OPTIMIZER\nSolver name: No optimizer attached.\n\njulia> @variable(model, x)\nx\n\njulia> @constraint(model, my_c, 2x <= 1)\nmy_c : 2 x ≤ 1\n\njulia> model\nA JuMP Model\nFeasibility problem with:\nVariable: 1\n`AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 1 constraint\nModel mode: AUTOMATIC\nCachingOptimizer state: NO_OPTIMIZER\nSolver name: No optimizer attached.\nNames registered in the model: my_c, x\n\njulia> model[:my_c] === my_c\ntrue","category":"page"},{"location":"manual/constraints/#Anonymous-constraints","page":"Constraints","title":"Anonymous constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"To reduce the likelihood of accidental bugs, and because JuMP registers constraints inside a model, creating two constraints with the same name is an error:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @constraint(model, c, 2x <= 1)\nc : 2 x ≤ 1\n\njulia> @constraint(model, c, 2x <= 1)\nERROR: An object of name c is already attached to this model. If this\n is intended, consider using the anonymous construction syntax, e.g.,\n `x = @variable(model, [1:N], ...)` where the name of the object does\n not appear inside the macro.\n\n Alternatively, use `unregister(model, :c)` to first unregister\n the existing name from the model. Note that this will not delete the\n object; it will just remove the reference at `model[:c]`.\n[...]","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"A common reason for encountering this error is adding constraints in a loop.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"As a work-around, JuMP provides anonymous constraints. Create an anonymous constraint by omitting the name argument:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> c = @constraint(model, 2x <= 1)\n2 x ≤ 1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Create a container of anonymous constraints by dropping the name in front of the [:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:3]);\n\njulia> c = @constraint(model, [i = 1:3], x[i] <= i)\n3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n x[1] ≤ 1\n x[2] ≤ 2\n x[3] ≤ 3","category":"page"},{"location":"manual/constraints/#Constraint-names","page":"Constraints","title":"Constraint names","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"In addition to the symbol that constraints are registered with, constraints have a String name that is used for printing and writing to file formats.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Get and set the name of a constraint using name(::JuMP.ConstraintRef) and set_name(::JuMP.ConstraintRef, ::String):","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model(); @variable(model, x);\n\njulia> @constraint(model, con, x <= 1)\ncon : x ≤ 1\n\njulia> name(con)\n\"con\"\n\njulia> set_name(con, \"my_con_name\")\n\njulia> con\nmy_con_name : x ≤ 1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Override the default choice of name using the base_name keyword:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model(); @variable(model, x);\n\njulia> con = @constraint(model, [i=1:2], x <= i, base_name = \"my_con\")\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n my_con[1] : x ≤ 1\n my_con[2] : x ≤ 2","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Note that names apply to each element of the container, not to the container of constraints:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> name(con[1])\n\"my_con[1]\"\n\njulia> set_name(con[1], \"c\")\n\njulia> con\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n c : x ≤ 1\n my_con[2] : x ≤ 2","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"tip: Tip\nFor some models, setting the string name of each constraint can take a non-trivial portion of the total time required to build the model. Turn off String names by passing set_string_name = false to @constraint:julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, con, x <= 2, set_string_name = false)\nx ≤ 2See Disable string names for more information.","category":"page"},{"location":"manual/constraints/#Retrieve-a-constraint-by-name","page":"Constraints","title":"Retrieve a constraint by name","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Retrieve a constraint from a model using constraint_by_name:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> constraint_by_name(model, \"c\")\nc : x ≤ 1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"If the name is not present, nothing will be returned:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> constraint_by_name(model, \"bad_name\")","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"You can only look up individual constraints using constraint_by_name. Something like this will not work:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model(); @variable(model, x);\n\njulia> con = @constraint(model, [i=1:2], x <= i, base_name = \"my_con\")\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n my_con[1] : x ≤ 1\n my_con[2] : x ≤ 2\n\njulia> constraint_by_name(model, \"my_con\")","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"To look up a collection of constraints, do not use constraint_by_name. Instead, register them using the model[:key] = value syntax:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model(); @variable(model, x);\n\njulia> model[:con] = @constraint(model, [i=1:2], x <= i, base_name = \"my_con\")\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n my_con[1] : x ≤ 1\n my_con[2] : x ≤ 2\n\njulia> model[:con]\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n my_con[1] : x ≤ 1\n my_con[2] : x ≤ 2","category":"page"},{"location":"manual/constraints/#String-names,-symbolic-names,-and-bindings","page":"Constraints","title":"String names, symbolic names, and bindings","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"It's common for new users to experience confusion relating to constraints. Part of the problem is the difference between the name that a constraint is registered under and the String name used for printing.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Here's a summary of the differences:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Constraints are created using @constraint.\nConstraints can be named or anonymous.\nNamed constraints have the form @constraint(model, c, expr). For named constraints:\nThe String name of the constraint is set to \"c\".\nA Julia variable c is created that binds c to the JuMP constraint.\nThe name :c is registered as a key in the model with the value c.\nAnonymous constraints have the form c = @constraint(model, expr). For anonymous constraints:\nThe String name of the constraint is set to \"\".\nYou control the name of the Julia variable used as the binding.\nNo name is registered as a key in the model.\nThe base_name keyword can override the String name of the constraint.\nYou can manually register names in the model via model[:key] = value.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Here's an example of the differences:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> c_binding = @constraint(model, 2x <= 1, base_name = \"c\")\nc : 2 x ≤ 1\n\njulia> model\nA JuMP Model\nFeasibility problem with:\nVariable: 1\n`AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 1 constraint\nModel mode: AUTOMATIC\nCachingOptimizer state: NO_OPTIMIZER\nSolver name: No optimizer attached.\nNames registered in the model: x\n\njulia> c\nERROR: UndefVarError: `c` not defined\n\njulia> c_binding\nc : 2 x ≤ 1\n\njulia> name(c_binding)\n\"c\"\n\njulia> model[:c_register] = c_binding\nc : 2 x ≤ 1\n\njulia> model\nA JuMP Model\nFeasibility problem with:\nVariable: 1\n`AffExpr`-in-`MathOptInterface.LessThan{Float64}`: 1 constraint\nModel mode: AUTOMATIC\nCachingOptimizer state: NO_OPTIMIZER\nSolver name: No optimizer attached.\nNames registered in the model: c_register, x\n\njulia> model[:c_register]\nc : 2 x ≤ 1\n\njulia> model[:c_register] === c_binding\ntrue\n\njulia> c\nERROR: UndefVarError: `c` not defined","category":"page"},{"location":"manual/constraints/#The-@constraints-macro","page":"Constraints","title":"The @constraints macro","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"If you have many @constraint calls, use the @constraints macro to improve readability:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraints(model, begin\n 2x <= 1\n c, x >= -1\n end)\n(2 x ≤ 1, c : x ≥ -1)\n\njulia> print(model)\nFeasibility\nSubject to\n c : x ≥ -1\n 2 x ≤ 1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The @constraints macro returns a tuple of the constraints that were defined.","category":"page"},{"location":"manual/constraints/#constraint_duality","page":"Constraints","title":"Duality","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"JuMP adopts the notion of conic duality from MathOptInterface. For linear programs, a feasible dual on a >= constraint is nonnegative and a feasible dual on a <= constraint is nonpositive. If the constraint is an equality constraint, it depends on which direction is binding.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"warning: Warning\nJuMP's definition of duality is independent of the objective sense. That is, the sign of feasible duals associated with a constraint depends on the direction of the constraint and not whether the problem is maximization or minimization. This is a different convention from linear programming duality in some common textbooks. If you have a linear program, and you want the textbook definition, you probably want to use shadow_price and reduced_cost instead.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The dual value associated with a constraint in the most recent solution can be accessed using the dual function. Use has_duals to check if the model has a dual solution available to query. For example:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model(HiGHS.Optimizer);\n\njulia> set_silent(model)\n\njulia> @variable(model, x)\nx\n\njulia> @constraint(model, con, x <= 1)\ncon : x ≤ 1\n\njulia> @objective(model, Min, -2x)\n-2 x\n\njulia> has_duals(model)\nfalse\n\njulia> optimize!(model)\n\njulia> has_duals(model)\ntrue\n\njulia> dual(con)\n-2.0\n\njulia> @objective(model, Max, 2x)\n2 x\n\njulia> optimize!(model)\n\njulia> dual(con)\n-2.0","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"To help users who may be less familiar with conic duality, JuMP provides shadow_price, which returns a value that can be interpreted as the improvement in the objective in response to an infinitesimal relaxation (on the scale of one unit) in the right-hand side of the constraint. shadow_price can be used only on linear constraints with a <=, >=, or == comparison operator.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"In the example above, dual(con) returned -2.0 regardless of the optimization sense. However, in the second case when the optimization sense is Max, shadow_price returns:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> shadow_price(con)\n2.0","category":"page"},{"location":"manual/constraints/#Duals-of-variable-bounds","page":"Constraints","title":"Duals of variable bounds","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"To query the dual variables associated with a variable bound, first obtain a constraint reference using one of UpperBoundRef, LowerBoundRef, or FixRef, and then call dual on the returned constraint reference. The reduced_cost function may simplify this process as it returns the shadow price of an active bound of a variable (or zero, if no active bound exists).","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model(HiGHS.Optimizer);\n\njulia> set_silent(model)\n\njulia> @variable(model, x <= 1)\nx\n\njulia> @objective(model, Min, -2x)\n-2 x\n\njulia> optimize!(model)\n\njulia> dual(UpperBoundRef(x))\n-2.0\n\njulia> reduced_cost(x)\n-2.0","category":"page"},{"location":"manual/constraints/#Modify-a-constant-term","page":"Constraints","title":"Modify a constant term","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"This section explains how to modify the constant term in a constraint. There are multiple ways to achieve this goal; we explain three options.","category":"page"},{"location":"manual/constraints/#Option-1:-change-the-right-hand-side","page":"Constraints","title":"Option 1: change the right-hand side","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Use set_normalized_rhs to modify the right-hand side (constant) term of a linear or quadratic constraint. Use normalized_rhs to query the right-hand side term.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, con, 2x <= 1)\ncon : 2 x ≤ 1\n\njulia> set_normalized_rhs(con, 3)\n\njulia> con\ncon : 2 x ≤ 3\n\njulia> normalized_rhs(con)\n3.0","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"warning: Warning\nset_normalized_rhs sets the right-hand side term of the normalized constraint. See Normalization for more details.","category":"page"},{"location":"manual/constraints/#Option-2:-use-fixed-variables","page":"Constraints","title":"Option 2: use fixed variables","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"If constraints are complicated, for example, they are composed of a number of components, each of which has a constant term, then it may be difficult to calculate what the right-hand side term is in the standard form.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"For this situation, JuMP includes the ability to fix variables to a value using the fix function. Fixing a variable sets its lower and upper bound to the same value. Thus, changes in a constant term can be simulated by adding a new variable and fixing it to different values. Here is an example:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @variable(model, const_term)\nconst_term\n\njulia> @constraint(model, con, 2x <= const_term + 1)\ncon : 2 x - const_term ≤ 1\n\njulia> fix(const_term, 1.0)","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The constraint con is now equivalent to 2x <= 2.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"warning: Warning\nFixed variables are not replaced with constants when communicating the problem to a solver. Therefore, even though const_term is fixed, it is still a decision variable, and so const_term * x is bilinear.","category":"page"},{"location":"manual/constraints/#Option-3:-modify-the-function's-constant-term","page":"Constraints","title":"Option 3: modify the function's constant term","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The third option is to use add_to_function_constant. The constant given is added to the function of a func-in-set constraint. In the following example, adding 2 to the function has the effect of removing 2 to the right-hand side:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, con, 2x <= 1)\ncon : 2 x ≤ 1\n\njulia> add_to_function_constant(con, 2)\n\njulia> con\ncon : 2 x ≤ -1\n\njulia> normalized_rhs(con)\n-1.0","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"In the case of interval constraints, the constant is removed from each bound:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, con, 0 <= 2x + 1 <= 2)\ncon : 2 x ∈ [-1, 1]\n\njulia> add_to_function_constant(con, 3)\n\njulia> con\ncon : 2 x ∈ [-4, -2]","category":"page"},{"location":"manual/constraints/#Modify-a-variable-coefficient","page":"Constraints","title":"Modify a variable coefficient","text":"","category":"section"},{"location":"manual/constraints/#Scalar-constraints","page":"Constraints","title":"Scalar constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"To modify the coefficients for a linear term (modifying the coefficient of a quadratic term is not supported) in a constraint, use set_normalized_coefficient. To query the current coefficient, use normalized_coefficient.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:2]);\n\njulia> @constraint(model, con, 2x[1] + x[2] <= 1)\ncon : 2 x[1] + x[2] ≤ 1\n\njulia> set_normalized_coefficient(con, x[2], 0)\n\njulia> con\ncon : 2 x[1] ≤ 1\n\njulia> normalized_coefficient(con, x[2])\n0.0","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"warning: Warning\nset_normalized_coefficient sets the coefficient of the normalized constraint. See Normalization for more details.","category":"page"},{"location":"manual/constraints/#Vector-constraints","page":"Constraints","title":"Vector constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"To modify the coefficients of a vector-valued constraint, use set_normalized_coefficients.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @constraint(model, con, [2x + 3x, 4x] in MOI.Nonnegatives(2))\ncon : [5 x, 4 x] ∈ MathOptInterface.Nonnegatives(2)\n\njulia> set_normalized_coefficients(con, x, [(1, 3.0)])\n\njulia> con\ncon : [3 x, 4 x] ∈ MathOptInterface.Nonnegatives(2)\n\njulia> set_normalized_coefficients(con, x, [(1, 2.0), (2, 5.0)])\n\njulia> con\ncon : [2 x, 5 x] ∈ MathOptInterface.Nonnegatives(2)","category":"page"},{"location":"manual/constraints/#Delete-a-constraint","page":"Constraints","title":"Delete a constraint","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Use delete to delete a constraint from a model. Use is_valid to check if a constraint belongs to a model and has not been deleted.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, con, 2x <= 1)\ncon : 2 x ≤ 1\n\njulia> is_valid(model, con)\ntrue\n\njulia> delete(model, con)\n\njulia> is_valid(model, con)\nfalse","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Deleting a constraint does not unregister the symbolic reference from the model. Therefore, creating a new constraint of the same name will throw an error:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, con, 2x <= 1)\nERROR: An object of name con is already attached to this model. If this\n is intended, consider using the anonymous construction syntax, e.g.,\n `x = @variable(model, [1:N], ...)` where the name of the object does\n not appear inside the macro.\n\n Alternatively, use `unregister(model, :con)` to first unregister\n the existing name from the model. Note that this will not delete the\n object; it will just remove the reference at `model[:con]`.\n[...]","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"After calling delete, call unregister to remove the symbolic reference:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> unregister(model, :con)\n\njulia> @constraint(model, con, 2x <= 1)\ncon : 2 x ≤ 1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"info: Info\ndelete does not automatically unregister because we do not distinguish between names that are automatically registered by JuMP macros, and names that are manually registered by the user by setting values in object_dictionary. In addition, deleting a constraint and then adding a new constraint of the same name is an easy way to introduce bugs into your code.","category":"page"},{"location":"manual/constraints/#Start-values","page":"Constraints","title":"Start values","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Provide a starting value (also called warmstart) for a constraint's primal and dual solutions using set_start_value and set_dual_start_value.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Query the starting value for a constraint's primal and dual solution using start_value and dual_start_value. If no start value has been set, the methods will return nothing.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @constraint(model, con, x >= 10)\ncon : x ≥ 10\n\njulia> start_value(con)\n\njulia> set_start_value(con, 10.0)\n\njulia> start_value(con)\n10.0\n\njulia> dual_start_value(con)\n\njulia> set_dual_start_value(con, 2)\n\njulia> dual_start_value(con)\n2.0","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Vector-valued constraints require a vector:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:3])\n3-element Vector{VariableRef}:\n x[1]\n x[2]\n x[3]\n\njulia> @constraint(model, con, x in SecondOrderCone())\ncon : [x[1], x[2], x[3]] in MathOptInterface.SecondOrderCone(3)\n\njulia> dual_start_value(con)\n\njulia> set_dual_start_value(con, [1.0, 2.0, 3.0])\n\njulia> dual_start_value(con)\n3-element Vector{Float64}:\n 1.0\n 2.0\n 3.0","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"tip: Tip\nTo simplify setting start values for all variables and constraints in a model, see set_start_values. The Primal and dual warm-starts tutorial also gives a detailed description of how to iterate over constraints in the model to set custom start values.","category":"page"},{"location":"manual/constraints/#Constraint-containers","page":"Constraints","title":"Constraint containers","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Like Variable containers, JuMP provides a mechanism for building groups of constraints compactly. References to these groups of constraints are returned in containers. Three types of constraint containers are supported: Arrays, DenseAxisArrays, and SparseAxisArrays. We explain each of these in the following.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"tip: Tip\nYou can read more about containers in the Containers section.","category":"page"},{"location":"manual/constraints/#constraint_arrays","page":"Constraints","title":"Arrays","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"One way of adding a group of constraints compactly is the following:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, con[i = 1:3], i * x <= i + 1)\n3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n con[1] : x ≤ 2\n con[2] : 2 x ≤ 3\n con[3] : 3 x ≤ 4","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"JuMP returns references to the three constraints in an Array that is bound to the Julia variable con. This array can be accessed and sliced as you would with any Julia array:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> con[1]\ncon[1] : x ≤ 2\n\njulia> con[2:3]\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n con[2] : 2 x ≤ 3\n con[3] : 3 x ≤ 4","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Anonymous containers can also be constructed by dropping the name (for example, con) before the square brackets:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> con = @constraint(model, [i = 1:2], i * x <= i + 1)\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n x ≤ 2\n 2 x ≤ 3","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Just like @variable, JuMP will form an Array of constraints when it can determine at parse time that the indices are one-based integer ranges. Therefore con[1:b] will create an Array, but con[a:b] will not. A special case is con[Base.OneTo(n)] which will produce an Array. If JuMP cannot determine that the indices are one-based integer ranges (for example, in the case of con[a:b]), JuMP will create a DenseAxisArray instead.","category":"page"},{"location":"manual/constraints/#DenseAxisArrays","page":"Constraints","title":"DenseAxisArrays","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The syntax for constructing a DenseAxisArray of constraints is very similar to the syntax for constructing a DenseAxisArray of variables.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, con[i = 1:2, j = 2:3], i * x <= j + 1)\n2-dimensional DenseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape},2,...} with index sets:\n Dimension 1, Base.OneTo(2)\n Dimension 2, 2:3\nAnd data, a 2×2 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:\n con[1,2] : x ≤ 3 con[1,3] : x ≤ 4\n con[2,2] : 2 x ≤ 3 con[2,3] : 2 x ≤ 4","category":"page"},{"location":"manual/constraints/#SparseAxisArrays","page":"Constraints","title":"SparseAxisArrays","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The syntax for constructing a SparseAxisArray of constraints is very similar to the syntax for constructing a SparseAxisArray of variables.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, con[i = 1:2, j = 1:2; i != j], i * x <= j + 1)\nJuMP.Containers.SparseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}, 2, Tuple{Int64, Int64}} with 2 entries:\n [1, 2] = con[1,2] : x ≤ 3\n [2, 1] = con[2,1] : 2 x ≤ 2","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"warning: Warning\nIf you have many index dimensions and a large amount of sparsity, read Performance considerations.","category":"page"},{"location":"manual/constraints/#Forcing-the-container-type","page":"Constraints","title":"Forcing the container type","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"When creating a container of constraints, JuMP will attempt to choose the tightest container type that can store the constraints. However, because this happens at parse time, it does not always make the best choice. Just like in @variable, you can force the type of container using the container keyword. For syntax and the reason behind this, take a look at the variable docs.","category":"page"},{"location":"manual/constraints/#Constraints-with-similar-indices","page":"Constraints","title":"Constraints with similar indices","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Containers are often used to create constraints over a set of indices. However, you'll often have cases in which you are repeating the indices:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:2]);\n\njulia> @variable(model, y[1:2]);\n\njulia> @constraints(model, begin\n [i=1:2, j=1:2, k=1:2], i * x[j] <= k\n [i=1:2, j=1:2, k=1:2], i * y[j] <= k\n end);","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"This is hard to read and leads to a lot of copy-paste. A more readable way is to use a for-loop:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> for i=1:2, j=1:2, k=1:2\n @constraints(model, begin\n i * x[j] <= k\n i * y[j] <= k\n end)\n end","category":"page"},{"location":"manual/constraints/#Accessing-constraints-from-a-model","page":"Constraints","title":"Accessing constraints from a model","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Query the types of function-in-set constraints in a model using list_of_constraint_types:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[i=1:2] >= i, Int);\n\njulia> @constraint(model, x[1] + x[2] <= 1);\n\njulia> list_of_constraint_types(model)\n3-element Vector{Tuple{Type, Type}}:\n (AffExpr, MathOptInterface.LessThan{Float64})\n (VariableRef, MathOptInterface.GreaterThan{Float64})\n (VariableRef, MathOptInterface.Integer)","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"For a given combination of function and set type, use num_constraints to access the number of constraints and all_constraints to access a list of their references:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> num_constraints(model, VariableRef, MOI.Integer)\n2\n\njulia> cons = all_constraints(model, VariableRef, MOI.Integer)\n2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.Integer}, ScalarShape}}:\n x[1] integer\n x[2] integer","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"You can also count the total number of constraints in the model, but you must explicitly choose whether to count VariableRef constraints such as bound and integrality constraints:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> num_constraints(model; count_variable_in_set_constraints = true)\n5\n\njulia> num_constraints(model; count_variable_in_set_constraints = false)\n1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The same also applies for all_constraints:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> all_constraints(model; include_variable_in_set_constraints = true)\n5-element Vector{ConstraintRef}:\n x[1] + x[2] ≤ 1\n x[1] ≥ 1\n x[2] ≥ 2\n x[1] integer\n x[2] integer\n\njulia> all_constraints(model; include_variable_in_set_constraints = false)\n1-element Vector{ConstraintRef}:\n x[1] + x[2] ≤ 1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"If you need finer-grained control on which constraints to include, use a variant of:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> sum(\n num_constraints(model, F, S) for\n (F, S) in list_of_constraint_types(model) if F != VariableRef\n )\n1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Use constraint_object to get an instance of an AbstractConstraint object that stores the constraint data:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> con = constraint_object(cons[1])\nScalarConstraint{VariableRef, MathOptInterface.Integer}(x[1], MathOptInterface.Integer())\n\njulia> con.func\nx[1]\n\njulia> con.set\nMathOptInterface.Integer()","category":"page"},{"location":"manual/constraints/#MathOptInterface-constraints","page":"Constraints","title":"MathOptInterface constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Because JuMP is based on MathOptInterface, you can add any constraints supported by MathOptInterface using the function-in-set syntax. For a list of supported functions and sets, read Standard form problem.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"note: Note\nWe use MOI as an alias for the MathOptInterface module. This alias is defined by using JuMP. You may also define it in your code as follows:import MathOptInterface as MOI","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"For example, the following two constraints are equivalent:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:3]);\n\njulia> @constraint(model, 2 * x[1] <= 1)\n2 x[1] ≤ 1\n\njulia> @constraint(model, 2 * x[1] in MOI.LessThan(1.0))\n2 x[1] ≤ 1","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"You can also use any set defined by MathOptInterface:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, x - [1; 2; 3] in MOI.Nonnegatives(3))\n[x[1] - 1, x[2] - 2, x[3] - 3] ∈ MathOptInterface.Nonnegatives(3)\n\njulia> @constraint(model, x in MOI.ExponentialCone())\n[x[1], x[2], x[3]] ∈ MathOptInterface.ExponentialCone()","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"info: Info\nSimilar to how JuMP defines the <= and >= syntax as a convenience way to specify MOI.LessThan and MOI.GreaterThan constraints, the remaining sections in this page describe functions and syntax that have been added for the convenience of common modeling situations.","category":"page"},{"location":"manual/constraints/#Set-inequality-syntax","page":"Constraints","title":"Set inequality syntax","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"For modeling convenience, the syntax @constraint(model, x >= y, Set()) is short-hand for @constraint(model, x - y in Set()). Therefore, the following calls are equivalent:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:2]);\n\njulia> y = [0.5, 0.75];\n\njulia> @constraint(model, x >= y, MOI.Nonnegatives(2))\n[x[1] - 0.5, x[2] - 0.75] ∈ MathOptInterface.Nonnegatives(2)\n\njulia> @constraint(model, y <= x, MOI.Nonnegatives(2))\n[x[1] - 0.5, x[2] - 0.75] ∈ MathOptInterface.Nonnegatives(2)\n\njulia> @constraint(model, x - y in MOI.Nonnegatives(2))\n[x[1] - 0.5, x[2] - 0.75] ∈ MathOptInterface.Nonnegatives(2)","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Non-zero constants are not supported in this syntax:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, x >= 1, MOI.Nonnegatives(2))\nERROR: Operation `sub_mul` between `Vector{VariableRef}` and `Int64` is not allowed. This most often happens when you write a constraint like `x >= y` where `x` is an array and `y` is a constant. Use the broadcast syntax `x .- y >= 0` instead.\nStacktrace:\n[...]","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Use instead:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, x .- 1 >= 0, MOI.Nonnegatives(2))\n[x[1] - 1, x[2] - 1] ∈ MathOptInterface.Nonnegatives(2)","category":"page"},{"location":"manual/constraints/#Second-order-cone-constraints","page":"Constraints","title":"Second-order cone constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"A SecondOrderCone constrains the variables t and x to the set:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"x_2 le t","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"and t ge 0. It can be added as follows:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, t)\nt\n\njulia> @variable(model, x[1:2])\n2-element Vector{VariableRef}:\n x[1]\n x[2]\n\njulia> @constraint(model, [t; x] in SecondOrderCone())\n[t, x[1], x[2]] ∈ MathOptInterface.SecondOrderCone(3)","category":"page"},{"location":"manual/constraints/#Rotated-second-order-cone-constraints","page":"Constraints","title":"Rotated second-order cone constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"A RotatedSecondOrderCone constrains the variables t, u, and x to the set:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"x_2^2 le 2 t cdot u","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"and t u ge 0. It can be added as follows:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, t)\nt\n\njulia> @variable(model, u)\nu\n\njulia> @variable(model, x[1:2])\n2-element Vector{VariableRef}:\n x[1]\n x[2]\n\njulia> @constraint(model, [t; u; x] in RotatedSecondOrderCone())\n[t, u, x[1], x[2]] ∈ MathOptInterface.RotatedSecondOrderCone(4)","category":"page"},{"location":"manual/constraints/#Semi-integer-and-semi-continuous-variables","page":"Constraints","title":"Semi-integer and semi-continuous variables","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Semi-continuous variables are constrained to the set x in 0 cup l u.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Create a semi-continuous variable using the Semicontinuous set:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, x in Semicontinuous(1.5, 3.5))\nx in MathOptInterface.Semicontinuous{Float64}(1.5, 3.5)","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Semi-integer variables are constrained to the set x in 0 cup l l+1 dots u.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Create a semi-integer variable using the Semiinteger set:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x);\n\njulia> @constraint(model, x in Semiinteger(1.0, 3.0))\nx in MathOptInterface.Semiinteger{Float64}(1.0, 3.0)","category":"page"},{"location":"manual/constraints/#Special-Ordered-Sets-of-Type-1","page":"Constraints","title":"Special Ordered Sets of Type 1","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"In a Special Ordered Set of Type 1 (often denoted SOS-I or SOS1), at most one element can take a non-zero value.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Construct SOS-I constraints using the SOS1 set:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x[1:3])\n3-element Vector{VariableRef}:\n x[1]\n x[2]\n x[3]\n\njulia> @constraint(model, x in SOS1())\n[x[1], x[2], x[3]] in MathOptInterface.SOS1{Float64}([1.0, 2.0, 3.0])","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Although not required for feasibility, solvers can benefit from an ordering of the variables (for example, the variables represent different factories to build, at most one factory can be built, and the factories can be ordered according to cost). To induce an ordering, a vector of weights can be provided, and the variables are ordered according to their corresponding weight.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"For example, in the constraint:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, x in SOS1([3.1, 1.2, 2.3]))\n[x[1], x[2], x[3]] in MathOptInterface.SOS1{Float64}([3.1, 1.2, 2.3])","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"the variables x have precedence x[2], x[3], x[1].","category":"page"},{"location":"manual/constraints/#Special-Ordered-Sets-of-Type-2","page":"Constraints","title":"Special Ordered Sets of Type 2","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"In a Special Ordered Set of Type 2 (SOS-II), at most two elements can be non-zero, and if there are two non-zeros, they must be consecutive according to the ordering induced by a weight vector.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Construct SOS-II constraints using the SOS2 set:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, x in SOS2([3.0, 1.0, 2.0]))\n[x[1], x[2], x[3]] in MathOptInterface.SOS2{Float64}([3.0, 1.0, 2.0])","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The possible non-zero pairs are (x[1], x[3]) and (x[2], x[3]):","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"If the weight vector is omitted, JuMP induces an ordering from 1:length(x):","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, x in SOS2())\n[x[1], x[2], x[3]] in MathOptInterface.SOS2{Float64}([1.0, 2.0, 3.0])","category":"page"},{"location":"manual/constraints/#Indicator-constraints","page":"Constraints","title":"Indicator constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Indicator constraints consist of a binary variable and a linear constraint. The constraint holds when the binary variable takes the value 1. The constraint may or may not hold when the binary variable takes the value 0.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"To enforce the constraint x + y <= 1 when the binary variable a is 1, use:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x)\nx\n\njulia> @variable(model, y)\ny\n\njulia> @variable(model, a, Bin)\na\n\njulia> @constraint(model, a => {x + y <= 1})\na => {x + y ≤ 1}","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"If the constraint must hold when a is zero, add ! or ¬ before the binary variable;","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, !a => {x + y <= 1})\n!a => {x + y ≤ 1}","category":"page"},{"location":"manual/constraints/#Semidefinite-constraints","page":"Constraints","title":"Semidefinite constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"To constrain a matrix to be positive semidefinite (PSD), use PSDCone:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, X[1:2, 1:2])\n2×2 Matrix{VariableRef}:\n X[1,1] X[1,2]\n X[2,1] X[2,2]\n\njulia> @constraint(model, X >= 0, PSDCone())\n[X[1,1] X[1,2];\n X[2,1] X[2,2]] ∈ PSDCone()","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"tip: Tip\nWhere possible, prefer constructing a matrix of Semidefinite variables using the @variable macro, rather than adding a constraint like @constraint(model, X >= 0, PSDCone()). In some solvers, adding the constraint via @constraint is less efficient, and can result in additional intermediate variables and constraints being added to the model.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The inequality X >= Y between two square matrices X and Y is understood as constraining X - Y to be positive semidefinite.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> Y = [1 2; 2 1]\n2×2 Matrix{Int64}:\n 1 2\n 2 1\n\njulia> @constraint(model, X >= Y, PSDCone())\n[X[1,1] - 1 X[1,2] - 2;\n X[2,1] - 2 X[2,2] - 1] ∈ PSDCone()","category":"page"},{"location":"manual/constraints/#Symmetry","page":"Constraints","title":"Symmetry","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Solvers supporting PSD constraints usually expect to be given a matrix that is symbolically symmetric, that is, for which the expression in corresponding off-diagonal entries are the same. In our example, the expressions of entries (1, 2) and (2, 1) are respectively X[1,2] - 2 and X[2,1] - 2 which are different.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"To bridge the gap between the constraint modeled and what the solver expects, solvers may add an equality constraint X[1,2] - 2 == X[2,1] - 2 to force symmetry. Use LinearAlgebra.Symmetric to explicitly tell the solver that the matrix is symmetric:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> import LinearAlgebra\n\njulia> Z = [X[1, 1] X[1, 2]; X[1, 2] X[2, 2]]\n2×2 Matrix{VariableRef}:\n X[1,1] X[1,2]\n X[1,2] X[2,2]\n\njulia> @constraint(model, LinearAlgebra.Symmetric(Z) >= 0, PSDCone())\n[X[1,1] X[1,2];\n X[1,2] X[2,2]] ∈ PSDCone()","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Note that the lower triangular entries are ignored even if they are different so use it with caution:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, LinearAlgebra.Symmetric(X) >= 0, PSDCone())\n[X[1,1] X[1,2];\n X[1,2] X[2,2]] ∈ PSDCone()","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"(Note the (2, 1) element of the constraint is X[1,2], not X[2,1].)","category":"page"},{"location":"manual/constraints/#Complementarity-constraints","page":"Constraints","title":"Complementarity constraints","text":"","category":"section"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"A mixed complementarity constraint F(x) ⟂ x consists of finding x in the interval [lb, ub], such that the following holds:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"F(x) == 0 if lb < x < ub\nF(x) >= 0 if lb == x\nF(x) <= 0 if x == ub","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"JuMP supports mixed complementarity constraints via complements(F(x), x) or F(x) ⟂ x in the @constraint macro. The interval set [lb, ub] is obtained from the variable bounds on x.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"For example, to define the problem 2x - 1 ⟂ x with x ∈ [0, ∞), do:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> model = Model();\n\njulia> @variable(model, x >= 0)\nx\n\njulia> @constraint(model, 2x - 1 ⟂ x)\n[2 x - 1, x] ∈ MathOptInterface.Complements(2)","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"This problem has a unique solution at x = 0.5.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"The perp operator ⟂ can be entered in most editors (and the Julia REPL) by typing \\perp.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"An alternative approach that does not require the ⟂ symbol uses the complements function as follows:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @constraint(model, complements(2x - 1, x))\n[2 x - 1, x] ∈ MathOptInterface.Complements(2)","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"In both cases, the mapping F(x) is supplied as the first argument, and the matching variable x is supplied as the second.","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"Vector-valued complementarity constraints are also supported:","category":"page"},{"location":"manual/constraints/","page":"Constraints","title":"Constraints","text":"julia> @variable(model, -2 <= y[1:2] <= 2)\n2-element Vector{VariableRef}:\n y[1]\n y[2]\n\njulia> M = [1 2; 3 4]\n2×2 Matrix{Int64}:\n 1 2\n 3 4\n\njulia> q = [5, 6]\n2-element Vector{Int64}:\n 5\n 6\n\njulia> @constraint(model, M * y + q ⟂ y)\n[y[1] + 2 y[2] + 5, 3 y[1] + 4 y[2] + 6, y[1], y[2]] ∈ MathOptInterface.Complements(4)","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"EditURL = \"power_systems.jl\"","category":"page"},{"location":"tutorials/applications/power_systems/#Power-Systems","page":"Power Systems","title":"Power Systems","text":"","category":"section"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"This tutorial was originally contributed by Yury Dvorkin and Miles Lubin.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"This tutorial demonstrates how to formulate basic power systems engineering models in JuMP.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"We will consider basic \"economic dispatch\" and \"unit commitment\" models without taking into account transmission constraints.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"For this tutorial, we use the following packages:","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"using JuMP\nimport DataFrames\nimport HiGHS\nimport Plots\nimport StatsPlots","category":"page"},{"location":"tutorials/applications/power_systems/#Economic-dispatch","page":"Power Systems","title":"Economic dispatch","text":"","category":"section"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Economic dispatch (ED) is an optimization problem that minimizes the cost of supplying energy demand subject to operational constraints on power system assets. In its simplest modification, ED is an LP problem solved for an aggregated load and wind forecast and for a single infinitesimal moment.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Mathematically, the ED problem can be written as follows:","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"min sum_i in I c^g_i cdot g_i + c^w cdot w","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"where c_i and g_i are the incremental cost ($/MWh) and power output (MW) of the i^th generator, respectively, and c^w and w are the incremental cost ($/MWh) and wind power injection (MW), respectively.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Subject to the constraints:","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Minimum (g^min) and maximum (g^max) limits on power outputs of generators: g^min_i leq g_i leq g^max_i\nConstraint on the wind power injection: 0 leq w leq w^f where w and w^f are the wind power injection and wind power forecast, respectively.\nPower balance constraint: sum_i in I g_i + w = d^f where d^f is the demand forecast.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Further reading on ED models can be found in A. J. Wood, B. F. Wollenberg, and G. B. Sheblé, \"Power Generation, Operation and Control,\" Wiley, 2013.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Define some input data about the test system.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"We define some thermal generators:","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"function ThermalGenerator(\n min::Float64,\n max::Float64,\n fixed_cost::Float64,\n variable_cost::Float64,\n)\n return (\n min = min,\n max = max,\n fixed_cost = fixed_cost,\n variable_cost = variable_cost,\n )\nend\n\ngenerators = [\n ThermalGenerator(0.0, 1000.0, 1000.0, 50.0),\n ThermalGenerator(300.0, 1000.0, 0.0, 100.0),\n]","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"A wind generator","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"WindGenerator(variable_cost::Float64) = (variable_cost = variable_cost,)\n\nwind_generator = WindGenerator(50.0)","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"And a scenario","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"function Scenario(demand::Float64, wind::Float64)\n return (demand = demand, wind = wind)\nend\n\nscenario = Scenario(1500.0, 200.0)","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Create a function solve_economic_dispatch, which solves the economic dispatch problem for a given set of input parameters.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"function solve_economic_dispatch(generators::Vector, wind, scenario)\n # Define the economic dispatch (ED) model\n model = Model(HiGHS.Optimizer)\n set_silent(model)\n # Define decision variables\n # power output of generators\n N = length(generators)\n @variable(model, generators[i].min <= g[i = 1:N] <= generators[i].max)\n # wind power injection\n @variable(model, 0 <= w <= scenario.wind)\n # Define the objective function\n @objective(\n model,\n Min,\n sum(generators[i].variable_cost * g[i] for i in 1:N) +\n wind.variable_cost * w,\n )\n # Define the power balance constraint\n @constraint(model, sum(g[i] for i in 1:N) + w == scenario.demand)\n # Solve statement\n optimize!(model)\n # return the optimal value of the objective function and its minimizers\n return (\n g = value.(g),\n w = value(w),\n wind_spill = scenario.wind - value(w),\n total_cost = objective_value(model),\n )\nend","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Solve the economic dispatch problem","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"solution = solve_economic_dispatch(generators, wind_generator, scenario);\n\nprintln(\"Dispatch of Generators: \", solution.g, \" MW\")\nprintln(\"Dispatch of Wind: \", solution.w, \" MW\")\nprintln(\"Wind spillage: \", solution.wind_spill, \" MW\")\nprintln(\"Total cost: \\$\", solution.total_cost)","category":"page"},{"location":"tutorials/applications/power_systems/#Economic-dispatch-with-adjustable-incremental-costs","page":"Power Systems","title":"Economic dispatch with adjustable incremental costs","text":"","category":"section"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"In the following exercise we adjust the incremental cost of generator G1 and observe its impact on the total cost.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"function scale_generator_cost(g, scale)\n return ThermalGenerator(g.min, g.max, g.fixed_cost, scale * g.variable_cost)\nend\n\nstart = time()\nc_g_scale_df = DataFrames.DataFrame(;\n # Scale factor\n scale = Float64[],\n # Dispatch of Generator 1 [MW]\n dispatch_G1 = Float64[],\n # Dispatch of Generator 2 [MW]\n dispatch_G2 = Float64[],\n # Dispatch of Wind [MW]\n dispatch_wind = Float64[],\n # Spillage of Wind [MW]\n spillage_wind = Float64[],\n # Total cost [$]\n total_cost = Float64[],\n)\nfor c_g1_scale in 0.5:0.1:3.0\n # Update the incremental cost of the first generator at every iteration.\n new_generators = scale_generator_cost.(generators, [c_g1_scale, 1.0])\n # Solve the economic-dispatch problem with the updated incremental cost\n sol = solve_economic_dispatch(new_generators, wind_generator, scenario)\n push!(\n c_g_scale_df,\n (c_g1_scale, sol.g[1], sol.g[2], sol.w, sol.wind_spill, sol.total_cost),\n )\nend\nprint(string(\"elapsed time: \", time() - start, \" seconds\"))","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"c_g_scale_df","category":"page"},{"location":"tutorials/applications/power_systems/#Modifying-the-JuMP-model-in-place","page":"Power Systems","title":"Modifying the JuMP model in-place","text":"","category":"section"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Note that in the previous exercise we entirely rebuilt the optimization model at every iteration of the internal loop, which incurs an additional computational burden. This burden can be alleviated if instead of re-building the entire model, we modify the constraints or objective function, as it shown in the example below.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Compare the computing time in case of the above and below models.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"function solve_economic_dispatch_inplace(\n generators::Vector,\n wind,\n scenario,\n scale::AbstractVector{Float64},\n)\n obj_out = Float64[]\n w_out = Float64[]\n g1_out = Float64[]\n g2_out = Float64[]\n # This function only works for two generators\n @assert length(generators) == 2\n model = Model(HiGHS.Optimizer)\n set_silent(model)\n N = length(generators)\n @variable(model, generators[i].min <= g[i = 1:N] <= generators[i].max)\n @variable(model, 0 <= w <= scenario.wind)\n @objective(\n model,\n Min,\n sum(generators[i].variable_cost * g[i] for i in 1:N) +\n wind.variable_cost * w,\n )\n @constraint(model, sum(g[i] for i in 1:N) + w == scenario.demand)\n for c_g1_scale in scale\n @objective(\n model,\n Min,\n c_g1_scale * generators[1].variable_cost * g[1] +\n generators[2].variable_cost * g[2] +\n wind.variable_cost * w,\n )\n optimize!(model)\n push!(obj_out, objective_value(model))\n push!(w_out, value(w))\n push!(g1_out, value(g[1]))\n push!(g2_out, value(g[2]))\n end\n df = DataFrames.DataFrame(;\n scale = scale,\n dispatch_G1 = g1_out,\n dispatch_G2 = g2_out,\n dispatch_wind = w_out,\n spillage_wind = scenario.wind .- w_out,\n total_cost = obj_out,\n )\n return df\nend\n\nstart = time()\ninplace_df = solve_economic_dispatch_inplace(\n generators,\n wind_generator,\n scenario,\n 0.5:0.1:3.0,\n)\nprint(string(\"elapsed time: \", time() - start, \" seconds\"))","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"For small models, adjusting specific constraints or the objective function is sometimes faster and sometimes slower than re-building the entire model. However, as the problem size increases, updating the model in-place is usually faster.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"inplace_df","category":"page"},{"location":"tutorials/applications/power_systems/#Inefficient-usage-of-wind-generators","page":"Power Systems","title":"Inefficient usage of wind generators","text":"","category":"section"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"The economic dispatch problem does not perform commitment decisions and, thus, assumes that all generators must be dispatched at least at their minimum power output limit. This approach is not cost efficient and may lead to absurd decisions. For example, if d = sum_i in I g^min_i, the wind power injection must be zero, that is, all available wind generation is spilled, to meet the minimum power output constraints on generators.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"In the following example, we adjust the total demand and observed how it affects wind spillage.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"demand_scale_df = DataFrames.DataFrame(;\n demand = Float64[],\n dispatch_G1 = Float64[],\n dispatch_G2 = Float64[],\n dispatch_wind = Float64[],\n spillage_wind = Float64[],\n total_cost = Float64[],\n)\n\nfunction scale_demand(scenario, scale)\n return Scenario(scale * scenario.demand, scenario.wind)\nend\n\nfor demand_scale in 0.2:0.1:1.4\n new_scenario = scale_demand(scenario, demand_scale)\n sol = solve_economic_dispatch(generators, wind_generator, new_scenario)\n push!(\n demand_scale_df,\n (\n new_scenario.demand,\n sol.g[1],\n sol.g[2],\n sol.w,\n sol.wind_spill,\n sol.total_cost,\n ),\n )\nend\n\ndemand_scale_df","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"dispatch_plot = StatsPlots.@df(\n demand_scale_df,\n Plots.plot(\n :demand,\n [:dispatch_G1, :dispatch_G2],\n labels = [\"G1\" \"G2\"],\n title = \"Thermal Dispatch\",\n legend = :bottomright,\n linewidth = 3,\n xlabel = \"Demand\",\n ylabel = \"Dispatch [MW]\",\n ),\n)\n\nwind_plot = StatsPlots.@df(\n demand_scale_df,\n Plots.plot(\n :demand,\n [:dispatch_wind, :spillage_wind],\n labels = [\"Dispatch\" \"Spillage\"],\n title = \"Wind\",\n legend = :bottomright,\n linewidth = 3,\n xlabel = \"Demand [MW]\",\n ylabel = \"Energy [MW]\",\n ),\n)\n\nPlots.plot(dispatch_plot, wind_plot)","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"This particular drawback can be overcome by introducing binary decisions on the \"on/off\" status of generators. This model is called unit commitment and considered later in these notes.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"For further reading on the interplay between wind generation and the minimum power output constraints of generators, we refer interested readers to R. Baldick, \"Wind and energy markets: a case study of Texas,\" IEEE Systems Journal, vol. 6, pp. 27-34, 2012.","category":"page"},{"location":"tutorials/applications/power_systems/#Unit-commitment","page":"Power Systems","title":"Unit commitment","text":"","category":"section"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"The Unit Commitment (UC) model can be obtained from ED model by introducing binary variable associated with each generator. This binary variable can attain two values: if it is \"1,\" the generator is synchronized and, thus, can be dispatched, otherwise, that is, if the binary variable is \"0,\" that generator is not synchronized and its power output is set to 0.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"To obtain the mathematical formulation of the UC model, we will modify the constraints of the ED model as follows:","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"g^min_i cdot u_ti leq g_i leq g^max_i cdot u_ti","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"where u_i in 01 In this constraint, if u_i = 0, then g_i = 0. On the other hand, if u_i = 1, then g^min_i leq g_i leq g^max_i.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"For further reading on the UC problem we refer interested readers to G. Morales-Espana, J. M. Latorre, and A. Ramos, \"Tight and Compact MILP Formulation for the Thermal Unit Commitment Problem,\" IEEE Transactions on Power Systems, vol. 28, pp. 4897-4908, 2013.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"In the following example we convert the ED model explained above to the UC model.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"function solve_unit_commitment(generators::Vector, wind, scenario)\n model = Model(HiGHS.Optimizer)\n set_silent(model)\n N = length(generators)\n @variable(model, 0 <= g[i = 1:N] <= generators[i].max)\n @variable(model, 0 <= w <= scenario.wind)\n @constraint(model, sum(g[i] for i in 1:N) + w == scenario.demand)\n # !!! New: add binary on-off variables for each generator\n @variable(model, u[i = 1:N], Bin)\n @constraint(model, [i = 1:N], g[i] <= generators[i].max * u[i])\n @constraint(model, [i = 1:N], g[i] >= generators[i].min * u[i])\n @objective(\n model,\n Min,\n sum(generators[i].variable_cost * g[i] for i in 1:N) +\n wind.variable_cost * w +\n # !!! new\n sum(generators[i].fixed_cost * u[i] for i in 1:N)\n )\n optimize!(model)\n status = termination_status(model)\n if status != OPTIMAL\n return (status = status,)\n end\n return (\n status = status,\n g = value.(g),\n w = value(w),\n wind_spill = scenario.wind - value(w),\n u = value.(u),\n total_cost = objective_value(model),\n )\nend","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Solve the unit commitment problem","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"solution = solve_unit_commitment(generators, wind_generator, scenario)\n\nprintln(\"Dispatch of Generators: \", solution.g, \" MW\")\nprintln(\"Commitments of Generators: \", solution.u)\nprintln(\"Dispatch of Wind: \", solution.w, \" MW\")\nprintln(\"Wind spillage: \", solution.wind_spill, \" MW\")\nprintln(\"Total cost: \\$\", solution.total_cost)","category":"page"},{"location":"tutorials/applications/power_systems/#Unit-commitment-as-a-function-of-demand","page":"Power Systems","title":"Unit commitment as a function of demand","text":"","category":"section"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"After implementing the unit commitment model, we can now assess the interplay between the minimum power output constraints on generators and wind generation.","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"uc_df = DataFrames.DataFrame(;\n demand = Float64[],\n commitment_G1 = Float64[],\n commitment_G2 = Float64[],\n dispatch_G1 = Float64[],\n dispatch_G2 = Float64[],\n dispatch_wind = Float64[],\n spillage_wind = Float64[],\n total_cost = Float64[],\n)\n\nfor demand_scale in 0.2:0.1:1.4\n new_scenario = scale_demand(scenario, demand_scale)\n sol = solve_unit_commitment(generators, wind_generator, new_scenario)\n if sol.status == OPTIMAL\n push!(\n uc_df,\n (\n new_scenario.demand,\n sol.u[1],\n sol.u[2],\n sol.g[1],\n sol.g[2],\n sol.w,\n sol.wind_spill,\n sol.total_cost,\n ),\n )\n end\n println(\"Status: $(sol.status) for demand_scale = $(demand_scale)\")\nend","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"uc_df","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"commitment_plot = StatsPlots.@df(\n uc_df,\n Plots.plot(\n :demand,\n [:commitment_G1, :commitment_G2],\n labels = [\"G1\" \"G2\"],\n title = \"Commitment\",\n legend = :bottomright,\n linewidth = 3,\n xlabel = \"Demand [MW]\",\n ylabel = \"Commitment decision {0, 1}\",\n ),\n)\n\ndispatch_plot = StatsPlots.@df(\n uc_df,\n Plots.plot(\n :demand,\n [:dispatch_G1, :dispatch_G2, :dispatch_wind],\n labels = [\"G1\" \"G2\" \"Wind\"],\n title = \"Dispatch [MW]\",\n legend = :bottomright,\n linewidth = 3,\n xlabel = \"Demand\",\n ylabel = \"Dispatch [MW]\",\n ),\n)\n\nPlots.plot(commitment_plot, dispatch_plot)","category":"page"},{"location":"tutorials/applications/power_systems/#Nonlinear-economic-dispatch","page":"Power Systems","title":"Nonlinear economic dispatch","text":"","category":"section"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"As a final example, we modify our economic dispatch problem in two ways:","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"The thermal cost function is user-defined\nThe output of the wind is only the square-root of the dispatch","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"import Ipopt\n\n\"\"\"\n thermal_cost_function(g)\n\nA user-defined thermal cost function in pure-Julia! You can include\nnonlinearities, and even things like control flow.\n\n!!! warning\n It's still up to you to make sure that the function has a meaningful\n derivative.\n\"\"\"\nfunction thermal_cost_function(g)\n if g <= 500\n return g\n else\n return g + 1e-2 * (g - 500)^2\n end\nend\n\nfunction solve_nonlinear_economic_dispatch(\n generators::Vector,\n wind,\n scenario;\n silent::Bool = false,\n)\n model = Model(Ipopt.Optimizer)\n if silent\n set_silent(model)\n end\n @operator(model, op_tcf, 1, thermal_cost_function)\n N = length(generators)\n @variable(model, generators[i].min <= g[i = 1:N] <= generators[i].max)\n @variable(model, 0 <= w <= scenario.wind)\n @objective(\n model,\n Min,\n sum(generators[i].variable_cost * op_tcf(g[i]) for i in 1:N) +\n wind.variable_cost * w,\n )\n @constraint(model, sum(g[i] for i in 1:N) + sqrt(w) == scenario.demand)\n optimize!(model)\n return (\n g = value.(g),\n w = value(w),\n wind_spill = scenario.wind - value(w),\n total_cost = objective_value(model),\n )\nend\n\nsolution =\n solve_nonlinear_economic_dispatch(generators, wind_generator, scenario)","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"Now let's see how the wind is dispatched as a function of the cost:","category":"page"},{"location":"tutorials/applications/power_systems/","page":"Power Systems","title":"Power Systems","text":"wind_cost = 0.0:1:100\nwind_dispatch = Float64[]\nfor c in wind_cost\n sol = solve_nonlinear_economic_dispatch(\n generators,\n WindGenerator(c),\n scenario;\n silent = true,\n )\n push!(wind_dispatch, sol.w)\nend\n\nPlots.plot(\n wind_cost,\n wind_dispatch;\n xlabel = \"Cost\",\n ylabel = \"Dispatch [MW]\",\n label = false,\n)","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"EditURL = \"logistic_regression.jl\"","category":"page"},{"location":"tutorials/conic/logistic_regression/#Logistic-regression","page":"Logistic regression","title":"Logistic regression","text":"","category":"section"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"This tutorial was originally contributed by François Pacaud.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"This tutorial shows how to solve a logistic regression problem with JuMP. Logistic regression is a well known method in machine learning, useful when we want to classify binary variables with the help of a given set of features. To this goal, we find the optimal combination of features maximizing the (log)-likelihood onto a training set. From a modern optimization glance, the resulting problem is convex and differentiable. On a modern optimization glance, it is even conic representable.","category":"page"},{"location":"tutorials/conic/logistic_regression/#Formulating-the-logistic-regression-problem","page":"Logistic regression","title":"Formulating the logistic regression problem","text":"","category":"section"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"Suppose we have a set of training data-point i = 1 cdots n, where for each i we have a vector of features x_i in mathbbR^p and a categorical observation y_i in -1 1.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"The log-likelihood is given by","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"l(theta) = sum_i=1^n log(dfrac11 + exp(-y_i theta^top x_i))","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"and the optimal theta minimizes the logistic loss function:","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"min_theta sum_i=1^n log(1 + exp(-y_i theta^top x_i))","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"Most of the time, instead of solving directly the previous optimization problem, we prefer to add a regularization term:","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"min_theta sum_i=1^n log(1 + exp(-y_i theta^top x_i)) + lambda theta ","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"with lambda in mathbbR_+ a penalty and a norm function. By adding such a regularization term, we avoid overfitting on the training set and usually achieve a greater score in cross-validation.","category":"page"},{"location":"tutorials/conic/logistic_regression/#Reformulation-as-a-conic-optimization-problem","page":"Logistic regression","title":"Reformulation as a conic optimization problem","text":"","category":"section"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"By introducing auxiliary variables t_1 cdots t_n and r, the optimization problem is equivalent to","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"beginaligned\nmin_t r theta sum_i=1^n t_i + lambda r \ntextsubject to quad t_i geq log(1 + exp(- y_i theta^top x_i)) \n quad r geq theta\nendaligned","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"Now, the trick is to reformulate the constraints t_i geq log(1 + exp(- y_i theta^top x_i)) with the help of the exponential cone","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"K_exp = (x y z) in mathbbR^3 y exp(x y) leq z ","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"Indeed, by passing to the exponential, we see that for all i=1 cdots n, the constraint t_i geq log(1 + exp(- y_i theta^top x_i)) is equivalent to","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"exp(-t_i) + exp(u_i - t_i) leq 1","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"with u_i = -y_i theta^top x_i. Then, by adding two auxiliary variables z_i1 and z_i2 such that z_i1 geq exp(u_i-t_i) and z_i2 geq exp(-t_i), we get the equivalent formulation","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"left\nbeginaligned\n(u_i -t_i 1 z_i1) in K_exp \n(-t_i 1 z_i2) in K_exp \nz_i1 + z_i2 leq 1\nendaligned\nright","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"In this setting, the conic version of the logistic regression problems writes out","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"beginaligned\nmin_t z r theta sum_i=1^n t_i + lambda r \ntextsubject to quad (u_i -t_i 1 z_i1) in K_exp \n quad (-t_i 1 z_i2) in K_exp \n quad z_i1 + z_i2 leq 1 \n quad u_i = -y_i x_i^top theta \n quad r geq theta\nendaligned","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"and thus encompasses 3n + p + 1 variables and 3n + 1 constraints (u_i = -y_i theta^top x_i is only a virtual constraint used to clarify the notation). Thus, if n gg 1, we get a large number of variables and constraints.","category":"page"},{"location":"tutorials/conic/logistic_regression/#Fitting-logistic-regression-with-a-conic-solver","page":"Logistic regression","title":"Fitting logistic regression with a conic solver","text":"","category":"section"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"It is now time to pass to the implementation. We choose SCS as a conic solver.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"using JuMP\nimport Random\nimport SCS","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"info: Info\nThis tutorial uses sets from MathOptInterface. By default, JuMP exports the MOI symbol as an alias for the MathOptInterface.jl package. We recommend making this more explicit in your code by adding the following lines:import MathOptInterface as MOI","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"Random.seed!(2713);\nnothing #hide","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"We start by implementing a function to generate a fake dataset, and where we could tune the correlation between the feature variables. The function is a direct transcription of the one used in this blog post.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"function generate_dataset(n_samples = 100, n_features = 10; shift = 0.0)\n X = randn(n_samples, n_features)\n w = randn(n_features)\n y = sign.(X * w)\n X .+= 0.8 * randn(n_samples, n_features) # add noise\n X .+= shift # shift the points in the feature space\n X = hcat(X, ones(n_samples, 1))\n return X, y\nend","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"We write a softplus function to formulate each constraint t geq log(1 + exp(u)) with two exponential cones.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"function softplus(model, t, u)\n z = @variable(model, [1:2], lower_bound = 0.0)\n @constraint(model, sum(z) <= 1.0)\n @constraint(model, [u - t, 1, z[1]] in MOI.ExponentialCone())\n @constraint(model, [-t, 1, z[2]] in MOI.ExponentialCone())\nend","category":"page"},{"location":"tutorials/conic/logistic_regression/#\\ell_2-regularized-logistic-regression","page":"Logistic regression","title":"ell_2 regularized logistic regression","text":"","category":"section"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"Then, with the help of the softplus function, we could write our optimization model. In the ell_2 regularization case, the constraint r geq theta_2 rewrites as a second order cone constraint.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"function build_logit_model(X, y, λ)\n n, p = size(X)\n model = Model()\n @variable(model, θ[1:p])\n @variable(model, t[1:n])\n for i in 1:n\n u = -(X[i, :]' * θ) * y[i]\n softplus(model, t[i], u)\n end\n # Add ℓ2 regularization\n @variable(model, 0.0 <= reg)\n @constraint(model, [reg; θ] in SecondOrderCone())\n # Define objective\n @objective(model, Min, sum(t) + λ * reg)\n return model\nend","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"We generate the dataset.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"warning: Warning\nBe careful here, for large n and p SCS could fail to converge.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"n, p = 200, 10\nX, y = generate_dataset(n, p; shift = 10.0);\n\n# We could now solve the logistic regression problem\nλ = 10.0\nmodel = build_logit_model(X, y, λ)\nset_optimizer(model, SCS.Optimizer)\nset_silent(model)\nJuMP.optimize!(model)","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"θ♯ = JuMP.value.(model[:θ])","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"It appears that the speed of convergence is not that impacted by the correlation of the dataset, nor by the penalty lambda.","category":"page"},{"location":"tutorials/conic/logistic_regression/#\\ell_1-regularized-logistic-regression","page":"Logistic regression","title":"ell_1 regularized logistic regression","text":"","category":"section"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"We now formulate the logistic problem with a ell_1 regularization term. The ell_1 regularization ensures sparsity in the optimal solution of the resulting optimization problem. Luckily, the ell_1 norm is implemented as a set in MathOptInterface. Thus, we could formulate the sparse logistic regression problem with the help of a MOI.NormOneCone set.","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"function build_sparse_logit_model(X, y, λ)\n n, p = size(X)\n model = Model()\n @variable(model, θ[1:p])\n @variable(model, t[1:n])\n for i in 1:n\n u = -(X[i, :]' * θ) * y[i]\n softplus(model, t[i], u)\n end\n # Add ℓ1 regularization\n @variable(model, 0.0 <= reg)\n @constraint(model, [reg; θ] in MOI.NormOneCone(p + 1))\n # Define objective\n @objective(model, Min, sum(t) + λ * reg)\n return model\nend\n\n# Auxiliary function to count non-null components:\ncount_nonzero(v::Vector; tol = 1e-6) = sum(abs.(v) .>= tol)\n\n# We solve the sparse logistic regression problem on the same dataset as before.\nλ = 10.0\nsparse_model = build_sparse_logit_model(X, y, λ)\nset_optimizer(sparse_model, SCS.Optimizer)\nset_silent(sparse_model)\nJuMP.optimize!(sparse_model)","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"θ♯ = JuMP.value.(sparse_model[:θ])\nprintln(\n \"Number of non-zero components: \",\n count_nonzero(θ♯),\n \" (out of \",\n p,\n \" features)\",\n)","category":"page"},{"location":"tutorials/conic/logistic_regression/#Extensions","page":"Logistic regression","title":"Extensions","text":"","category":"section"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"A direct extension would be to consider the sparse logistic regression with hard thresholding, which, on contrary to the soft version using a ell_1 regularization, adds an explicit cardinality constraint in its formulation:","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"beginaligned\nmin_theta sum_i=1^n log(1 + exp(-y_i theta^top x_i)) + lambda theta _2^2 \ntextsubject to quad theta _0 = k\nendaligned","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"where k is the maximum number of non-zero components in the vector theta, and _0 is the ell_0 pseudo-norm:","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":" x_0 = i x_i neq 0","category":"page"},{"location":"tutorials/conic/logistic_regression/","page":"Logistic regression","title":"Logistic regression","text":"The cardinality constraint theta_0 leq k could be reformulated with binary variables. Thus the hard sparse regression problem could be solved by any solver supporting mixed integer conic problems.","category":"page"},{"location":"moi/background/motivation/","page":"Motivation","title":"Motivation","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/background/motivation.md\"","category":"page"},{"location":"moi/background/motivation/#Motivation","page":"Motivation","title":"Motivation","text":"","category":"section"},{"location":"moi/background/motivation/","page":"Motivation","title":"Motivation","text":"MathOptInterface (MOI) is a replacement for MathProgBase, the first-generation abstraction layer for mathematical optimization previously used by JuMP and Convex.jl.","category":"page"},{"location":"moi/background/motivation/","page":"Motivation","title":"Motivation","text":"To address a number of limitations of MathProgBase, MOI is designed to:","category":"page"},{"location":"moi/background/motivation/","page":"Motivation","title":"Motivation","text":"Be simple and extensible\nunifying linear, quadratic, and conic optimization,\nseamlessly facilitating extensions to essentially arbitrary constraints and functions (for example, indicator constraints, complementarity constraints, and piecewise-linear functions)\nBe fast\nby allowing access to a solver's in-memory representation of a problem without writing intermediate files (when possible)\nby using multiple dispatch and avoiding requiring containers of non-concrete types\nAllow a solver to return multiple results (for example, a pool of solutions)\nAllow a solver to return extra arbitrary information via attributes (for example, variable- and constraint-wise membership in an irreducible inconsistent subset for infeasibility analysis)\nProvide a greatly expanded set of status codes explaining what happened during the optimization procedure\nEnable a solver to more precisely specify which problem classes it supports\nEnable both primal and dual warm starts\nEnable adding and removing both variables and constraints by indices that are not required to be consecutive\nEnable any modification that the solver supports to an existing model\nAvoid requiring the solver wrapper to store an additional copy of the problem data","category":"page"},{"location":"tutorials/conic/introduction/#Introduction","page":"Introduction","title":"Introduction","text":"","category":"section"},{"location":"tutorials/conic/introduction/","page":"Introduction","title":"Introduction","text":"Conic programs are a class of convex nonlinear optimization problems which use cones to represent the nonlinearities. They have the form:","category":"page"},{"location":"tutorials/conic/introduction/","page":"Introduction","title":"Introduction","text":"beginalign\n min_x in mathbbR^n f_0(x) \n textst f_j(x) in mathcalS_j j = 1 ldots m\nendalign","category":"page"},{"location":"tutorials/conic/introduction/","page":"Introduction","title":"Introduction","text":"Mixed-integer conic programs (MICPs) are extensions of conic programs in which some (or all) of the decision variables take discrete values.","category":"page"},{"location":"tutorials/conic/introduction/#How-to-choose-a-solver","page":"Introduction","title":"How to choose a solver","text":"","category":"section"},{"location":"tutorials/conic/introduction/","page":"Introduction","title":"Introduction","text":"JuMP supports a range of conic solvers, although support differs on what types of cones each solver supports. In the list of Supported solvers, \"SOCP\" denotes solvers supporting second-order cones and \"SDP\" denotes solvers supporting semidefinite cones. In addition, solvers such as SCS and Mosek have support for the exponential cone. Moreover, due to the bridging system in MathOptInterface, many of these solvers support a much wider range of exotic cones than they natively support. Solvers supporting discrete variables start with \"(MI)\" in the list of Supported solvers.","category":"page"},{"location":"tutorials/conic/introduction/","page":"Introduction","title":"Introduction","text":"tip: Tip\nDuality plays a large role in solving conic optimization models. Depending on the solver, it can be more efficient to solve the dual instead of the primal. If performance is an issue, see the Dualization tutorial for more details.","category":"page"},{"location":"tutorials/conic/introduction/#How-these-tutorials-are-structured","page":"Introduction","title":"How these tutorials are structured","text":"","category":"section"},{"location":"tutorials/conic/introduction/","page":"Introduction","title":"Introduction","text":"Having a high-level overview of how this part of the documentation is structured will help you know where to look for certain things.","category":"page"},{"location":"tutorials/conic/introduction/","page":"Introduction","title":"Introduction","text":"The following tutorials are worked examples that present a problem in words, then formulate it in mathematics, and then solve it in JuMP. This usually involves some sort of visualization of the solution. Start here if you are new to JuMP.\nExperiment design\nLogistic regression\nThe Tips and tricks tutorial contains a number of helpful reformulations and tricks you can use when modeling conic programs. Look here if you are stuck trying to formulate a problem as a conic program.\nThe remaining tutorials are less verbose and styled in the form of short code examples. These tutorials have less explanation, but may contain useful code snippets, particularly if they are similar to a problem you are trying to solve.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/tutorials/manipulating_expressions.md\"","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"CurrentModule = MathOptInterface\nDocTestSetup = quote\n import MathOptInterface as MOI\nend\nDocTestFilters = [r\"MathOptInterface|MOI\"]","category":"page"},{"location":"moi/tutorials/manipulating_expressions/#Manipulating-expressions","page":"Manipulating expressions","title":"Manipulating expressions","text":"","category":"section"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"This guide highlights a syntactically appealing way to build expressions at the MOI level, but also to look at their contents. It may be especially useful when writing models or bridge code.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/#Creating-functions","page":"Manipulating expressions","title":"Creating functions","text":"","category":"section"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"This section details the ways to create functions with MathOptInterface.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/#Creating-scalar-affine-functions","page":"Manipulating expressions","title":"Creating scalar affine functions","text":"","category":"section"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"The simplest scalar function is simply a variable:","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"julia> x = MOI.add_variable(model) # Create the variable x\nMOI.VariableIndex(1)","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"This type of function is extremely simple; to express more complex functions, other types must be used. For instance, a ScalarAffineFunction is a sum of linear terms (a factor times a variable) and a constant. Such an object can be built using the standard constructor:","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"julia> f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1, x)], 2) # x + 2\n(2) + (1) MOI.VariableIndex(1)","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"However, you can also use operators to build the same scalar function:","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"julia> f = x + 2\n(2) + (1) MOI.VariableIndex(1)","category":"page"},{"location":"moi/tutorials/manipulating_expressions/#Creating-scalar-quadratic-functions","page":"Manipulating expressions","title":"Creating scalar quadratic functions","text":"","category":"section"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"Scalar quadratic functions are stored in ScalarQuadraticFunction objects, in a way that is highly similar to scalar affine functions. You can obtain a quadratic function as a product of affine functions:","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"julia> 1 * x * x\n(0) + 1.0 MOI.VariableIndex(1)²\n\njulia> f * f # (x + 2)²\n(4) + (2) MOI.VariableIndex(1) + (2) MOI.VariableIndex(1) + 1.0 MOI.VariableIndex(1)²\n\njulia> f^2 # (x + 2)² too\n(4) + (2) MOI.VariableIndex(1) + (2) MOI.VariableIndex(1) + 1.0 MOI.VariableIndex(1)²","category":"page"},{"location":"moi/tutorials/manipulating_expressions/#Creating-vector-functions","page":"Manipulating expressions","title":"Creating vector functions","text":"","category":"section"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"A vector function is a function with several values, irrespective of the number of input variables. Similarly to scalar functions, there are three main types of vector functions: VectorOfVariables, VectorAffineFunction, and VectorQuadraticFunction.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"The easiest way to create a vector function is to stack several scalar functions using Utilities.vectorize. It takes a vector as input, and the generated vector function (of the most appropriate type) has each dimension corresponding to a dimension of the vector.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"julia> g = MOI.Utilities.vectorize([f, 2 * f])\n┌ ┐\n│(2) + (1) MOI.VariableIndex(1)│\n│(4) + (2) MOI.VariableIndex(1)│\n└ ┘","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"warning: Warning\nUtilities.vectorize only takes a vector of similar scalar functions: you cannot mix VariableIndex and ScalarAffineFunction, for instance. In practice, it means that Utilities.vectorize([x, f]) does not work; you should rather use Utilities.vectorize([1 * x, f]) instead to only have ScalarAffineFunction objects.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/#Canonicalizing-functions","page":"Manipulating expressions","title":"Canonicalizing functions","text":"","category":"section"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"In more advanced use cases, you might need to ensure that a function is \"canonical.\" Functions are stored as an array of terms, but there is no check that these terms are redundant: a ScalarAffineFunction object might have two terms with the same variable, like x + x + 1. These terms could be merged without changing the semantics of the function: 2x + 1.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"Working with these objects might be cumbersome. Canonicalization helps maintain redundancy to zero.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"Utilities.is_canonical checks whether a function is already in its canonical form:","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"julia> MOI.Utilities.is_canonical(f + f) # (x + 2) + (x + 2) is stored as x + x + 4\nfalse","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"Utilities.canonical returns the equivalent canonical version of the function:","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"julia> MOI.Utilities.canonical(f + f) # Returns 2x + 4\n(4) + (2) MOI.VariableIndex(1)","category":"page"},{"location":"moi/tutorials/manipulating_expressions/#Exploring-functions","page":"Manipulating expressions","title":"Exploring functions","text":"","category":"section"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"At some point, you might need to dig into a function, for instance to map it into solver constructs.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/#Vector-functions","page":"Manipulating expressions","title":"Vector functions","text":"","category":"section"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"Utilities.scalarize returns a vector of scalar functions from a vector function:","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"julia> MOI.Utilities.scalarize(g) # Returns a vector [f, 2 * f].\n2-element Vector{MathOptInterface.ScalarAffineFunction{Int64}}:\n (2) + (1) MOI.VariableIndex(1)\n (4) + (2) MOI.VariableIndex(1)","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"note: Note\nUtilities.eachscalar returns an iterator on the dimensions, which serves the same purpose as Utilities.scalarize.","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"output_dimension returns the number of dimensions of the output of a function:","category":"page"},{"location":"moi/tutorials/manipulating_expressions/","page":"Manipulating expressions","title":"Manipulating expressions","text":"julia> MOI.output_dimension(g)\n2","category":"page"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"EditURL = \"https://github.com/jump-dev/MosekTools.jl/blob/v0.15.1/README.md\"","category":"page"},{"location":"packages/MosekTools/#MosekTools.jl","page":"jump-dev/MosekTools.jl","title":"MosekTools.jl","text":"","category":"section"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"MosekTools.jl is the MathOptInterface.jl implementation for the MOSEK solver.","category":"page"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"The low-level solver API for MOSEK is found in the package Mosek.jl.","category":"page"},{"location":"packages/MosekTools/#Affiliation","page":"jump-dev/MosekTools.jl","title":"Affiliation","text":"","category":"section"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"MosekTools.jl is maintained by the JuMP community and is not officially supported by MOSEK. However, Mosek.jl is an officially supported product of MOSEK.","category":"page"},{"location":"packages/MosekTools/#License","page":"jump-dev/MosekTools.jl","title":"License","text":"","category":"section"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"MosekTools.jl is licensed under the MIT License.","category":"page"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"The underlying solver is a closed-source commercial product for which you must obtain a license.","category":"page"},{"location":"packages/MosekTools/#Installation","page":"jump-dev/MosekTools.jl","title":"Installation","text":"","category":"section"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"The latest release of this package and the master branch are to be used with the latest release of Mosek.jl (which uses MOSEK v10).","category":"page"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"To use MOSEK v9 (resp. v8), use the v0.12.x (resp. v0.7.x) releases of this package, and the mosekv9 (resp. mosekv8) branch and v1.2.x (resp. v0.9.x) releases of Mosek.jl.","category":"page"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"See the following table for a summary:","category":"page"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"MOSEK Mosek.jl MosekTools.jl release MosekTools.jl branch\nv10 v10 v0.13 master\nv9 v0.12 v0.12 mosekv9\nv8 v0.9 v0.7 mosekv8","category":"page"},{"location":"packages/MosekTools/#Use-with-JuMP","page":"jump-dev/MosekTools.jl","title":"Use with JuMP","text":"","category":"section"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"using JuMP\nusing MosekTools\nmodel = Model(Mosek.Optimizer)\nset_attribute(model, \"QUIET\", true)\nset_attribute(model, \"INTPNT_CO_TOL_DFEAS\", 1e-7)","category":"page"},{"location":"packages/MosekTools/#Options","page":"jump-dev/MosekTools.jl","title":"Options","text":"","category":"section"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"The parameter QUIET is a special parameter that when set to true disables all Mosek printing output.","category":"page"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"All other parameters can be found in the Mosek documentation.","category":"page"},{"location":"packages/MosekTools/","page":"jump-dev/MosekTools.jl","title":"jump-dev/MosekTools.jl","text":"Note that the prefix MSK_IPAR_ (for integer parameters), MSK_DPAR_ (for floating point parameters) or MSK_SPAR_ (for string parameters) are optional. If they are not given, they are inferred from the type of the value. For example, in the example above, as 1e-7 is a floating point number, the parameters name used is MSK_DPAR_INTPNT_CO_TOL_DFEAS.","category":"page"},{"location":"developers/style/#Style-guide-and-design-principles","page":"Style Guide","title":"Style guide and design principles","text":"","category":"section"},{"location":"developers/style/#Style-guide","page":"Style Guide","title":"Style guide","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"This section describes the coding style rules that apply to JuMP code and that we recommend for JuMP models and surrounding Julia code. The motivations for a style guide include:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"conveying best practices for writing readable and maintainable code\nreducing the amount of time spent on bike-shedding by establishing basic naming and formatting conventions\nlowering the barrier for new contributors by codifying the existing practices (for example, you can be more confident your code will pass review if you follow the style guide)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"In some cases, the JuMP style guide diverges from the Julia style guide. All such cases will be explicitly noted and justified.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"The JuMP style guide adopts many recommendations from the Google style guides.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"info: Info\nThe style guide is always a work in progress, and not all JuMP code follows the rules. When modifying JuMP, please fix the style violations of the surrounding code (that is, leave the code tidier than when you started). If large changes are needed, consider separating them into another PR.","category":"page"},{"location":"developers/style/#JuliaFormatter","page":"Style Guide","title":"JuliaFormatter","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"JuMP uses JuliaFormatter.jl as an auto-formatting tool.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"We use the options contained in .JuliaFormatter.toml.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"To format code, cd to the JuMP directory, then run:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"] add JuliaFormatter@1\nusing JuliaFormatter\nformat(\"docs\")\nformat(\"src\")\nformat(\"test\")","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"info: Info\nA continuous integration check verifies that all PRs made to JuMP have passed the formatter.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"The following sections outline extra style guide points that are not fixed automatically by JuliaFormatter.","category":"page"},{"location":"developers/style/#Abstract-types-and-composition","page":"Style Guide","title":"Abstract types and composition","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Specifying types for method arguments is mostly optional in Julia. The benefit of abstract method arguments is that it enables functions and types from one package to be used with functions and types from another package via multiple dispatch.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"However, abstractly typed methods have two main drawbacks:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"It's possible to find out that you are working with unexpected types deep in the call chain, potentially leading to hard-to-diagnose MethodErrors.\nUntyped function arguments can lead to correctness problems if the user's choice of input type does not satisfy the assumptions made by the author of the function.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"As a motivating example, consider the following function:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> function my_sum(x)\n y = 0.0\n for i in 1:length(x)\n y += x[i]\n end\n return y\n end\nmy_sum (generic function with 1 method)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"This function contains a number of implicit assumptions about the type of x:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"x supports 1-based getindex and implements length\nThe element type of x supports addition with 0.0, and then with the result of x + 0.0.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"info: Info\nAs a motivating example for the second point, VariableRef plus Float64 produces an AffExpr. Do not assume that +(::A, ::B) produces an instance of the type A or B.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"my_sum works as expected if the user passes in Vector{Float64}:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> my_sum([1.0, 2.0, 3.0])\n6.0","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"but it doesn't respect input types, for example returning a Float64 if the user passes Vector{Int}:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> my_sum([1, 2, 3])\n6.0","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"but it throws a MethodError if the user passes String:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> my_sum(\"abc\")\nERROR: MethodError: no method matching +(::Float64, ::Char)\n[...]","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"This particular MethodError is hard to debug, particularly for new users, because it mentions +, Float64, and Char, none of which were called or passed by the user.","category":"page"},{"location":"developers/style/#Dealing-with-MethodErrors","page":"Style Guide","title":"Dealing with MethodErrors","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"This section diverges from the Julia style guide, as well as other common guides like SciML. The following suggestions are intended to provide a friendlier experience for novice Julia programmers, at the cost of limiting the power and flexibility of advanced Julia programmers.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Code should follow the MethodError principle:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"info: The MethodError principle\nA user should see a MethodError only for methods that they called directly.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Bad:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"_internal_function(x::Integer) = x + 1\n# The user sees a MethodError for _internal_function when calling\n# public_function(\"a string\"). This is not very helpful.\npublic_function(x) = _internal_function(x)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Good:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"_internal_function(x::Integer) = x + 1\n# The user sees a MethodError for public_function when calling\n# public_function(\"a string\"). This is easy to understand.\npublic_function(x::Integer) = _internal_function(x)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"If it is hard to provide an error message at the top of the call chain, then the following pattern is also ok:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"_internal_function(x::Integer) = x + 1\nfunction _internal_function(x)\n error(\n \"Internal error. This probably means that you called \" *\n \"`public_function()`s with the wrong type.\",\n )\nend\npublic_function(x) = _internal_function(x)","category":"page"},{"location":"developers/style/#Dealing-with-correctness","page":"Style Guide","title":"Dealing with correctness","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Dealing with correctness is harder, because Julia has no way of formally specifying interfaces that abstract types must implement. Instead, here are two options that you can use when writing and interacting with generic code:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Option 1: use concrete types and let users extend new methods.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"In this option, explicitly restrict input arguments to concrete types that are tested and have been validated for correctness. For example:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> function my_sum_option_1(x::Vector{Float64})\n y = 0.0\n for i in 1:length(x)\n y += x[i]\n end\n return y\n end\nmy_sum_option_1 (generic function with 1 method)\n\njulia> my_sum_option_1([1.0, 2.0, 3.0])\n6.0","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Using concrete types satisfies the MethodError principle:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> my_sum_option_1(\"abc\")\nERROR: MethodError: no method matching my_sum_option_1(::String)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"and it allows other types to be supported in future by defining new methods:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> function my_sum_option_1(x::Array{T,N}) where {T<:Number,N}\n y = zero(T)\n for i in eachindex(x)\n y += x[i]\n end\n return y\n end\nmy_sum_option_1 (generic function with 2 methods)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Importantly, these methods do not have to be defined in the original package.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"info: Info\nSome usage of abstract types is okay. For example, in my_sum_option_1, we allowed the element type, T, to be a subtype of Number. This is fairly safe, but it still has an implicit assumption that T supports zero(T) and +(::T, ::T).","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Option 2: program defensively, and validate all assumptions.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"An alternative is to program defensively, and to rigorously document and validate all assumptions that the code makes. In particular:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"All assumptions on abstract types that aren't guaranteed by the definition of the abstract type (for example, optional methods without a fallback) should be documented.\nIf practical, the assumptions should be checked in code, and informative error messages should be provided to the user if the assumptions are not met. In general, these checks may be expensive, so you should prefer to do this once, at the highest level of the call-chain.\nTests should cover for a range of corner cases and argument types.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"For example:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"\"\"\"\n test_my_sum_defensive_assumptions(x::AbstractArray{T}) where {T}\n\nTest the assumptions made by `my_sum_defensive`.\n\"\"\"\nfunction test_my_sum_defensive_assumptions(x::AbstractArray{T}) where {T}\n try\n # Some types may not define zero.\n @assert zero(T) isa T\n # Check iteration supported\n @assert iterate(x) isa Union{Nothing,Tuple{T,Int}}\n # Check that + is defined\n @assert +(zero(T), zero(T)) isa Any\n catch err\n error(\n \"Unable to call my_sum_defensive(::$(typeof(x))) because \" *\n \"it failed an internal assumption\",\n )\n end\n return\nend\n\n\"\"\"\n my_sum_defensive(x::AbstractArray{T}) where {T}\n\nReturn the sum of the elements in the abstract array `x`.\n\n## Assumptions\n\nThis function makes the following assumptions:\n\n * That `zero(T)` is defined\n * That `x` supports the iteration interface\n * That `+(::T, ::T)` is defined\n\"\"\"\nfunction my_sum_defensive(x::AbstractArray{T}) where {T}\n test_my_sum_defensive_assumptions(x)\n y = zero(T)\n for xi in x\n y += xi\n end\n return y\nend\n\n# output\n\nmy_sum_defensive","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"This function works on Vector{Float64}:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> my_sum_defensive([1.0, 2.0, 3.0])\n6.0","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"as well as Matrix{Rational{Int}}:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> my_sum_defensive([(1//2) + (4//3)im; (6//5) + (7//11)im])\n17//10 + 65//33*im","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"and it throws an error when the assumptions aren't met:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"julia> my_sum_defensive(['a', 'b', 'c'])\nERROR: Unable to call my_sum_defensive(::Vector{Char}) because it failed an internal assumption\n[...]","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"As an alternative, you may choose not to call test_my_sum_defensive_assumptions within my_sum_defensive, and instead ask users of my_sum_defensive to call it in their tests.","category":"page"},{"location":"developers/style/#Juxtaposed-multiplication","page":"Style Guide","title":"Juxtaposed multiplication","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Only use juxtaposed multiplication when the right-hand side is a symbol.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Good:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"2x # Acceptable if there are space constraints.\n2 * x # This is preferred if space is not an issue.\n2 * (x + 1)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Bad:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"2(x + 1)","category":"page"},{"location":"developers/style/#Empty-vectors","page":"Style Guide","title":"Empty vectors","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"For a type T, T[] and Vector{T}() are equivalent ways to create an empty vector with element type T. Prefer T[] because it is more concise.","category":"page"},{"location":"developers/style/#Comments","page":"Style Guide","title":"Comments","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"For non-native speakers and for general clarity, comments in code must be proper English sentences with appropriate punctuation.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Good:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"# This is a comment demonstrating a good comment.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Bad:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"# a bad comment","category":"page"},{"location":"developers/style/#JuMP-macro-syntax","page":"Style Guide","title":"JuMP macro syntax","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"For consistency, always use parentheses.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Good:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"@variable(model, x >= 0)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Bad:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"@variable model x >= 0","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"For consistency, always use constant * variable as opposed to variable * constant. This makes it easier to read models in ambiguous cases like a * x.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Good:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"a = 4\n@constraint(model, 3 * x <= 1)\n@constraint(model, a * x <= 1)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Bad:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"a = 4\n@constraint(model, x * 3 <= 1)\n@constraint(model, x * a <= 1)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"In order to reduce boilerplate code, prefer the plural form of macros over lots of repeated calls to singular forms.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Good:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"@variables(model, begin\n x >= 0\n y >= 1\n z <= 2\nend)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Bad:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"@variable(model, x >= 0)\n@variable(model, y >= 1)\n@variable(model, z <= 2)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"An exception is made for calls with many keyword arguments, since these need to be enclosed in parentheses in order to parse properly.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Acceptable:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"@variable(model, x >= 0, start = 0.0, base_name = \"my_x\")\n@variable(model, y >= 1, start = 2.0)\n@variable(model, z <= 2, start = -1.0)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Also acceptable:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"@variables(model, begin\n x >= 0, (start = 0.0, base_name = \"my_x\")\n y >= 1, (start = 2.0)\n z <= 2, (start = -1.0)\nend)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"While we always use in for for-loops, it is acceptable to use = in the container declarations of JuMP macros.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Okay:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"@variable(model, x[i=1:3])","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Also okay:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"@variable(model, x[i in 1:3])","category":"page"},{"location":"developers/style/#Naming","page":"Style Guide","title":"Naming","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"module SomeModule end\nfunction some_function end\nconst SOME_CONSTANT = ...\nstruct SomeStruct\n some_field::SomeType\nend\n@enum SomeEnum ENUM_VALUE_A ENUM_VALUE_B\nsome_local_variable = ...\nsome_file.jl # Except for ModuleName.jl.","category":"page"},{"location":"developers/style/#Exported-and-non-exported-names","page":"Style Guide","title":"Exported and non-exported names","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Begin private module level functions and constants with an underscore. All other objects in the scope of a module should be exported. (See JuMP.jl for an example of how to do this.)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Names beginning with an underscore should only be used for distinguishing between exported (public) and non-exported (private) objects. Therefore, never begin the name of a local variable with an underscore.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"module MyModule\n\nexport public_function, PUBLIC_CONSTANT\n\nfunction _private_function()\n local_variable = 1\n return\nend\n\nfunction public_function end\n\nconst _PRIVATE_CONSTANT = 3.14159\nconst PUBLIC_CONSTANT = 1.41421\n\nend","category":"page"},{"location":"developers/style/#Use-of-underscores-within-names","page":"Style Guide","title":"Use of underscores within names","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"The Julia style guide recommends avoiding underscores \"when readable,\" for example, haskey, isequal, remotecall, and remotecall_fetch. This convention creates the potential for unnecessary bikeshedding and also forces the user to recall the presence/absence of an underscore, for example, \"was that argument named basename or base_name?\". For consistency, always use underscores in variable names and function names to separate words.","category":"page"},{"location":"developers/style/#Use-of-!","page":"Style Guide","title":"Use of !","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Julia has a convention of appending ! to a function name if the function modifies its arguments. We recommend to:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Omit ! when the name itself makes it clear that modification is taking place, for example, add_constraint and set_name. We depart from the Julia style guide because ! does not provide a reader with any additional information in this case, and adherence to this convention is not uniform even in base Julia itself (consider Base.println and Base.finalize).\nUse ! in all other cases. In particular it can be used to distinguish between modifying and non-modifying variants of the same function like scale and scale!.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Note that ! is not a self-documenting feature because it is still ambiguous which arguments are modified when multiple arguments are present. Be sure to document which arguments are modified in the method's docstring.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"See also the Julia style guide recommendations for ordering of function arguments.","category":"page"},{"location":"developers/style/#Abbreviations","page":"Style Guide","title":"Abbreviations","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Abbreviate names to make the code more readable, not to save typing. Don't arbitrarily delete letters from a word to abbreviate it (for example, indx). Use abbreviations consistently within a body of code (for example, do not mix con and constr, idx and indx).","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Common abbreviations:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"num for number\ncon for constraint","category":"page"},{"location":"developers/style/#No-one-letter-variable-names","page":"Style Guide","title":"No one-letter variable names","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Where possible, avoid one-letter variable names.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Use model = Model() instead of m = Model()","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Exceptions are made for indices in loops.","category":"page"},{"location":"developers/style/#@enum-vs.-Symbol","page":"Style Guide","title":"@enum vs. Symbol","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"The @enum macro lets you define types with a finite number of values that are explicitly enumerated (like enum in C/C++). Symbols are lightweight strings that are used to represent identifiers in Julia (for example, :x).","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"@enum provides type safety and can have docstrings attached to explain the possible values. Use @enums when applicable, for example, for reporting statuses. Use strings to provide long-form additional information like error messages.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Use of Symbol should typically be reserved for identifiers, for example, for lookup in the JuMP model (model[:my_variable]).","category":"page"},{"location":"developers/style/#using-vs.-import","page":"Style Guide","title":"using vs. import","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"using ModuleName brings all symbols exported by the module ModuleName into scope, while import ModuleName brings only the module itself into scope. (See the Julia manual) for examples and more details.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"For the same reason that from import * is not recommended in python (PEP 8), avoid using ModuleName except in throw-away scripts or at the REPL. The using statement makes it harder to track where symbols come from and exposes the code to ambiguities when two modules export the same symbol.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Prefer using ModuleName: x, p to import ModuleName.x, ModuleName.p and import MyModule: x, p because the import versions allow method extension without qualifying with the module name.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Similarly, using ModuleName: ModuleName is an acceptable substitute for import ModuleName, because it does not bring all symbols exported by ModuleName into scope. However, we prefer import ModuleName for consistency.","category":"page"},{"location":"developers/style/#Documentation","page":"Style Guide","title":"Documentation","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"This section describes the writing style that should be used when writing documentation for JuMP (and supporting packages).","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"We can recommend the documentation style guides by Divio, Google, and Write the Docs as general reading for those writing documentation. This guide delegates a thorough handling of the topic to those guides and instead elaborates on the points more specific to Julia and documentation that use Documenter.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Be concise\nUse lists instead of long sentences\nUse numbered lists when describing a sequence, for example, (1) do X, (2) then Y\nUse bullet points when the items are not ordered\nExample code should be covered by doctests\nWhen a word is a Julia symbol and not an English word, enclose it with backticks. In addition, if it has a docstring in this doc add a link using @ref. If it is a plural, add the \"s\" after the closing backtick. For example,\n[`VariableRef`](@ref)s\nUse @meta blocks for TODOs and other comments that shouldn't be visible to readers. For example,\n```@meta\n# TODO: Mention also X, Y, and Z.\n```","category":"page"},{"location":"developers/style/#Docstrings","page":"Style Guide","title":"Docstrings","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Every exported object needs a docstring\nAll examples in docstrings should be jldoctests\nAlways use complete English sentences with proper punctuation\nDo not terminate lists with punctuation (for example, as in this doc)","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Here is an example:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"\"\"\"\n signature(args; kwargs...)\n\nShort sentence describing the function.\n\nOptional: add a slightly longer paragraph describing the function.\n\n## Notes\n\n - List any notes that the user should be aware of\n\n## Examples\n\n```jldoctest\njulia> 1 + 1\n2\n```\n\"\"\"","category":"page"},{"location":"developers/style/#Testing","page":"Style Guide","title":"Testing","text":"","category":"section"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Use a module to encapsulate tests, and structure all tests as functions. This avoids leaking local variables between tests.","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Here is a basic skeleton:","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"module TestPkg\n\nusing Test\n\nfunction runtests()\n for name in names(@__MODULE__; all = true)\n if startswith(\"$(name)\", \"test_\")\n @testset \"$(name)\" begin\n getfield(@__MODULE__, name)()\n end\n end\n end\n return\nend\n\n_helper_function() = 2\n\nfunction test_addition()\n @test 1 + 1 == _helper_function()\n return\nend\n\nend # module TestPkg\n\nTestPkg.runtests()","category":"page"},{"location":"developers/style/","page":"Style Guide","title":"Style Guide","text":"Break the tests into multiple files, with one module per file, so that subsets of the codebase can be tested by calling include with the relevant file.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/submodules/Test/overview.md\"","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"CurrentModule = MathOptInterface\nDocTestSetup = quote\n import MathOptInterface as MOI\nend\nDocTestFilters = [r\"MathOptInterface|MOI\"]","category":"page"},{"location":"moi/submodules/Test/overview/#test_module","page":"Overview","title":"The Test submodule","text":"","category":"section"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"The Test submodule provides tools to help solvers implement unit tests in order to ensure they implement the MathOptInterface API correctly, and to check for solver-correctness.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"We use a centralized repository of tests, so that if we find a bug in one solver, instead of adding a test to that particular repository, we add it here so that all solvers can benefit.","category":"page"},{"location":"moi/submodules/Test/overview/#How-to-test-a-solver","page":"Overview","title":"How to test a solver","text":"","category":"section"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"The skeleton below can be used for the wrapper test file of a solver named FooBar.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"# ============================ /test/MOI_wrapper.jl ============================\nmodule TestFooBar\n\nimport FooBar\nusing Test\n\nimport MathOptInterface as MOI\n\nconst OPTIMIZER = MOI.instantiate(\n MOI.OptimizerWithAttributes(FooBar.Optimizer, MOI.Silent() => true),\n)\n\nconst BRIDGED = MOI.instantiate(\n MOI.OptimizerWithAttributes(FooBar.Optimizer, MOI.Silent() => true),\n with_bridge_type = Float64,\n)\n\n# See the docstring of MOI.Test.Config for other arguments.\nconst CONFIG = MOI.Test.Config(\n # Modify tolerances as necessary.\n atol = 1e-6,\n rtol = 1e-6,\n # Use MOI.LOCALLY_SOLVED for local solvers.\n optimal_status = MOI.OPTIMAL,\n # Pass attributes or MOI functions to `exclude` to skip tests that\n # rely on this functionality.\n exclude = Any[MOI.VariableName, MOI.delete],\n)\n\n\"\"\"\n runtests()\n\nThis function runs all functions in the this Module starting with `test_`.\n\"\"\"\nfunction runtests()\n for name in names(@__MODULE__; all = true)\n if startswith(\"$(name)\", \"test_\")\n @testset \"$(name)\" begin\n getfield(@__MODULE__, name)()\n end\n end\n end\nend\n\n\"\"\"\n test_runtests()\n\nThis function runs all the tests in MathOptInterface.Test.\n\nPass arguments to `exclude` to skip tests for functionality that is not\nimplemented or that your solver doesn't support.\n\"\"\"\nfunction test_runtests()\n MOI.Test.runtests(\n BRIDGED,\n CONFIG,\n exclude = [\n \"test_attribute_NumberOfThreads\",\n \"test_quadratic_\",\n ],\n # This argument is useful to prevent tests from failing on future\n # releases of MOI that add new tests. Don't let this number get too far\n # behind the current MOI release though. You should periodically check\n # for new tests to fix bugs and implement new features.\n exclude_tests_after = v\"0.10.5\",\n )\n return\nend\n\n\"\"\"\n test_SolverName()\n\nYou can also write new tests for solver-specific functionality. Write each new\ntest as a function with a name beginning with `test_`.\n\"\"\"\nfunction test_SolverName()\n @test MOI.get(FooBar.Optimizer(), MOI.SolverName()) == \"FooBar\"\n return\nend\n\nend # module TestFooBar\n\n# This line at tne end of the file runs all the tests!\nTestFooBar.runtests()","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Then modify your runtests.jl file to include the MOI_wrapper.jl file:","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"# ============================ /test/runtests.jl ============================\n\nusing Test\n\n@testset \"MOI\" begin\n include(\"test/MOI_wrapper.jl\")\nend","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"info: Info\nThe optimizer BRIDGED constructed with instantiate automatically bridges constraints that are not supported by OPTIMIZER using the bridges listed in Bridges. It is recommended for an implementation of MOI to only support constraints that are natively supported by the solver and let bridges transform the constraint to the appropriate form. For this reason it is expected that tests may not pass if OPTIMIZER is used instead of BRIDGED.","category":"page"},{"location":"moi/submodules/Test/overview/#How-to-debug-a-failing-test","page":"Overview","title":"How to debug a failing test","text":"","category":"section"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"When writing a solver, it's likely that you will initially fail many tests. Some failures will be bugs, but other failures you may choose to exclude.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"There are two ways to exclude tests:","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Exclude tests whose names contain a string using:\nMOI.Test.runtests(\n model,\n config;\n exclude = String[\"test_to_exclude\", \"test_conic_\"],\n)\nThis will exclude tests whose name contains either of the two strings provided.\nExclude tests which rely on specific functionality using:\nMOI.Test.Config(exclude = Any[MOI.VariableName, MOI.optimize!])\nThis will exclude tests which use the MOI.VariableName attribute, or which call MOI.optimize!.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Each test that fails can be independently called as:","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"model = FooBar.Optimizer()\nconfig = MOI.Test.Config()\nMOI.empty!(model)\nMOI.Test.test_category_name_that_failed(model, config)","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"You can look-up the source code of the test that failed by searching for it in the src/Test/test_category.jl file.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"tip: Tip\nEach test function also has a docstring that explains what the test is for. Use ? MOI.Test.test_category_name_that_failed from the REPL to read it.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Periodically, you should re-run excluded tests to see if they now pass. The easiest way to do this is to swap the exclude keyword argument of runtests to include. For example:","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"MOI.Test.runtests(\n model,\n config;\n exclude = String[\"test_to_exclude\", \"test_conic_\"],\n)","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"becomes","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"MOI.Test.runtests(\n model,\n config;\n include = String[\"test_to_exclude\", \"test_conic_\"],\n)","category":"page"},{"location":"moi/submodules/Test/overview/#How-to-add-a-test","page":"Overview","title":"How to add a test","text":"","category":"section"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"To detect bugs in solvers, we add new tests to MOI.Test.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"As an example, ECOS errored calling optimize! twice in a row. (See ECOS.jl PR #72.) We could add a test to ECOS.jl, but that would only stop us from re-introducing the bug to ECOS.jl in the future, but it would not catch other solvers in the ecosystem with the same bug. Instead, if we add a test to MOI.Test, then all solvers will also check that they handle a double optimize call.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"For this test, we care about correctness, rather than performance. therefore, we don't expect solvers to efficiently decide that they have already solved the problem, only that calling optimize! twice doesn't throw an error or give the wrong answer.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Step 1","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Install the MathOptInterface julia package in dev mode:","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"julia> ]\n(@v1.6) pkg> dev MathOptInterface","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Step 2","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"From here on, proceed with making the following changes in the ~/.julia/dev/MathOptInterface folder (or equivalent dev path on your machine).","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Step 3","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Since the double-optimize error involves solving an optimization problem, add a new test to src/Test/test_solve.jl:","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"\"\"\"\n test_unit_optimize!_twice(model::MOI.ModelLike, config::Config)\n\nTest that calling `MOI.optimize!` twice does not error.\n\nThis problem was first detected in ECOS.jl PR#72:\nhttps://github.com/jump-dev/ECOS.jl/pull/72\n\"\"\"\nfunction test_unit_optimize!_twice(\n model::MOI.ModelLike,\n config::Config{T},\n) where {T}\n # Use the `@requires` macro to check conditions that the test function\n # requires to run. Models failing this `@requires` check will silently skip\n # the test.\n @requires MOI.supports_constraint(\n model,\n MOI.VariableIndex,\n MOI.GreaterThan{Float64},\n )\n @requires _supports(config, MOI.optimize!)\n # If needed, you can test that the model is empty at the start of the test.\n # You can assume that this will be the case for tests run via `runtests`.\n # User's calling tests individually need to call `MOI.empty!` themselves.\n @test MOI.is_empty(model)\n # Create a simple model. Try to make this as simple as possible so that the\n # majority of solvers can run the test.\n x = MOI.add_variable(model)\n MOI.add_constraint(model, x, MOI.GreaterThan(one(T)))\n MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)\n MOI.set(\n model,\n MOI.ObjectiveFunction{MOI.VariableIndex}(),\n x,\n )\n # The main component of the test: does calling `optimize!` twice error?\n MOI.optimize!(model)\n MOI.optimize!(model)\n # Check we have a solution.\n @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL\n # There is a three-argument version of `Base.isapprox` for checking\n # approximate equality based on the tolerances defined in `config`:\n @test isapprox(MOI.get(model, MOI.VariablePrimal(), x), one(T), config)\n # For code-style, these tests should always `return` `nothing`.\n return\nend","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"info: Info\nMake sure the function is agnostic to the number type T; don't assume it is a Float64 capable solver.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"We also need to write a test for the test. Place this function immediately below the test you just wrote in the same file:","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"function setup_test(\n ::typeof(test_unit_optimize!_twice),\n model::MOI.Utilities.MockOptimizer,\n ::Config,\n)\n MOI.Utilities.set_mock_optimize!(\n model,\n (mock::MOI.Utilities.MockOptimizer) -> MOIU.mock_optimize!(\n mock,\n MOI.OPTIMAL,\n (MOI.FEASIBLE_POINT, [1.0]),\n ),\n )\n return\nend","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Finally, you also need to implement Test.version_added. If we added this test when the latest released version of MOI was v0.10.5, define:","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"version_added(::typeof(test_unit_optimize!_twice)) = v\"0.10.6\"","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Step 6","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"Commit the changes to git from ~/.julia/dev/MathOptInterface and submit the PR for review.","category":"page"},{"location":"moi/submodules/Test/overview/","page":"Overview","title":"Overview","text":"tip: Tip\nIf you need help writing a test, open an issue on GitHub, or ask the Developer Chatroom.","category":"page"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/submodules/Utilities/reference.md\"","category":"page"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"CurrentModule = MathOptInterface\nDocTestSetup = quote\n import MathOptInterface as MOI\nend\nDocTestFilters = [r\"MathOptInterface|MOI\"]","category":"page"},{"location":"moi/submodules/Utilities/reference/#Utilities.Model","page":"API Reference","title":"Utilities.Model","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.Model","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.Model","page":"API Reference","title":"MathOptInterface.Utilities.Model","text":"MOI.Utilities.Model{T}() where {T}\n\nAn implementation of ModelLike that supports all functions and sets defined in MOI. It is parameterized by the coefficient type.\n\nExamples\n\njulia> import MathOptInterface as MOI\n\njulia> model = MOI.Utilities.Model{Float64}()\nMOIU.Model{Float64}\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#Utilities.UniversalFallback","page":"API Reference","title":"Utilities.UniversalFallback","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.UniversalFallback","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.UniversalFallback","page":"API Reference","title":"MathOptInterface.Utilities.UniversalFallback","text":"UniversalFallback\n\nThe UniversalFallback can be applied on a MOI.ModelLike model to create the model UniversalFallback(model) supporting any constraint and attribute. This allows to have a specialized implementation in model for performance critical constraints and attributes while still supporting other attributes with a small performance penalty. Note that model is unaware of constraints and attributes stored by UniversalFallback so this is not appropriate if model is an optimizer (for this reason, MOI.optimize! has not been implemented). In that case, optimizer bridges should be used instead.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#Utilities.@model","page":"API Reference","title":"Utilities.@model","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.@model\nUtilities.GenericModel\nUtilities.GenericOptimizer","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.@model","page":"API Reference","title":"MathOptInterface.Utilities.@model","text":"macro model(\n model_name,\n scalar_sets,\n typed_scalar_sets,\n vector_sets,\n typed_vector_sets,\n scalar_functions,\n typed_scalar_functions,\n vector_functions,\n typed_vector_functions,\n is_optimizer = false\n)\n\nCreates a type model_name implementing the MOI model interface and supporting all combinations of the provided functions and sets.\n\nEach typed_ scalar/vector sets/functions argument is a tuple of types. A type is \"typed\" if it has a coefficient {T} as the first type parameter.\n\nTuple syntax\n\nTo give no set/function, write (). To give one set or function X, write (X,).\n\nis_optimizer\n\nIf is_optimizer = true, the resulting struct is a of GenericOptimizer, which is a subtype of MOI.AbstractOptimizer, otherwise, it is a GenericModel, which is a subtype of MOI.ModelLike.\n\nVariableIndex\n\nThe function MOI.VariableIndex must not be given in scalar_functions.\nThe model supports MOI.VariableIndex-in-S constraints where S is MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval, MOI.Integer, MOI.ZeroOne, MOI.Semicontinuous or MOI.Semiinteger.\nThe sets supported with MOI.VariableIndex cannot be controlled from the macro; use UniversalFallback to support more sets.\n\nExamples\n\nThe model describing a linear program would be:\n\n@model(\n LPModel, # model_name\n (), # untyped scalar sets\n (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), # typed scalar sets\n (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives), # untyped vector sets\n (), # typed vector sets\n (), # untyped scalar functions\n (MOI.ScalarAffineFunction,), # typed scalar functions\n (MOI.VectorOfVariables,), # untyped vector functions\n (MOI.VectorAffineFunction,), # typed vector functions\n false, # is_optimizer\n)\n\n\n\n\n\n","category":"macro"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.GenericModel","page":"API Reference","title":"MathOptInterface.Utilities.GenericModel","text":"mutable struct GenericModel{T,O,V,C} <: AbstractModelLike{T}\n\nImplements a model supporting coefficients of type T and:\n\nAn objective function stored in .objective::O\nVariables and VariableIndex constraints stored in .variable_bounds::V\nF-in-S constraints (excluding VariableIndex constraints) stored in .constraints::C\n\nAll interactions take place via the MOI interface, so the types O, V, and C must implement the API as needed for their functionality.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.GenericOptimizer","page":"API Reference","title":"MathOptInterface.Utilities.GenericOptimizer","text":"mutable struct GenericOptimizer{T,O,V,C} <: AbstractOptimizer{T}\n\nImplements a model supporting coefficients of type T and:\n\nAn objective function stored in .objective::O\nVariables and VariableIndex constraints stored in .variable_bounds::V\nF-in-S constraints (excluding VariableIndex constraints) stored in .constraints::C\n\nAll interactions take place via the MOI interface, so the types O, V, and C must implement the API as needed for their functionality.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#.objective","page":"API Reference","title":".objective","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.ObjectiveContainer","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.ObjectiveContainer","page":"API Reference","title":"MathOptInterface.Utilities.ObjectiveContainer","text":"ObjectiveContainer{T}\n\nA helper struct to simplify the handling of objective functions in Utilities.Model.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#.variables","page":"API Reference","title":".variables","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.VariablesContainer\nUtilities.FreeVariables","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.VariablesContainer","page":"API Reference","title":"MathOptInterface.Utilities.VariablesContainer","text":"struct VariablesContainer{T} <: AbstractVectorBounds\n set_mask::Vector{UInt16}\n lower::Vector{T}\n upper::Vector{T}\nend\n\nA struct for storing variables and VariableIndex-related constraints. Used in MOI.Utilities.Model by default.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.FreeVariables","page":"API Reference","title":"MathOptInterface.Utilities.FreeVariables","text":"mutable struct FreeVariables <: MOI.ModelLike\n n::Int64\n FreeVariables() = new(0)\nend\n\nA struct for storing free variables that can be used as the variables field of GenericModel or GenericModel. It represents a model that does not support any constraint nor objective function.\n\nExample\n\nThe following model type represents a conic model in geometric form. As opposed to VariablesContainer, FreeVariables does not support constraint bounds so they are bridged into an affine constraint in the MOI.Nonnegatives cone as expected for the geometric conic form.\n\njulia> MOI.Utilities.@product_of_sets(\n Cones,\n MOI.Zeros,\n MOI.Nonnegatives,\n MOI.SecondOrderCone,\n MOI.PositiveSemidefiniteConeTriangle,\n);\n\njulia> const ConicModel{T} = MOI.Utilities.GenericOptimizer{\n T,\n MOI.Utilities.ObjectiveContainer{T},\n MOI.Utilities.FreeVariables,\n MOI.Utilities.MatrixOfConstraints{\n T,\n MOI.Utilities.MutableSparseMatrixCSC{\n T,\n Int,\n MOI.Utilities.OneBasedIndexing,\n },\n Vector{T},\n Cones{T},\n },\n};\n\njulia> model = MOI.instantiate(ConicModel{Float64}, with_bridge_type=Float64);\n\njulia> x = MOI.add_variable(model)\nMathOptInterface.VariableIndex(1)\n\njulia> c = MOI.add_constraint(model, x, MOI.GreaterThan(1.0))\nMathOptInterface.ConstraintIndex{MathOptInterface.VariableIndex, MathOptInterface.GreaterThan{Float64}}(1)\n\njulia> MOI.Bridges.is_bridged(model, c)\ntrue\n\njulia> bridge = MOI.Bridges.bridge(model, c)\nMathOptInterface.Bridges.Constraint.VectorizeBridge{Float64, MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives, MathOptInterface.VariableIndex}(MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives}(1), 1.0)\n\njulia> bridge.vector_constraint\nMathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives}(1)\n\njulia> MOI.Bridges.is_bridged(model, bridge.vector_constraint)\nfalse\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#.constraints","page":"API Reference","title":".constraints","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.VectorOfConstraints\nUtilities.StructOfConstraints\nUtilities.@struct_of_constraints_by_function_types\nUtilities.@struct_of_constraints_by_set_types\nUtilities.struct_of_constraint_code","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.VectorOfConstraints","page":"API Reference","title":"MathOptInterface.Utilities.VectorOfConstraints","text":"mutable struct VectorOfConstraints{\n F<:MOI.AbstractFunction,\n S<:MOI.AbstractSet,\n} <: MOI.ModelLike\n constraints::CleverDicts.CleverDict{\n MOI.ConstraintIndex{F,S},\n Tuple{F,S},\n typeof(CleverDicts.key_to_index),\n typeof(CleverDicts.index_to_key),\n }\nend\n\nA struct storing F-in-S constraints as a mapping between the constraint indices to the corresponding tuple of function and set.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.StructOfConstraints","page":"API Reference","title":"MathOptInterface.Utilities.StructOfConstraints","text":"abstract type StructOfConstraints <: MOI.ModelLike end\n\nA struct storing a subfields other structs storing constraints of different types.\n\nSee Utilities.@struct_of_constraints_by_function_types and Utilities.@struct_of_constraints_by_set_types.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.@struct_of_constraints_by_function_types","page":"API Reference","title":"MathOptInterface.Utilities.@struct_of_constraints_by_function_types","text":"Utilities.@struct_of_constraints_by_function_types(name, func_types...)\n\nGiven a vector of n function types (F1, F2,..., Fn) in func_types, defines a subtype of StructOfConstraints of name name and which type parameters {T, C1, C2, ..., Cn}. It contains n field where the ith field has type Ci and stores the constraints of function type Fi.\n\nThe expression Fi can also be a union in which case any constraint for which the function type is in the union is stored in the field with type Ci.\n\n\n\n\n\n","category":"macro"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.@struct_of_constraints_by_set_types","page":"API Reference","title":"MathOptInterface.Utilities.@struct_of_constraints_by_set_types","text":"Utilities.@struct_of_constraints_by_set_types(name, func_types...)\n\nGiven a vector of n set types (S1, S2,..., Sn) in func_types, defines a subtype of StructOfConstraints of name name and which type parameters {T, C1, C2, ..., Cn}. It contains n field where the ith field has type Ci and stores the constraints of set type Si. The expression Si can also be a union in which case any constraint for which the set type is in the union is stored in the field with type Ci. This can be useful if Ci is a MatrixOfConstraints in order to concatenate the coefficients of constraints of several different set types in the same matrix.\n\n\n\n\n\n","category":"macro"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.struct_of_constraint_code","page":"API Reference","title":"MathOptInterface.Utilities.struct_of_constraint_code","text":"struct_of_constraint_code(struct_name, types, field_types = nothing)\n\nGiven a vector of n Union{SymbolFun,_UnionSymbolFS{SymbolFun}} or Union{SymbolSet,_UnionSymbolFS{SymbolSet}} in types, defines a subtype of StructOfConstraints of name name and which type parameters {T, F1, F2, ..., Fn} if field_types is nothing and a {T} otherwise. It contains n field where the ith field has type Ci if field_types is nothing and type field_types[i] otherwise. If types is vector of Union{SymbolFun,_UnionSymbolFS{SymbolFun}} (resp. Union{SymbolSet,_UnionSymbolFS{SymbolSet}}) then the constraints of that function (resp. set) type are stored in the corresponding field.\n\nThis function is used by the macros @model, @struct_of_constraints_by_function_types and @struct_of_constraints_by_set_types.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#Caching-optimizer","page":"API Reference","title":"Caching optimizer","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.CachingOptimizer\nUtilities.attach_optimizer\nUtilities.reset_optimizer\nUtilities.drop_optimizer\nUtilities.state\nUtilities.mode","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.CachingOptimizer","page":"API Reference","title":"MathOptInterface.Utilities.CachingOptimizer","text":"CachingOptimizer\n\nCachingOptimizer is an intermediate layer that stores a cache of the model and links it with an optimizer. It supports incremental model construction and modification even when the optimizer doesn't.\n\nConstructors\n\n CachingOptimizer(cache::MOI.ModelLike, optimizer::AbstractOptimizer)\n\nCreates a CachingOptimizer in AUTOMATIC mode, with the optimizer optimizer.\n\nThe type of the optimizer returned is CachingOptimizer{typeof(optimizer), typeof(cache)} so it does not support the function reset_optimizer(::CachingOptimizer, new_optimizer) if the type of new_optimizer is different from the type of optimizer.\n\n CachingOptimizer(cache::MOI.ModelLike, mode::CachingOptimizerMode)\n\nCreates a CachingOptimizer in the NO_OPTIMIZER state and mode mode.\n\nThe type of the optimizer returned is CachingOptimizer{MOI.AbstractOptimizer,typeof(cache)} so it does support the function reset_optimizer(::CachingOptimizer, new_optimizer) if the type of new_optimizer is different from the type of optimizer.\n\nAbout the type\n\nStates\n\nA CachingOptimizer may be in one of three possible states (CachingOptimizerState):\n\nNO_OPTIMIZER: The CachingOptimizer does not have any optimizer.\nEMPTY_OPTIMIZER: The CachingOptimizer has an empty optimizer. The optimizer is not synchronized with the cached model.\nATTACHED_OPTIMIZER: The CachingOptimizer has an optimizer, and it is synchronized with the cached model.\n\nModes\n\nA CachingOptimizer has two modes of operation (CachingOptimizerMode):\n\nMANUAL: The only methods that change the state of the CachingOptimizer are Utilities.reset_optimizer, Utilities.drop_optimizer, and Utilities.attach_optimizer. Attempting to perform an operation in the incorrect state results in an error.\nAUTOMATIC: The CachingOptimizer changes its state when necessary. For example, optimize! will automatically call attach_optimizer (an optimizer must have been previously set). Attempting to add a constraint or perform a modification not supported by the optimizer results in a drop to EMPTY_OPTIMIZER mode.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.attach_optimizer","page":"API Reference","title":"MathOptInterface.Utilities.attach_optimizer","text":"attach_optimizer(model::CachingOptimizer)\n\nAttaches the optimizer to model, copying all model data into it. Can be called only from the EMPTY_OPTIMIZER state. If the copy succeeds, the CachingOptimizer will be in state ATTACHED_OPTIMIZER after the call, otherwise an error is thrown; see MOI.copy_to for more details on which errors can be thrown.\n\n\n\n\n\nMOIU.attach_optimizer(model::GenericModel)\n\nCall MOIU.attach_optimizer on the backend of model.\n\nCannot be called in direct mode.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.reset_optimizer","page":"API Reference","title":"MathOptInterface.Utilities.reset_optimizer","text":"reset_optimizer(m::CachingOptimizer, optimizer::MOI.AbstractOptimizer)\n\nSets or resets m to have the given empty optimizer optimizer.\n\nCan be called from any state. An assertion error will be thrown if optimizer is not empty.\n\nThe CachingOptimizer m will be in state EMPTY_OPTIMIZER after the call.\n\n\n\n\n\nreset_optimizer(m::CachingOptimizer)\n\nDetaches and empties the current optimizer. Can be called from ATTACHED_OPTIMIZER or EMPTY_OPTIMIZER state. The CachingOptimizer will be in state EMPTY_OPTIMIZER after the call.\n\n\n\n\n\nMOIU.reset_optimizer(model::GenericModel, optimizer::MOI.AbstractOptimizer)\n\nCall MOIU.reset_optimizer on the backend of model.\n\nCannot be called in direct mode.\n\n\n\n\n\nMOIU.reset_optimizer(model::GenericModel)\n\nCall MOIU.reset_optimizer on the backend of model.\n\nCannot be called in direct mode.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.drop_optimizer","page":"API Reference","title":"MathOptInterface.Utilities.drop_optimizer","text":"drop_optimizer(m::CachingOptimizer)\n\nDrops the optimizer, if one is present. Can be called from any state. The CachingOptimizer will be in state NO_OPTIMIZER after the call.\n\n\n\n\n\nMOIU.drop_optimizer(model::GenericModel)\n\nCall MOIU.drop_optimizer on the backend of model.\n\nCannot be called in direct mode.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.state","page":"API Reference","title":"MathOptInterface.Utilities.state","text":"state(m::CachingOptimizer)::CachingOptimizerState\n\nReturns the state of the CachingOptimizer m. See Utilities.CachingOptimizer.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.mode","page":"API Reference","title":"MathOptInterface.Utilities.mode","text":"mode(m::CachingOptimizer)::CachingOptimizerMode\n\nReturns the operating mode of the CachingOptimizer m. See Utilities.CachingOptimizer.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#Mock-optimizer","page":"API Reference","title":"Mock optimizer","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.MockOptimizer","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.MockOptimizer","page":"API Reference","title":"MathOptInterface.Utilities.MockOptimizer","text":"MockOptimizer\n\nMockOptimizer is a fake optimizer especially useful for testing. Its main feature is that it can store the values that should be returned for each attribute.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#Printing","page":"API Reference","title":"Printing","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.latex_formulation","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.latex_formulation","page":"API Reference","title":"MathOptInterface.Utilities.latex_formulation","text":"latex_formulation(model::MOI.ModelLike; kwargs...)\n\nWrap model in a type so that it can be pretty-printed as text/latex in a notebook like IJulia, or in Documenter.\n\nTo render the model, end the cell with latex_formulation(model), or call display(latex_formulation(model)) in to force the display of the model from inside a function.\n\nPossible keyword arguments are:\n\nsimplify_coefficients : Simplify coefficients if possible by omitting them or removing trailing zeros.\ndefault_name : The name given to variables with an empty name.\nprint_types : Print the MOI type of each function and set for clarity.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#Copy-utilities","page":"API Reference","title":"Copy utilities","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.default_copy_to\nUtilities.IndexMap\nUtilities.identity_index_map\nUtilities.ModelFilter","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.default_copy_to","page":"API Reference","title":"MathOptInterface.Utilities.default_copy_to","text":"default_copy_to(dest::MOI.ModelLike, src::MOI.ModelLike)\n\nA default implementation of MOI.copy_to(dest, src) for models that implement the incremental interface, i.e., MOI.supports_incremental_interface returns true.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.IndexMap","page":"API Reference","title":"MathOptInterface.Utilities.IndexMap","text":"IndexMap()\n\nThe dictionary-like object returned by MOI.copy_to.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.identity_index_map","page":"API Reference","title":"MathOptInterface.Utilities.identity_index_map","text":"identity_index_map(model::MOI.ModelLike)\n\nReturn an IndexMap that maps all variable and constraint indices of model to themselves.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.ModelFilter","page":"API Reference","title":"MathOptInterface.Utilities.ModelFilter","text":"ModelFilter(filter::Function, model::MOI.ModelLike)\n\nA layer to filter out various components of model.\n\nThe filter function takes a single argument, which is each element from the list returned by the attributes below. It returns true if the element should be visible in the filtered model and false otherwise.\n\nThe components that are filtered are:\n\nEntire constraint types via:\nMOI.ListOfConstraintTypesPresent\nIndividual constraints via:\nMOI.ListOfConstraintIndices{F,S}\nSpecific attributes via:\nMOI.ListOfModelAttributesSet\nMOI.ListOfConstraintAttributesSet\nMOI.ListOfVariableAttributesSet\n\nwarning: Warning\nThe list of attributes filtered may change in a future release. You should write functions that are generic and not limited to the five types listed above. Thus, you should probably define a fallback filter(::Any) = true.\n\nSee below for examples of how this works.\n\nnote: Note\nThis layer has a limited scope. It is intended by be used in conjunction with MOI.copy_to.\n\nExample: copy model excluding integer constraints\n\nUse the do syntax to provide a single function.\n\nfiltered_src = MOI.Utilities.ModelFilter(src) do item\n return item != (MOI.VariableIndex, MOI.Integer)\nend\nMOI.copy_to(dest, filtered_src)\n\nExample: copy model excluding names\n\nUse type dispatch to simplify the implementation:\n\nmy_filter(::Any) = true # Note the generic fallback!\nmy_filter(::MOI.VariableName) = false\nmy_filter(::MOI.ConstraintName) = false\nfiltered_src = MOI.Utilities.ModelFilter(my_filter, src)\nMOI.copy_to(dest, filtered_src)\n\nExample: copy irreducible infeasible subsystem\n\nmy_filter(::Any) = true # Note the generic fallback!\nfunction my_filter(ci::MOI.ConstraintIndex)\n status = MOI.get(dest, MOI.ConstraintConflictStatus(), ci)\n return status != MOI.NOT_IN_CONFLICT\nend\nfiltered_src = MOI.Utilities.ModelFilter(my_filter, src)\nMOI.copy_to(dest, filtered_src)\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#Penalty-relaxation","page":"API Reference","title":"Penalty relaxation","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.PenaltyRelaxation\nUtilities.ScalarPenaltyRelaxation","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.PenaltyRelaxation","page":"API Reference","title":"MathOptInterface.Utilities.PenaltyRelaxation","text":"PenaltyRelaxation(\n penalties = Dict{MOI.ConstraintIndex,Float64}();\n default::Union{Nothing,T} = 1.0,\n)\n\nA problem modifier that, when passed to MOI.modify, destructively modifies the model in-place to create a penalized relaxation of the constraints.\n\nwarning: Warning\nThis is a destructive routine that modifies the model in-place. If you don't want to modify the original model, use JuMP.copy_model to create a copy before calling MOI.modify.\n\nReformulation\n\nSee Utilities.ScalarPenaltyRelaxation for details of the reformulation.\n\nFor each constraint ci, the penalty passed to Utilities.ScalarPenaltyRelaxation is get(penalties, ci, default). If the value is nothing, because ci does not exist in penalties and default = nothing, then the constraint is skipped.\n\nReturn value\n\nMOI.modify(model, PenaltyRelaxation()) returns a Dict{MOI.ConstraintIndex,MOI.ScalarAffineFunction} that maps each constraint index to the corresponding y + z as a MOI.ScalarAffineFunction. In an optimal solution, query the value of these functions to compute the violation of each constraint.\n\nRelax a subset of constraints\n\nTo relax a subset of constraints, pass a penalties dictionary and set default = nothing.\n\nSupported constraint types\n\nThe penalty relaxation is currently limited to modifying MOI.ScalarAffineFunction and MOI.ScalarQuadraticFunction constraints in the linear sets MOI.LessThan, MOI.GreaterThan, MOI.EqualTo and MOI.Interval.\n\nIt does not include variable bound or integrality constraints, because these cannot be modified in-place.\n\nTo modify variable bounds, rewrite them as linear constraints.\n\nExamples\n\njulia> model = MOI.Utilities.Model{Float64}();\n\njulia> x = MOI.add_variable(model);\n\njulia> c = MOI.add_constraint(model, 1.0 * x, MOI.LessThan(2.0));\n\njulia> map = MOI.modify(model, MOI.Utilities.PenaltyRelaxation(default = 2.0));\n\njulia> print(model)\nMinimize ScalarAffineFunction{Float64}:\n 0.0 + 2.0 v[2]\n\nSubject to:\n\nScalarAffineFunction{Float64}-in-LessThan{Float64}\n 0.0 + 1.0 v[1] - 1.0 v[2] <= 2.0\n\nVariableIndex-in-GreaterThan{Float64}\n v[2] >= 0.0\n\njulia> map[c] isa MOI.ScalarAffineFunction{Float64}\ntrue\n\njulia> model = MOI.Utilities.Model{Float64}();\n\njulia> x = MOI.add_variable(model);\n\njulia> c = MOI.add_constraint(model, 1.0 * x, MOI.LessThan(2.0));\n\njulia> map = MOI.modify(model, MOI.Utilities.PenaltyRelaxation(Dict(c => 3.0)));\n\njulia> print(model)\nMinimize ScalarAffineFunction{Float64}:\n 0.0 + 3.0 v[2]\n\nSubject to:\n\nScalarAffineFunction{Float64}-in-LessThan{Float64}\n 0.0 + 1.0 v[1] - 1.0 v[2] <= 2.0\n\nVariableIndex-in-GreaterThan{Float64}\n v[2] >= 0.0\n\njulia> map[c] isa MOI.ScalarAffineFunction{Float64}\ntrue\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.ScalarPenaltyRelaxation","page":"API Reference","title":"MathOptInterface.Utilities.ScalarPenaltyRelaxation","text":"ScalarPenaltyRelaxation(penalty::T) where {T}\n\nA problem modifier that, when passed to MOI.modify, destructively modifies the constraint in-place to create a penalized relaxation of the constraint.\n\nwarning: Warning\nThis is a destructive routine that modifies the constraint in-place. If you don't want to modify the original model, use JuMP.copy_model to create a copy before calling MOI.modify.\n\nReformulation\n\nThe penalty relaxation modifies constraints of the form f(x) in S into f(x) + y - z in S, where y z ge 0, and then it introduces a penalty term into the objective of a times (y + z) (if minimizing, else -a), where a is penalty\n\nWhen S is MOI.LessThan or MOI.GreaterThan, we omit y or z respectively as a performance optimization.\n\nReturn value\n\nMOI.modify(model, ci, ScalarPenaltyRelaxation(penalty)) returns y + z as a MOI.ScalarAffineFunction. In an optimal solution, query the value of this function to compute the violation of the constraint.\n\nExamples\n\njulia> model = MOI.Utilities.Model{Float64}();\n\njulia> x = MOI.add_variable(model);\n\njulia> c = MOI.add_constraint(model, 1.0 * x, MOI.LessThan(2.0));\n\njulia> f = MOI.modify(model, c, MOI.Utilities.ScalarPenaltyRelaxation(2.0));\n\njulia> print(model)\nMinimize ScalarAffineFunction{Float64}:\n 0.0 + 2.0 v[2]\n\nSubject to:\n\nScalarAffineFunction{Float64}-in-LessThan{Float64}\n 0.0 + 1.0 v[1] - 1.0 v[2] <= 2.0\n\nVariableIndex-in-GreaterThan{Float64}\n v[2] >= 0.0\n\njulia> f isa MOI.ScalarAffineFunction{Float64}\ntrue\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MatrixOfConstraints","page":"API Reference","title":"MatrixOfConstraints","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.MatrixOfConstraints","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.MatrixOfConstraints","page":"API Reference","title":"MathOptInterface.Utilities.MatrixOfConstraints","text":"mutable struct MatrixOfConstraints{T,AT,BT,ST} <: MOI.ModelLike\n coefficients::AT\n constants::BT\n sets::ST\n caches::Vector{Any}\n are_indices_mapped::Vector{BitSet}\n final_touch::Bool\nend\n\nRepresent ScalarAffineFunction and VectorAffinefunction constraints in a matrix form where the linear coefficients of the functions are stored in the coefficients field, the constants of the functions or sets are stored in the constants field. Additional information about the sets are stored in the sets field.\n\nThis model can only be used as the constraints field of a MOI.Utilities.AbstractModel.\n\nWhen the constraints are added, they are stored in the caches field. They are only loaded in the coefficients and constants fields once MOI.Utilities.final_touch is called. For this reason, MatrixOfConstraints should not be used by an incremental interface. Use MOI.copy_to instead.\n\nThe constraints can be added in two different ways:\n\nWith add_constraint, in which case a canonicalized copy of the function is stored in caches.\nWith pass_nonvariable_constraints, in which case the functions and sets are stored themselves in caches without mapping the variable indices. The corresponding index in caches is added in are_indices_mapped. This avoids doing a copy of the function in case the getter of CanonicalConstraintFunction does not make a copy for the source model, e.g., this is the case of VectorOfConstraints.\n\nWe illustrate this with an example. Suppose a model is copied from a src::MOI.Utilities.Model to a bridged model with a MatrixOfConstraints. For all the types that are not bridged, the constraints will be copied with pass_nonvariable_constraints. Hence the functions stored in caches are exactly the same as the ones stored in src. This is ok since this is only during the copy_to operation during which src cannot be modified. On the other hand, for the types that are bridged, the functions added may contain duplicates even if the functions did not contain duplicates in src so duplicates are removed with MOI.Utilities.canonical.\n\nInterface\n\nThe .coefficients::AT type must implement:\n\nAT()\nMOI.empty(::AT)!\nMOI.Utilities.add_column\nMOI.Utilities.set_number_of_rows\nMOI.Utilities.allocate_terms\nMOI.Utilities.load_terms\nMOI.Utilities.final_touch\n\nThe .constants::BT type must implement:\n\nBT()\nBase.empty!(::BT)\nBase.resize(::BT)\nMOI.Utilities.load_constants\nMOI.Utilities.function_constants\nMOI.Utilities.set_from_constants\n\nThe .sets::ST type must implement:\n\nST()\nMOI.is_empty(::ST)\nMOI.empty(::ST)\nMOI.dimension(::ST)\nMOI.is_valid(::ST, ::MOI.ConstraintIndex)\nMOI.get(::ST, ::MOI.ListOfConstraintTypesPresent)\nMOI.get(::ST, ::MOI.NumberOfConstraints)\nMOI.get(::ST, ::MOI.ListOfConstraintIndices)\nMOI.Utilities.set_types\nMOI.Utilities.set_index\nMOI.Utilities.add_set\nMOI.Utilities.rows\nMOI.Utilities.final_touch\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#.coefficients","page":"API Reference","title":".coefficients","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.add_column\nUtilities.allocate_terms\nUtilities.set_number_of_rows\nUtilities.load_terms\nUtilities.final_touch\nUtilities.extract_function","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.add_column","page":"API Reference","title":"MathOptInterface.Utilities.add_column","text":"add_column(coefficients)::Nothing\n\nTell coefficients to pre-allocate datastructures as needed to store one column.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.allocate_terms","page":"API Reference","title":"MathOptInterface.Utilities.allocate_terms","text":"allocate_terms(coefficients, index_map, func)::Nothing\n\nTell coefficients that the terms of the function func where the variable indices are mapped with index_map will be loaded with load_terms.\n\nThe function func must be canonicalized before calling allocate_terms. See is_canonical.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.set_number_of_rows","page":"API Reference","title":"MathOptInterface.Utilities.set_number_of_rows","text":"set_number_of_rows(coefficients, n)::Nothing\n\nTell coefficients to pre-allocate datastructures as needed to store n rows.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.load_terms","page":"API Reference","title":"MathOptInterface.Utilities.load_terms","text":"load_terms(coefficients, index_map, func, offset)::Nothing\n\nLoads the terms of func to coefficients, mapping the variable indices with index_map.\n\nThe ith dimension of func is loaded at the (offset + i)th row of coefficients.\n\nThe function must be allocated first with allocate_terms.\n\nThe function func must be canonicalized, see is_canonical.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.final_touch","page":"API Reference","title":"MathOptInterface.Utilities.final_touch","text":"final_touch(coefficients)::Nothing\n\nInforms the coefficients that all functions have been added with load_terms. No more modification is allowed unless MOI.empty! is called.\n\nfinal_touch(sets)::Nothing\n\nInforms the sets that all functions have been added with add_set. No more modification is allowed unless MOI.empty! is called.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.extract_function","page":"API Reference","title":"MathOptInterface.Utilities.extract_function","text":"extract_function(coefficients, row::Integer, constant::T) where {T}\n\nReturn the MOI.ScalarAffineFunction{T} function corresponding to row row in coefficients.\n\nextract_function(\n coefficients,\n rows::UnitRange,\n constants::Vector{T},\n) where{T}\n\nReturn the MOI.VectorAffineFunction{T} function corresponding to rows rows in coefficients.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.MutableSparseMatrixCSC\nUtilities.AbstractIndexing\nUtilities.ZeroBasedIndexing\nUtilities.OneBasedIndexing","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.MutableSparseMatrixCSC","page":"API Reference","title":"MathOptInterface.Utilities.MutableSparseMatrixCSC","text":"mutable struct MutableSparseMatrixCSC{Tv,Ti<:Integer,I<:AbstractIndexing}\n indexing::I\n m::Int\n n::Int\n colptr::Vector{Ti}\n rowval::Vector{Ti}\n nzval::Vector{Tv}\n nz_added::Vector{Ti}\nend\n\nMatrix type loading sparse matrices in the Compressed Sparse Column format. The indexing used is indexing, see AbstractIndexing. The other fields have the same meaning than for SparseArrays.SparseMatrixCSC except that the indexing is different unless indexing is OneBasedIndexing. In addition, nz_added is used to cache the number of non-zero terms that have been added to each column due to the incremental nature of load_terms.\n\nThe matrix is loaded in 5 steps:\n\nMOI.empty! is called.\nMOI.Utilities.add_column and MOI.Utilities.allocate_terms are called in any order.\nMOI.Utilities.set_number_of_rows is called.\nMOI.Utilities.load_terms is called for each affine function.\nMOI.Utilities.final_touch is called.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.AbstractIndexing","page":"API Reference","title":"MathOptInterface.Utilities.AbstractIndexing","text":"abstract type AbstractIndexing end\n\nIndexing to be used for storing the row and column indices of MutableSparseMatrixCSC. See ZeroBasedIndexing and OneBasedIndexing.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.ZeroBasedIndexing","page":"API Reference","title":"MathOptInterface.Utilities.ZeroBasedIndexing","text":"struct ZeroBasedIndexing <: AbstractIndexing end\n\nZero-based indexing: the ith row or column has index i - 1. This is useful when the vectors of row and column indices need to be communicated to a library using zero-based indexing such as C libraries.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.OneBasedIndexing","page":"API Reference","title":"MathOptInterface.Utilities.OneBasedIndexing","text":"struct ZeroBasedIndexing <: AbstractIndexing end\n\nOne-based indexing: the ith row or column has index i. This enables an allocation-free conversion of MutableSparseMatrixCSC to SparseArrays.SparseMatrixCSC.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#.constants","page":"API Reference","title":".constants","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.load_constants\nUtilities.function_constants\nUtilities.set_from_constants","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.load_constants","page":"API Reference","title":"MathOptInterface.Utilities.load_constants","text":"load_constants(constants, offset, func_or_set)::Nothing\n\nThis function loads the constants of func_or_set in constants at an offset of offset. Where offset is the sum of the dimensions of the constraints already loaded. The storage should be preallocated with resize! before calling this function.\n\nThis function should be implemented to be usable as storage of constants for MatrixOfConstraints.\n\nThe constants are loaded in three steps:\n\nBase.empty! is called.\nBase.resize! is called with the sum of the dimensions of all constraints.\nMOI.Utilities.load_constants is called for each function for vector constraint or set for scalar constraint.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.function_constants","page":"API Reference","title":"MathOptInterface.Utilities.function_constants","text":"function_constants(constants, rows)\n\nThis function returns the function constants that were loaded with load_constants at the rows rows.\n\nThis function should be implemented to be usable as storage of constants for MatrixOfConstraints.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.set_from_constants","page":"API Reference","title":"MathOptInterface.Utilities.set_from_constants","text":"set_from_constants(constants, S::Type, rows)::S\n\nThis function returns an instance of the set S for which the constants where loaded with load_constants at the rows rows.\n\nThis function should be implemented to be usable as storage of constants for MatrixOfConstraints.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.Hyperrectangle","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.Hyperrectangle","page":"API Reference","title":"MathOptInterface.Utilities.Hyperrectangle","text":"struct Hyperrectangle{T} <: AbstractVectorBounds\n lower::Vector{T}\n upper::Vector{T}\nend\n\nA struct for the .constants field in MatrixOfConstraints.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#.sets","page":"API Reference","title":".sets","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.set_index\nUtilities.set_types\nUtilities.add_set\nUtilities.rows\nUtilities.num_rows\nUtilities.set_with_dimension","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.set_index","page":"API Reference","title":"MathOptInterface.Utilities.set_index","text":"set_index(sets, ::Type{S})::Union{Int,Nothing} where {S<:MOI.AbstractSet}\n\nReturn an integer corresponding to the index of the set type in the list given by set_types.\n\nIf S is not part of the list, return nothing.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.set_types","page":"API Reference","title":"MathOptInterface.Utilities.set_types","text":"set_types(sets)::Vector{Type}\n\nReturn the list of the types of the sets allowed in sets.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.add_set","page":"API Reference","title":"MathOptInterface.Utilities.add_set","text":"add_set(sets, i)::Int64\n\nAdd a scalar set of type index i.\n\nadd_set(sets, i, dim)::Int64\n\nAdd a vector set of type index i and dimension dim.\n\nBoth methods return a unique Int64 of the set that can be used to reference this set.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.rows","page":"API Reference","title":"MathOptInterface.Utilities.rows","text":"rows(sets, ci::MOI.ConstraintIndex)::Union{Int,UnitRange{Int}}\n\nReturn the rows in 1:MOI.dimension(sets) corresponding to the set of id ci.value.\n\nFor scalar sets, this returns an Int. For vector sets, this returns an UnitRange{Int}.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.num_rows","page":"API Reference","title":"MathOptInterface.Utilities.num_rows","text":"num_rows(sets::OrderedProductOfSets, ::Type{S}) where {S}\n\nReturn the number of rows corresponding to a set of type S. That is, it is the sum of the dimensions of the sets of type S.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.set_with_dimension","page":"API Reference","title":"MathOptInterface.Utilities.set_with_dimension","text":"set_with_dimension(::Type{S}, dim) where {S<:MOI.AbstractVectorSet}\n\nReturns the instance of S of MOI.dimension dim. This needs to be implemented for sets of type S to be useable with MatrixOfConstraints.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.ProductOfSets\nUtilities.MixOfScalarSets\nUtilities.@mix_of_scalar_sets\nUtilities.OrderedProductOfSets\nUtilities.@product_of_sets","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.ProductOfSets","page":"API Reference","title":"MathOptInterface.Utilities.ProductOfSets","text":"abstract type ProductOfSets{T} end\n\nRepresents a cartesian product of sets of given types.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.MixOfScalarSets","page":"API Reference","title":"MathOptInterface.Utilities.MixOfScalarSets","text":"abstract type MixOfScalarSets{T} <: ProductOfSets{T} end\n\nProduct of scalar sets in the order the constraints are added, mixing the constraints of different types.\n\nUse @mix_of_scalar_sets to generate a new subtype.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.@mix_of_scalar_sets","page":"API Reference","title":"MathOptInterface.Utilities.@mix_of_scalar_sets","text":"@mix_of_scalar_sets(name, set_types...)\n\nGenerate a new MixOfScalarSets subtype.\n\nExample\n\n@mix_of_scalar_sets(\n MixedIntegerLinearProgramSets,\n MOI.GreaterThan{T},\n MOI.LessThan{T},\n MOI.EqualTo{T},\n MOI.Integer,\n)\n\n\n\n\n\n","category":"macro"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.OrderedProductOfSets","page":"API Reference","title":"MathOptInterface.Utilities.OrderedProductOfSets","text":"abstract type OrderedProductOfSets{T} <: ProductOfSets{T} end\n\nProduct of sets in the order the constraints are added, grouping the constraints of the same types contiguously.\n\nUse @product_of_sets to generate new subtypes.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.@product_of_sets","page":"API Reference","title":"MathOptInterface.Utilities.@product_of_sets","text":"@product_of_sets(name, set_types...)\n\nGenerate a new OrderedProductOfSets subtype.\n\nExample\n\n@product_of_sets(\n LinearOrthants,\n MOI.Zeros,\n MOI.Nonnegatives,\n MOI.Nonpositives,\n MOI.ZeroOne,\n)\n\n\n\n\n\n","category":"macro"},{"location":"moi/submodules/Utilities/reference/#Fallbacks","page":"API Reference","title":"Fallbacks","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.get_fallback","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.get_fallback","page":"API Reference","title":"MathOptInterface.Utilities.get_fallback","text":"get_fallback(model::MOI.ModelLike, ::MOI.ObjectiveValue)\n\nCompute the objective function value using the VariablePrimal results and the ObjectiveFunction value.\n\n\n\n\n\nget_fallback(model::MOI.ModelLike, ::MOI.DualObjectiveValue, T::Type)::T\n\nCompute the dual objective value of type T using the ConstraintDual results and the ConstraintFunction and ConstraintSet values. Note that the nonlinear part of the model is ignored.\n\n\n\n\n\nget_fallback(model::MOI.ModelLike, ::MOI.ConstraintPrimal,\n constraint_index::MOI.ConstraintIndex)\n\nCompute the value of the function of the constraint of index constraint_index using the VariablePrimal results and the ConstraintFunction values.\n\n\n\n\n\nget_fallback(model::MOI.ModelLike, attr::MOI.ConstraintDual,\n ci::MOI.ConstraintIndex{Union{MOI.VariableIndex,\n MOI.VectorOfVariables}})\n\nCompute the dual of the constraint of index ci using the ConstraintDual of other constraints and the ConstraintFunction values. Throws an error if some constraints are quadratic or if there is one another MOI.VariableIndex-in-S or MOI.VectorOfVariables-in-S constraint with one of the variables in the function of the constraint ci.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#Function-utilities","page":"API Reference","title":"Function utilities","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"The following utilities are available for functions:","category":"page"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.eval_variables\nUtilities.map_indices\nUtilities.substitute_variables\nUtilities.filter_variables\nUtilities.remove_variable\nUtilities.all_coefficients\nUtilities.unsafe_add\nUtilities.isapprox_zero\nUtilities.modify_function\nUtilities.zero_with_output_dimension","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.eval_variables","page":"API Reference","title":"MathOptInterface.Utilities.eval_variables","text":"eval_variables(value_fn::Function, f::MOI.AbstractFunction)\n\nReturns the value of function f if each variable index vi is evaluated as value_fn(vi).\n\nNote that value_fn must return a Number. See substitute_variables for a similar function where value_fn returns an MOI.AbstractScalarFunction.\n\nwarning: Warning\nThe two-argument version of eval_variables is deprecated and may be removed in MOI v2.0.0. Use the three-argument method eval_variables(::Function, ::MOI.ModelLike, ::MOI.AbstractFunction) instead.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.map_indices","page":"API Reference","title":"MathOptInterface.Utilities.map_indices","text":"map_indices(index_map::Function, attr::MOI.AnyAttribute, x::X)::X where {X}\n\nSubstitute any MOI.VariableIndex (resp. MOI.ConstraintIndex) in x by the MOI.VariableIndex (resp. MOI.ConstraintIndex) of the same type given by index_map(x).\n\nWhen to implement this method for new types X\n\nThis function is used by implementations of MOI.copy_to on constraint functions, attribute values and submittable values. If you define a new attribute whose values x::X contain variable or constraint indices, you must also implement this function.\n\n\n\n\n\nmap_indices(\n variable_map::AbstractDict{T,T},\n x::X,\n)::X where {T<:MOI.Index,X}\n\nShortcut for map_indices(vi -> variable_map[vi], x).\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.substitute_variables","page":"API Reference","title":"MathOptInterface.Utilities.substitute_variables","text":"substitute_variables(variable_map::Function, x)\n\nSubstitute any MOI.VariableIndex in x by variable_map(x). The variable_map function returns either MOI.VariableIndex or MOI.ScalarAffineFunction, see eval_variables for a similar function where variable_map returns a number.\n\nThis function is used by bridge optimizers on constraint functions, attribute values and submittable values when at least one variable bridge is used hence it needs to be implemented for custom types that are meant to be used as attribute or submittable value.\n\nnote: Note\nWhen implementing a new method, don't use substitute_variables(::Function, because Julia will not specialize on it. Use instead substitute_variables(::F, ...) where {F<:Function}.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.filter_variables","page":"API Reference","title":"MathOptInterface.Utilities.filter_variables","text":"filter_variables(keep::Function, f::AbstractFunction)\n\nReturn a new function f with the variable vi such that !keep(vi) removed.\n\nWARNING: Don't define filter_variables(::Function, ...) because Julia will not specialize on this. Define instead filter_variables(::F, ...) where {F<:Function}.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.remove_variable","page":"API Reference","title":"MathOptInterface.Utilities.remove_variable","text":"remove_variable(f::AbstractFunction, vi::VariableIndex)\n\nReturn a new function f with the variable vi removed.\n\n\n\n\n\nremove_variable(\n f::MOI.AbstractFunction,\n s::MOI.AbstractSet,\n vi::MOI.VariableIndex,\n)\n\nReturn a tuple (g, t) representing the constraint f-in-s with the variable vi removed. That is, the terms containing the variable vi in the function f are removed and the dimension of the set s is updated if needed (e.g. when f is a VectorOfVariables with vi being one of the variables).\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.all_coefficients","page":"API Reference","title":"MathOptInterface.Utilities.all_coefficients","text":"all_coefficients(p::Function, f::MOI.AbstractFunction)\n\nDetermine whether predicate p returns true for all coefficients of f, returning false as soon as the first coefficient of f for which p returns false is encountered (short-circuiting). Similar to all.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.unsafe_add","page":"API Reference","title":"MathOptInterface.Utilities.unsafe_add","text":"unsafe_add(t1::MOI.ScalarAffineTerm, t2::MOI.ScalarAffineTerm)\n\nSums the coefficients of t1 and t2 and returns an output MOI.ScalarAffineTerm. It is unsafe because it uses the variable of t1 as the variable of the output without checking that it is equal to that of t2.\n\n\n\n\n\nunsafe_add(t1::MOI.ScalarQuadraticTerm, t2::MOI.ScalarQuadraticTerm)\n\nSums the coefficients of t1 and t2 and returns an output MOI.ScalarQuadraticTerm. It is unsafe because it uses the variable's of t1 as the variable's of the output without checking that they are the same (up to permutation) to those of t2.\n\n\n\n\n\nunsafe_add(t1::MOI.VectorAffineTerm, t2::MOI.VectorAffineTerm)\n\nSums the coefficients of t1 and t2 and returns an output MOI.VectorAffineTerm. It is unsafe because it uses the output_index and variable of t1 as the output_index and variable of the output term without checking that they are equal to those of t2.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.isapprox_zero","page":"API Reference","title":"MathOptInterface.Utilities.isapprox_zero","text":"isapprox_zero(f::MOI.AbstractFunction, tol)\n\nReturn a Bool indicating whether the function f is approximately zero using tol as a tolerance.\n\nImportant note\n\nThis function assumes that f does not contain any duplicate terms, you might want to first call canonical if that is not guaranteed. For instance, given\n\nf = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, -1], [x, x]), 0)\n\nthen isapprox_zero(f) is false but isapprox_zero(MOIU.canonical(f)) is true.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.modify_function","page":"API Reference","title":"MathOptInterface.Utilities.modify_function","text":"modify_function(f::AbstractFunction, change::AbstractFunctionModification)\n\nReturn a copy of the function f, modified according to change.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.zero_with_output_dimension","page":"API Reference","title":"MathOptInterface.Utilities.zero_with_output_dimension","text":"zero_with_output_dimension(::Type{T}, output_dimension::Integer) where {T}\n\nCreate an instance of type T with the output dimension output_dimension.\n\nThis is mostly useful in Bridges, when code needs to be agnostic to the type of vector-valued function that is passed in.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"The following functions can be used to canonicalize a function:","category":"page"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.is_canonical\nUtilities.canonical\nUtilities.canonicalize!","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.is_canonical","page":"API Reference","title":"MathOptInterface.Utilities.is_canonical","text":"is_canonical(f::Union{ScalarAffineFunction, VectorAffineFunction})\n\nReturns a Bool indicating whether the function is in canonical form. See canonical.\n\n\n\n\n\nis_canonical(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction})\n\nReturns a Bool indicating whether the function is in canonical form. See canonical.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.canonical","page":"API Reference","title":"MathOptInterface.Utilities.canonical","text":"canonical(f::MOI.AbstractFunction)\n\nReturns the function in a canonical form, i.e.\n\nA term appear only once.\nThe coefficients are nonzero.\nThe terms appear in increasing order of variable where there the order of the variables is the order of their value.\nFor a AbstractVectorFunction, the terms are sorted in ascending order of output index.\n\nThe output of canonical can be assumed to be a copy of f, even for VectorOfVariables.\n\nExamples\n\nIf x (resp. y, z) is VariableIndex(1) (resp. 2, 3). The canonical representation of ScalarAffineFunction([y, x, z, x, z], [2, 1, 3, -2, -3], 5) is ScalarAffineFunction([x, y], [-1, 2], 5).\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.canonicalize!","page":"API Reference","title":"MathOptInterface.Utilities.canonicalize!","text":"canonicalize!(f::Union{ScalarAffineFunction, VectorAffineFunction})\n\nConvert a function to canonical form in-place, without allocating a copy to hold the result. See canonical.\n\n\n\n\n\ncanonicalize!(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction})\n\nConvert a function to canonical form in-place, without allocating a copy to hold the result. See canonical.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"The following functions can be used to manipulate functions with basic algebra:","category":"page"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.scalar_type\nUtilities.scalarize\nUtilities.eachscalar\nUtilities.promote_operation\nUtilities.operate\nUtilities.operate!\nUtilities.operate_output_index!\nUtilities.vectorize","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.scalar_type","page":"API Reference","title":"MathOptInterface.Utilities.scalar_type","text":"scalar_type(F::Type{<:MOI.AbstractVectorFunction})\n\nType of functions obtained by indexing objects obtained by calling eachscalar on functions of type F.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.scalarize","page":"API Reference","title":"MathOptInterface.Utilities.scalarize","text":"scalarize(func::MOI.VectorOfVariables, ignore_constants::Bool = false)\n\nReturns a vector of scalar functions making up the vector function in the form of a Vector{MOI.SingleVariable}.\n\nSee also eachscalar.\n\n\n\n\n\nscalarize(func::MOI.VectorAffineFunction{T}, ignore_constants::Bool = false)\n\nReturns a vector of scalar functions making up the vector function in the form of a Vector{MOI.ScalarAffineFunction{T}}.\n\nSee also eachscalar.\n\n\n\n\n\nscalarize(func::MOI.VectorQuadraticFunction{T}, ignore_constants::Bool = false)\n\nReturns a vector of scalar functions making up the vector function in the form of a Vector{MOI.ScalarQuadraticFunction{T}}.\n\nSee also eachscalar.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.eachscalar","page":"API Reference","title":"MathOptInterface.Utilities.eachscalar","text":"eachscalar(f::MOI.AbstractVectorFunction)\n\nReturns an iterator for the scalar components of the vector function.\n\nSee also scalarize.\n\n\n\n\n\neachscalar(f::MOI.AbstractVector)\n\nReturns an iterator for the scalar components of the vector.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.promote_operation","page":"API Reference","title":"MathOptInterface.Utilities.promote_operation","text":"promote_operation(\n op::Function,\n ::Type{T},\n ArgsTypes::Type{<:Union{T,AbstractVector{T},MOI.AbstractFunction}}...,\n) where {T<:Number}\n\nCompute the return type of the call operate(op, T, args...), where the types of the arguments args are ArgsTypes.\n\nOne assumption is that the element type T is invariant under each operation. That is, op(::T, ::T)::T where op is a +, -, *, and /.\n\nThere are six methods for which we implement Utilities.promote_operation:\n\n+ a. promote_operation(::typeof(+), ::Type{T}, ::Type{F1}, ::Type{F2})\n- a. promote_operation(::typeof(-), ::Type{T}, ::Type{F}) b. promote_operation(::typeof(-), ::Type{T}, ::Type{F1}, ::Type{F2})\n* a. promote_operation(::typeof(*), ::Type{T}, ::Type{T}, ::Type{F}) b. promote_operation(::typeof(*), ::Type{T}, ::Type{F}, ::Type{T}) c. promote_operation(::typeof(*), ::Type{T}, ::Type{F1}, ::Type{F2}) where F1 and F2 are VariableIndex or ScalarAffineFunction d. promote_operation(::typeof(*), ::Type{T}, ::Type{<:Diagonal{T}}, ::Type{F}\n/ a. promote_operation(::typeof(/), ::Type{T}, ::Type{F}, ::Type{T})\nvcat a. promote_operation(::typeof(vcat), ::Type{T}, ::Type{F}...)\nimag a. promote_operation(::typeof(imag), ::Type{T}, ::Type{F}) where F is VariableIndex or VectorOfVariables\n\nIn each case, F (or F1 and F2) is one of the ten supported types, with a restriction that the mathematical operation makes sense, for example, we don't define promote_operation(-, T, F1, F2) where F1 is a scalar-valued function and F2 is a vector-valued function. The ten supported types are:\n\n::T\n::VariableIndex\n::ScalarAffineFunction{T}\n::ScalarQuadraticFunction{T}\n::ScalarNonlinearFunction\n::AbstractVector{T}\n::VectorOfVariables\n::VectorAffineFunction{T}\n::VectorQuadraticFunction{T}\n::VectorNonlinearFunction\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.operate","page":"API Reference","title":"MathOptInterface.Utilities.operate","text":"operate(\n op::Function,\n ::Type{T},\n args::Union{T,MOI.AbstractFunction}...,\n)::MOI.AbstractFunction where {T<:Number}\n\nReturns an MOI.AbstractFunction representing the function resulting from the operation op(args...) on functions of coefficient type T.\n\nNo argument can be modified.\n\nMethods\n\n+ a. operate(::typeof(+), ::Type{T}, ::F1) b. operate(::typeof(+), ::Type{T}, ::F1, ::F2) c. operate(::typeof(+), ::Type{T}, ::F1...)\n- a. operate(::typeof(-), ::Type{T}, ::F) b. operate(::typeof(-), ::Type{T}, ::F1, ::F2)\n* a. operate(::typeof(*), ::Type{T}, ::T, ::F) b. operate(::typeof(*), ::Type{T}, ::F, ::T) c. operate(::typeof(*), ::Type{T}, ::F1, ::F2) where F1 and F2 are VariableIndex or ScalarAffineFunction d. operate(::typeof(*), ::Type{T}, ::Diagonal{T}, ::F)\n/ a. operate(::typeof(/), ::Type{T}, ::F, ::T)\nvcat a. operate(::typeof(vcat), ::Type{T}, ::F...)\nimag a. operate(::typeof(imag), ::Type{T}, ::F) where F is VariableIndex or VectorOfVariables\n\nOne assumption is that the element type T is invariant under each operation. That is, op(::T, ::T)::T where op is a +, -, *, and /.\n\nIn each case, F (or F1 and F2) is one of the ten supported types, with a restriction that the mathematical operation makes sense, for example, we don't define promote_operation(-, T, F1, F2) where F1 is a scalar-valued function and F2 is a vector-valued function. The ten supported types are:\n\n::T\n::VariableIndex\n::ScalarAffineFunction{T}\n::ScalarQuadraticFunction{T}\n::ScalarNonlinearFunction\n::AbstractVector{T}\n::VectorOfVariables\n::VectorAffineFunction{T}\n::VectorQuadraticFunction{T}\n::VectorNonlinearFunction\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.operate!","page":"API Reference","title":"MathOptInterface.Utilities.operate!","text":"operate!(\n op::Function,\n ::Type{T},\n args::Union{T,MOI.AbstractFunction}...,\n)::MOI.AbstractFunction where {T<:Number}\n\nReturns an MOI.AbstractFunction representing the function resulting from the operation op(args...) on functions of coefficient type T.\n\nThe first argument may be modified, in which case the return value is identical to the first argument. For operations which cannot be implemented in-place, this function returns a new object.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.operate_output_index!","page":"API Reference","title":"MathOptInterface.Utilities.operate_output_index!","text":"operate_output_index!(\n op::Union{typeof(+),typeof(-)},\n ::Type{T},\n output_index::Integer,\n f::Union{AbstractVector{T},MOI.AbstractVectorFunction}\n g::Union{T,MOI.AbstractScalarFunction}...\n) where {T<:Number}\n\nReturn an MOI.AbstractVectorFunction in which the scalar function in row output_index is the result of op(f[output_index], g).\n\nThe functions at output index different to output_index are the same as the functions at the same output index in func. The first argument may be modified.\n\nMethods\n\n+ a. operate_output_index!(+, ::Type{T}, ::Int, ::VectorF, ::ScalarF)\n- a. operate_output_index!(-, ::Type{T}, ::Int, ::VectorF, ::ScalarF)\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.vectorize","page":"API Reference","title":"MathOptInterface.Utilities.vectorize","text":"vectorize(x::AbstractVector{<:Number})\n\nReturns x.\n\n\n\n\n\nvectorize(x::AbstractVector{MOI.VariableIndex})\n\nReturns the vector of scalar affine functions in the form of a MOI.VectorAffineFunction{T}.\n\n\n\n\n\nvectorize(funcs::AbstractVector{MOI.ScalarAffineFunction{T}}) where T\n\nReturns the vector of scalar affine functions in the form of a MOI.VectorAffineFunction{T}.\n\n\n\n\n\nvectorize(funcs::AbstractVector{MOI.ScalarQuadraticFunction{T}}) where T\n\nReturns the vector of scalar quadratic functions in the form of a MOI.VectorQuadraticFunction{T}.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#Constraint-utilities","page":"API Reference","title":"Constraint utilities","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"The following utilities are available for moving the function constant to the set for scalar constraints:","category":"page"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.shift_constant\nUtilities.supports_shift_constant\nUtilities.normalize_and_add_constraint\nUtilities.normalize_constant","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.shift_constant","page":"API Reference","title":"MathOptInterface.Utilities.shift_constant","text":"shift_constant(set::MOI.AbstractScalarSet, offset)\n\nReturns a new scalar set new_set such that func-in-set is equivalent to func + offset-in-new_set.\n\nOnly define this function if it makes sense to!\n\nUse supports_shift_constant to check if the set supports shifting:\n\nif supports_shift_constant(typeof(old_set))\n new_set = shift_constant(old_set, offset)\n f.constant = 0\n add_constraint(model, f, new_set)\nelse\n add_constraint(model, f, old_set)\nend\n\nSee also supports_shift_constant.\n\nExamples\n\nThe call shift_constant(MOI.Interval(-2, 3), 1) is equal to MOI.Interval(-1, 4).\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.supports_shift_constant","page":"API Reference","title":"MathOptInterface.Utilities.supports_shift_constant","text":"supports_shift_constant(::Type{S}) where {S<:MOI.AbstractSet}\n\nReturn true if shift_constant is defined for set S.\n\nSee also shift_constant.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.normalize_and_add_constraint","page":"API Reference","title":"MathOptInterface.Utilities.normalize_and_add_constraint","text":"normalize_and_add_constraint(\n model::MOI.ModelLike,\n func::MOI.AbstractScalarFunction,\n set::MOI.AbstractScalarSet;\n allow_modify_function::Bool = false,\n)\n\nAdds the scalar constraint obtained by moving the constant term in func to the set in model. If allow_modify_function is true then the function func can be modified.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.normalize_constant","page":"API Reference","title":"MathOptInterface.Utilities.normalize_constant","text":"normalize_constant(\n func::MOI.AbstractScalarFunction,\n set::MOI.AbstractScalarSet;\n allow_modify_function::Bool = false,\n)\n\nReturn the func-in-set constraint in normalized form. That is, if func is MOI.ScalarQuadraticFunction or MOI.ScalarAffineFunction, the constant is moved to the set. If allow_modify_function is true then the function func can be modified.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"The following utility identifies those constraints imposing bounds on a given variable, and returns those bound values:","category":"page"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.get_bounds","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.get_bounds","page":"API Reference","title":"MathOptInterface.Utilities.get_bounds","text":"get_bounds(model::MOI.ModelLike, ::Type{T}, x::MOI.VariableIndex)\n\nReturn a tuple (lb, ub) of type Tuple{T, T}, where lb and ub are lower and upper bounds, respectively, imposed on x in model.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"The following utilities are useful when working with symmetric matrix cones.","category":"page"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.is_diagonal_vectorized_index\nUtilities.side_dimension_for_vectorized_dimension","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.is_diagonal_vectorized_index","page":"API Reference","title":"MathOptInterface.Utilities.is_diagonal_vectorized_index","text":"is_diagonal_vectorized_index(index::Base.Integer)\n\nReturn whether index is the index of a diagonal element in a MOI.AbstractSymmetricMatrixSetTriangle set.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.side_dimension_for_vectorized_dimension","page":"API Reference","title":"MathOptInterface.Utilities.side_dimension_for_vectorized_dimension","text":"side_dimension_for_vectorized_dimension(n::Integer)\n\nReturn the dimension d such that MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(d)) is n.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#Set-utilities","page":"API Reference","title":"Set utilities","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"The following utilities are available for sets:","category":"page"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.AbstractDistance\nUtilities.ProjectionUpperBoundDistance\nUtilities.distance_to_set\nUtilities.set_dot","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.AbstractDistance","page":"API Reference","title":"MathOptInterface.Utilities.AbstractDistance","text":"abstract type AbstractDistance end\n\nAn abstract type used to enabble dispatch of Utilities.distance_to_set.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.ProjectionUpperBoundDistance","page":"API Reference","title":"MathOptInterface.Utilities.ProjectionUpperBoundDistance","text":"ProjectionUpperBoundDistance() <: AbstractDistance\n\nAn upper bound on the minimum distance between point and the closest feasible point in set.\n\nDefinition of distance\n\nThe minimum distance is computed as:\n\nd(x mathcalK) = min_y in mathcalK x - y \n\nwhere x is point and mathcalK is set. The norm is computed as:\n\nx = sqrtf(x x mathcalK)\n\nwhere f is Utilities.set_dot.\n\nIn the default case, where the set does not have a specialized method for Utilities.set_dot, the norm is equivalent to the Euclidean norm x = sqrtsum x_i^2.\n\nWhy an upper bound?\n\nIn most cases, distance_to_set should return the smallest upper bound, but it may return a larger value if the smallest upper bound is expensive to compute.\n\nFor example, given an epigraph from of a conic set, (t x) f(x) le t, it may be simpler to return delta such that f(x) le t + delta, rather than computing the nearest projection onto the set.\n\nIf the distance is not the smallest upper bound, the docstring of the appropriate distance_to_set method must describe the way that the distance is computed.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.distance_to_set","page":"API Reference","title":"MathOptInterface.Utilities.distance_to_set","text":"distance_to_set(\n [d::AbstractDistance = ProjectionUpperBoundDistance()],]\n point::T,\n set::MOI.AbstractScalarSet,\n) where {T}\n\ndistance_to_set(\n [d::AbstractDistance = ProjectionUpperBoundDistance(),]\n point::AbstractVector{T},\n set::MOI.AbstractVectorSet,\n) where {T}\n\nCompute the distance between point and set using the distance metric d. If point is in the set set, this function must return zero(T).\n\nIf d is omitted, the default distance is Utilities.ProjectionUpperBoundDistance.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.set_dot","page":"API Reference","title":"MathOptInterface.Utilities.set_dot","text":"set_dot(x::AbstractVector, y::AbstractVector, set::AbstractVectorSet)\n\nReturn the scalar product between a vector x of the set set and a vector y of the dual of the set s.\n\n\n\n\n\nset_dot(x, y, set::AbstractScalarSet)\n\nReturn the scalar product between a number x of the set set and a number y of the dual of the set s.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#DoubleDicts","page":"API Reference","title":"DoubleDicts","text":"","category":"section"},{"location":"moi/submodules/Utilities/reference/","page":"API Reference","title":"API Reference","text":"Utilities.DoubleDicts.DoubleDict\nUtilities.DoubleDicts.DoubleDictInner\nUtilities.DoubleDicts.IndexDoubleDict\nUtilities.DoubleDicts.IndexDoubleDictInner\nUtilities.DoubleDicts.outer_keys\nUtilities.DoubleDicts.nonempty_outer_keys","category":"page"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.DoubleDicts.DoubleDict","page":"API Reference","title":"MathOptInterface.Utilities.DoubleDicts.DoubleDict","text":"DoubleDict{V}\n\nAn optimized dictionary to map MOI.ConstraintIndex to values of type V.\n\nWorks as a AbstractDict{MOI.ConstraintIndex,V} with minimal differences.\n\nIf V is also a MOI.ConstraintIndex, use IndexDoubleDict.\n\nNote that MOI.ConstraintIndex is not a concrete type, opposed to MOI.ConstraintIndex{MOI.VariableIndex, MOI.Integers}, which is a concrete type.\n\nWhen looping through multiple keys of the same Function-in-Set type, use\n\ninner = dict[F, S]\n\nto return a type-stable DoubleDictInner.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.DoubleDicts.DoubleDictInner","page":"API Reference","title":"MathOptInterface.Utilities.DoubleDicts.DoubleDictInner","text":"DoubleDictInner{F,S,V}\n\nA type stable inner dictionary of DoubleDict.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.DoubleDicts.IndexDoubleDict","page":"API Reference","title":"MathOptInterface.Utilities.DoubleDicts.IndexDoubleDict","text":"IndexDoubleDict\n\nA specialized version of [DoubleDict] in which the values are of type MOI.ConstraintIndex\n\nWhen looping through multiple keys of the same Function-in-Set type, use\n\ninner = dict[F, S]\n\nto return a type-stable IndexDoubleDictInner.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.DoubleDicts.IndexDoubleDictInner","page":"API Reference","title":"MathOptInterface.Utilities.DoubleDicts.IndexDoubleDictInner","text":"IndexDoubleDictInner{F,S}\n\nA type stable inner dictionary of IndexDoubleDict.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.DoubleDicts.outer_keys","page":"API Reference","title":"MathOptInterface.Utilities.DoubleDicts.outer_keys","text":"outer_keys(d::AbstractDoubleDict)\n\nReturn an iterator over the outer keys of the AbstractDoubleDict d. Each outer key is a Tuple{Type,Type} so that a double loop can be easily used:\n\nfor (F, S) in DoubleDicts.outer_keys(dict)\n for (k, v) in dict[F, S]\n # ...\n end\nend\n\nFor performance, it is recommended that the inner loop lies in a separate function to gurantee type-stability. Some outer keys (F, S) might lead to an empty dict[F, S]. If you want only nonempty dict[F, S], use nonempty_outer_keys.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Utilities/reference/#MathOptInterface.Utilities.DoubleDicts.nonempty_outer_keys","page":"API Reference","title":"MathOptInterface.Utilities.DoubleDicts.nonempty_outer_keys","text":"nonempty_outer_keys(d::AbstractDoubleDict)\n\nReturn a vector of outer keys of the AbstractDoubleDict d.\n\nOnly outer keys that have a nonempty set of inner keys will be returned.\n\nEach outer key is a Tuple{Type,Type} so that a double loop can be easily used\n\nfor (F, S) in DoubleDicts.nonempty_outer_keys(dict)\n for (k, v) in dict[F, S]\n # ...\n end\nend\nFor performance, it is recommended that the inner loop lies in a separate\nfunction to gurantee type-stability.\n\nIf you want an iterator of all current outer keys, use [`outer_keys`](@ref).\n\n\n\n\n\n","category":"function"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"EditURL = \"https://github.com/jump-dev/Clp.jl/blob/v1.0.3/README.md\"","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"(Image: )","category":"page"},{"location":"packages/Clp/#Clp.jl","page":"jump-dev/Clp.jl","title":"Clp.jl","text":"","category":"section"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"(Image: Build Status) (Image: codecov)","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"Clp.jl is a wrapper for the COIN-OR Linear Programming solver.","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"The wrapper has two components:","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"a thin wrapper around the complete C API\nan interface to MathOptInterface","category":"page"},{"location":"packages/Clp/#Affiliation","page":"jump-dev/Clp.jl","title":"Affiliation","text":"","category":"section"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"This wrapper is maintained by the JuMP community and is not a COIN-OR project.","category":"page"},{"location":"packages/Clp/#License","page":"jump-dev/Clp.jl","title":"License","text":"","category":"section"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"Clp.jl is licensed under the MIT License.","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"The underlying solver, coin-or/Clp, is licensed under the Eclipse public license.","category":"page"},{"location":"packages/Clp/#Installation","page":"jump-dev/Clp.jl","title":"Installation","text":"","category":"section"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"Install Clp using Pkg.add:","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"import Pkg\nPkg.add(\"Clp\")","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"In addition to installing the Clp.jl package, this will also download and install the Clp binaries. You do not need to install Clp separately.","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"To use a custom binary, read the Custom solver binaries section of the JuMP documentation.","category":"page"},{"location":"packages/Clp/#Use-with-JuMP","page":"jump-dev/Clp.jl","title":"Use with JuMP","text":"","category":"section"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"To use Clp with JuMP, use Clp.Optimizer:","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"using JuMP, Clp\nmodel = Model(Clp.Optimizer)\nset_attribute(model, \"LogLevel\", 1)\nset_attribute(model, \"Algorithm\", 4)","category":"page"},{"location":"packages/Clp/#MathOptInterface-API","page":"jump-dev/Clp.jl","title":"MathOptInterface API","text":"","category":"section"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"The Clp optimizer supports the following constraints and attributes.","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"List of supported objective functions:","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"List of supported variable types:","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"MOI.Reals","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"List of supported constraint types:","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"MOI.ScalarAffineFunction{Float64} in MOI.EqualTo{Float64}\nMOI.ScalarAffineFunction{Float64} in MOI.GreaterThan{Float64}\nMOI.ScalarAffineFunction{Float64} in MOI.Interval{Float64}\nMOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}\nMOI.VariableIndex in MOI.EqualTo{Float64}\nMOI.VariableIndex in MOI.GreaterThan{Float64}\nMOI.VariableIndex in MOI.Interval{Float64}\nMOI.VariableIndex in MOI.LessThan{Float64}","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"List of supported model attributes:","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"MOI.ObjectiveSense()","category":"page"},{"location":"packages/Clp/#Options","page":"jump-dev/Clp.jl","title":"Options","text":"","category":"section"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"Options are, unfortunately, not well documented.","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"The following options are likely to be the most useful:","category":"page"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"Parameter Example Explanation\nPrimalTolerance 1e-7 Primal feasibility tolerance\nDualTolerance 1e-7 Dual feasibility tolerance\nDualObjectiveLimit 1e308 When using dual simplex (where the objective is monotonically changing), terminate when the objective exceeds this limit\nMaximumIterations 2147483647 Terminate after performing this number of simplex iterations\nMaximumSeconds -1.0 Terminate after this many seconds have passed. A negative value means no time limit\nLogLevel 1 Set to 1, 2, 3, or 4 for increasing output. Set to 0 to disable output\nPresolveType 0 Set to 1 to disable presolve\nSolveType 5 Solution method: dual simplex (0), primal simplex (1), sprint (2), barrier with crossover (3), barrier without crossover (4), automatic (5)\nInfeasibleReturn 0 Set to 1 to return as soon as the problem is found to be infeasible (by default, an infeasibility proof is computed as well)\nScaling 3 0 -off, 1 equilibrium, 2 geometric, 3 auto, 4 dynamic(later)\nPerturbation 100 switch on perturbation (50), automatic (100), don't try perturbing (102)","category":"page"},{"location":"packages/Clp/#C-API","page":"jump-dev/Clp.jl","title":"C API","text":"","category":"section"},{"location":"packages/Clp/","page":"jump-dev/Clp.jl","title":"jump-dev/Clp.jl","text":"The C API can be accessed via Clp.Clp_XXX functions, where the names and arguments are identical to the C API.","category":"page"},{"location":"moi/","page":"Introduction","title":"Introduction","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/index.md\"","category":"page"},{"location":"moi/#moi_documentation","page":"Introduction","title":"Introduction","text":"","category":"section"},{"location":"moi/","page":"Introduction","title":"Introduction","text":"warning: Warning\nThis documentation in this section is a copy of the official MathOptInterface documentation available at https://jump.dev/MathOptInterface.jl/v1.20.1. It is included here to make it easier to link concepts between JuMP and MathOptInterface.","category":"page"},{"location":"moi/#What-is-MathOptInterface?","page":"Introduction","title":"What is MathOptInterface?","text":"","category":"section"},{"location":"moi/","page":"Introduction","title":"Introduction","text":"MathOptInterface.jl (MOI) is an abstraction layer designed to provide a unified interface to mathematical optimization solvers so that users do not need to understand multiple solver-specific APIs.","category":"page"},{"location":"moi/","page":"Introduction","title":"Introduction","text":"tip: Tip\nThis 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 or Convex.jl.","category":"page"},{"location":"moi/#How-the-documentation-is-structured","page":"Introduction","title":"How the documentation is structured","text":"","category":"section"},{"location":"moi/","page":"Introduction","title":"Introduction","text":"Having a high-level overview of how this documentation is structured will help you know where to look for certain things.","category":"page"},{"location":"moi/","page":"Introduction","title":"Introduction","text":"The Tutorials section contains articles on how to use and implement the MathOptInteraface API. Look here if you want to write a model in MOI, or write an interface to a new solver.\nThe Manual contains short code-snippets that explain how to use the MOI API. Look here for more details on particular areas of MOI.\nThe Background section contains articles on the theory behind MathOptInterface. Look here if you want to understand why, rather than how.\nThe API Reference contains a complete list of functions and types that comprise the MOI API. Look here is you want to know how to use (or implement) a particular function.\nThe Submodules section contains stand-alone documentation for each of the submodules within MOI. These submodules are not required to interface a solver with MOI, but they make the job much easier.","category":"page"},{"location":"moi/#Citing-MathOptInterface","page":"Introduction","title":"Citing MathOptInterface","text":"","category":"section"},{"location":"moi/","page":"Introduction","title":"Introduction","text":"A paper describing the design and features of MathOptInterface is available on arXiv.","category":"page"},{"location":"moi/","page":"Introduction","title":"Introduction","text":"If you find MathOptInterface useful in your work, we kindly request that you cite the following paper:","category":"page"},{"location":"moi/","page":"Introduction","title":"Introduction","text":"@article{legat2021mathoptinterface,\n title={{MathOptInterface}: a data structure for mathematical optimization problems},\n author={Legat, Beno{\\^\\i}t and Dowson, Oscar and Garcia, Joaquim Dias and Lubin, Miles},\n journal={INFORMS Journal on Computing},\n year={2021},\n doi={10.1287/ijoc.2021.1067},\n publisher={INFORMS}\n}","category":"page"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"EditURL = \"https://github.com/osqp/OSQP.jl/blob/443706e34c2619acbe65281c60bbe850ca4a8fac/README.md\"","category":"page"},{"location":"packages/OSQP/#OSQP.jl","page":"osqp/OSQP.jl","title":"OSQP.jl","text":"","category":"section"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"(Image: Build Status) (Image: codecov.io)","category":"page"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"OSQP.jl is a Julia wrapper for OSQP: the Operator Splitting QP Solver.","category":"page"},{"location":"packages/OSQP/#License","page":"osqp/OSQP.jl","title":"License","text":"","category":"section"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"OSQP.jl is licensed under the Apache-2.0 license.","category":"page"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"The upstream solver, osqp/osqp is also licensed under the Apache-2.0 license.","category":"page"},{"location":"packages/OSQP/#Installation","page":"osqp/OSQP.jl","title":"Installation","text":"","category":"section"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"Install OSQP.jl using the Julia package manager","category":"page"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"import Pkg\nPkg.add(\"OSQP\")","category":"page"},{"location":"packages/OSQP/#Problem-class","page":"osqp/OSQP.jl","title":"Problem class","text":"","category":"section"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"The OSQP (Operator Splitting Quadratic Program) solver is a numerical optimization package for solving problems in the form","category":"page"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"minimize 0.5 x' P x + q' x\n\nsubject to l <= A x <= u","category":"page"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"where x in R^n is the optimization variable. The objective function is defined by a positive semidefinite matrix P in S^n_+ and vector q in R^n. The linear constraints are defined by matrix A in R^{m x n} and vectors l in R^m U {-inf}^m, u in R^m U {+inf}^m.","category":"page"},{"location":"packages/OSQP/#Documentation","page":"osqp/OSQP.jl","title":"Documentation","text":"","category":"section"},{"location":"packages/OSQP/","page":"osqp/OSQP.jl","title":"osqp/OSQP.jl","text":"Detailed documentation is available at https://osqp.org/.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"CurrentModule = JuMP\nDocTestSetup = quote\n using JuMP, HiGHS, SCS\nend\nDocTestFilters = [r\"≤|<=\", r\"≥|>=\", r\" == | = \", r\" ∈ | in \", r\"MathOptInterface|MOI\"]","category":"page"},{"location":"manual/models/#jump_models","page":"Models","title":"Models","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"JuMP models are the fundamental building block that we use to construct optimization problems. They hold things like the variables and constraints, as well as which solver to use and even solution information.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"info: Info\nJuMP uses \"optimizer\" as a synonym for \"solver.\" Our convention is to use \"solver\" to refer to the underlying software, and use \"optimizer\" to refer to the Julia object that wraps the solver. For example, HiGHS is a solver, and HiGHS.Optimizer is an optimizer.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"tip: Tip\nSee Supported solvers for a list of available solvers.","category":"page"},{"location":"manual/models/#Create-a-model","page":"Models","title":"Create a model","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"Create a model by passing an optimizer to Model:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(HiGHS.Optimizer)\nA JuMP Model\nFeasibility problem with:\nVariables: 0\nModel mode: AUTOMATIC\nCachingOptimizer state: EMPTY_OPTIMIZER\nSolver name: HiGHS","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"If you don't know which optimizer you will be using at creation time, create a model without an optimizer, and then call set_optimizer at any time prior to optimize!:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model()\nA JuMP Model\nFeasibility problem with:\nVariables: 0\nModel mode: AUTOMATIC\nCachingOptimizer state: NO_OPTIMIZER\nSolver name: No optimizer attached.\n\njulia> set_optimizer(model, HiGHS.Optimizer)","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"tip: Tip\nDon't know what the fields Model mode and CachingOptimizer state mean? Read the Backends section.","category":"page"},{"location":"manual/models/#What-is-the-difference?","page":"Models","title":"What is the difference?","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"For most models, there is no difference between passing the optimizer to Model, and calling set_optimizer.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"However, if an optimizer does not support a constraint in the model, the timing of when an error will be thrown can differ:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"If you pass an optimizer, an error will be thrown when you try to add the constraint.\nIf you call set_optimizer, an error will be thrown when you try to solve the model via optimize!.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Therefore, most users should pass an optimizer to Model because it provides the earliest warning that your solver is not suitable for the model you are trying to build. However, if you are modifying a problem by adding and deleting different constraint types, you may need to use set_optimizer. See Switching optimizer for the relaxed problem for an example of when this is useful.","category":"page"},{"location":"manual/models/#Reducing-time-to-first-solve-latency","page":"Models","title":"Reducing time-to-first-solve latency","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"By default, JuMP uses bridges to reformulate the model you are building into an equivalent model supported by the solver.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"However, if your model is already supported by the solver, bridges add latency (read The \"time-to-first-solve\" issue). This is particularly noticeable for small models.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"To reduce the \"time-to-first-solve,s\" try passing add_bridges = false.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(HiGHS.Optimizer; add_bridges = false);","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"or","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model();\n\njulia> set_optimizer(model, HiGHS.Optimizer; add_bridges = false)","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"However, be wary. If your model and solver combination needs bridges, an error will be thrown:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(SCS.Optimizer; add_bridges = false);\n\n\njulia> @variable(model, x)\nx\n\njulia> @constraint(model, 2x <= 1)\nERROR: Constraints of type MathOptInterface.ScalarAffineFunction{Float64}-in-MathOptInterface.LessThan{Float64} are not supported by the solver.\n\nIf you expected the solver to support your problem, you may have an error in your formulation. Otherwise, consider using a different solver.\n\nThe list of available solvers, along with the problem types they support, is available at https://jump.dev/JuMP.jl/stable/installation/#Supported-solvers.\n[...]","category":"page"},{"location":"manual/models/#Solvers-which-expect-environments","page":"Models","title":"Solvers which expect environments","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"Some solvers accept (or require) positional arguments such as a license environment or a path to a binary executable. For these solvers, you can pass a function to Model which takes zero arguments and returns an instance of the optimizer.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"A common use-case for this is passing an environment or sub-solver to the optimizer:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> import HiGHS\n\njulia> import MultiObjectiveAlgorithms as MOA\n\njulia> model = Model(() -> MOA.Optimizer(HiGHS.Optimizer))\nA JuMP Model\nFeasibility problem with:\nVariables: 0\nModel mode: AUTOMATIC\nCachingOptimizer state: EMPTY_OPTIMIZER\nSolver name: MOA[algorithm=MultiObjectiveAlgorithms.Lexicographic, optimizer=HiGHS]","category":"page"},{"location":"manual/models/#solver_options","page":"Models","title":"Solver options","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"JuMP uses \"attribute\" as a synonym for \"option.\" Use optimizer_with_attributes to create an optimizer with some attributes initialized:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(\n optimizer_with_attributes(HiGHS.Optimizer, \"output_flag\" => false),\n )\nA JuMP Model\nFeasibility problem with:\nVariables: 0\nModel mode: AUTOMATIC\nCachingOptimizer state: EMPTY_OPTIMIZER\nSolver name: HiGHS","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Alternatively, use set_attribute to set an attribute after the model has been created:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(HiGHS.Optimizer);\n\njulia> set_attribute(model, \"output_flag\", false)\n\njulia> get_attribute(model, \"output_flag\")\nfalse","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"You can also modify attributes within an optimizer_with_attributes object:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> solver = optimizer_with_attributes(HiGHS.Optimizer, \"output_flag\" => true);\n\njulia> get_attribute(solver, \"output_flag\")\ntrue\n\njulia> set_attribute(solver, \"output_flag\", false)\n\njulia> get_attribute(solver, \"output_flag\")\nfalse\n\njulia> model = Model(solver);","category":"page"},{"location":"manual/models/#Changing-the-number-types","page":"Models","title":"Changing the number types","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"By default, the coefficients of affine and quadratic expressions are numbers of type either Float64 or Complex{Float64} (see Complex number support).","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"The type Float64 can be changed using the GenericModel constructor:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = GenericModel{Rational{BigInt}}();\n\njulia> @variable(model, x)\nx\n\njulia> @expression(model, expr, 1 // 3 * x)\n1//3 x\n\njulia> typeof(expr)\nGenericAffExpr{Rational{BigInt}, GenericVariableRef{Rational{BigInt}}}","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Using a value_type other than Float64 is an advanced operation and should be used only if the underlying solver actually solves the problem using the provided value type.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"warning: Warning\nNonlinear Modeling is currently restricted to the Float64 number type.","category":"page"},{"location":"manual/models/#Print-the-model","page":"Models","title":"Print the model","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"By default, show(model) will print a summary of the problem:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(); @variable(model, x >= 0); @objective(model, Max, x);\n\njulia> model\nA JuMP Model\nMaximization problem with:\nVariable: 1\nObjective function type: VariableRef\n`VariableRef`-in-`MathOptInterface.GreaterThan{Float64}`: 1 constraint\nModel mode: AUTOMATIC\nCachingOptimizer state: NO_OPTIMIZER\nSolver name: No optimizer attached.\nNames registered in the model: x","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Use print to print the formulation of the model (in IJulia, this will render as LaTeX.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> print(model)\nMax x\nSubject to\n x ≥ 0","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"warning: Warning\nThis format is specific to JuMP and may change in any future release. It is not intended to be an instance format. To write the model to a file, use write_to_file instead.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Use latex_formulation to display the model in LaTeX form.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> latex_formulation(model)\n$$ \\begin{aligned}\n\\max\\quad & x\\\\\n\\text{Subject to} \\quad & x \\geq 0\\\\\n\\end{aligned} $$","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"In IJulia (and Documenter), ending a cell in with latex_formulation will render the model in LaTeX:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"using JuMP # hide\nmodel = Model() # hide\n@variable(model, x >= 0) # hide\n@objective(model, Max, x) # hide\nlatex_formulation(model)","category":"page"},{"location":"manual/models/#Turn-off-output","page":"Models","title":"Turn off output","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"Use set_silent and unset_silent to disable or enable printing output from the solver.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(HiGHS.Optimizer);\n\njulia> set_silent(model)\n\njulia> unset_silent(model)","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"tip: Tip\nMost solvers will also have a solver-specific option to provide finer-grained control over the output. Consult their README's for details.","category":"page"},{"location":"manual/models/#Set-a-time-limit","page":"Models","title":"Set a time limit","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"Use set_time_limit_sec, unset_time_limit_sec, and time_limit_sec to manage time limits.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(HiGHS.Optimizer);\n\njulia> set_time_limit_sec(model, 60.0)\n\n\njulia> time_limit_sec(model)\n60.0\n\njulia> unset_time_limit_sec(model)\n\njulia> limit = time_limit_sec(model)\n\njulia> limit === nothing\ntrue","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"If your time limit is encoded as a Dates.Period object, use the following code to convert it to Float64 for set_time_limit_sec:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> import Dates\n\njulia> seconds(x::Dates.Period) = 1e-3 * Dates.value(round(x, Dates.Millisecond))\nseconds (generic function with 1 method)\n\njulia> set_time_limit_sec(model, seconds(Dates.Hour(1)))\n\njulia> time_limit_sec(model)\n3600.0","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"info: Info\nSome solvers do not support time limits. In these cases, an error will be thrown.","category":"page"},{"location":"manual/models/#Write-a-model-to-file","page":"Models","title":"Write a model to file","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"JuMP can write models to a variety of file-formats using write_to_file and Base.write.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"For most common file formats, the file type will be detected from the extension.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"For example, here is how to write an MPS file:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model();\n\njulia> write_to_file(model, \"model.mps\")","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Other supported file formats include:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":".cbf for the Conic Benchmark Format\n.lp for the LP file format\n.mof.json for the MathOptFormat\n.nl for AMPL's NL file format\n.rew for the REW file format\n.sdpa and \".dat-s\" for the SDPA file format","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"To write to a specific io::IO, use Base.write. Specify the file type by passing a MOI.FileFormats.FileFormat enum.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model();\n\njulia> io = IOBuffer();\n\njulia> write(io, model; format = MOI.FileFormats.FORMAT_MPS)","category":"page"},{"location":"manual/models/#Read-a-model-from-file","page":"Models","title":"Read a model from file","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"JuMP models can be created from file formats using read_from_file and Base.read.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = read_from_file(\"model.mps\")\nA JuMP Model\nMinimization problem with:\nVariables: 0\nObjective function type: AffExpr\nModel mode: AUTOMATIC\nCachingOptimizer state: NO_OPTIMIZER\nSolver name: No optimizer attached.\n\njulia> seekstart(io);\n\njulia> model2 = read(io, Model; format = MOI.FileFormats.FORMAT_MPS)\nA JuMP Model\nMinimization problem with:\nVariables: 0\nObjective function type: AffExpr\nModel mode: AUTOMATIC\nCachingOptimizer state: NO_OPTIMIZER\nSolver name: No optimizer attached.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"note: Note\nBecause file formats do not serialize the containers of JuMP variables and constraints, the names in the model will not be registered. Therefore, you cannot access named variables and constraints via model[:x]. Instead, use variable_by_name or constraint_by_name to access specific variables or constraints.","category":"page"},{"location":"manual/models/#Relax-integrality","page":"Models","title":"Relax integrality","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"Use relax_integrality to remove any integrality constraints from the model, such as integer and binary restrictions on variables. relax_integrality returns a function that can be later called with zero arguments to re-add the removed constraints:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model();\n\njulia> @variable(model, x, Int)\nx\n\njulia> num_constraints(model, VariableRef, MOI.Integer)\n1\n\njulia> undo = relax_integrality(model);\n\njulia> num_constraints(model, VariableRef, MOI.Integer)\n0\n\njulia> undo()\n\njulia> num_constraints(model, VariableRef, MOI.Integer)\n1","category":"page"},{"location":"manual/models/#Switching-optimizer-for-the-relaxed-problem","page":"Models","title":"Switching optimizer for the relaxed problem","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"A common reason for relaxing integrality is to compute dual variables of the relaxed problem. However, some mixed-integer linear solvers (for example, Cbc) do not return dual solutions, even if the problem does not have integrality restrictions.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Therefore, after relax_integrality you should call set_optimizer with a solver that does support dual solutions, such as Clp.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"For example, instead of:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"using JuMP, Cbc\nmodel = Model(Cbc.Optimizer)\n@variable(model, x, Int)\nundo = relax_integrality(model)\noptimize!(model)\nreduced_cost(x) # Errors","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"do:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"using JuMP, Cbc, Clp\nmodel = Model(Cbc.Optimizer)\n@variable(model, x, Int)\nundo = relax_integrality(model)\nset_optimizer(model, Clp.Optimizer)\noptimize!(model)\nreduced_cost(x) # Works","category":"page"},{"location":"manual/models/#Backends","page":"Models","title":"Backends","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"info: Info\nThis section discusses advanced features of JuMP. For new users, you may want to skip this section. You don't need to know how JuMP manages problems behind the scenes to create and solve JuMP models.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"A JuMP Model is a thin layer around a backend of type MOI.ModelLike that stores the optimization problem and acts as the optimization solver.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"However, if you construct a model like Model(HiGHS.Optimizer), the backend is not a HiGHS.Optimizer, but a more complicated object.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"From JuMP, the MOI backend can be accessed using the backend function. Let's see what the backend of a JuMP Model is:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(HiGHS.Optimizer);\n\njulia> b = backend(model)\nMOIU.CachingOptimizer{MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}, MOIU.UniversalFallback{MOIU.Model{Float64}}}\nin state EMPTY_OPTIMIZER\nin mode AUTOMATIC\nwith model cache MOIU.UniversalFallback{MOIU.Model{Float64}}\n fallback for MOIU.Model{Float64}\nwith optimizer MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}\n with 0 variable bridges\n with 0 constraint bridges\n with 0 objective bridges\n with inner model A HiGHS model with 0 columns and 0 rows.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Uh oh. Even though we passed a HiGHS.Optimizer, the backend is a much more complicated object.","category":"page"},{"location":"manual/models/#CachingOptimizer","page":"Models","title":"CachingOptimizer","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"A MOIU.CachingOptimizer is a layer that abstracts the difference between solvers that support incremental modification (for example, they support adding variables one-by-one), and solvers that require the entire problem in a single API call (for example, they only accept the A, b and c matrices of a linear program).","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"It has two parts:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"A cache, where the model can be built and modified incrementally\njulia> b.model_cache\nMOIU.UniversalFallback{MOIU.Model{Float64}}\nfallback for MOIU.Model{Float64}\nAn optimizer, which is used to solve the problem\njulia> b.optimizer\nMOIB.LazyBridgeOptimizer{HiGHS.Optimizer}\nwith 0 variable bridges\nwith 0 constraint bridges\nwith 0 objective bridges\nwith inner model A HiGHS model with 0 columns and 0 rows.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"info: Info\nThe LazyBridgeOptimizer section explains what a LazyBridgeOptimizer is.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"The CachingOptimizer has logic to decide when to copy the problem from the cache to the optimizer, and when it can efficiently update the optimizer in-place.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"A CachingOptimizer may be in one of three possible states:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"NO_OPTIMIZER: The CachingOptimizer does not have any optimizer.\nEMPTY_OPTIMIZER: The CachingOptimizer has an empty optimizer, and it is not synchronized with the cached model.\nATTACHED_OPTIMIZER: The CachingOptimizer has an optimizer, and it is synchronized with the cached model.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"A CachingOptimizer has two modes of operation:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"AUTOMATIC: The CachingOptimizer changes its state when necessary. For example, optimize! will automatically call attach_optimizer (an optimizer must have been previously set). Attempting to add a constraint or perform a modification not supported by the optimizer results in a drop to EMPTY_OPTIMIZER mode.\nMANUAL: The user must change the state of the CachingOptimizer using MOIU.reset_optimizer(::JuMP.Model), MOIU.drop_optimizer(::JuMP.Model), and MOIU.attach_optimizer(::JuMP.Model). Attempting to perform an operation in the incorrect state results in an error.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"By default Model will create a CachingOptimizer in AUTOMATIC mode.","category":"page"},{"location":"manual/models/#LazyBridgeOptimizer","page":"Models","title":"LazyBridgeOptimizer","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"The second layer that JuMP applies automatically is a MOI.Bridges.LazyBridgeOptimizer. A MOI.Bridges.LazyBridgeOptimizer is an MOI layer that attempts to transform the problem from the formulation provided by the user into an equivalent problem supported by the solver. This may involve adding new variables and constraints to the optimizer. The transformations are selected from a set of known recipes called bridges.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"A common example of a bridge is one that splits an interval constraint like @constraint(model, 1 <= x + y <= 2) into two constraints, @constraint(model, x + y >= 1) and @constraint(model, x + y <= 2).","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Use the add_bridges = false keyword to remove the bridging layer:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = Model(HiGHS.Optimizer; add_bridges = false)\nA JuMP Model\nFeasibility problem with:\nVariables: 0\nModel mode: AUTOMATIC\nCachingOptimizer state: EMPTY_OPTIMIZER\nSolver name: HiGHS\n\njulia> backend(model)\nMOIU.CachingOptimizer{HiGHS.Optimizer, MOIU.UniversalFallback{MOIU.Model{Float64}}}\nin state EMPTY_OPTIMIZER\nin mode AUTOMATIC\nwith model cache MOIU.UniversalFallback{MOIU.Model{Float64}}\n fallback for MOIU.Model{Float64}\nwith optimizer A HiGHS model with 0 columns and 0 rows.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"Bridges can be added and removed from a MOI.Bridges.LazyBridgeOptimizer using add_bridge and remove_bridge. Use print_active_bridges to see which bridges are used to reformulate the model. Read the Ellipsoid approximation tutorial for more details.","category":"page"},{"location":"manual/models/#Unsafe-backend","page":"Models","title":"Unsafe backend","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"In some advanced use-cases, it is necessary to work with the inner optimization model directly. To access this model, use unsafe_backend:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> backend(model)\nMOIU.CachingOptimizer{MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}, MOIU.UniversalFallback{MOIU.Model{Float64}}}\nin state EMPTY_OPTIMIZER\nin mode AUTOMATIC\nwith model cache MOIU.UniversalFallback{MOIU.Model{Float64}}\n fallback for MOIU.Model{Float64}\nwith optimizer MOIB.LazyBridgeOptimizer{HiGHS.Optimizer}\n with 0 variable bridges\n with 0 constraint bridges\n with 0 objective bridges\n with inner model A HiGHS model with 0 columns and 0 rows.\n\njulia> unsafe_backend(model)\nA HiGHS model with 0 columns and 0 rows.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"warning: Warning\nbackend and unsafe_backend are advanced routines. Read their docstrings to understand the caveats of their usage, and only call them if you wish to access low-level solver-specific functions.","category":"page"},{"location":"manual/models/#Direct-mode","page":"Models","title":"Direct mode","text":"","category":"section"},{"location":"manual/models/","page":"Models","title":"Models","text":"Using a CachingOptimizer results in an additional copy of the model being stored by JuMP in the .model_cache field. To avoid this overhead, create a JuMP model using direct_model:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = direct_model(HiGHS.Optimizer())\nA JuMP Model\nFeasibility problem with:\nVariables: 0\nModel mode: DIRECT\nSolver name: HiGHS","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"warning: Warning\nSolvers that do not support incremental modification do not support direct_model. An error will be thrown, telling you to use a CachingOptimizer instead.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"The benefit of using direct_model is that there are no extra layers (for example, Cachingoptimizer or LazyBridgeOptimizer) between model and the provided optimizer:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> backend(model)\nA HiGHS model with 0 columns and 0 rows.","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"A downside of direct mode is that there is no bridging layer. Therefore, only constraints which are natively supported by the solver are supported. For example, HiGHS.jl does not implement quadratic constraints:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"julia> model = direct_model(HiGHS.Optimizer());\n\njulia> set_silent(model)\n\njulia> @variable(model, x[1:2]);\n\njulia> @constraint(model, x[1]^2 + x[2]^2 <= 2)\nERROR: Constraints of type MathOptInterface.ScalarQuadraticFunction{Float64}-in-MathOptInterface.LessThan{Float64} are not supported by the solver.\n\nIf you expected the solver to support your problem, you may have an error in your formulation. Otherwise, consider using a different solver.\n\nThe list of available solvers, along with the problem types they support, is available at https://jump.dev/JuMP.jl/stable/installation/#Supported-solvers.\nStacktrace:","category":"page"},{"location":"manual/models/","page":"Models","title":"Models","text":"warning: Warning\nAnother downside of direct mode is that the behavior of querying solution information after modifying the problem is solver-specific. This can lead to errors, or the solver silently returning an incorrect value. See OptimizeNotCalled errors for more information.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/background/duality.md\"","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"CurrentModule = MathOptInterface\nDocTestSetup = quote\n import MathOptInterface as MOI\nend\nDocTestFilters = [r\"MathOptInterface|MOI\"]","category":"page"},{"location":"moi/background/duality/#Duality","page":"Duality","title":"Duality","text":"","category":"section"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"Conic duality is the starting point for MOI's duality conventions. When all functions are affine (or coordinate projections), and all constraint sets are closed convex cones, the model may be called a conic optimization problem.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"For a minimization problem in geometric conic form, the primal is:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n min_x in mathbbR^n a_0^T x + b_0\n\n textst A_i x + b_i in mathcalC_i i = 1 ldots m\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"and the dual is a maximization problem in standard conic form:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n max_y_1 ldots y_m -sum_i=1^m b_i^T y_i + b_0\n\n textst a_0 - sum_i=1^m A_i^T y_i = 0\n\n y_i in mathcalC_i^* i = 1 ldots m\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"where each mathcalC_i is a closed convex cone and mathcalC_i^* is its dual cone.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"For a maximization problem in geometric conic form, the primal is:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n max_x in mathbbR^n a_0^T x + b_0\n\n textst A_i x + b_i in mathcalC_i i = 1 ldots m\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"and the dual is a minimization problem in standard conic form:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n min_y_1 ldots y_m sum_i=1^m b_i^T y_i + b_0\n\n textst a_0 + sum_i=1^m A_i^T y_i = 0\n\n y_i in mathcalC_i^* i = 1 ldots m\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"A linear inequality constraint a^T x + b ge c is equivalent to a^T x + b - c in mathbbR_+, and a^T x + b le c is equivalent to a^T x + b - c in mathbbR_-. Variable-wise constraints are affine constraints with the appropriate identity mapping in place of A_i.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"For the special case of minimization LPs, the MOI primal form can be stated as:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n min_x in mathbbR^n a_0^T x + b_0\n\n textst\nA_1 x ge b_1\n A_2 x le b_2\n A_3 x = b_3\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"By applying the stated transformations to conic form, taking the dual, and transforming back into linear inequality form, one obtains the following dual:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n max_y_1y_2y_3 b_1^Ty_1 + b_2^Ty_2 + b_3^Ty_3 + b_0\n\n textst\nA_1^Ty_1 + A_2^Ty_2 + A_3^Ty_3 = a_0\n y_1 ge 0\n y_2 le 0\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"For maximization LPs, the MOI primal form can be stated as:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n max_x in mathbbR^n a_0^T x + b_0\n\n textst\nA_1 x ge b_1\n A_2 x le b_2\n A_3 x = b_3\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"and similarly, the dual is:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n min_y_1y_2y_3 -b_1^Ty_1 - b_2^Ty_2 - b_3^Ty_3 + b_0\n\n textst\nA_1^Ty_1 + A_2^Ty_2 + A_3^Ty_3 = -a_0\n y_1 ge 0\n y_2 le 0\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"warning: Warning\nFor the LP case, the signs of the feasible dual variables depend only on the sense of the corresponding primal inequality and not on the objective sense.","category":"page"},{"location":"moi/background/duality/#Duality-and-scalar-product","page":"Duality","title":"Duality and scalar product","text":"","category":"section"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"The scalar product is different from the canonical one for the sets PositiveSemidefiniteConeTriangle, LogDetConeTriangle, RootDetConeTriangle.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"If the set C_i of the section Duality is one of these three cones, then the rows of the matrix A_i corresponding to off-diagonal entries are twice the value of the coefficients field in the VectorAffineFunction for the corresponding rows. See PositiveSemidefiniteConeTriangle for details.","category":"page"},{"location":"moi/background/duality/#Dual-for-problems-with-quadratic-functions","page":"Duality","title":"Dual for problems with quadratic functions","text":"","category":"section"},{"location":"moi/background/duality/#Quadratic-Programs-(QPs)","page":"Duality","title":"Quadratic Programs (QPs)","text":"","category":"section"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"For quadratic programs with only affine conic constraints,","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign*\n min_x in mathbbR^n frac12x^TQ_0x + a_0^T x + b_0\n\n textst A_i x + b_i in mathcalC_i i = 1 ldots m\nendalign*","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"with cones mathcalC_i subseteq mathbbR^m_i for i = 1 ldots m, consider the Lagrangian function","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"L(x y) = frac12x^TQ_0x + a_0^T x + b_0 - sum_i = 1^m y_i^T (A_i x + b_i)","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"Let z(y) denote sum_i = 1^m A_i^T y_i - a_0, the Lagrangian can be rewritten as","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"L(x y) = frac12x^TQ_0x - z(y)^T x + b_0 - sum_i = 1^m y_i^T b_i","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"The condition nabla_x L(x y) = 0 gives","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"0 = nabla_x L(x y) = Q_0x + a_0 - sum_i = 1^m y_i^T b_i","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"which gives Q_0x = z(y). This allows to obtain that","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"min_x in mathbbR^n L(x y) = -frac12x^TQ_0x + b_0 - sum_i = 1^m y_i^T b_i","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"so the dual problem is","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"max_y_i in mathcalC_i^* min_x in mathbbR^n -frac12x^TQ_0x + b_0 - sum_i = 1^m y_i^T b_i","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"If Q_0 is invertible, we have x = Q_0^-1z(y) hence","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"min_x in mathbbR^n L(x y) = -frac12z(y)^TQ_0^-1z(y) + b_0 - sum_i = 1^m y_i^T b_i","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"so the dual problem is","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"max_y_i in mathcalC_i^* -frac12z(y)^TQ_0^-1z(y) + b_0 - sum_i = 1^m y_i^T b_i","category":"page"},{"location":"moi/background/duality/#Quadratically-Constrained-Quadratic-Programs-(QCQPs)","page":"Duality","title":"Quadratically Constrained Quadratic Programs (QCQPs)","text":"","category":"section"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"Given a problem with both quadratic function and quadratic objectives:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign*\n min_x in mathbbR^n frac12x^TQ_0x + a_0^T x + b_0\n\n textst frac12x^TQ_ix + a_i^T x + b_i in mathcalC_i i = 1 ldots m\nendalign*","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"with cones mathcalC_i subseteq mathbbR for i = 1 ldots m, consider the Lagrangian function","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"L(x y) = frac12x^TQ_0x + a_0^T x + b_0 - sum_i = 1^m y_i (frac12x^TQ_ix + a_i^T x + b_i)","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"A pair of primal-dual variables (x^star y^star) is optimal if","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"x^star is a minimizer of\nmin_x in mathbbR^n L(x y^star)\nThat is,\n0 = nabla_x L(x y^star) = Q_0x + a_0 - sum_i = 1^m y_i^star (Q_ix + a_i)\nand y^star is a maximizer of\nmax_y_i in mathcalC_i^* L(x^star y)\nThat is, for all i = 1 ldots m, frac12x^TQ_ix + a_i^T x + b_i is either zero or in the normal cone of mathcalC_i^* at y^star. For instance, if mathcalC_i is z in mathbbR z le 0 , this means that if frac12x^TQ_ix + a_i^T x + b_i is nonzero at x^star then y_i^star = 0. This is the classical complementary slackness condition.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"If mathcalC_i is a vector set, the discussion remains valid with y_i(frac12x^TQ_ix + a_i^T x + b_i) replaced with the scalar product between y_i and the vector of scalar-valued quadratic functions.","category":"page"},{"location":"moi/background/duality/#Dual-for-square-semidefinite-matrices","page":"Duality","title":"Dual for square semidefinite matrices","text":"","category":"section"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"The set PositiveSemidefiniteConeTriangle is a self-dual. That is, querying ConstraintDual of a PositiveSemidefiniteConeTriangle constraint returns a vector that is itself a member of PositiveSemidefiniteConeTriangle.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"However, the dual of PositiveSemidefiniteConeSquare is not so straight forward. This section explains the duality convention we use, and how it is derived.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"info: Info\nIf you have a PositiveSemidefiniteConeSquare constraint, the result matrix A from ConstraintDual is not positive semidefinite. However, A + A^top is positive semidefinite.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"Let mathcalS_+ be the cone of symmetric semidefinite matrices in the fracn(n+1)2 dimensional space of symmetric mathbbR^n times n matrices. That is, mathcalS_+ is the set PositiveSemidefiniteConeTriangle. It is well known that mathcalS_+ is a self-dual proper cone.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"Let mathcalP_+ be the cone of symmetric semidefinite matrices in the n^2 dimensional space of mathbbR^n times n matrices. That is mathcalP_+ is the set PositiveSemidefiniteConeSquare.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"In addition, let mathcalD_+ be the cone of matrices A such that A+A^top in mathcalP_+.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"mathcalP_+ is not proper because it is not solid (it is not n^2 dimensional), so it is not necessarily true that mathcalP_+^** = mathcalP_+.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"However, this is the case, because we will show that mathcalP_+^* = mathcalD_+ and mathcalD_+^* = mathcalP_+.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"First, let us see why mathcalP_+^* = mathcalD_+.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"If B is symmetric, then","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"langle AB rangle = langle A^top B^top rangle = langle A^top Brangle","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"so","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"2langle A B rangle = langle A B rangle + langle A^top B rangle = langle A + A^top B rangle","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"Therefore, langle ABrangle ge 0 for all B in mathcalP_+ if and only if langle A+A^topBrangle ge 0 for all B in mathcalP_+. Since A+A^top is symmetric, and we know that mathcalS_+ is self-dual, we have shown that mathcalP_+^* is the set of matrices A such that A+A^top in mathcalP_+.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"Second, let us see why mathcalD_+^* = mathcalP_+.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"Since A in mathcalD_+ implies that A^top in mathcalD_+, B in mathcalD_+^* means that langle A+A^topBrangle ge 0 for all A in mathcalD_+, and hence B in mathcalP_+.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"To see why it should be symmetric, simply notice that if B_ij B_ji, then langle ABrangle can be made arbitrarily small by setting A_ij = A_ij + s and A_ji = A_ji - s, with s arbitrarily large, and A stays in mathcalD_+ because A+A^top does not change.","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"Typically, the primal/dual pair for semidefinite programs is presented as:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n min langle C X rangle \ntextst langle A_k Xrangle = b_k forall k \n X in mathcalS_+\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"with the dual","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n max sum_k b_k y_k \ntextst C - sum A_k y_k in mathcalS_+\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"If we allow A_k to be non-symmetric, we should instead use:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n min langle C X rangle \ntextst langle A_k Xrangle = b_k forall k \n X in mathcalD_+\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"with the dual","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n max sum b_k y_k \ntextst C - sum A_k y_k in mathcalP_+\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"This is implemented as:","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n min langle C Z rangle + langle C - C^top S rangle \ntextst langle A_k Z rangle + langle A_k - A_k^top S rangle = b_k forall k \n Z in mathcalS_+\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"with the dual","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"beginalign\n max sum b_k y_k \ntextst C+C^top - sum (A_k+A_k^top) y_k in mathcalS_+ \n C-C^top - sum(A_k-A_k^top) y_k = 0\nendalign","category":"page"},{"location":"moi/background/duality/","page":"Duality","title":"Duality","text":"and we recover Z = X + X^top.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"EditURL = \"tips_and_tricks.jl\"","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#conic_tips_and_tricks","page":"Tips and Tricks","title":"Tips and Tricks","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"This tutorial was originally contributed by Arpit Bhatia.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"This tutorial is aimed at providing a simplistic introduction to conic programming using JuMP.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"It uses the following packages:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"using JuMP\nimport SCS\nimport LinearAlgebra","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"info: Info\nThis tutorial uses sets from MathOptInterface. By default, JuMP exports the MOI symbol as an alias for the MathOptInterface.jl package. We recommend making this more explicit in your code by adding the following lines:import MathOptInterface as MOI","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"import Random # hide\nRandom.seed!(1234) # hide\nnothing # hide","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"tip: Tip\nA good resource for learning more about functions which can be modeled using cones is the MOSEK Modeling Cookbook.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Background-theory","page":"Tips and Tricks","title":"Background theory","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"A subset C of a vector space V is a cone if forall x in C and positive scalars lambda 0, the product lambda x in C.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"A cone C is a convex cone if lambda x + (1 - lambda) y in C, for any lambda in 0 1, and any x y in C.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"Conic programming problems are convex optimization problems in which a convex function is minimized over the intersection of an affine subspace and a convex cone. An example of a conic-form minimization problems, in the primal form is:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"beginaligned\n min_x in mathbbR^n a_0^T x + b_0 \n textst A_i x + b_i in mathcalC_i i = 1 ldots m\nendaligned","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The corresponding dual problem is:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"beginaligned\n max_y_1 ldots y_m -sum_i=1^m b_i^T y_i + b_0 \n textst a_0 - sum_i=1^m A_i^T y_i = 0 \n y_i in mathcalC_i^* i = 1 ldots m\nendaligned","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"where each mathcalC_i is a closed convex cone and mathcalC_i^* is its dual cone.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Second-Order-Cone","page":"Tips and Tricks","title":"Second-Order Cone","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The SecondOrderCone (or Lorentz Cone) of dimension n is a cone of the form:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"K_soc = (t x) in mathbbR^n t ge x_2 ","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"It is most commonly used to represent the L2-norm of the vector x:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, x[1:3])\n@variable(model, t)\n@constraint(model, sum(x) == 1)\n@constraint(model, [t; x] in SecondOrderCone())\n@objective(model, Min, t)\noptimize!(model)\nvalue(t), value.(x)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Rotated-Second-Order-Cone","page":"Tips and Tricks","title":"Rotated Second-Order Cone","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"A Second-Order Cone rotated by pi4 in the (x_1x_2) plane is called a RotatedSecondOrderCone. It is a cone of the form:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"K_rsoc = (tux) in mathbbR^n 2tu ge x_2^2 tu ge 0 ","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"When u = 0.5, it represents the sum of squares of a vector x:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"data = [1.0, 2.0, 3.0, 4.0]\ntarget = [0.45, 1.04, 1.51, 1.97]\nmodel = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, θ)\n@variable(model, t)\n@expression(model, residuals, θ * data .- target)\n@constraint(model, [t; 0.5; residuals] in RotatedSecondOrderCone())\n@objective(model, Min, t)\noptimize!(model)\nvalue(θ), value(t)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Exponential-Cone","page":"Tips and Tricks","title":"Exponential Cone","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The MOI.ExponentialCone is a set of the form:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"K_exp = (xyz) in mathbbR^3 y exp (xy) le z y 0 ","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"It can be used to model problems involving log and exp.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Exponential","page":"Tips and Tricks","title":"Exponential","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"To model exp(x) le z, use (x, 1, z):","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, x == 1.5)\n@variable(model, z)\n@objective(model, Min, z)\n@constraint(model, [x, 1, z] in MOI.ExponentialCone())\noptimize!(model)\nvalue(z), exp(1.5)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Logarithm","page":"Tips and Tricks","title":"Logarithm","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"To model x le log(z), use (x, 1, z):","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, x)\n@variable(model, z == 1.5)\n@objective(model, Max, x)\n@constraint(model, [x, 1, z] in MOI.ExponentialCone())\noptimize!(model)\nvalue(x), log(1.5)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Log-sum-exp","page":"Tips and Tricks","title":"Log-sum-exp","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"To model t ge logleft(sum e^x_iright), use:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"N = 3\nx0 = rand(N)\nmodel = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, x[i = 1:N] == x0[i])\n@variable(model, t)\n@objective(model, Min, t)\n@variable(model, u[1:N])\n@constraint(model, sum(u) <= 1)\n@constraint(model, [i = 1:N], [x[i] - t, 1, u[i]] in MOI.ExponentialCone())\noptimize!(model)\nvalue(t), log(sum(exp.(x0)))","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Entropy","page":"Tips and Tricks","title":"Entropy","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The entropy maximization problem consists of maximizing the entropy function, H(x) = -xlogx subject to linear inequality constraints.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"beginaligned\n max - sum_i=1^n x_i log x_i \n textst mathbf1^top x = 1 \n Ax leq b\nendaligned","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"We can model this problem using an exponential cone by using the following transformation:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"tleq -xlogx iff tleq xlog(1x) iff (t x 1) in K_exp","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"Thus, our problem becomes,","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"beginaligned\n max 1^Tt \n textst Ax leq b \n 1^T x = 1 \n (t_i x_i 1) in K_exp forall i = 1 ldots n \nendaligned","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"m, n = 10, 15\nA, b = randn(m, n), rand(m, 1)\nmodel = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, t[1:n])\n@variable(model, x[1:n])\n@objective(model, Max, sum(t))\n@constraint(model, sum(x) == 1)\n@constraint(model, A * x .<= b)\n@constraint(model, [i = 1:n], [t[i], x[i], 1] in MOI.ExponentialCone())\noptimize!(model)\nobjective_value(model)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The MOI.ExponentialCone has a dual, the MOI.DualExponentialCone, that offers an alternative formulation that can be more efficient for some formulations.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"There is also the MOI.RelativeEntropyCone for explicitly encoding the relative entropy function","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, t)\n@variable(model, x[1:n])\n@objective(model, Max, -t)\n@constraint(model, sum(x) == 1)\n@constraint(model, A * x .<= b)\n@constraint(model, [t; ones(n); x] in MOI.RelativeEntropyCone(2n + 1))\noptimize!(model)\nobjective_value(model)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#PowerCone","page":"Tips and Tricks","title":"PowerCone","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The MOI.PowerCone is a three-dimensional set parameterized by a scalar value α. It has the form:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"K_p = (xyz) in mathbbR^3 x^alpha y^1-alpha ge z x ge 0 y ge 0 ","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The power cone permits a number of reformulations. For example, when p 1, we can model t ge x^p using the power cone (t 1 x) with alpha = 1 p. Thus, to model t ge x^3 with x ge 0","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, t)\n@variable(model, x >= 1.5)\n@constraint(model, [t, 1, x] in MOI.PowerCone(1 / 3))\n@objective(model, Min, t)\noptimize!(model)\nvalue(t), value(x)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The MOI.PowerCone has a dual, the MOI.DualPowerCone, that offers an alternative formulation that can be more efficient for some formulations.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#P-Norm","page":"Tips and Tricks","title":"P-Norm","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The p-norm x_p = left(sumlimits_i x_i^pright)^frac1p can be modeled using MOI.PowerCones. See the Mosek Modeling Cookbook for the derivation.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"function p_norm(x::Vector, p)\n N = length(x)\n model = Model(SCS.Optimizer)\n set_silent(model)\n @variable(model, r[1:N])\n @variable(model, t)\n @constraint(model, [i = 1:N], [r[i], t, x[i]] in MOI.PowerCone(1 / p))\n @constraint(model, sum(r) == t)\n @objective(model, Min, t)\n optimize!(model)\n return value(t)\nend\n\nx = rand(5);\nLinearAlgebra.norm(x, 4), p_norm(x, 4)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Positive-Semidefinite-Cone","page":"Tips and Tricks","title":"Positive Semidefinite Cone","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The set of positive semidefinite matrices (PSD) of dimension n form a cone in mathbbR^n. We write this set mathematically as:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"mathcalS_+^n = X in mathcalS^n mid z^T X z geq 0 forall zin mathbbR^n ","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"A PSD cone is represented in JuMP using the MOI sets PositiveSemidefiniteConeTriangle (for upper triangle of a PSD matrix) and PositiveSemidefiniteConeSquare (for a complete PSD matrix). However, it is preferable to use the PSDCone shortcut as illustrated below.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Example:-largest-eigenvalue-of-a-symmetric-matrix","page":"Tips and Tricks","title":"Example: largest eigenvalue of a symmetric matrix","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"Suppose A has eigenvalues lambda_1 geq lambda_2 ldots geq lambda_n. Then the matrix t I-A has eigenvalues t-lambda_1 t-lambda_2 ldots t-lambda_n. Note that t I-A is PSD exactly when all these eigenvalues are non-negative, and this happens for values t geq lambda_1. Thus, we can model the problem of finding the largest eigenvalue of a symmetric matrix as:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"beginaligned\nlambda_1 = min t \ntext st t I-A succeq 0\nendaligned","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"A = [3 2 4; 2 0 2; 4 2 3]\nI = Matrix{Float64}(LinearAlgebra.I, 3, 3)\nmodel = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, t)\n@objective(model, Min, t)\n@constraint(model, t .* I - A in PSDCone())\noptimize!(model)\nobjective_value(model)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#GeometricMeanCone","page":"Tips and Tricks","title":"GeometricMeanCone","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The MOI.GeometricMeanCone is a cone of the form:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"K_geo = (t x) in mathbbR^n x ge 0 t le sqrtn-1x_1 x_2 cdots x_n-1 ","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, x[1:4])\n@variable(model, t)\n@constraint(model, sum(x) == 1)\n@constraint(model, [t; x] in MOI.GeometricMeanCone(5))\noptimize!(model)\nvalue(t), value.(x)","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#RootDetCone","page":"Tips and Tricks","title":"RootDetCone","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The MOI.RootDetConeSquare is a cone of the form:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"K = (t X) in mathbbR^1+d^2 t le det(X)^frac1d ","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, t)\n@variable(model, X[1:2, 1:2])\n@objective(model, Max, t)\n@constraint(model, [t; vec(X)] in MOI.RootDetConeSquare(2))\n@constraint(model, X .== [2 1; 1 3])\noptimize!(model)\nvalue(t), sqrt(LinearAlgebra.det(value.(X)))","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"If X is symmetric, then you can use MOI.RootDetConeTriangle instead. This can be more efficient because the solver does not need to add additional constraints to ensure X is symmetric.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"When forming the function, use triangle_vec to obtain the column-wise upper triangle of the matrix as a vector in the order that JuMP requires.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, t)\n@variable(model, X[1:2, 1:2], Symmetric)\n@objective(model, Max, t)\n@constraint(model, [t; triangle_vec(X)] in MOI.RootDetConeTriangle(2))\n@constraint(model, X .== [2 1; 1 3])\noptimize!(model)\nvalue(t), sqrt(LinearAlgebra.det(value.(X)))","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#LogDetCone","page":"Tips and Tricks","title":"LogDetCone","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"The MOI.LogDetConeSquare is a cone of the form:","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"K = (t u X) in mathbbR^2+d^2 t le u log(det(X u)) ","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, t)\n@variable(model, u)\n@variable(model, X[1:2, 1:2])\n@objective(model, Max, t)\n@constraint(model, [t; u; vec(X)] in MOI.LogDetConeSquare(2))\n@constraint(model, X .== [2 1; 1 3])\n@constraint(model, u == 0.5)\noptimize!(model)\nvalue(t), 0.5 * log(LinearAlgebra.det(value.(X) ./ 0.5))","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"If X is symmetric, then you can use MOI.LogDetConeTriangle instead. This can be more efficient because the solver does not need to add additional constraints to ensure X is symmetric.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"When forming the function, use triangle_vec to obtain the column-wise upper triangle of the matrix as a vector in the order that JuMP requires.","category":"page"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"model = Model(SCS.Optimizer)\nset_silent(model)\n@variable(model, t)\n@variable(model, u)\n@variable(model, X[1:2, 1:2], Symmetric)\n@objective(model, Max, t)\n@constraint(model, [t; u; triangle_vec(X)] in MOI.LogDetConeTriangle(2))\n@constraint(model, X .== [2 1; 1 3])\n@constraint(model, u == 0.5)\noptimize!(model)\nvalue(t), 0.5 * log(LinearAlgebra.det(value.(X) ./ 0.5))","category":"page"},{"location":"tutorials/conic/tips_and_tricks/#Other-Cones-and-Functions","page":"Tips and Tricks","title":"Other Cones and Functions","text":"","category":"section"},{"location":"tutorials/conic/tips_and_tricks/","page":"Tips and Tricks","title":"Tips and Tricks","text":"For other cones supported by JuMP, check out the MathOptInterface Manual.","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"EditURL = \"https://github.com/jump-dev/BARON.jl/blob/v0.8.2/README.md\"","category":"page"},{"location":"packages/BARON/#BARON.jl","page":"jump-dev/BARON.jl","title":"BARON.jl","text":"","category":"section"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"(Image: Build Status) (Image: codecov)","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"BARON.jl is a wrapper for BARON by The Optimization Firm.","category":"page"},{"location":"packages/BARON/#Affiliation","page":"jump-dev/BARON.jl","title":"Affiliation","text":"","category":"section"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"This wrapper is maintained by the JuMP community and is not officially supported by The Optimization Firm.","category":"page"},{"location":"packages/BARON/#License","page":"jump-dev/BARON.jl","title":"License","text":"","category":"section"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"BARON.jl is licensed under the MIT License.","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"The underlying solver is a closed-source commercial product for which you must obtain a license from The Optimization Firm, although a small trial version is available for free.","category":"page"},{"location":"packages/BARON/#Installation","page":"jump-dev/BARON.jl","title":"Installation","text":"","category":"section"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"First, download a copy of the BARON solver and unpack the executable in a location of your choosing.","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"Once installed, set the BARON_EXEC environment variable pointing to the BARON executable (full path, including file name as it differs across platforms), and run Pkg.add(\"BARON\"). For example:","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"ENV[\"BARON_EXEC\"] = \"/path/to/baron.exe\"\nusing Pkg\nPkg.add(\"BARON\")","category":"page"},{"location":"packages/BARON/#Use-with-JuMP","page":"jump-dev/BARON.jl","title":"Use with JuMP","text":"","category":"section"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"using JuMP, BARON\nmodel = Model(BARON.Optimizer)","category":"page"},{"location":"packages/BARON/#MathOptInterface-API","page":"jump-dev/BARON.jl","title":"MathOptInterface API","text":"","category":"section"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"The BARON optimizer supports the following constraints and attributes.","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"List of supported objective functions:","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}\nMOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}\nMOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"List of supported variable types:","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"MOI.Reals","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"List of supported constraint types:","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"MOI.ScalarAffineFunction{Float64} in MOI.EqualTo{Float64}\nMOI.ScalarAffineFunction{Float64} in MOI.GreaterThan{Float64}\nMOI.ScalarAffineFunction{Float64} in MOI.Interval{Float64}\nMOI.ScalarAffineFunction{Float64} in MOI.LessThan{Float64}\nMOI.ScalarQuadraticFunction{Float64} in MOI.EqualTo{Float64}\nMOI.ScalarQuadraticFunction{Float64} in MOI.GreaterThan{Float64}\nMOI.ScalarQuadraticFunction{Float64} in MOI.Interval{Float64}\nMOI.ScalarQuadraticFunction{Float64} in MOI.LessThan{Float64}\nMOI.ScalarNonlinearFunction in MOI.EqualTo{Float64}\nMOI.ScalarNonlinearFunction in MOI.GreaterThan{Float64}\nMOI.ScalarNonlinearFunction in MOI.Interval{Float64}\nMOI.ScalarNonlinearFunction in MOI.LessThan{Float64}\nMOI.VariableIndex in MOI.EqualTo{Float64}\nMOI.VariableIndex in MOI.GreaterThan{Float64}\nMOI.VariableIndex in MOI.Integer\nMOI.VariableIndex in MOI.Interval{Float64}\nMOI.VariableIndex in MOI.LessThan{Float64}\nMOI.VariableIndex in MOI.ZeroOne","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"List of supported model attributes:","category":"page"},{"location":"packages/BARON/","page":"jump-dev/BARON.jl","title":"jump-dev/BARON.jl","text":"MOI.NLPBlock()\nMOI.ObjectiveSense()","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"EditURL = \"getting_started_with_julia.jl\"","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Getting-started-with-Julia","page":"Getting started with Julia","title":"Getting started with Julia","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Because JuMP is embedded in Julia, knowing some basic Julia is important before you start learning JuMP.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nThis tutorial is designed to provide a minimalist crash course in the basics of Julia. You can find resources that provide a more comprehensive introduction to Julia here.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Installing-Julia","page":"Getting started with Julia","title":"Installing Julia","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"To install Julia, download the latest stable release, then follow the platform specific install instructions.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nUnless you know otherwise, you probably want the 64-bit version.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Next, you need an IDE to develop in. VS Code is a popular choice, so follow these install instructions.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Julia can also be used with Jupyter notebooks or the reactive notebooks of Pluto.jl.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#The-Julia-REPL","page":"Getting started with Julia","title":"The Julia REPL","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"The main way of interacting with Julia is via its REPL (Read Evaluate Print Loop). To access the REPL, start the Julia executable to arrive at the julia> prompt, and then start coding:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"1 + 1","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"As your programs become larger, write a script as a text file, and then run that file using:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"julia> include(\"path/to/file.jl\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"warning: Warning\nBecause of Julia's startup latency, running scripts from the command line like the following is slow:$ julia path/to/file.jlUse the REPL or a notebook instead, and read The \"time-to-first-solve\" issue for more information.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Code-blocks-in-this-documentation","page":"Getting started with Julia","title":"Code blocks in this documentation","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"In this documentation you'll see a mix of code examples with and without the julia>.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"The Julia prompt is mostly used to demonstrate short code snippets, and the output is exactly what you will see if run from the REPL.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Blocks without the julia> can be copy-pasted into the REPL, but they are used because they enable richer output like plots or LaTeX to be displayed in the online and PDF versions of the documentation. If you run them from the REPL you may see different output.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Where-to-get-help","page":"Getting started with Julia","title":"Where to get help","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Read the documentation\nJuMP https://jump.dev/JuMP.jl/stable/\nJulia https://docs.julialang.org/en/v1/\nAsk (or browse) the Julia community forum: https://discourse.julialang.org\nIf the question is JuMP-related, ask in the Optimization (Mathematical) section, or tag your question with \"jump\"","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"To access the built-in help at the REPL, type ? to enter help-mode, followed by the name of the function to lookup:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"help?> print\nsearch: print println printstyled sprint isprint prevind parentindices precision escape_string\n\n print([io::IO], xs...)\n\n Write to io (or to the default output stream stdout if io is not given) a canonical\n (un-decorated) text representation. The representation used by print includes minimal formatting\n and tries to avoid Julia-specific details.\n\n print falls back to calling show, so most types should just define show. Define print if your\n type has a separate \"plain\" representation. For example, show displays strings with quotes, and\n print displays strings without quotes.\n\n string returns the output of print as a string.\n\n Examples\n ≡≡≡≡≡≡≡≡≡≡\n\n julia> print(\"Hello World!\")\n Hello World!\n julia> io = IOBuffer();\n\n julia> print(io, \"Hello\", ' ', :World!)\n\n julia> String(take!(io))\n \"Hello World!\"","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Numbers-and-arithmetic","page":"Getting started with Julia","title":"Numbers and arithmetic","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Since we want to solve optimization problems, we're going to be using a lot of math. Luckily, Julia is great for math, with all the usual operators:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"1 + 1\n1 - 2\n2 * 2\n4 / 5\n3^2","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Did you notice how Julia didn't print .0 after some of the numbers? Julia is a dynamic language, which means you never have to explicitly declare the type of a variable. However, in the background, Julia is giving each variable a type. Check the type of something using the typeof function:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"typeof(1)\ntypeof(1.0)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Here 1 is an Int64, which is an integer with 64 bits of precision, and 1.0 is a Float64, which is a floating point number with 64-bits of precision.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nIf you aren't familiar with floating point numbers, make sure to read the Floating point numbers section.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"We create complex numbers using im:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"x = 2 + 1im\nreal(x)\nimag(x)\ntypeof(x)\nx * (1 - 2im)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"info: Info\nThe curly brackets surround what we call the parameters of a type. You can read Complex{Int64} as \"a complex number, where the real and imaginary parts are represented by Int64.\" If we call typeof(1.0 + 2.0im) it will be Complex{Float64}, which a complex number with the parts represented by Float64.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"There are also some cool things like an irrational representation of π.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"π","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nTo make π (and most other Greek letters), type \\pi and then press [TAB].","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"typeof(π)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"However, if we do math with irrational numbers, they get converted to Float64:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"typeof(2π / 3)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Floating-point-numbers","page":"Getting started with Julia","title":"Floating point numbers","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"warning: Warning\nIf you aren't familiar with floating point numbers, make sure to read this section carefully.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"A Float64 is a floating point approximation of a real number using 64-bits of information.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Because it is an approximation, things we know hold true in mathematics don't hold true in a computer. For example:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"0.1 * 3 == 0.3","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"A more complicated example is:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"sin(2π / 3) == √3 / 2","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nGet √ by typing \\sqrt then press [TAB].","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Let's see what the differences are:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"0.1 * 3 - 0.3\nsin(2π / 3) - √3 / 2","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"They are small, but not zero.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"One way of explaining this difference is to consider how we would write 1 / 3 and 2 / 3 using only four digits after the decimal point. We would write 1 / 3 as 0.3333, and 2 / 3 as 0.6667. So, despite the fact that 2 * (1 / 3) == 2 / 3, 2 * 0.3333 == 0.6666 != 0.6667.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Let's try that again using ≈ (\\approx + [TAB]) instead of ==:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"0.1 * 3 ≈ 0.3\nsin(2π / 3) ≈ √3 / 2","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"≈ is a clever way of calling the isapprox function:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"isapprox(sin(2π / 3), √3 / 2; atol = 1e-8)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"warning: Warning\nFloating point is the reason solvers use tolerances when they solve optimization models. A common mistake you're likely to make is checking whether a binary variable is 0 using value(z) == 0. Always remember to use something like isapprox when comparing floating point numbers.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Note that isapprox will always return false if one of the number being compared is 0 and atol is zero (its default value).","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"1e-300 ≈ 0.0","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"so always set a nonzero value of atol if one of the arguments can be zero.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"isapprox(1e-9, 0.0; atol = 1e-8)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nGurobi has a good series of articles on the implications of floating point in optimization if you want to read more.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"If you aren't careful, floating point arithmetic can throw up all manner of issues. For example:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"1 + 1e-16 == 1","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"It even turns out that floating point numbers aren't associative:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"(1 + 1e-16) - 1e-16 == 1 + (1e-16 - 1e-16)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"It's important to note that this issue isn't Julia-specific. It happens in every programming language (try it out in Python).","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Vectors,-matrices,-and-arrays","page":"Getting started with Julia","title":"Vectors, matrices, and arrays","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Similar to MATLAB, Julia has native support for vectors, matrices and tensors; all of which are represented by arrays of different dimensions. Vectors are constructed by comma-separated elements surrounded by square brackets:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"b = [5, 6]","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Matrices can be constructed with spaces separating the columns, and semicolons separating the rows:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"A = [1.0 2.0; 3.0 4.0]","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"We can do linear algebra:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"x = A \\ b","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"info: Info\nHere is floating point at work again; x is approximately [-4, 4.5].","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"A * x\nA * x ≈ b","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Note that when multiplying vectors and matrices, dimensions matter. For example, you can't multiply a vector by a vector:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"try #hide\n b * b\ncatch err #hide\n showerror(stderr, err) #hide\nend #hide","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"But multiplying transposes works:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"b' * b\nb * b'","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Other-common-types","page":"Getting started with Julia","title":"Other common types","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/#Comments","page":"Getting started with Julia","title":"Comments","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Although not technically a type, code comments begin with the # character:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"1 + 1 # This is a comment","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Multiline comments begin with #= and end with =#:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"#=\nHere is a\nmultiline comment\n=#","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Comments can even be nested inside expressions. This is sometimes helpful when documenting inputs to functions:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"isapprox(\n sin(π),\n 0.0;\n #= We need an explicit atol here because we are comparing with 0 =#\n atol = 0.001,\n)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Strings","page":"Getting started with Julia","title":"Strings","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Double quotes are used for strings:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"typeof(\"This is Julia\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Unicode is fine in strings:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"typeof(\"π is about 3.1415\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Use println to print a string:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"println(\"Hello, World!\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Use $() to interpolate values into a string:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"x = 123\nprintln(\"The value of x is: $(x)\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Use triple-quotes for multiline strings:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"s = \"\"\"\nHere is\na\nmultiline string\n\"\"\"\n\nprintln(s)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Symbols","page":"Getting started with Julia","title":"Symbols","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Julia Symbols are a data structure from the compiler that represent Julia identifiers (that is, variable names).","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"println(\"The value of x is: $(eval(:x))\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"warning: Warning\nWe used eval here to demonstrate how Julia links Symbols to variables. However, avoid calling eval in your code. It is usually a sign that your code is doing something that could be more easily achieved a different way. The Community Forum is a good place to ask for advice on alternative approaches.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"typeof(:x)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"You can think of a Symbol as a String that takes up less memory, and that can't be modified.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Convert between String and Symbol using their constructors:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"String(:abc)\nSymbol(\"abc\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nSymbols are often (ab)used to stand in for a String or an Enum, when one of the latter is likely a better choice. The JuMP Style guide recommends reserving Symbols for identifiers. See @enum vs. Symbol for more.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Tuples","page":"Getting started with Julia","title":"Tuples","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Julia makes extensive use of a simple data structure called Tuples. Tuples are immutable collections of values. For example:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"t = (\"hello\", 1.2, :foo)\ntypeof(t)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Tuples can be accessed by index, similar to arrays:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"t[2]","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"And they can be \"unpacked\" like so:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"a, b, c = t\nb","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"The values can also be given names, which is a convenient way of making light-weight data structures.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"t = (word = \"hello\", num = 1.2, sym = :foo)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Values can be accessed using dot syntax:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"t.word","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Dictionaries","page":"Getting started with Julia","title":"Dictionaries","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Similar to Python, Julia has native support for dictionaries. Dictionaries provide a very generic way of mapping keys to values. For example, a map of integers to strings:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"d1 = Dict(1 => \"A\", 2 => \"B\", 4 => \"D\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"info: Info\nType-stuff again: Dict{Int64,String} is a dictionary with Int64 keys and String values.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Looking up a value uses the bracket syntax:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"d1[2]","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Dictionaries support non-integer keys and can mix data types:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Dict(\"A\" => 1, \"B\" => 2.5, \"D\" => 2 - 3im)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"info: Info\nJulia types form a hierarchy. Here the value type of the dictionary is Number, which is a generalization of Int64, Float64, and Complex{Int}. Leaf nodes in this hierarchy are called \"concrete\" types, and all others are called \"Abstract.\" In general, having variables with abstract types like Number can lead to slower code, so you should try to make sure every element in a dictionary or vector is the same type. For example, in this case we could represent every element as a Complex{Float64}:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Dict(\"A\" => 1.0 + 0.0im, \"B\" => 2.5 + 0.0im, \"D\" => 2.0 - 3.0im)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Dictionaries can be nested:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"d2 = Dict(\"A\" => 1, \"B\" => 2, \"D\" => Dict(:foo => 3, :bar => 4))\nd2[\"B\"]\nd2[\"D\"][:foo]","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Structs","page":"Getting started with Julia","title":"Structs","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"You can define custom datastructures with struct:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"struct MyStruct\n x::Int\n y::String\n z::Dict{Int,Int}\nend\n\na = MyStruct(1, \"a\", Dict(2 => 3))\na.x","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"By default, these are not mutable","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"try #hide\n a.x = 2\ncatch err #hide\n showerror(stderr, err) #hide\nend #hide","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"However, you can declare a mutable struct which is mutable:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"mutable struct MyStructMutable\n x::Int\n y::String\n z::Dict{Int,Int}\nend\n\na = MyStructMutable(1, \"a\", Dict(2 => 3))\na.x\na.x = 2\na","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Loops","page":"Getting started with Julia","title":"Loops","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Julia has native support for for-each style loops with the syntax for in end:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"for i in 1:5\n println(i)\nend","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"info: Info\nRanges are constructed as start:stop, or start:step:stop.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"for i in 1.2:1.1:5.6\n println(i)\nend","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"This for-each loop also works with dictionaries:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"for (key, value) in Dict(\"A\" => 1, \"B\" => 2.5, \"D\" => 2 - 3im)\n println(\"$(key): $(value)\")\nend","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Note that in contrast to vector languages like MATLAB and R, loops do not result in a significant performance degradation in Julia.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Control-flow","page":"Getting started with Julia","title":"Control flow","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Julia control flow is similar to MATLAB, using the keywords if-elseif-else-end, and the logical operators || and && for or and and respectively:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"for i in 0:5:15\n if i < 5\n println(\"$(i) is less than 5\")\n elseif i < 10\n println(\"$(i) is less than 10\")\n else\n if i == 10\n println(\"the value is 10\")\n else\n println(\"$(i) is bigger than 10\")\n end\n end\nend","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Comprehensions","page":"Getting started with Julia","title":"Comprehensions","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Similar to languages like Haskell and Python, Julia supports the use of simple loops in the construction of arrays and dictionaries, called comprehensions.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"A list of increasing integers:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"[i for i in 1:5]","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Matrices can be built by including multiple indices:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"[i * j for i in 1:5, j in 5:10]","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Conditional statements can be used to filter out some values:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"[i for i in 1:10 if i % 2 == 1]","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"A similar syntax can be used for building dictionaries:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Dict(\"$(i)\" => i for i in 1:10 if i % 2 == 1)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Functions","page":"Getting started with Julia","title":"Functions","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"A simple function is defined as follows:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"function print_hello()\n return println(\"hello\")\nend\nprint_hello()","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Arguments can be added to a function:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"function print_it(x)\n return println(x)\nend\nprint_it(\"hello\")\nprint_it(1.234)\nprint_it(:my_id)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Optional keyword arguments are also possible:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"function print_it(x; prefix = \"value:\")\n return println(\"$(prefix) $(x)\")\nend\nprint_it(1.234)\nprint_it(1.234; prefix = \"val:\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"The keyword return is used to specify the return values of a function:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"function mult(x; y = 2.0)\n return x * y\nend\n\nmult(4.0)\nmult(4.0; y = 5.0)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Anonymous-functions","page":"Getting started with Julia","title":"Anonymous functions","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"The syntax input -> output creates an anonymous function. These are most useful when passed to other functions. For example:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"f = x -> x^2\nf(2)\nmap(x -> x^2, 1:4)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Type-parameters","page":"Getting started with Julia","title":"Type parameters","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"We can constrain the inputs to a function using type parameters, which are :: followed by the type of the input we want. For example:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"function foo(x::Int)\n return x^2\nend\n\nfunction foo(x::Float64)\n return exp(x)\nend\n\nfunction foo(x::Number)\n return x + 1\nend\n\nfoo(2)\nfoo(2.0)\nfoo(1 + 1im)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"But what happens if we call foo with something we haven't defined it for?","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"try #hide\n foo([1, 2, 3])\ncatch err #hide\n showerror(stderr, err) #hide\nend #hide","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"A MethodError means that you passed a function something that didn't match the type that it was expecting. In this case, the error message says that it doesn't know how to handle an Vector{Int64}, but it does know how to handle Float64, Int64, and Number.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nRead the \"Closest candidates\" part of the error message carefully to get a hint as to what was expected.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Broadcasting","page":"Getting started with Julia","title":"Broadcasting","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"In the example above, we didn't define what to do if f was passed a Vector. Luckily, Julia provides a convenient syntax for mapping f element-wise over arrays. Just add a . between the name of the function and the opening (. This works for any function, including functions with multiple arguments. For example:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"foo.([1, 2, 3])","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nGet a MethodError when calling a function that takes a Vector, Matrix, or Array? Try broadcasting.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Mutable-vs-immutable-objects","page":"Getting started with Julia","title":"Mutable vs immutable objects","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Some types in Julia are mutable, which means you can change the values inside them. A good example is an array. You can modify the contents of an array without having to make a new array.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"In contrast, types like Float64 are immutable. You cannot modify the contents of a Float64.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"This is something to be aware of when passing types into functions. For example:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"function mutability_example(mutable_type::Vector{Int}, immutable_type::Int)\n mutable_type[1] += 1\n immutable_type += 1\n return\nend\n\nmutable_type = [1, 2, 3]\nimmutable_type = 1\n\nmutability_example(mutable_type, immutable_type)\n\nprintln(\"mutable_type: $(mutable_type)\")\nprintln(\"immutable_type: $(immutable_type)\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Because Vector{Int} is a mutable type, modifying the variable inside the function changed the value outside of the function. In contrast, the change to immutable_type didn't modify the value outside the function.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"You can check mutability with the isimmutable function:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"isimmutable([1, 2, 3])\nisimmutable(1)","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#The-package-manager","page":"Getting started with Julia","title":"The package manager","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/#Installing-packages","page":"Getting started with Julia","title":"Installing packages","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"No matter how wonderful Julia's base language is, at some point you will want to use an extension package. Some of these are built-in, for example random number generation is available in the Random package in the standard library. These packages are loaded with the commands using and import.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"using Random # The equivalent of Python's `from Random import *`\nimport Random # The equivalent of Python's `import Random`\n\nRandom.seed!(33)\n\n[rand() for i in 1:10]","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"The Package Manager is used to install packages that are not part of Julia's standard library.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"For example the following can be used to install JuMP,","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"using Pkg\nPkg.add(\"JuMP\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"For a complete list of registered Julia packages see the package listing at JuliaHub.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"From time to you may wish to use a Julia package that is not registered. In this case a git repository URL can be used to install the package.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"using Pkg\nPkg.add(\"https://github.com/user-name/MyPackage.jl.git\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/#Package-environments","page":"Getting started with Julia","title":"Package environments","text":"","category":"section"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"By default, Pkg.add will add packages to Julia's global environment. However, Julia also has built-in support for virtual environments.","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"Activate a virtual environment with:","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"import Pkg; Pkg.activate(\"/path/to/environment\")","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"You can see what packages are installed in the current environment with Pkg.status().","category":"page"},{"location":"tutorials/getting_started/getting_started_with_julia/","page":"Getting started with Julia","title":"Getting started with Julia","text":"tip: Tip\nWe strongly recommend you create a Pkg environment for each project that you create in Julia, and add only the packages that you need, instead of adding lots of packages to the global environment. The Pkg manager documentation has more information on this topic.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"EditURL = \"tips_and_tricks.jl\"","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#linear_tips_and_tricks","page":"Tips and tricks","title":"Tips and tricks","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"This tutorial was originally contributed by Arpit Bhatia.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"tip: Tip\nA good source of tips is the Mosek Modeling Cookbook.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"This tutorial collates some tips and tricks you can use when formulating mixed-integer programs. It uses the following packages:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"using JuMP","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Absolute-value","page":"Tips and tricks","title":"Absolute value","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"To model the absolute value function t ge x, there are a few options. In all cases, these reformulations only work if you are minimizing t \"down\" into x. They do not work if you are trying to maximize x.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Option-1","page":"Tips and tricks","title":"Option 1","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"This option adds two linear inequality constraints:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x)\n@variable(model, t)\n@constraint(model, t >= x)\n@constraint(model, t >= -x)","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Option-2","page":"Tips and tricks","title":"Option 2","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"This option uses two non-negative variables and forms expressions for x and t:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, z[1:2] >= 0)\n@expression(model, t, z[1] + z[2])\n@expression(model, x, z[1] - z[2])","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Option-3","page":"Tips and tricks","title":"Option 3","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"This option uses MOI.NormOneCone and lets JuMP choose the reformulation:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x)\n@variable(model, t)\n@constraint(model, [t; x] in MOI.NormOneCone(2))","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#L1-norm","page":"Tips and tricks","title":"L1-norm","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"To model min x_1, that is, min sumlimits_i x_i, use the MOI.NormOneCone:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:3])\n@variable(model, t)\n@constraint(model, [t; x] in MOI.NormOneCone(1 + length(x)))\n@objective(model, Min, t)","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Infinity-norm","page":"Tips and tricks","title":"Infinity-norm","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"To model min x_infty, that is, min maxlimits_i x_i, use the MOI.NormInfinityCone:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:3])\n@variable(model, t)\n@constraint(model, [t; x] in MOI.NormInfinityCone(1 + length(x)))\n@objective(model, Min, t)","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Max","page":"Tips and tricks","title":"Max","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"To model t ge maxx y, do:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, t)\n@variable(model, x)\n@variable(model, y)\n@constraint(model, t >= x)\n@constraint(model, t >= y)","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"This reformulation does not work for t ge minx y.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Min","page":"Tips and tricks","title":"Min","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"To model t le minx y, do:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, t)\n@variable(model, x)\n@variable(model, y)\n@constraint(model, t <= x)\n@constraint(model, t <= y)","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"This reformulation does not work for t le maxx y.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Boolean-operators","page":"Tips and tricks","title":"Boolean operators","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Binary variables can be used to construct logical operators. Here are some example.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Or","page":"Tips and tricks","title":"Or","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"x_3 = x_1 lor x_2","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:3], Bin)\n@constraints(model, begin\n x[1] <= x[3]\n x[2] <= x[3]\n x[3] <= x[1] + x[2]\nend)","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#And","page":"Tips and tricks","title":"And","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"x_3 = x_1 land x_2","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:3], Bin)\n@constraints(model, begin\n x[3] <= x[1]\n x[3] <= x[2]\n x[3] >= x[1] + x[2] - 1\nend)","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Not","page":"Tips and tricks","title":"Not","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"x_1 neg x_2","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:2], Bin)\n@constraint(model, x[1] == 1 - x[2])","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Implies","page":"Tips and tricks","title":"Implies","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"x_1 implies x_2","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:2], Bin)\n@constraint(model, x[1] <= x[2])","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Disjunctions","page":"Tips and tricks","title":"Disjunctions","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/#Problem","page":"Tips and tricks","title":"Problem","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Suppose that we have two constraints a^top x leq b and c^top x leq d, and we want at least one to hold.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Trick","page":"Tips and tricks","title":"Trick","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Introduce a \"big-M\" multiplied by a binary variable to relax one of the constraints.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Example Either x_1 leq 1 or x_2 leq 2.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:2])\n@variable(model, y, Bin)\nM = 100\n@constraint(model, x[1] <= 1 + M * y)\n@constraint(model, x[2] <= 2 + M * (1 - y))","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"warning: Warning\nIf M is too small, the solution may be suboptimal. If M is too big, the solver may encounter numerical issues. Try to use domain knowledge to choose an M that is just right. Gurobi has a good documentation section on this topic.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Indicator-constraints","page":"Tips and tricks","title":"Indicator constraints","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/#Problem-2","page":"Tips and tricks","title":"Problem","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Suppose we want to model that a certain linear inequality must be satisfied when some other event occurs, that is, for a binary variable z, we want to model the implication:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"z = 1 implies a^top x leq b","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Trick-1","page":"Tips and tricks","title":"Trick 1","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Some solvers have native support for indicator constraints.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Example x_1 + x_2 leq 1 if z = 1.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:2])\n@variable(model, z, Bin)\n@constraint(model, z => {sum(x) <= 1})","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Example x_1 + x_2 leq 1 if z = 0.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:2])\n@variable(model, z, Bin)\n@constraint(model, !z => {sum(x) <= 1})","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Trick-2","page":"Tips and tricks","title":"Trick 2","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"If the solver doesn't support indicator constraints, you an use the big-M trick.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Example x_1 + x_2 leq 1 if z = 1.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:2])\n@variable(model, z, Bin)\nM = 100\n@constraint(model, sum(x) <= 1 + M * (1 - z))","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Example x_1 + x_2 leq 1 if z = 0.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:2])\n@variable(model, z, Bin)\nM = 100\n@constraint(model, sum(x) <= 1 + M * z)","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Semi-continuous-variables","page":"Tips and tricks","title":"Semi-continuous variables","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"info: Info\nThis section uses sets from MathOptInterface. By default, JuMP exports the MOI symbol as an alias for the MathOptInterface.jl package. We recommend making this more explicit in your code by adding the following lines:import MathOptInterface as MOI","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"A semi-continuous variable is a continuous variable between bounds lu that also can assume the value zero, that is: x in 0 cup lu","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Example x in 0cup 1 2","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x in Semicontinuous(1.0, 2.0))","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Semi-integer-variables","page":"Tips and tricks","title":"Semi-integer variables","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"A semi-integer variable is a variable which assumes integer values between bounds lu and can also assume the value zero: x in 0 cup l u cap mathbbZ","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x in Semiinteger(5.0, 10.0))","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Special-Ordered-Sets-of-Type-1","page":"Tips and tricks","title":"Special Ordered Sets of Type 1","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"A Special Ordered Set of Type 1 is a set of variables, at most one of which can take a non-zero value, all others being at 0.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"They most frequently apply where a set of variables are actually binary variables. In other words, we have to choose at most one from a set of possibilities.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:3], Bin)\n@constraint(model, x in SOS1())","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"You can optionally pass SOS1 a weight vector like","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"@constraint(model, x in SOS1([0.2, 0.5, 0.3]))","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"If the decision variables are related and have a physical ordering, then the weight vector, although not used directly in the constraint, can help the solver make a better decision in the solution process.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#tip_sos2","page":"Tips and tricks","title":"Special Ordered Sets of Type 2","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"A Special Ordered Set of type 2 is a set of non-negative variables, of which at most two can be non-zero, and if two are non-zero these must be consecutive in their ordering.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"model = Model();\n@variable(model, x[1:3])\n@constraint(model, x in SOS2([3.0, 1.0, 2.0]))","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"The ordering provided by the weight vector is more important in this case as the variables need to be consecutive according to the ordering. For example, in the above constraint, the possible pairs are:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Consecutive\n(x[1] and x[3]) as they correspond to 3 and 2 resp. and thus can be non-zero\n(x[2] and x[3]) as they correspond to 1 and 2 resp. and thus can be non-zero\nNon-consecutive\n(x[1] and x[2]) as they correspond to 3 and 1 resp. and thus cannot be non-zero","category":"page"},{"location":"tutorials/linear/tips_and_tricks/#Piecewise-linear-approximations","page":"Tips and tricks","title":"Piecewise linear approximations","text":"","category":"section"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"SOSII constraints are most often used to form piecewise linear approximations of a function.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"Given a set of points for x:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"x̂ = -1:0.5:2","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"and a set of corresponding points for y:","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"ŷ = x̂ .^ 2","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"the piecewise linear approximation is constructed by representing x and y as convex combinations of x̂ and ŷ.","category":"page"},{"location":"tutorials/linear/tips_and_tricks/","page":"Tips and tricks","title":"Tips and tricks","text":"N = length(x̂)\nmodel = Model();\n@variable(model, -1 <= x <= 2)\n@variable(model, y)\n@variable(model, 0 <= λ[1:N] <= 1)\n@objective(model, Max, y)\n@constraints(model, begin\n x == sum(x̂[i] * λ[i] for i in 1:N)\n y == sum(ŷ[i] * λ[i] for i in 1:N)\n sum(λ) == 1\n λ in SOS2()\nend)","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"EditURL = \"web_app.jl\"","category":"page"},{"location":"tutorials/applications/web_app/#Serving-web-apps","page":"Serving web apps","title":"Serving web apps","text":"","category":"section"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"This tutorial demonstrates how to setup and serve JuMP models via a REST API.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"In the example app we are building, we solve a trivial mixed-integer program, which is parameterized by the lower bound of a variable. To call the service, users send an HTTP POST request with JSON contents indicating the lower bound. The returned value is the solution of the mixed-integer program as JSON.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"First, we need JuMP and a solver:","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"using JuMP\nimport HiGHS","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"We also need HTTP.jl to act as our REST server, and JSON.jl to marshal data.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"import HTTP\nimport JSON","category":"page"},{"location":"tutorials/applications/web_app/#The-server-side","page":"Serving web apps","title":"The server side","text":"","category":"section"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"The core components of our REST server are endpoints. These are functions which accept a Dict{String,Any} of input parameters, and return a Dict{String,Any} as output. The types are Dict{String,Any} because we're going to read these to and from JSON.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"Here's a very simple endpoint: it accepts params as input, formulates and solves a trivial mixed-integer program, and then returns a dictionary with the result.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"function endpoint_solve(params::Dict{String,Any})\n if !haskey(params, \"lower_bound\")\n return Dict{String,Any}(\n \"status\" => \"failure\",\n \"reason\" => \"missing lower_bound param\",\n )\n elseif !(params[\"lower_bound\"] isa Real)\n return Dict{String,Any}(\n \"status\" => \"failure\",\n \"reason\" => \"lower_bound is not a number\",\n )\n end\n model = Model(HiGHS.Optimizer)\n set_silent(model)\n @variable(model, x >= params[\"lower_bound\"], Int)\n optimize!(model)\n ret = Dict{String,Any}(\n \"status\" => \"okay\",\n \"terminaton_status\" => termination_status(model),\n \"primal_status\" => primal_status(model),\n )\n # Only include the `x` key if it has a value.\n if has_values(model)\n ret[\"x\"] = value(x)\n end\n return ret\nend","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"When we call this, we get:","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"endpoint_solve(Dict{String,Any}(\"lower_bound\" => 1.2))","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"endpoint_solve(Dict{String,Any}())","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"For a second function, we need a function that accepts an HTTP.Request object and returns an HTTP.Response object.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"function serve_solve(request::HTTP.Request)\n data = JSON.parse(String(request.body))\n solution = endpoint_solve(data)\n return HTTP.Response(200, JSON.json(solution))\nend","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"Finally, we need an HTTP server. There are a variety of ways you can do this in HTTP.jl. We use an explicit Sockets.listen so we have manual control of when we shutdown the server.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"function setup_server(host, port)\n server = HTTP.Sockets.listen(host, port)\n HTTP.serve!(host, port; server = server) do request\n try\n # Extend the server by adding other endpoints here.\n if request.target == \"/api/solve\"\n return serve_solve(request)\n else\n return HTTP.Response(404, \"target $(request.target) not found\")\n end\n catch err\n # Log details about the exception server-side\n @info \"Unhandled exception: $err\"\n # Return a response to the client\n return HTTP.Response(500, \"internal error\")\n end\n end\n return server\nend","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"warning: Warning\nHTTP.jl does not serve requests on a separate thread. Therefore, a long-running job will block the main thread, preventing concurrent users from submitting requests. To work-around this, read HTTP.jl issue 798 or watch Building Microservices and Applications in Julia from JuliaCon 2020.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"server = setup_server(HTTP.ip\"127.0.0.1\", 8080)","category":"page"},{"location":"tutorials/applications/web_app/#The-client-side","page":"Serving web apps","title":"The client side","text":"","category":"section"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"Now that we have a server, we can send it requests via this function:","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"function send_request(data::Dict; endpoint::String = \"solve\")\n ret = HTTP.request(\n \"POST\",\n # This should match the URL and endpoint we defined for our server.\n \"http://127.0.0.1:8080/api/$endpoint\",\n [\"Content-Type\" => \"application/json\"],\n JSON.json(data),\n )\n if ret.status != 200\n # This could happen if there are time-outs, network errors, etc.\n return Dict(\n \"status\" => \"failure\",\n \"code\" => ret.status,\n \"body\" => String(ret.body),\n )\n end\n return JSON.parse(String(ret.body))\nend","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"Let's see what happens:","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"send_request(Dict(\"lower_bound\" => 0))","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"send_request(Dict(\"lower_bound\" => 1.2))","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"If we don't send a lower_bound, we get:","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"send_request(Dict(\"invalid_param\" => 1.2))","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"If we don't send a lower_bound that is a number, we get:","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"send_request(Dict(\"lower_bound\" => \"1.2\"))","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"Finally, we can shutdown our HTTP server:","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"close(server)","category":"page"},{"location":"tutorials/applications/web_app/#Next-steps","page":"Serving web apps","title":"Next steps","text":"","category":"section"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"For more complicated examples relating to HTTP servers, consult the HTTP.jl documentation.","category":"page"},{"location":"tutorials/applications/web_app/","page":"Serving web apps","title":"Serving web apps","text":"To see how you can integrate this with a larger JuMP model, read Design patterns for larger models.","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"EditURL = \"https://github.com/jump-dev/SDPT3.jl/blob/b565aac2a58818090d521f2340e71f597688e4fb/README.md\"","category":"page"},{"location":"packages/SDPT3/#SDPT3.jl","page":"jump-dev/SDPT3.jl","title":"SDPT3.jl","text":"","category":"section"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"SDPT3.jl is wrapper for the SDPT3 solver.","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"The wrapper has two components:","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"an exported sdpt3 function that is a thin wrapper on top of the sdpt3 MATLAB function\nan interface to MathOptInterface","category":"page"},{"location":"packages/SDPT3/#Affiliation","page":"jump-dev/SDPT3.jl","title":"Affiliation","text":"","category":"section"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"This wrapper is maintained by the JuMP community and is not an official wrapper of SDPT3.","category":"page"},{"location":"packages/SDPT3/#License","page":"jump-dev/SDPT3.jl","title":"License","text":"","category":"section"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"SDPT3.jl is licensed under the MIT License.","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"The underlying solver, SDPT3 is licensed under the GPL v2 License.","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"In addition, SDPT3 requires an installation of MATLAB, which is a closed-source commercial product for which you must obtain a license.","category":"page"},{"location":"packages/SDPT3/#Use-with-JuMP","page":"jump-dev/SDPT3.jl","title":"Use with JuMP","text":"","category":"section"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"To use SDPT3 with JuMP, do:","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"using JuMP, SDPT3\nmodel = Model(SDPT3.Optimizer)\nset_attribute(model, \"printlevel\", 0)","category":"page"},{"location":"packages/SDPT3/#Installation","page":"jump-dev/SDPT3.jl","title":"Installation","text":"","category":"section"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"First, make sure that you satisfy the requirements of the MATLAB.jl Julia package, and that the SeDuMi software is installed in your MATLAB™ installation.","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"Then, install SDPT3.jl using Pkg.add:","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"import Pkg\nPkg.add(\"SDPT3\")","category":"page"},{"location":"packages/SDPT3/#SDPT3-not-in-PATH","page":"jump-dev/SDPT3.jl","title":"SDPT3 not in PATH","text":"","category":"section"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"If you get the error:","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"Error using save\nVariable 'jx_sdpt3_arg_out_1' not found.\n\nERROR: LoadError: MATLAB.MEngineError(\"failed to get variable jx_sdpt3_arg_out_1 from MATLAB session\")\nStacktrace:\n[...]","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"The error means that we could not find the sdpt3 function with one output argument using the MATLAB C API. This most likely means that you did not add SDPT3 to the MATLAB's path (that is, the toolbox/local/pathdef.m file).","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"If modifying toolbox/local/pathdef.m does not work, the following should work, where /path/to/sdpt3/ is the directory where the sdpt3 folder is located:","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"julia> using MATLAB\n\njulia> cd(\"/path/to/sdpt3/\") do\n MATLAB.mat\"install_sdpt3\"\n end\n\njulia> MATLAB.mat\"savepath\"","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"An alternative fix is suggested in the following issue.","category":"page"},{"location":"packages/SDPT3/#Error-in-validate","page":"jump-dev/SDPT3.jl","title":"Error in validate","text":"","category":"section"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"If you get the error:","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"Brace indexing is not supported for variables of this type.\n\nError in validate\n\nError in sdpt3 (line 171)\n [blk,At,C,b,blkdim,numblk,parbarrier] = validate(blk,At,C,b,par,parbarrier);\n\nError using save\nVariable 'jx_sdpt3_arg_out_1' not found.","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"It might mean that you have added SDPNAL in addition to SDPT3 in the MATLAB's path (that is, the toolbox/local/pathdef.m file). Because SDPNAL also defines a validate function, this can make sdpt3 call SDPNAL's validate function instead of SDPT3's validate function, which causes the issue.","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"One way to fix this from the Julia REPL is to reset the search path to the factory-installed state using restoredefaultpath:","category":"page"},{"location":"packages/SDPT3/","page":"jump-dev/SDPT3.jl","title":"jump-dev/SDPT3.jl","text":"julia> using MATLAB\n\njulia> MATLAB.restoredefaultpath()\n\njulia> MATLAB.mat\"savepath\"","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"EditURL = \"dualization.jl\"","category":"page"},{"location":"tutorials/conic/dualization/#Dualization","page":"Dualization","title":"Dualization","text":"","category":"section"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"The purpose of this tutorial is to explain how to use Dualization.jl to improve the performance of some conic optimization models. There are two important takeaways:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"JuMP reformulates problems to meet the input requirements of the solver, potentially increasing the problem size by adding slack variables and constraints.\nSolving the dual of a conic model can be more efficient than solving the primal.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"Dualization.jl is a package which fixes these problems, allowing you to solve the dual instead of the primal with a one-line change to your code.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"This tutorial uses the following packages","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"using JuMP\nimport Dualization\nimport SCS","category":"page"},{"location":"tutorials/conic/dualization/#Background","page":"Dualization","title":"Background","text":"","category":"section"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"Conic optimization solvers typically accept one of two input formulations.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"The first is the standard conic form:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"beginalign\n min_x in mathbbR^n c^top x \n textst A x = b \n x in mathcalK\nendalign","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"in which we have a set of linear equality constraints Ax = b and the variables belong to a cone mathcalK.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"The second is the geometric conic form:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"beginalign\n min_x in mathbbR^n c^top x \n textst A x - b in mathcalK\nendalign","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"in which an affine function Ax - b belongs to a cone mathcalK and the variables are free.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"It is trivial to convert between these two representations, for example, to go from the geometric conic form to the standard conic form we introduce slack variables y:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"beginalign\n min_x in mathbbR^n c^top x \n textst beginbmatrixA -Iendbmatrix beginbmatrixxyendbmatrix = b \n beginbmatrixxyendbmatrix in mathbbR^n times mathcalK\nendalign","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"and to go from the standard conic form to the geometric conic form, we can rewrite the equality constraint as a function belonging to the {0} cone:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"beginalign\n min_x in mathbbR^n c^top x \n textst beginbmatrixAIendbmatrix x - beginbmatrixb0endbmatrix in 0 times mathcalK\nendalign","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"From a theoretical perspective, the two formulations are equivalent, and if you implement a model in the standard conic form and pass it to a geometric conic form solver (or vice versa), then JuMP will automatically reformulate the problem into the correct formulation.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"From a practical perspective though, the reformulations are problematic because the additional slack variables and constraints can make the problem much larger and therefore harder to solve.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"You should also note many problems contain a mix of conic constraints and variables, and so they do not neatly fall into one of the two formulations. In these cases, JuMP reformulates only the variables and constraints as necessary to convert the problem into the desired form.","category":"page"},{"location":"tutorials/conic/dualization/#Primal-and-dual-formulations","page":"Dualization","title":"Primal and dual formulations","text":"","category":"section"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"Duality plays a large role in conic optimization. For a detailed description of conic duality, see Duality.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"A useful observation is that if the primal problem is in standard conic form, then the dual problem is in geometric conic form, and vice versa. Moreover, the primal and dual may have a different number of variables and constraints, although which one is smaller depends on the problem. Therefore, instead of reformulating the problem from one form to the other, it can be more efficient to solve the dual instead of the primal.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"To demonstrate, we use a variation of the Maximum cut via SDP example.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"The primal formulation (in standard conic form) is:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"model_primal = Model()\n@variable(model_primal, X[1:2, 1:2], PSD)\n@objective(model_primal, Max, sum([1 -1; -1 1] .* X))\n@constraint(model_primal, primal_c[i = 1:2], 1 - X[i, i] == 0)\nprint(model_primal)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"This problem has three scalar decision variables (the matrix X is symmetric), two scalar equality constraints, and a constraint that X is positive semidefinite.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"The dual of model_primal is:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"model_dual = Model()\n@variable(model_dual, y[1:2])\n@objective(model_dual, Min, sum(y))\n@constraint(model_dual, dual_c, [y[1]-1 1; 1 y[2]-1] in PSDCone())\nprint(model_dual)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"This problem has two scalar decision variables, and a 2x2 positive semidefinite matrix constraint.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"tip: Tip\nIf you haven't seen conic duality before, try deriving the dual problem based on the description in Duality. You'll need to know that the dual cone of PSDCone is the PSDCone.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"When we solve model_primal with SCS.Optimizer, SCS reports three variables (variables n: 3), five rows in the constraint matrix (constraints m: 5), and five non-zeros in the matrix (nnz(A): 5):","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"set_optimizer(model_primal, SCS.Optimizer)\noptimize!(model_primal)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"(There are five rows in the constraint matrix because SCS expects problems in geometric conic form, and so JuMP has reformulated the X, PSD variable constraint into the affine constraint X .+ 0 in PSDCone().)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"The solution we obtain is:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"value.(X)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"dual.(primal_c)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"objective_value(model_primal)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"When we solve model_dual with SCS.Optimizer, SCS reports two variables (variables n: 2), three rows in the constraint matrix (constraints m: 3), and two non-zeros in the matrix (nnz(A): 2):","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"set_optimizer(model_dual, SCS.Optimizer)\noptimize!(model_dual)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"and the solution we obtain is:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"dual.(dual_c)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"value.(y)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"objective_value(model_dual)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"This particular problem is small enough that it isn't meaningful to compare the solve times, but in general, we should expect model_dual to solve faster than model_primal because it contains fewer variables and constraints. The difference is particularly noticeable on large-scale optimization problems.","category":"page"},{"location":"tutorials/conic/dualization/#dual_optimizer","page":"Dualization","title":"dual_optimizer","text":"","category":"section"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"Manually deriving the conic dual is difficult and error-prone. The package Dualization.jl provides the Dualization.dual_optimizer meta-solver, which wraps any MathOptInterface-compatible solver in an interface that automatically formulates and solves the dual of an input problem.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"To demonstrate, we use Dualization.dual_optimizer to solve model_primal:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"set_optimizer(model_primal, Dualization.dual_optimizer(SCS.Optimizer))\noptimize!(model_primal)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"The performance is the same as if we solved model_dual, and the correct solution is returned to X:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"value.(X)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"dual.(primal_c)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"Moreover, if we use dual_optimizer on model_dual, then we get the same performance as if we had solved model_primal:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"set_optimizer(model_dual, Dualization.dual_optimizer(SCS.Optimizer))\noptimize!(model_dual)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"dual.(dual_c)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"value.(y)","category":"page"},{"location":"tutorials/conic/dualization/#A-mixed-example","page":"Dualization","title":"A mixed example","text":"","category":"section"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"The Maximum cut via SDP example is nicely defined because the primal is in standard conic form and the dual is in geometric conic form. However, many practical models contain a mix of the two formulations. One example is The minimum distortion problem:","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"D = [0 1 1 1; 1 0 2 2; 1 2 0 2; 1 2 2 0]\nmodel = Model()\n@variable(model, c²)\n@variable(model, Q[1:4, 1:4], PSD)\n@objective(model, Min, c²)\nfor i in 1:4, j in (i+1):4\n @constraint(model, D[i, j]^2 <= Q[i, i] + Q[j, j] - 2 * Q[i, j])\n @constraint(model, Q[i, i] + Q[j, j] - 2 * Q[i, j] <= c² * D[i, j]^2)\nend\n@constraint(model, Q[1, 1] == 0)\n@constraint(model, c² >= 1)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"In this formulation, the Q variable is of the form xinmathcalK, but there is also a free variable, c², a linear equality constraint, Q[1, 1] == 0, and some linear inequality constraints. Rather than attempting to derive the formulation that JuMP would pass to SCS and its dual, the simplest solution is to try solving the problem with and without dual_optimizer to see which formulation is most efficient.","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"set_optimizer(model, SCS.Optimizer)\noptimize!(model)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"set_optimizer(model, Dualization.dual_optimizer(SCS.Optimizer))\noptimize!(model)","category":"page"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"For this problem, SCS reports that the primal has variables n: 11, constraints m: 24 and that the dual has variables n: 14, constraints m: 24. Therefore, we should probably use the primal formulation because it has fewer variables and the same number of constraints.","category":"page"},{"location":"tutorials/conic/dualization/#When-to-use-dual_optimizer","page":"Dualization","title":"When to use dual_optimizer","text":"","category":"section"},{"location":"tutorials/conic/dualization/","page":"Dualization","title":"Dualization","text":"Because it can make the problem larger or smaller, depending on the problem and the choice of solver, there is no definitive rule on when you should use dual_optimizer. However, you should try dual_optimizer if your conic optimization problem takes a long time to solve, or if you need to repeatedly solve similarly structured problems with different data. In some cases solving the dual instead of the primal can make a large difference.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"EditURL = \"ellipse_approx.jl\"","category":"page"},{"location":"tutorials/conic/ellipse_approx/#Ellipsoid-approximation","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"","category":"section"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"This tutorial considers the problem of computing extremal ellipsoids: finding ellipsoids that best approximate a given set. As an extension, we show how to use JuMP to inspect the bridges that were used, and how to explore alternative formulations.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"The model comes from Section 4.9 \"Applications VII: extremal ellipsoids\" of the book Lectures on Modern Convex Optimization by Ben-Tal and Nemirovski (2001).","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"For a related example, see also the Minimal ellipses tutorial.","category":"page"},{"location":"tutorials/conic/ellipse_approx/#Problem-formulation","page":"Ellipsoid approximation","title":"Problem formulation","text":"","category":"section"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Suppose that we are given a set mathcalS consisting of m points in n-dimensional space:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"mathcalS = x_1 ldots x_m subset mathbbR^n","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Our goal is to determine an optimal vector c in mathbbR^n and an optimal n times n real symmetric matrix D such that the ellipse:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"E(D c) = x (x - c)^top D ( x - c) leq 1 ","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"contains mathcalS and has the smallest possible volume.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"The optimal D and c are given by the optimization problem:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"beginaligned\nmax quad t \ntextst quad Z succeq 0 \n beginbmatrix s z^top z Z endbmatrix succeq 0 \n x_i^top Z x_i - 2x_i^top z + s leq 1 quad i=1 ldots m \n t le sqrtndet(Z)\nendaligned","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"where D = Z_* and c = Z_*^-1 z_*.","category":"page"},{"location":"tutorials/conic/ellipse_approx/#Required-packages","page":"Ellipsoid approximation","title":"Required packages","text":"","category":"section"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"This tutorial uses the following packages:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"using JuMP\nimport LinearAlgebra\nimport Plots\nimport Random\nimport SCS","category":"page"},{"location":"tutorials/conic/ellipse_approx/#Data","page":"Ellipsoid approximation","title":"Data","text":"","category":"section"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"We first need to generate some points to work with.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"function generate_point_cloud(\n m; # number of 2-dimensional points\n a = 10, # scaling in x direction\n b = 2, # scaling in y direction\n rho = π / 6, # rotation of points around origin\n random_seed = 1,\n)\n rng = Random.MersenneTwister(random_seed)\n P = randn(rng, Float64, m, 2)\n Phi = [a*cos(rho) a*sin(rho); -b*sin(rho) b*cos(rho)]\n S = P * Phi\n return S\nend","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"For the sake of this example, let's take m = 600:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"S = generate_point_cloud(600);\nnothing #hide","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"We will visualise the points (and ellipse) using the Plots package:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"r = 1.1 * maximum(abs.(S))\nplot = Plots.scatter(\n S[:, 1],\n S[:, 2];\n xlim = (-r, r),\n ylim = (-r, r),\n label = nothing,\n c = :green,\n shape = :x,\n size = (600, 600),\n)","category":"page"},{"location":"tutorials/conic/ellipse_approx/#JuMP-formulation","page":"Ellipsoid approximation","title":"JuMP formulation","text":"","category":"section"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Now let's build and the JuMP model. We'll compute D and c after the solve.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"model = Model(SCS.Optimizer)\n# We need to use a tighter tolerance for this example, otherwise the bounding\n# ellipse won't actually be bounding...\nset_attribute(model, \"eps_rel\", 1e-6)\nset_silent(model)\nm, n = size(S)\n@variable(model, z[1:n])\n@variable(model, Z[1:n, 1:n], PSD)\n@variable(model, s)\n@variable(model, t)\n@constraint(model, [s z'; z Z] >= 0, PSDCone())\n@constraint(\n model,\n [i in 1:m],\n S[i, :]' * Z * S[i, :] - 2 * S[i, :]' * z + s <= 1,\n)\n@constraint(model, [t; vec(Z)] in MOI.RootDetConeSquare(n))\n@objective(model, Max, t)\noptimize!(model)\nsolution_summary(model)","category":"page"},{"location":"tutorials/conic/ellipse_approx/#Results","page":"Ellipsoid approximation","title":"Results","text":"","category":"section"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"After solving the model to optimality we can recover the solution in terms of D and c:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"D = value.(Z)","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"c = D \\ value.(z)","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Finally, overlaying the solution in the plot we see the minimal volume approximating ellipsoid:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"P = sqrt(D)\nq = -P * c\ndata = [tuple(P \\ [cos(θ) - q[1], sin(θ) - q[2]]...) for θ in 0:0.05:(2pi+0.05)]\nPlots.plot!(plot, data; c = :crimson, label = nothing)","category":"page"},{"location":"tutorials/conic/ellipse_approx/#Alternative-formulations","page":"Ellipsoid approximation","title":"Alternative formulations","text":"","category":"section"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"The formulation of model uses MOI.RootDetConeSquare. However, because SCS does not natively support this cone, JuMP automatically reformulates the problem into an equivalent problem that SCS does support. You can see the reformulation that JuMP chose using print_active_bridges:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"print_active_bridges(model)","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"There's a lot going on here, but the first bullet is:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"* Unsupported objective: MOI.VariableIndex\n| bridged by:\n| MOIB.Objective.FunctionizeBridge{Float64}\n| introduces:\n| * Supported objective: MOI.ScalarAffineFunction{Float64}","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"This says that SCS does not support a MOI.VariableIndex objective function, and that JuMP used a MOI.Bridges.Objective.FunctionizeBridge to convert it into a MOI.ScalarAffineFunction{Float64} objective function.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"We can leave JuMP to do the reformulation, or we can rewrite our model to have an objective function that SCS natively supports:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"@objective(model, Max, 1.0 * t + 0.0);\nnothing #hide","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Re-printing the active bridges:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"print_active_bridges(model)","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"we get * Supported objective: MOI.ScalarAffineFunction{Float64}.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"We can manually implement some other reformulations to change our model to something that SCS more closely supports by:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Replacing the MOI.VectorOfVariables in MOI.PositiveSemidefiniteConeTriangle constraint @variable(model, Z[1:n, 1:n], PSD) with the MOI.VectorAffineFunction in MOI.PositiveSemidefiniteConeTriangle @constraint(model, Z >= 0, PSDCone()).\nReplacing the MOI.VectorOfVariables in MOI.PositiveSemidefiniteConeSquare constraint [s z'; z Z] >= 0, PSDCone() with the MOI.VectorAffineFunction in MOI.PositiveSemidefiniteConeTriangle @constraint(model, LinearAlgebra.Symmetric([s z'; z Z]) >= 0, PSDCone()).\nReplacing the MOI.ScalarAffineFunction in MOI.GreaterThan constraints with the vectorized equivalent of MOI.VectorAffineFunction in MOI.Nonnegatives\nReplacing the MOI.VectorOfVariables in MOI.RootDetConeSquare constraint with MOI.VectorAffineFunction in MOI.RootDetConeTriangle.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Note that we still need to bridge MOI.PositiveSemidefiniteConeTriangle constraints because SCS uses an internal SCS.ScaledPSDCone set instead.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"model = Model(SCS.Optimizer)\nset_attribute(model, \"eps_rel\", 1e-6)\nset_silent(model)\n@variable(model, z[1:n])\n@variable(model, s)\n@variable(model, t)\n# The former @variable(model, Z[1:n, 1:n], PSD)\n@variable(model, Z[1:n, 1:n], Symmetric)\n@constraint(model, Z >= 0, PSDCone())\n# The former [s z'; z Z] >= 0, PSDCone()\n@constraint(model, LinearAlgebra.Symmetric([s z'; z Z]) >= 0, PSDCone())\n# The former constraint S[i, :]' * Z * S[i, :] - 2 * S[i, :]' * z + s <= 1\nf = [1 - S[i, :]' * Z * S[i, :] + 2 * S[i, :]' * z - s for i in 1:m]\n@constraint(model, f in MOI.Nonnegatives(m))\n# The former constraint [t; vec(Z)] in MOI.RootDetConeSquare(n)\n@constraint(model, 1 * [t; triangle_vec(Z)] .+ 0 in MOI.RootDetConeTriangle(n))\n# The former @objective(model, Max, t)\n@objective(model, Max, 1 * t + 0)\noptimize!(model)\nsolve_time_1 = solve_time(model)","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"This formulation gives the much smaller graph:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"print_active_bridges(model)","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"The last bullet shows how JuMP reformulated the MOI.RootDetConeTriangle constraint by adding a mix of MOI.PositiveSemidefiniteConeTriangle and MOI.GeometricMeanCone constraints.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Because SCS doesn't natively support the MOI.GeometricMeanCone, these constraints were further bridged using a MOI.Bridges.Constraint.GeoMeanToPowerBridge to a series of MOI.PowerCone constraints.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"However, there are many other ways that a MOI.GeometricMeanCone can be reformulated into something that SCS supports. Let's see what happens if we use remove_bridge to remove the MOI.Bridges.Constraint.GeoMeanToPowerBridge:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"remove_bridge(model, MOI.Bridges.Constraint.GeoMeanToPowerBridge)\noptimize!(model)","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"This time, the solve took:","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"solve_time_2 = solve_time(model)","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"where previously it took","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"solve_time_1","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Why was the solve time different?","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"print_active_bridges(model)","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"This time, JuMP used a MOI.Bridges.Constraint.GeoMeanBridge to reformulate the constraint into a set of MOI.RotatedSecondOrderCone constraints, which were further reformulated into a set of supported MOI.SecondOrderCone constraints.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"Since the two models are equivalent, we can conclude that for this particular model, the MOI.SecondOrderCone formulation is more efficient.","category":"page"},{"location":"tutorials/conic/ellipse_approx/","page":"Ellipsoid approximation","title":"Ellipsoid approximation","text":"In general though, the performance of a particular reformulation is problem- and solver-specific. Therefore, JuMP chooses to minimize the number of bridges in the default reformulation, leaving you to explore alternative formulations using the tools and techniques shown in this tutorial.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"DocTestSetup = quote\n using JuMP\nend","category":"page"},{"location":"manual/containers/#Containers","page":"Containers","title":"Containers","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"JuMP provides specialized containers similar to AxisArrays that enable multi-dimensional arrays with non-integer indices.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"These containers are created automatically by JuMP's macros. Each macro has the same basic syntax:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"@macroname(model, name[key1=index1, index2; optional_condition], other stuff)","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"The containers are generated by the name[key1=index1, index2; optional_condition] syntax. Everything else is specific to the particular macro.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Containers can be named, for example, name[key=index], or unnamed, for example, [key=index]. We call unnamed containers anonymous.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"We call the bits inside the square brackets and before the ; the index sets. The index sets can be named, for example, [i = 1:4], or they can be unnamed, for example, [1:4].","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"We call the bit inside the square brackets and after the ; the condition. Conditions are optional.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"In addition to the standard JuMP macros like @variable and @constraint, which construct containers of variables and constraints respectively, you can use Containers.@container to construct containers with arbitrary elements.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"We will use this macro to explain the three types of containers that are natively supported by JuMP: Array, Containers.DenseAxisArray, and Containers.SparseAxisArray.","category":"page"},{"location":"manual/containers/#Array","page":"Containers","title":"Array","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"An Array is created when the index sets are rectangular and the index sets are of the form 1:n.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> Containers.@container(x[i = 1:2, j = 1:3], (i, j))\n2×3 Matrix{Tuple{Int64, Int64}}:\n (1, 1) (1, 2) (1, 3)\n (2, 1) (2, 2) (2, 3)","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"The result is a normal Julia Array, so you can do all the usual things.","category":"page"},{"location":"manual/containers/#Slicing","page":"Containers","title":"Slicing","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Arrays can be sliced","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> x[:, 1]\n2-element Vector{Tuple{Int64, Int64}}:\n (1, 1)\n (2, 1)\n\njulia> x[2, :]\n3-element Vector{Tuple{Int64, Int64}}:\n (2, 1)\n (2, 2)\n (2, 3)","category":"page"},{"location":"manual/containers/#Looping","page":"Containers","title":"Looping","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Use eachindex to loop over the elements:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> for key in eachindex(x)\n println(x[key])\n end\n(1, 1)\n(2, 1)\n(1, 2)\n(2, 2)\n(1, 3)\n(2, 3)","category":"page"},{"location":"manual/containers/#Get-the-index-sets","page":"Containers","title":"Get the index sets","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Use axes to obtain the index sets:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> axes(x)\n(Base.OneTo(2), Base.OneTo(3))","category":"page"},{"location":"manual/containers/#Broadcasting","page":"Containers","title":"Broadcasting","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Broadcasting over an Array returns an Array","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> swap(x::Tuple) = (last(x), first(x))\nswap (generic function with 1 method)\n\njulia> swap.(x)\n2×3 Matrix{Tuple{Int64, Int64}}:\n (1, 1) (2, 1) (3, 1)\n (1, 2) (2, 2) (3, 2)","category":"page"},{"location":"manual/containers/#Tables","page":"Containers","title":"Tables","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Use Containers.rowtable to convert the Array into a Tables.jl compatible Vector{<:NamedTuple}:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> table = Containers.rowtable(x; header = [:I, :J, :value])\n6-element Vector{NamedTuple{(:I, :J, :value), Tuple{Int64, Int64, Tuple{Int64, Int64}}}}:\n (I = 1, J = 1, value = (1, 1))\n (I = 2, J = 1, value = (2, 1))\n (I = 1, J = 2, value = (1, 2))\n (I = 2, J = 2, value = (2, 2))\n (I = 1, J = 3, value = (1, 3))\n (I = 2, J = 3, value = (2, 3))","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Because it supports the Tables.jl interface, you can pass it to any function which accepts a table as input:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> import DataFrames;\n\njulia> DataFrames.DataFrame(table)\n6×3 DataFrame\n Row │ I J value\n │ Int64 Int64 Tuple…\n─────┼──────────────────────\n 1 │ 1 1 (1, 1)\n 2 │ 2 1 (2, 1)\n 3 │ 1 2 (1, 2)\n 4 │ 2 2 (2, 2)\n 5 │ 1 3 (1, 3)\n 6 │ 2 3 (2, 3)","category":"page"},{"location":"manual/containers/#DenseAxisArray","page":"Containers","title":"DenseAxisArray","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"A Containers.DenseAxisArray is created when the index sets are rectangular, but not of the form 1:n. The index sets can be of any type.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> x = Containers.@container([i = 1:2, j = [:A, :B]], (i, j))\n2-dimensional DenseAxisArray{Tuple{Int64, Symbol},2,...} with index sets:\n Dimension 1, Base.OneTo(2)\n Dimension 2, [:A, :B]\nAnd data, a 2×2 Matrix{Tuple{Int64, Symbol}}:\n (1, :A) (1, :B)\n (2, :A) (2, :B)","category":"page"},{"location":"manual/containers/#Slicing-2","page":"Containers","title":"Slicing","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"DenseAxisArrays can be sliced","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> x[:, :A]\n1-dimensional DenseAxisArray{Tuple{Int64, Symbol},1,...} with index sets:\n Dimension 1, Base.OneTo(2)\nAnd data, a 2-element Vector{Tuple{Int64, Symbol}}:\n (1, :A)\n (2, :A)\n\njulia> x[1, :]\n1-dimensional DenseAxisArray{Tuple{Int64, Symbol},1,...} with index sets:\n Dimension 1, [:A, :B]\nAnd data, a 2-element Vector{Tuple{Int64, Symbol}}:\n (1, :A)\n (1, :B)","category":"page"},{"location":"manual/containers/#Looping-2","page":"Containers","title":"Looping","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Use eachindex to loop over the elements:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> for key in eachindex(x)\n println(x[key])\n end\n(1, :A)\n(2, :A)\n(1, :B)\n(2, :B)","category":"page"},{"location":"manual/containers/#Get-the-index-sets-2","page":"Containers","title":"Get the index sets","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Use axes to obtain the index sets:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> axes(x)\n(Base.OneTo(2), [:A, :B])","category":"page"},{"location":"manual/containers/#Broadcasting-2","page":"Containers","title":"Broadcasting","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Broadcasting over a DenseAxisArray returns a DenseAxisArray","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> swap(x::Tuple) = (last(x), first(x))\nswap (generic function with 1 method)\n\njulia> swap.(x)\n2-dimensional DenseAxisArray{Tuple{Symbol, Int64},2,...} with index sets:\n Dimension 1, Base.OneTo(2)\n Dimension 2, [:A, :B]\nAnd data, a 2×2 Matrix{Tuple{Symbol, Int64}}:\n (:A, 1) (:B, 1)\n (:A, 2) (:B, 2)","category":"page"},{"location":"manual/containers/#Access-internal-data","page":"Containers","title":"Access internal data","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Use Array(x) to copy the internal data array into a new Array:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> Array(x)\n2×2 Matrix{Tuple{Int64, Symbol}}:\n (1, :A) (1, :B)\n (2, :A) (2, :B)","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"To access the internal data without a copy, use x.data.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> x.data\n2×2 Matrix{Tuple{Int64, Symbol}}:\n (1, :A) (1, :B)\n (2, :A) (2, :B)","category":"page"},{"location":"manual/containers/#Tables-2","page":"Containers","title":"Tables","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Use Containers.rowtable to convert the DenseAxisArray into a Tables.jl compatible Vector{<:NamedTuple}:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> table = Containers.rowtable(x; header = [:I, :J, :value])\n4-element Vector{NamedTuple{(:I, :J, :value), Tuple{Int64, Symbol, Tuple{Int64, Symbol}}}}:\n (I = 1, J = :A, value = (1, :A))\n (I = 2, J = :A, value = (2, :A))\n (I = 1, J = :B, value = (1, :B))\n (I = 2, J = :B, value = (2, :B))","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Because it supports the Tables.jl interface, you can pass it to any function which accepts a table as input:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> import DataFrames;\n\njulia> DataFrames.DataFrame(table)\n4×3 DataFrame\n Row │ I J value\n │ Int64 Symbol Tuple…\n─────┼────────────────────────\n 1 │ 1 A (1, :A)\n 2 │ 2 A (2, :A)\n 3 │ 1 B (1, :B)\n 4 │ 2 B (2, :B)","category":"page"},{"location":"manual/containers/#Keyword-indexing","page":"Containers","title":"Keyword indexing","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"If all axes are named, you can use keyword indexing:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> x[i = 2, j = :A]\n(2, :A)\n\njulia> x[i = :, j = :B]\n1-dimensional DenseAxisArray{Tuple{Int64, Symbol},1,...} with index sets:\n Dimension 1, Base.OneTo(2)\nAnd data, a 2-element Vector{Tuple{Int64, Symbol}}:\n (1, :B)\n (2, :B)","category":"page"},{"location":"manual/containers/#SparseAxisArray","page":"Containers","title":"SparseAxisArray","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"A Containers.SparseAxisArray is created when the index sets are non-rectangular. This occurs in two circumstances:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"An index depends on a prior index:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> Containers.@container([i = 1:2, j = i:2], (i, j))\nJuMP.Containers.SparseAxisArray{Tuple{Int64, Int64}, 2, Tuple{Int64, Int64}} with 3 entries:\n [1, 1] = (1, 1)\n [1, 2] = (1, 2)\n [2, 2] = (2, 2)","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"The [indices; condition] syntax is used:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> x = Containers.@container([i = 1:3, j = [:A, :B]; i > 1], (i, j))\nJuMP.Containers.SparseAxisArray{Tuple{Int64, Symbol}, 2, Tuple{Int64, Symbol}} with 4 entries:\n [2, A] = (2, :A)\n [2, B] = (2, :B)\n [3, A] = (3, :A)\n [3, B] = (3, :B)","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Here we have the index sets i = 1:3, j = [:A, :B], followed by ;, and then a condition, which evaluates to true or false: i > 1.","category":"page"},{"location":"manual/containers/#Slicing-3","page":"Containers","title":"Slicing","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Slicing is supported:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> y = x[:, :B]\nJuMP.Containers.SparseAxisArray{Tuple{Int64, Symbol}, 1, Tuple{Int64}} with 2 entries:\n [2] = (2, :B)\n [3] = (3, :B)","category":"page"},{"location":"manual/containers/#Looping-3","page":"Containers","title":"Looping","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Use eachindex to loop over the elements:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> for key in eachindex(y)\n println(y[key])\n end\n(2, :B)\n(3, :B)","category":"page"},{"location":"manual/containers/#Broadcasting-3","page":"Containers","title":"Broadcasting","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Broadcasting over a SparseAxisArray returns a SparseAxisArray","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> swap(x::Tuple) = (last(x), first(x))\nswap (generic function with 1 method)\n\njulia> swap.(y)\nJuMP.Containers.SparseAxisArray{Tuple{Symbol, Int64}, 1, Tuple{Int64}} with 2 entries:\n [2] = (:B, 2)\n [3] = (:B, 3)","category":"page"},{"location":"manual/containers/#Tables-3","page":"Containers","title":"Tables","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Use Containers.rowtable to convert the SparseAxisArray into a Tables.jl compatible Vector{<:NamedTuple}:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> table = Containers.rowtable(x; header = [:I, :J, :value])\n4-element Vector{NamedTuple{(:I, :J, :value), Tuple{Int64, Symbol, Tuple{Int64, Symbol}}}}:\n (I = 2, J = :A, value = (2, :A))\n (I = 2, J = :B, value = (2, :B))\n (I = 3, J = :A, value = (3, :A))\n (I = 3, J = :B, value = (3, :B))","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Because it supports the Tables.jl interface, you can pass it to any function which accepts a table as input:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> import DataFrames;\n\njulia> DataFrames.DataFrame(table)\n4×3 DataFrame\n Row │ I J value\n │ Int64 Symbol Tuple…\n─────┼────────────────────────\n 1 │ 2 A (2, :A)\n 2 │ 2 B (2, :B)\n 3 │ 3 A (3, :A)\n 4 │ 3 B (3, :B)","category":"page"},{"location":"manual/containers/#Keyword-indexing-2","page":"Containers","title":"Keyword indexing","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"If all axes are named, you can use keyword indexing:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> x[i = 2, j = :A]\n(2, :A)\n\njulia> x[i = :, j = :B]\nJuMP.Containers.SparseAxisArray{Tuple{Int64, Symbol}, 1, Tuple{Int64}} with 2 entries:\n [2] = (2, :B)\n [3] = (3, :B)","category":"page"},{"location":"manual/containers/#Forcing-the-container-type","page":"Containers","title":"Forcing the container type","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Pass container = T to use T as the container. For example:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> Containers.@container([i = 1:2, j = 1:2], i + j, container = Array)\n2×2 Matrix{Int64}:\n 2 3\n 3 4\n\njulia> Containers.@container([i = 1:2, j = 1:2], i + j, container = Dict)\nDict{Tuple{Int64, Int64}, Int64} with 4 entries:\n (1, 2) => 3\n (1, 1) => 2\n (2, 2) => 4\n (2, 1) => 3","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"You can also pass DenseAxisArray or SparseAxisArray.","category":"page"},{"location":"manual/containers/#How-different-container-types-are-chosen","page":"Containers","title":"How different container types are chosen","text":"","category":"section"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"If the compiler can prove at compile time that the index sets are rectangular, and indexed by a compact set of integers that start at 1, Containers.@container will return an array. This is the case if your index sets are visible to the macro as 1:n:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> Containers.@container([i=1:3, j=1:5], i + j)\n3×5 Matrix{Int64}:\n 2 3 4 5 6\n 3 4 5 6 7\n 4 5 6 7 8","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"or an instance of Base.OneTo:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> set = Base.OneTo(3)\nBase.OneTo(3)\n\njulia> Containers.@container([i=set, j=1:5], i + j)\n3×5 Matrix{Int64}:\n 2 3 4 5 6\n 3 4 5 6 7\n 4 5 6 7 8","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"If the compiler can prove that the index set is rectangular, but not necessarily of the form 1:n at compile time, then a Containers.DenseAxisArray will be constructed instead:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> set = 1:3\n1:3\n\njulia> Containers.@container([i=set, j=1:5], i + j)\n2-dimensional DenseAxisArray{Int64,2,...} with index sets:\n Dimension 1, 1:3\n Dimension 2, Base.OneTo(5)\nAnd data, a 3×5 Matrix{Int64}:\n 2 3 4 5 6\n 3 4 5 6 7\n 4 5 6 7 8","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"info: Info\nWhat happened here? Although we know that set contains 1:3, at compile time the typeof(set) is a UnitRange{Int}. Therefore, Julia can't prove that the range starts at 1 (it only finds this out at runtime), and it defaults to a DenseAxisArray. The case where we explicitly wrote i = 1:3 worked because the macro can \"see\" the 1 at compile time.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"However, if you know that the indices do form an Array, you can force the container type with container = Array:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> set = 1:3\n1:3\n\njulia> Containers.@container([i=set, j=1:5], i + j, container = Array)\n3×5 Matrix{Int64}:\n 2 3 4 5 6\n 3 4 5 6 7\n 4 5 6 7 8","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Here's another example with something similar:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> a = 1\n1\n\njulia> Containers.@container([i=a:3, j=1:5], i + j)\n2-dimensional DenseAxisArray{Int64,2,...} with index sets:\n Dimension 1, 1:3\n Dimension 2, Base.OneTo(5)\nAnd data, a 3×5 Matrix{Int64}:\n 2 3 4 5 6\n 3 4 5 6 7\n 4 5 6 7 8\n\njulia> Containers.@container([i=1:a, j=1:5], i + j)\n1×5 Matrix{Int64}:\n 2 3 4 5 6","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"Finally, if the compiler cannot prove that the index set is rectangular, a Containers.SparseAxisArray will be created.","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"This occurs when some indices depend on a previous one:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> Containers.@container([i=1:3, j=1:i], i + j)\nJuMP.Containers.SparseAxisArray{Int64, 2, Tuple{Int64, Int64}} with 6 entries:\n [1, 1] = 2\n [2, 1] = 3\n [2, 2] = 4\n [3, 1] = 4\n [3, 2] = 5\n [3, 3] = 6","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"or if there is a condition on the index sets:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> Containers.@container([i = 1:5; isodd(i)], i^2)\nJuMP.Containers.SparseAxisArray{Int64, 1, Tuple{Int64}} with 3 entries:\n [1] = 1\n [3] = 9\n [5] = 25","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"The condition can depend on multiple indices, the only requirement is that it is an expression that returns true or false:","category":"page"},{"location":"manual/containers/","page":"Containers","title":"Containers","text":"julia> condition(i, j) = isodd(i) && iseven(j)\ncondition (generic function with 1 method)\n\njulia> Containers.@container([i = 1:2, j = 1:4; condition(i, j)], i + j)\nJuMP.Containers.SparseAxisArray{Int64, 2, Tuple{Int64, Int64}} with 2 entries:\n [1, 2] = 3\n [1, 4] = 5","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/manual/solutions.md\"","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"CurrentModule = MathOptInterface\nDocTestSetup = quote\n import MathOptInterface as MOI\nend\nDocTestFilters = [r\"MathOptInterface|MOI\"]","category":"page"},{"location":"moi/manual/solutions/#manual_solutions","page":"Solutions","title":"Solutions","text":"","category":"section"},{"location":"moi/manual/solutions/#Solving-and-retrieving-the-results","page":"Solutions","title":"Solving and retrieving the results","text":"","category":"section"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Once an optimizer is loaded with the objective function and all of the constraints, we can ask the solver to solve the model by calling optimize!.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"MOI.optimize!(optimizer)","category":"page"},{"location":"moi/manual/solutions/#Why-did-the-solver-stop?","page":"Solutions","title":"Why did the solver stop?","text":"","category":"section"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"The optimization procedure may stop for a number of reasons. The TerminationStatus attribute of the optimizer returns a TerminationStatusCode object which explains why the solver stopped.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"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.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"A typical usage of the TerminationStatus attribute is as follows:","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"status = MOI.get(optimizer, TerminationStatus())\nif status == MOI.OPTIMAL\n # Ok, we solved the problem!\nelse\n # Handle other cases.\nend","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"After checking the TerminationStatus, check ResultCount. 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 infeasibility, a solver may return no result or a proof of infeasibility. The ResultCount attribute distinguishes between these two cases.","category":"page"},{"location":"moi/manual/solutions/#Primal-solutions","page":"Solutions","title":"Primal solutions","text":"","category":"section"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Use the PrimalStatus optimizer attribute to return a ResultStatusCode describing the status of the primal solution.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Common returns are described below in the Common status situations section.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Query the primal solution using the VariablePrimal and ConstraintPrimal attributes.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Query the objective function value using the ObjectiveValue attribute.","category":"page"},{"location":"moi/manual/solutions/#Dual-solutions","page":"Solutions","title":"Dual solutions","text":"","category":"section"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"warning: Warning\nSee Duality for a discussion of the MOI conventions for primal-dual pairs and certificates.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Use the DualStatus optimizer attribute to return a ResultStatusCode describing the status of the dual solution.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Query the dual solution using the ConstraintDual attribute.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Query the dual objective function value using the DualObjectiveValue attribute.","category":"page"},{"location":"moi/manual/solutions/#Common-status-situations","page":"Solutions","title":"Common status situations","text":"","category":"section"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"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.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"info: Info\n* in the tables indicate that multiple different values are possible.","category":"page"},{"location":"moi/manual/solutions/#Primal-dual-convex-solver","page":"Solutions","title":"Primal-dual convex solver","text":"","category":"section"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Linear programming and conic optimization solvers fall into this category.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"What happened? TerminationStatus ResultCount PrimalStatus DualStatus\nProved optimality OPTIMAL 1 FEASIBLE_POINT FEASIBLE_POINT\nProved infeasible INFEASIBLE 1 NO_SOLUTION INFEASIBILITY_CERTIFICATE\nOptimal within relaxed tolerances ALMOST_OPTIMAL 1 FEASIBLE_POINT FEASIBLE_POINT\nOptimal within relaxed tolerances ALMOST_OPTIMAL 1 ALMOST_FEASIBLE_POINT ALMOST_FEASIBLE_POINT\nDetected an unbounded ray of the primal DUAL_INFEASIBLE 1 INFEASIBILITY_CERTIFICATE NO_SOLUTION\nStall SLOW_PROGRESS 1 * *","category":"page"},{"location":"moi/manual/solutions/#Global-branch-and-bound-solvers","page":"Solutions","title":"Global branch-and-bound solvers","text":"","category":"section"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Mixed-integer programming solvers fall into this category.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"What happened? TerminationStatus ResultCount PrimalStatus DualStatus\nProved optimality OPTIMAL 1 FEASIBLE_POINT NO_SOLUTION\nPresolve detected infeasibility or unboundedness INFEASIBLE_OR_UNBOUNDED 0 NO_SOLUTION NO_SOLUTION\nProved infeasibility INFEASIBLE 0 NO_SOLUTION NO_SOLUTION\nTimed out (no solution) TIME_LIMIT 0 NO_SOLUTION NO_SOLUTION\nTimed out (with a solution) TIME_LIMIT 1 FEASIBLE_POINT NO_SOLUTION\nCPXMIP_OPTIMAL_INFEAS ALMOST_OPTIMAL 1 INFEASIBLE_POINT NO_SOLUTION","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"info: Info\nCPXMIP_OPTIMAL_INFEAS 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.","category":"page"},{"location":"moi/manual/solutions/#Local-search-solvers","page":"Solutions","title":"Local search solvers","text":"","category":"section"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Nonlinear programming solvers fall into this category. It also includes non-global tree search solvers like Juniper.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"What happened? TerminationStatus ResultCount PrimalStatus DualStatus\nConverged to a stationary point LOCALLY_SOLVED 1 FEASIBLE_POINT FEASIBLE_POINT\nCompleted a non-global tree search (with a solution) LOCALLY_SOLVED 1 FEASIBLE_POINT FEASIBLE_POINT\nConverged to an infeasible point LOCALLY_INFEASIBLE 1 INFEASIBLE_POINT *\nCompleted a non-global tree search (no solution found) LOCALLY_INFEASIBLE 0 NO_SOLUTION NO_SOLUTION\nIteration limit ITERATION_LIMIT 1 * *\nDiverging iterates NORM_LIMIT or OBJECTIVE_LIMIT 1 * *","category":"page"},{"location":"moi/manual/solutions/#Querying-solution-attributes","page":"Solutions","title":"Querying solution attributes","text":"","category":"section"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"Some solvers will not implement every solution attribute. Therefore, a call like MOI.get(model, MOI.SolveTimeSec()) may throw an UnsupportedAttribute error.","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"If you need to write code that is agnostic to the solver (for example, you are writing a library that an end-user passes their choice of solver to), you can work-around this problem using a try-catch:","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"function get_solve_time(model)\n try\n return MOI.get(model, MOI.SolveTimeSec())\n catch err\n if err isa MOI.UnsupportedAttribute\n return NaN # Solver doesn't support. Return a placeholder value.\n end\n rethrow(err) # Something else went wrong. Rethrow the error\n end\nend","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"If, after careful profiling, you find that the try-catch is taking a significant portion of your runtime, you can improve performance by caching the result of the try-catch:","category":"page"},{"location":"moi/manual/solutions/","page":"Solutions","title":"Solutions","text":"mutable struct CachedSolveTime{M}\n model::M\n supports_solve_time::Bool\n CachedSolveTime(model::M) where {M} = new(model, true)\nend\n\nfunction get_solve_time(model::CachedSolveTime)\n if !model.supports_solve_time\n return NaN\n end\n try\n return MOI.get(model, MOI.SolveTimeSec())\n catch err\n if err isa MOI.UnsupportedAttribute\n model.supports_solve_time = false\n return NaN\n end\n rethrow(err) # Something else went wrong. Rethrow the error\n end\nend","category":"page"},{"location":"tutorials/getting_started/introduction/#Introduction","page":"Introduction","title":"Introduction","text":"","category":"section"},{"location":"tutorials/getting_started/introduction/","page":"Introduction","title":"Introduction","text":"The purpose of these \"Getting started\" tutorials is to teach new users the basics of Julia and JuMP.","category":"page"},{"location":"tutorials/getting_started/introduction/#How-these-tutorials-are-structured","page":"Introduction","title":"How these tutorials are structured","text":"","category":"section"},{"location":"tutorials/getting_started/introduction/","page":"Introduction","title":"Introduction","text":"Having a high-level overview of how this part of the documentation is structured will help you know where to look for certain things.","category":"page"},{"location":"tutorials/getting_started/introduction/","page":"Introduction","title":"Introduction","text":"The \"Getting started with\" tutorials are basic introductions to different aspects of JuMP and Julia. If you are new to JuMP and Julia, start by reading them in the following order:\nGetting started with Julia\nGetting started with JuMP\nGetting started with sets and indexing\nGetting started with data and plotting\nJulia has a reputation for being \"fast.\" Unfortunately, it is also easy to write slow Julia code. Performance tips contains a number of important tips on how to improve the performance of models you write in JuMP.\nDesign patterns for larger models is a more advanced tutorial that is aimed at users writing large JuMP models. It's in the \"Getting started\" section to give you an early preview of how JuMP makes it easy to structure larger models. If you are new to JuMP you may want to skip or briefly skim this tutorial, and come back to it once you have written a few JuMP models.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"EditURL = \"two_stage_stochastic.jl\"","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/#Two-stage-stochastic-programs","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"","category":"section"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"The purpose of this tutorial is to demonstrate how to model and solve a two-stage stochastic program.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"This tutorial uses the following packages","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"using JuMP\nimport Distributions\nimport HiGHS\nimport Plots\nimport StatsPlots\nimport Statistics","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/#Background","page":"Two-stage stochastic programs","title":"Background","text":"","category":"section"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"During the week, you are a busy practitioner of Operations Research. To escape the drudgery of mathematics, you decide to open a side business selling creamy mushroom pies with puff pastry. After a few weeks, it quickly becomes apparent that operating a food business is not so easy.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"The pies must be prepared in the morning, before you open for the day and can gauge the level of demand. If you bake too many, the unsold pies at the end of the day must be discarded and you have wasted time and money on their production. But if you bake too few, then there may be un-served customers and you could have made more money by baking more pies.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"After a few weeks of poor decision making, you decide to put your knowledge of Operations Research to good use, starting with some data collection.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"Each pie costs you $2 to make, and you sell them at $5 each. Disposal of an unsold pie costs $0.10. Based on three weeks of data collected, in which you made 200 pies each week, you sold 150, 190, and 200 pies. Thus, as a guess, you assume a triangular distribution of demand with a minimum of 150, a median of 200, and a maximum of 250.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"We can model this problem by a two-stage stochastic program. In the first stage, we decide a quantity of pies to make x. We make this decision before we observe the demand d_omega. In the second stage, we sell y_omega pies, and incur any costs for unsold pies.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"We can formulate this problem as follows:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"beginaligned\nmaxlimits_xy_omega -2x + mathbbE_omega5y_omega - 01(x - y_omega) \n y_omega le x quad forall omega in Omega \n 0 le y_omega le d_omega quad forall omega in Omega \n x ge 0\nendaligned","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/#Sample-Average-approximation","page":"Two-stage stochastic programs","title":"Sample Average approximation","text":"","category":"section"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"If the distribution of demand is continuous, then our problem has an infinite number of variables and constraints. To form a computationally tractable problem, we instead use a finite set of samples drawn from the distribution. This is called sample average approximation (SAA).","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"D = Distributions.TriangularDist(150.0, 250.0, 200.0)\nN = 100\nd = sort!(rand(D, N));\nΩ = 1:N\nP = fill(1 / N, N);\nStatsPlots.histogram(d; bins = 20, label = \"\", xlabel = \"Demand\")","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/#JuMP-model","page":"Two-stage stochastic programs","title":"JuMP model","text":"","category":"section"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"The implementation of our two-stage stochastic program in JuMP is:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"model = Model(HiGHS.Optimizer)\nset_silent(model)\n@variable(model, x >= 0)\n@variable(model, 0 <= y[ω in Ω] <= d[ω])\n@constraint(model, [ω in Ω], y[ω] <= x)\n@expression(model, z[ω in Ω], 5y[ω] - 0.1 * (x - y[ω]))\n@objective(model, Max, -2x + sum(P[ω] * z[ω] for ω in Ω))\noptimize!(model)\nsolution_summary(model)","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"The optimal number of pies to make is:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"value(x)","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"The distribution of total profit is:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"total_profit = [-2 * value(x) + value(z[ω]) for ω in Ω]","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"Let's plot it:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"\"\"\"\n bin_distribution(x::Vector{Float64}, N::Int)\n\nA helper function that discretizes `x` into bins of width `N`.\n\"\"\"\nbin_distribution(x, N) = N * (floor(minimum(x) / N):ceil(maximum(x) / N))\n\nplot = StatsPlots.histogram(\n total_profit;\n bins = bin_distribution(total_profit, 25),\n label = \"\",\n xlabel = \"Profit [\\$]\",\n ylabel = \"Number of outcomes\",\n)\nμ = Statistics.mean(total_profit)\nPlots.vline!(\n plot,\n [μ];\n label = \"Expected profit (\\$$(round(Int, μ)))\",\n linewidth = 3,\n)\nplot","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/#Risk-measures","page":"Two-stage stochastic programs","title":"Risk measures","text":"","category":"section"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"A risk measure is a function which maps a random variable to a real number. Common risk measures include the mean (expectation), median, mode, and maximum. We need a risk measure to convert the distribution of second stage costs into a single number that can be optimized.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"Our model currently uses the expectation risk measure, but others are possible too. One popular risk measure is the conditional value at risk (CVaR).","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"CVaR has a parameter gamma, and it computes the expectation of the worst gamma fraction of outcomes.","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"If we are maximizing, so that small outcomes are bad, the definition of CVaR is:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"CVaR_gammaZ = maxlimits_xi xi - frac1gammamathbbE_omegaleft(xi - Z)_+right","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"which can be formulated as the linear program:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"beginaligned\nCVaR_gammaZ = maxlimits_xi z_omega xi - frac1gammasum P_omega z_omega\n z_omega ge xi - Z_omega quad forall omega \n z_omega ge 0 quad forall omega\nendaligned","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"function CVaR(Z::Vector{Float64}, P::Vector{Float64}; γ::Float64)\n @assert 0 < γ <= 1\n N = length(Z)\n model = Model(HiGHS.Optimizer)\n set_silent(model)\n @variable(model, ξ)\n @variable(model, z[1:N] >= 0)\n @constraint(model, [i in 1:N], z[i] >= ξ - Z[i])\n @objective(model, Max, ξ - 1 / γ * sum(P[i] * z[i] for i in 1:N))\n optimize!(model)\n return objective_value(model)\nend","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"When γ is 1.0, we compute the mean of the profit:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"cvar_10 = CVaR(total_profit, P; γ = 1.0)","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"Statistics.mean(total_profit)","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"As γ approaches 0.0, we compute the worst-case (minimum) profit:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"cvar_00 = CVaR(total_profit, P; γ = 0.0001)","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"minimum(total_profit)","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"By varying γ between 0 and 1 we can compute some trade-off of these two extremes:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"cvar_05 = CVaR(total_profit, P; γ = 0.5)","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"Let's plot these outcomes on our distribution:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"plot = StatsPlots.histogram(\n total_profit;\n bins = bin_distribution(total_profit, 25),\n label = \"\",\n xlabel = \"Profit [\\$]\",\n ylabel = \"Number of outcomes\",\n)\nPlots.vline!(\n plot,\n [cvar_10 cvar_05 cvar_00];\n label = [\"γ = 1.0\" \"γ = 0.5\" \"γ = 0.0\"],\n linewidth = 3,\n)\nplot","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/#Risk-averse-sample-average-approximation","page":"Two-stage stochastic programs","title":"Risk averse sample average approximation","text":"","category":"section"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"Because CVaR can be formulated as a linear program, we can form a risk averse sample average approximation model by combining the two formulations:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"γ = 0.4\nmodel = Model(HiGHS.Optimizer)\nset_silent(model)\n@variable(model, x >= 0)\n@variable(model, 0 <= y[ω in Ω] <= d[ω])\n@constraint(model, [ω in Ω], y[ω] <= x)\n@expression(model, Z[ω in Ω], 5 * y[ω] - 0.1(x - y[ω]))\n@variable(model, ξ)\n@variable(model, z[ω in Ω] >= 0)\n@constraint(model, [ω in Ω], z[ω] >= ξ - Z[ω])\n@objective(model, Max, -2x + ξ - 1 / γ * sum(P[ω] * z[ω] for ω in Ω))\noptimize!(model)","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"When gamma = 04, the optimal number of pies to bake is:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"value(x)","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"The distribution of total profit is:","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"risk_averse_total_profit = [value(-2x + Z[ω]) for ω in Ω]\nbins = bin_distribution([total_profit; risk_averse_total_profit], 25)\nplot = StatsPlots.histogram(total_profit; label = \"Expectation\", bins = bins)\nStatsPlots.histogram!(\n plot,\n risk_averse_total_profit;\n label = \"CV@R\",\n bins = bins,\n alpha = 0.5,\n)\nplot","category":"page"},{"location":"tutorials/applications/two_stage_stochastic/#Next-steps","page":"Two-stage stochastic programs","title":"Next steps","text":"","category":"section"},{"location":"tutorials/applications/two_stage_stochastic/","page":"Two-stage stochastic programs","title":"Two-stage stochastic programs","text":"Try solving this problem for different numbers of samples and different distributions.\nRefactor the example to avoid hard-coding the costs. What happens to the solution if the cost of disposing unsold pies increases?\nPlot the optimal number of pies to make for different values of the risk aversion parameter gamma. What is the relationship?","category":"page"},{"location":"packages/solvers/#Introduction","page":"Introduction","title":"Introduction","text":"","category":"section"},{"location":"packages/solvers/","page":"Introduction","title":"Introduction","text":"This section of the documentation contains brief documentation for some of the solvers that JuMP supports. The list of solvers is not exhaustive, but instead is intended to help you discover commonly used solvers.","category":"page"},{"location":"packages/solvers/#Affiliation","page":"Introduction","title":"Affiliation","text":"","category":"section"},{"location":"packages/solvers/","page":"Introduction","title":"Introduction","text":"Packages beginning with jump-dev/ are developed and maintained by the JuMP developers. In many cases, these packages wrap external solvers that are not developed by the JuMP developers and, while the Julia packages are all open-source, in some cases the solvers themselves are closed source commercial products.","category":"page"},{"location":"packages/solvers/","page":"Introduction","title":"Introduction","text":"Packages that do not begin with jump-dev/ are developed independently. The developers of these packages requested or consented to the inclusion of their README contents in the JuMP documentation for the benefit of users.","category":"page"},{"location":"packages/solvers/#Adding-new-solvers","page":"Introduction","title":"Adding new solvers","text":"","category":"section"},{"location":"packages/solvers/","page":"Introduction","title":"Introduction","text":"Written a solver? Add it to this section of the JuMP documentation by making a pull request to the docs/packages.toml file.","category":"page"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/reference/models.md\"","category":"page"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"CurrentModule = MathOptInterface\nDocTestSetup = quote\n import MathOptInterface as MOI\nend\nDocTestFilters = [r\"MathOptInterface|MOI\"]","category":"page"},{"location":"moi/reference/models/#Models","page":"Models","title":"Models","text":"","category":"section"},{"location":"moi/reference/models/#Attribute-interface","page":"Models","title":"Attribute interface","text":"","category":"section"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"is_set_by_optimize\nis_copyable\nget\nget!\nset\nsupports\nattribute_value_type","category":"page"},{"location":"moi/reference/models/#MathOptInterface.is_set_by_optimize","page":"Models","title":"MathOptInterface.is_set_by_optimize","text":"is_set_by_optimize(::AnyAttribute)\n\nReturn a Bool indicating whether the value of the attribute is modified during an optimize! call, that is, the attribute is used to query the result of the optimization.\n\nImportant note when defining new attributes\n\nThis function returns false by default so it should be implemented for attributes that are modified by optimize!.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.is_copyable","page":"Models","title":"MathOptInterface.is_copyable","text":"is_copyable(::AnyAttribute)\n\nReturn a Bool indicating whether the value of the attribute may be copied during copy_to using set.\n\nImportant note when defining new attributes\n\nBy default is_copyable(attr) returns !is_set_by_optimize(attr). A specific method should be defined for attributes which are copied indirectly during copy_to. For instance, both is_copyable and is_set_by_optimize return false for the following attributes:\n\nListOfOptimizerAttributesSet, ListOfModelAttributesSet, ListOfConstraintAttributesSet and ListOfVariableAttributesSet.\nSolverName and RawSolver: these attributes cannot be set.\nNumberOfVariables and ListOfVariableIndices: these attributes are set indirectly by add_variable and add_variables.\nObjectiveFunctionType: this attribute is set indirectly when setting the ObjectiveFunction attribute.\nNumberOfConstraints, ListOfConstraintIndices, ListOfConstraintTypesPresent, CanonicalConstraintFunction, ConstraintFunction and ConstraintSet: these attributes are set indirectly by add_constraint and add_constraints.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.get","page":"Models","title":"MathOptInterface.get","text":"get(model::GenericModel, attr::MathOptInterface.AbstractOptimizerAttribute)\n\nReturn the value of the attribute attr from the model's MOI backend.\n\n\n\n\n\nget(model::GenericModel, attr::MathOptInterface.AbstractModelAttribute)\n\nReturn the value of the attribute attr from the model's MOI backend.\n\n\n\n\n\nMOI.get(b::AbstractBridge, ::MOI.NumberOfVariables)::Int64\n\nReturn the number of variables created by the bridge b in the model.\n\nSee also MOI.NumberOfConstraints.\n\nImplementation notes\n\nThere is a default fallback, so you need only implement this if the bridge adds new variables.\n\n\n\n\n\nMOI.get(b::AbstractBridge, ::MOI.ListOfVariableIndices)\n\nReturn the list of variables created by the bridge b.\n\nSee also MOI.ListOfVariableIndices.\n\nImplementation notes\n\nThere is a default fallback, so you need only implement this if the bridge adds new variables.\n\n\n\n\n\nMOI.get(b::AbstractBridge, ::MOI.NumberOfConstraints{F,S})::Int64 where {F,S}\n\nReturn the number of constraints of the type F-in-S created by the bridge b.\n\nSee also MOI.NumberOfConstraints.\n\nImplementation notes\n\nThere is a default fallback, so you need only implement this for the constraint types returned by added_constraint_types.\n\n\n\n\n\nMOI.get(b::AbstractBridge, ::MOI.ListOfConstraintIndices{F,S}) where {F,S}\n\nReturn a Vector{ConstraintIndex{F,S}} with indices of all constraints of type F-in-S created by the bride b.\n\nSee also MOI.ListOfConstraintIndices.\n\nImplementation notes\n\nThere is a default fallback, so you need only implement this for the constraint types returned by added_constraint_types.\n\n\n\n\n\nfunction MOI.get(\n model::MOI.ModelLike,\n attr::MOI.AbstractConstraintAttribute,\n bridge::AbstractBridge,\n)\n\nReturn the value of the attribute attr of the model model for the constraint bridged by bridge.\n\n\n\n\n\nget(optimizer::AbstractOptimizer, attr::AbstractOptimizerAttribute)\n\nReturn an attribute attr of the optimizer optimizer.\n\nget(model::ModelLike, attr::AbstractModelAttribute)\n\nReturn an attribute attr of the model model.\n\nget(model::ModelLike, attr::AbstractVariableAttribute, v::VariableIndex)\n\nIf the attribute attr is set for the variable v in the model model, return its value, return nothing otherwise. If the attribute attr is not supported by model then an error should be thrown instead of returning nothing.\n\nget(model::ModelLike, attr::AbstractVariableAttribute, v::Vector{VariableIndex})\n\nReturn a vector of attributes corresponding to each variable in the collection v in the model model.\n\nget(model::ModelLike, attr::AbstractConstraintAttribute, c::ConstraintIndex)\n\nIf the attribute attr is set for the constraint c in the model model, return its value, return nothing otherwise. If the attribute attr is not supported by model then an error should be thrown instead of returning nothing.\n\nget(\n model::ModelLike,\n attr::AbstractConstraintAttribute,\n c::Vector{ConstraintIndex{F,S}},\n) where {F,S}\n\nReturn a vector of attributes corresponding to each constraint in the collection c in the model model.\n\nget(model::ModelLike, ::Type{VariableIndex}, name::String)\n\nIf a variable with name name exists in the model model, return the corresponding index, otherwise return nothing. Errors if two variables have the same name.\n\nget(\n model::ModelLike,\n ::Type{ConstraintIndex{F,S}},\n name::String,\n) where {F,S}\n\nIf an F-in-S constraint with name name exists in the model model, return the corresponding index, otherwise return nothing. Errors if two constraints have the same name.\n\nget(model::ModelLike, ::Type{ConstraintIndex}, name::String)\n\nIf any constraint with name name exists in the model model, return the corresponding index, otherwise return nothing. This version is available for convenience but may incur a performance penalty because it is not type stable. Errors if two constraints have the same name.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.get!","page":"Models","title":"MathOptInterface.get!","text":"get!(output, model::ModelLike, args...)\n\nAn in-place version of get.\n\nThe signature matches that of get except that the the result is placed in the vector output.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.set","page":"Models","title":"MathOptInterface.set","text":"function MOI.set(\n model::MOI.ModelLike,\n attr::MOI.AbstractConstraintAttribute,\n bridge::AbstractBridge,\n value,\n)\n\nSet the value of the attribute attr of the model model for the constraint bridged by bridge.\n\n\n\n\n\nset(optimizer::AbstractOptimizer, attr::AbstractOptimizerAttribute, value)\n\nAssign value to the attribute attr of the optimizer optimizer.\n\nset(model::ModelLike, attr::AbstractModelAttribute, value)\n\nAssign value to the attribute attr of the model model.\n\nset(model::ModelLike, attr::AbstractVariableAttribute, v::VariableIndex, value)\n\nAssign value to the attribute attr of variable v in model model.\n\nset(\n model::ModelLike,\n attr::AbstractVariableAttribute,\n v::Vector{VariableIndex},\n vector_of_values,\n)\n\nAssign a value respectively to the attribute attr of each variable in the collection v in model model.\n\nset(\n model::ModelLike,\n attr::AbstractConstraintAttribute,\n c::ConstraintIndex,\n value,\n)\n\nAssign a value to the attribute attr of constraint c in model model.\n\nset(\n model::ModelLike,\n attr::AbstractConstraintAttribute,\n c::Vector{ConstraintIndex{F,S}},\n vector_of_values,\n) where {F,S}\n\nAssign a value respectively to the attribute attr of each constraint in the collection c in model model.\n\nAn UnsupportedAttribute error is thrown if model does not support the attribute attr (see supports) and a SetAttributeNotAllowed error is thrown if it supports the attribute attr but it cannot be set.\n\nset(\n model::ModelLike,\n ::ConstraintSet,\n c::ConstraintIndex{F,S},\n set::S,\n) where {F,S}\n\nChange the set of constraint c to the new set set which should be of the same type as the original set.\n\nset(\n model::ModelLike,\n ::ConstraintFunction,\n c::ConstraintIndex{F,S},\n func::F,\n) where {F,S}\n\nReplace the function in constraint c with func. F must match the original function type used to define the constraint.\n\nnote: Note\nSetting the constraint function is not allowed if F is VariableIndex; a SettingVariableIndexNotAllowed error is thrown instead. This is because, it would require changing the index c since the index of VariableIndex constraints must be the same as the index of the variable.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.supports","page":"Models","title":"MathOptInterface.supports","text":"MOI.supports(\n model::MOI.ModelLike,\n attr::MOI.AbstractConstraintAttribute,\n BT::Type{<:AbstractBridge},\n)\n\nReturn a Bool indicating whether BT supports setting attr to model.\n\n\n\n\n\nsupports(model::ModelLike, sub::AbstractSubmittable)::Bool\n\nReturn a Bool indicating whether model supports the submittable sub.\n\nsupports(model::ModelLike, attr::AbstractOptimizerAttribute)::Bool\n\nReturn a Bool indicating whether model supports the optimizer attribute attr. That is, it returns false if copy_to(model, src) shows a warning in case attr is in the ListOfOptimizerAttributesSet of src; see copy_to for more details on how unsupported optimizer attributes are handled in copy.\n\nsupports(model::ModelLike, attr::AbstractModelAttribute)::Bool\n\nReturn a Bool indicating whether model supports the model attribute attr. That is, it returns false if copy_to(model, src) cannot be performed in case attr is in the ListOfModelAttributesSet of src.\n\nsupports(\n model::ModelLike,\n attr::AbstractVariableAttribute,\n ::Type{VariableIndex},\n)::Bool\n\nReturn a Bool indicating whether model supports the variable attribute attr. That is, it returns false if copy_to(model, src) cannot be performed in case attr is in the ListOfVariableAttributesSet of src.\n\nsupports(\n model::ModelLike,\n attr::AbstractConstraintAttribute,\n ::Type{ConstraintIndex{F,S}},\n)::Bool where {F,S}\n\nReturn a Bool indicating whether model supports the constraint attribute attr applied to an F-in-S constraint. That is, it returns false if copy_to(model, src) cannot be performed in case attr is in the ListOfConstraintAttributesSet of src.\n\nFor all five methods, if the attribute is only not supported in specific circumstances, it should still return true.\n\nNote that supports is only defined for attributes for which is_copyable returns true as other attributes do not appear in the list of attributes set obtained by ListOf...AttributesSet.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.attribute_value_type","page":"Models","title":"MathOptInterface.attribute_value_type","text":"attribute_value_type(attr::AnyAttribute)\n\nGiven an attribute attr, return the type of value expected by get, or returned by set.\n\nNotes\n\nOnly implement this if it make sense to do so. If un-implemented, the default is Any.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#Model-interface","page":"Models","title":"Model interface","text":"","category":"section"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"ModelLike\nis_empty\nempty!\nwrite_to_file\nread_from_file\nsupports_incremental_interface\ncopy_to\nIndexMap","category":"page"},{"location":"moi/reference/models/#MathOptInterface.ModelLike","page":"Models","title":"MathOptInterface.ModelLike","text":"ModelLike\n\nAbstract supertype for objects that implement the \"Model\" interface for defining an optimization problem.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.is_empty","page":"Models","title":"MathOptInterface.is_empty","text":"is_empty(model::ModelLike)\n\nReturns false if the model has any model attribute set or has any variables or constraints.\n\nNote that an empty model can have optimizer attributes set.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.empty!","page":"Models","title":"MathOptInterface.empty!","text":"empty!(model::ModelLike)\n\nEmpty the model, that is, remove all variables, constraints and model attributes but not optimizer attributes.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.write_to_file","page":"Models","title":"MathOptInterface.write_to_file","text":"write_to_file(model::ModelLike, filename::String)\n\nWrite the current model to the file at filename.\n\nSupported file types depend on the model type.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.read_from_file","page":"Models","title":"MathOptInterface.read_from_file","text":"read_from_file(model::ModelLike, filename::String)\n\nRead the file filename into the model model. If model is non-empty, this may throw an error.\n\nSupported file types depend on the model type.\n\nNote\n\nOnce the contents of the file are loaded into the model, users can query the variables via get(model, ListOfVariableIndices()). However, some filetypes, such as LP files, do not maintain an explicit ordering of the variables. Therefore, the returned list may be in an arbitrary order.\n\nTo avoid depending on the order of the indices, look up each variable index by name using get(model, VariableIndex, \"name\").\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.supports_incremental_interface","page":"Models","title":"MathOptInterface.supports_incremental_interface","text":"supports_incremental_interface(model::ModelLike)\n\nReturn a Bool indicating whether model supports building incrementally via add_variable and add_constraint.\n\nThe main purpose of this function is to determine whether a model can be loaded into model incrementally or whether it should be cached and copied at once instead.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.copy_to","page":"Models","title":"MathOptInterface.copy_to","text":"copy_to(dest::ModelLike, src::ModelLike)::IndexMap\n\nCopy the model from src into dest.\n\nThe target dest is emptied, and all previous indices to variables and constraints in dest are invalidated.\n\nReturns an IndexMap object that translates variable and constraint indices from the src model to the corresponding indices in the dest model.\n\nNotes\n\nIf a constraint that in src is not supported by dest, then an UnsupportedConstraint error is thrown.\nIf an AbstractModelAttribute, AbstractVariableAttribute, or AbstractConstraintAttribute is set in src but not supported by dest, then an UnsupportedAttribute error is thrown.\n\nAbstractOptimizerAttributes are not copied to the dest model.\n\nIndexMap\n\nImplementations of copy_to must return an IndexMap. For technical reasons, this type is defined in the Utilities submodule as MOI.Utilities.IndexMap. However, since it is an integral part of the MOI API, we provide MOI.IndexMap as an alias.\n\nExample\n\n# Given empty `ModelLike` objects `src` and `dest`.\n\nx = add_variable(src)\n\nis_valid(src, x) # true\nis_valid(dest, x) # false (`dest` has no variables)\n\nindex_map = copy_to(dest, src)\nis_valid(dest, x) # false (unless index_map[x] == x)\nis_valid(dest, index_map[x]) # true\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.IndexMap","page":"Models","title":"MathOptInterface.IndexMap","text":"IndexMap()\n\nThe dictionary-like object returned by copy_to.\n\nIndexMap\n\nImplementations of copy_to must return an IndexMap. For technical reasons, the IndexMap type is defined in the Utilities submodule as MOI.Utilities.IndexMap. However, since it is an integral part of the MOI API, we provide this MOI.IndexMap as an alias.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#Model-attributes","page":"Models","title":"Model attributes","text":"","category":"section"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"AbstractModelAttribute\nName\nObjectiveFunction\nObjectiveFunctionType\nObjectiveSense\nOptimizationSense\nMIN_SENSE\nMAX_SENSE\nFEASIBILITY_SENSE\nNumberOfVariables\nListOfVariableIndices\nListOfConstraintTypesPresent\nNumberOfConstraints\nListOfConstraintIndices\nListOfOptimizerAttributesSet\nListOfModelAttributesSet\nListOfVariableAttributesSet\nListOfConstraintAttributesSet\nUserDefinedFunction\nListOfSupportedNonlinearOperators","category":"page"},{"location":"moi/reference/models/#MathOptInterface.AbstractModelAttribute","page":"Models","title":"MathOptInterface.AbstractModelAttribute","text":"AbstractModelAttribute\n\nAbstract supertype for attribute objects that can be used to set or get attributes (properties) of the model.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.Name","page":"Models","title":"MathOptInterface.Name","text":"Name()\n\nA model attribute for the string identifying the model. It has a default value of \"\" if not set`.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ObjectiveFunction","page":"Models","title":"MathOptInterface.ObjectiveFunction","text":"ObjectiveFunction{F<:AbstractScalarFunction}()\n\nA model attribute for the objective function which has a type F<:AbstractScalarFunction.\n\nF should be guaranteed to be equivalent but not necessarily identical to the function type provided by the user.\n\nThrows an InexactError if the objective function cannot be converted to F, e.g., the objective function is quadratic and F is ScalarAffineFunction{Float64} or it has non-integer coefficient and F is ScalarAffineFunction{Int}.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ObjectiveFunctionType","page":"Models","title":"MathOptInterface.ObjectiveFunctionType","text":"ObjectiveFunctionType()\n\nA model attribute for the type F of the objective function set using the ObjectiveFunction{F} attribute.\n\nExamples\n\nIn the following code, attr should be equal to MOI.VariableIndex:\n\nx = MOI.add_variable(model)\nMOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x)\nattr = MOI.get(model, MOI.ObjectiveFunctionType())\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ObjectiveSense","page":"Models","title":"MathOptInterface.ObjectiveSense","text":"ObjectiveSense()\n\nA model attribute for the objective sense of the objective function, which must be an OptimizationSense: MIN_SENSE, MAX_SENSE, or FEASIBILITY_SENSE. The default is FEASIBILITY_SENSE.\n\nInteraction with ObjectiveFunction\n\nSetting the sense to FEASIBILITY_SENSE unsets the ObjectiveFunction attribute. That is, if you first set ObjectiveFunction and then set ObjectiveSense to be FEASIBILITY_SENSE, no objective function will be passed to the solver.\n\nIn addition, some reformulations of ObjectiveFunction via bridges rely on the value of ObjectiveSense. Therefore, you should set ObjectiveSense before setting ObjectiveFunction.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.OptimizationSense","page":"Models","title":"MathOptInterface.OptimizationSense","text":"OptimizationSense\n\nAn enum for the value of the ObjectiveSense attribute.\n\nValues\n\nPossible values are:\n\nMIN_SENSE: the goal is to minimize the objective function\nMAX_SENSE: the goal is to maximize the objective function\nFEASIBILITY_SENSE: the model does not have an objective function\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.MIN_SENSE","page":"Models","title":"MathOptInterface.MIN_SENSE","text":"MIN_SENSE::OptimizationSense\n\nAn instance of the OptimizationSense enum.\n\nMIN_SENSE: the goal is to minimize the objective function\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.MAX_SENSE","page":"Models","title":"MathOptInterface.MAX_SENSE","text":"MAX_SENSE::OptimizationSense\n\nAn instance of the OptimizationSense enum.\n\nMAX_SENSE: the goal is to maximize the objective function\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.FEASIBILITY_SENSE","page":"Models","title":"MathOptInterface.FEASIBILITY_SENSE","text":"FEASIBILITY_SENSE::OptimizationSense\n\nAn instance of the OptimizationSense enum.\n\nFEASIBILITY_SENSE: the model does not have an objective function\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.NumberOfVariables","page":"Models","title":"MathOptInterface.NumberOfVariables","text":"NumberOfVariables()\n\nA model attribute for the number of variables in the model.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ListOfVariableIndices","page":"Models","title":"MathOptInterface.ListOfVariableIndices","text":"ListOfVariableIndices()\n\nA model attribute for the Vector{VariableIndex} of all variable indices present in the model (i.e., of length equal to the value of NumberOfVariables in the order in which they were added.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ListOfConstraintTypesPresent","page":"Models","title":"MathOptInterface.ListOfConstraintTypesPresent","text":"ListOfConstraintTypesPresent()\n\nA model attribute for the list of tuples of the form (F,S), where F is a function type and S is a set type indicating that the attribute NumberOfConstraints{F,S} has a value greater than zero.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.NumberOfConstraints","page":"Models","title":"MathOptInterface.NumberOfConstraints","text":"NumberOfConstraints{F,S}()\n\nA model attribute for the number of constraints of the type F-in-S present in the model.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ListOfConstraintIndices","page":"Models","title":"MathOptInterface.ListOfConstraintIndices","text":"ListOfConstraintIndices{F,S}()\n\nA model attribute for the Vector{ConstraintIndex{F,S}} of all constraint indices of type F-in-S in the model (i.e., of length equal to the value of NumberOfConstraints{F,S}) in the order in which they were added.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ListOfOptimizerAttributesSet","page":"Models","title":"MathOptInterface.ListOfOptimizerAttributesSet","text":"ListOfOptimizerAttributesSet()\n\nAn optimizer attribute for the Vector{AbstractOptimizerAttribute} of all optimizer attributes that were set.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ListOfModelAttributesSet","page":"Models","title":"MathOptInterface.ListOfModelAttributesSet","text":"ListOfModelAttributesSet()\n\nA model attribute for the Vector{AbstractModelAttribute} of all model attributes attr such that:\n\nis_copyable(attr) returns true, and\nthe attribute was set to the model\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ListOfVariableAttributesSet","page":"Models","title":"MathOptInterface.ListOfVariableAttributesSet","text":"ListOfVariableAttributesSet()\n\nA model attribute for the Vector{AbstractVariableAttribute} of all variable attributes attr such that 1) is_copyable(attr) returns true and 2) the attribute was set to variables.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ListOfConstraintAttributesSet","page":"Models","title":"MathOptInterface.ListOfConstraintAttributesSet","text":"ListOfConstraintAttributesSet{F, S}()\n\nA model attribute for the Vector{AbstractConstraintAttribute} of all constraint attributes attr such that:\n\nis_copyable(attr) returns true and\nthe attribute was set to F-in-S constraints.\n\nNote\n\nThe attributes ConstraintFunction and ConstraintSet should not be included in the list even if then have been set with set.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.UserDefinedFunction","page":"Models","title":"MathOptInterface.UserDefinedFunction","text":"UserDefinedFunction(name::Symbol, arity::Int) <: AbstractModelAttribute\n\nSet this attribute to register a user-defined function by the name of name with arity arguments.\n\nOnce registered, name will appear in ListOfSupportedNonlinearOperators.\n\nYou cannot register multiple UserDefinedFunctions with the same name but different arity.\n\nValue type\n\nThe value to be set is a tuple containing one, two, or three functions to evaluate the function, the first-order derivative, and the second-order derivative respectively. Both derivatives are optional, but if you pass the second-order derivative you must also pass the first-order derivative.\n\nFor univariate functions with arity == 1, the functions in the tuple must have the form:\n\nf(x::T)::T: returns the value of the function at x\n∇f(x::T)::T: returns the first-order derivative of f with respect to x\n∇²f(x::T)::T: returns the second-order derivative of f with respect to x.\n\nFor multivariate functions with arity > 1, the functions in the tuple must have the form:\n\nf(x::T...)::T: returns the value of the function at x\n∇f(g::AbstractVector{T}, x::T...)::Nothing: fills the components of g, with g[i] being the first-order partial derivative of f with respect to x[i]\n∇²f(H::AbstractMatrix{T}, x::T...)::Nothing: fills the non-zero components of H, with H[i, j] being the second-order partial derivative of f with respect to x[i] and then x[j]. H is initialized to the zero matrix, so you do not need to set any zero elements.\n\nExamples\n\njulia> import MathOptInterface as MOI\n\njulia> f(x, y) = x^2 + y^2\nf (generic function with 1 method)\n\njulia> function ∇f(g, x, y)\n g .= 2 * x, 2 * y\n return\n end\n∇f (generic function with 1 method)\n\njulia> function ∇²f(H, x...)\n H[1, 1] = H[2, 2] = 2.0\n return\n end\n∇²f (generic function with 1 method)\n\njulia> model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())\nMOIU.UniversalFallback{MOIU.Model{Float64}}\nfallback for MOIU.Model{Float64}\n\njulia> MOI.set(model, MOI.UserDefinedFunction(:f, 2), (f,))\n\njulia> MOI.set(model, MOI.UserDefinedFunction(:g, 2), (f, ∇f))\n\njulia> MOI.set(model, MOI.UserDefinedFunction(:h, 2), (f, ∇f, ∇²f))\n\njulia> x = MOI.add_variables(model, 2)\n2-element Vector{MathOptInterface.VariableIndex}:\n MOI.VariableIndex(1)\n MOI.VariableIndex(2)\n\njulia> MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)\n\njulia> obj_f = MOI.ScalarNonlinearFunction(:f, Any[x[1], x[2]])\nf(MOI.VariableIndex(1), MOI.VariableIndex(2))\n\njulia> MOI.set(model, MOI.ObjectiveFunction{typeof(obj_f)}(), obj_f)\n\njulia> print(model)\nMinimize ScalarNonlinearFunction:\n f(v[1], v[2])\n\nSubject to:\n\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ListOfSupportedNonlinearOperators","page":"Models","title":"MathOptInterface.ListOfSupportedNonlinearOperators","text":"ListOfSupportedNonlinearOperators() <: AbstractModelAttribute\n\nWhen queried with get, return a Vector{Symbol} listing the operators supported by the model.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#Optimizer-interface","page":"Models","title":"Optimizer interface","text":"","category":"section"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"AbstractOptimizer\nOptimizerWithAttributes\noptimize!\noptimize!(::ModelLike, ::ModelLike)\ninstantiate\ndefault_cache","category":"page"},{"location":"moi/reference/models/#MathOptInterface.AbstractOptimizer","page":"Models","title":"MathOptInterface.AbstractOptimizer","text":"AbstractOptimizer <: ModelLike\n\nAbstract supertype for objects representing an instance of an optimization problem tied to a particular solver. This is typically a solver's in-memory representation. In addition to ModelLike, AbstractOptimizer objects let you solve the model and query the solution.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.OptimizerWithAttributes","page":"Models","title":"MathOptInterface.OptimizerWithAttributes","text":"struct OptimizerWithAttributes\n optimizer_constructor\n params::Vector{Pair{AbstractOptimizerAttribute,<:Any}}\nend\n\nObject grouping an optimizer constructor and a list of optimizer attributes. Instances are created with instantiate.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.optimize!","page":"Models","title":"MathOptInterface.optimize!","text":"optimize!(optimizer::AbstractOptimizer)\n\nOptimize the problem contained in optimizer.\n\nBefore calling optimize!, the problem should first be constructed using the incremental interface (see supports_incremental_interface) or copy_to.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.optimize!-Tuple{MathOptInterface.ModelLike, MathOptInterface.ModelLike}","page":"Models","title":"MathOptInterface.optimize!","text":"optimize!(dest::AbstractOptimizer, src::ModelLike)::Tuple{IndexMap,Bool}\n\nA \"one-shot\" call that copies the problem from src into dest and then uses dest to optimize the problem.\n\nReturns a tuple of an IndexMap and a Bool copied.\n\nThe IndexMap object translates variable and constraint indices from the src model to the corresponding indices in the dest optimizer. See copy_to for details.\nIf copied == true, src was copied to dest and then cached, allowing incremental modification if supported by the solver.\nIf copied == false, a cache of the model was not kept in dest. Therefore, only the solution information (attributes for which is_set_by_optimize is true) is available to query.\n\nnote: Note\nThe main purpose of optimize! method with two arguments is for use in Utilities.CachingOptimizer.\n\nRelationship to the single-argument optimize!\n\nThe default fallback of optimize!(dest::AbstractOptimizer, src::ModelLike) is\n\nfunction optimize!(dest::AbstractOptimizer, src::ModelLike)\n index_map = copy_to(dest, src)\n optimize!(dest)\n return index_map, true\nend\n\nTherefore, subtypes of AbstractOptimizer should either implement this two-argument method, or implement both copy_to(::Optimizer, ::ModelLike) and optimize!(::Optimizer).\n\n\n\n\n\n","category":"method"},{"location":"moi/reference/models/#MathOptInterface.instantiate","page":"Models","title":"MathOptInterface.instantiate","text":"instantiate(\n optimizer_constructor,\n with_cache_type::Union{Nothing,Type} = nothing,\n with_bridge_type::Union{Nothing,Type} = nothing,\n)\n\nCreate an instance of an optimizer by either:\n\ncalling optimizer_constructor.optimizer_constructor() and setting the parameters in optimizer_constructor.params if optimizer_constructor is a OptimizerWithAttributes\ncalling optimizer_constructor() if optimizer_constructor is callable.\n\nwithcachetype\n\nIf with_cache_type is not nothing, then the optimizer is wrapped in a Utilities.CachingOptimizer to store a cache of the model. This is most useful if the optimizer you are constructing does not support the incremental interface (see supports_incremental_interface).\n\nwithbridgetype\n\nIf with_bridge_type is not nothing, the optimizer is wrapped in a Bridges.full_bridge_optimizer, enabling all the bridges defined in the MOI.Bridges submodule with coefficient type with_bridge_type.\n\nIn addition, if the optimizer created by optimizer_constructor does not support the incremental interface (see supports_incremental_interface), then, irrespective of with_cache_type, the optimizer is wrapped in a Utilities.CachingOptimizer to store a cache of the bridged model.\n\nIf with_cache_type and with_bridge_type are both not nothing, then they must be the same type.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.default_cache","page":"Models","title":"MathOptInterface.default_cache","text":"default_cache(optimizer::ModelLike, ::Type{T}) where {T}\n\nReturn a new instance of the default model type to be used as cache for optimizer in a Utilities.CachingOptimizer for holding constraints of coefficient type T. By default, this returns Utilities.UniversalFallback(Utilities.Model{T}()). If copying from a instance of a given model type is faster for optimizer then a new method returning an instance of this model type should be defined.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#Optimizer-attributes","page":"Models","title":"Optimizer attributes","text":"","category":"section"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"AbstractOptimizerAttribute\nSolverName\nSolverVersion\nSilent\nTimeLimitSec\nObjectiveLimit\nRawOptimizerAttribute\nNumberOfThreads\nRawSolver\nAbsoluteGapTolerance\nRelativeGapTolerance","category":"page"},{"location":"moi/reference/models/#MathOptInterface.AbstractOptimizerAttribute","page":"Models","title":"MathOptInterface.AbstractOptimizerAttribute","text":"AbstractOptimizerAttribute\n\nAbstract supertype for attribute objects that can be used to set or get attributes (properties) of the optimizer.\n\nNotes\n\nThe difference between AbstractOptimizerAttribute and AbstractModelAttribute lies in the behavior of is_empty, empty! and copy_to. Typically optimizer attributes affect only how the model is solved.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.SolverName","page":"Models","title":"MathOptInterface.SolverName","text":"SolverName()\n\nAn optimizer attribute for the string identifying the solver/optimizer.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.SolverVersion","page":"Models","title":"MathOptInterface.SolverVersion","text":"SolverVersion()\n\nAn optimizer attribute for the string identifying the version of the solver.\n\nnote: Note\nFor solvers supporting semantic versioning, the SolverVersion should be a string of the form \"vMAJOR.MINOR.PATCH\", so that it can be converted to a Julia VersionNumber (e.g., `VersionNumber(\"v1.2.3\")).We do not require Semantic Versioning because some solvers use alternate versioning systems. For example, CPLEX uses Calendar Versioning, so SolverVersion will return a string like \"202001\".\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.Silent","page":"Models","title":"MathOptInterface.Silent","text":"Silent()\n\nAn optimizer attribute for silencing the output of an optimizer. When set to true, it takes precedence over any other attribute controlling verbosity and requires the solver to produce no output. The default value is false which has no effect. In this case the verbosity is controlled by other attributes.\n\nNote\n\nEvery optimizer should have verbosity on by default. For instance, if a solver has a solver-specific log level attribute, the MOI implementation should set it to 1 by default. If the user sets Silent to true, then the log level should be set to 0, even if the user specifically sets a value of log level. If the value of Silent is false then the log level set to the solver is the value given by the user for this solver-specific parameter or 1 if none is given.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.TimeLimitSec","page":"Models","title":"MathOptInterface.TimeLimitSec","text":"TimeLimitSec()\n\nAn optimizer attribute for setting a time limit (in seconnds) for an optimization. When set to nothing, it deactivates the solver time limit. The default value is nothing.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ObjectiveLimit","page":"Models","title":"MathOptInterface.ObjectiveLimit","text":"ObjectiveLimit()\n\nAn optimizer attribute for setting a limit on the objective value.\n\nThe provided limit must be a Union{Real,Nothing}.\n\nWhen set to nothing, the limit reverts to the solver's default.\n\nThe default value is nothing.\n\nThe solver may stop when the ObjectiveValue is better (lower for minimization, higher for maximization) than the ObjectiveLimit. If stopped, the TerminationStatus should be OBJECTIVE_LIMIT.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.RawOptimizerAttribute","page":"Models","title":"MathOptInterface.RawOptimizerAttribute","text":"RawOptimizerAttribute(name::String)\n\nAn optimizer attribute for the solver-specific parameter identified by name.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.NumberOfThreads","page":"Models","title":"MathOptInterface.NumberOfThreads","text":"NumberOfThreads()\n\nAn optimizer attribute for setting the number of threads used for an optimization. When set to nothing uses solver default. Values are positive integers. The default value is nothing.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.RawSolver","page":"Models","title":"MathOptInterface.RawSolver","text":"RawSolver()\n\nA model attribute for the object that may be used to access a solver-specific API for this optimizer.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.AbsoluteGapTolerance","page":"Models","title":"MathOptInterface.AbsoluteGapTolerance","text":"AbsoluteGapTolerance()\n\nAn optimizer attribute for setting the absolute gap tolerance for an optimization. This is an optimizer attribute, and should be set before calling optimize!. When set to nothing (if supported), uses solver default.\n\nTo set a relative gap tolerance, see RelativeGapTolerance.\n\nwarning: Warning\nThe mathematical definition of \"absolute gap\", and its treatment during the optimization, are solver-dependent. However, assuming no other limit nor issue is encountered during the optimization, most solvers that implement this attribute will stop once f - b g_abs, where b is the best bound, f is the best feasible objective value, and g_abs is the absolute gap.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.RelativeGapTolerance","page":"Models","title":"MathOptInterface.RelativeGapTolerance","text":"RelativeGapTolerance()\n\nAn optimizer attribute for setting the relative gap tolerance for an optimization. This is an optimizer attribute, and should be set before calling optimize!. When set to nothing (if supported), uses solver default.\n\nIf you are looking for the relative gap of the current best solution, see RelativeGap. If no limit nor issue is encountered during the optimization, the value of RelativeGap should be at most as large as RelativeGapTolerance.\n\n# Before optimizing: set relative gap tolerance\n# set 0.1% relative gap tolerance\nMOI.set(model, MOI.RelativeGapTolerance(), 1e-3)\nMOI.optimize!(model)\n\n# After optimizing (assuming all went well)\n# The relative gap tolerance has not changed...\nMOI.get(model, MOI.RelativeGapTolerance()) # returns 1e-3\n# ... and the relative gap of the obtained solution is smaller or equal to the\n# tolerance\nMOI.get(model, MOI.RelativeGap()) # should return something ≤ 1e-3\n\nwarning: Warning\nThe mathematical definition of \"relative gap\", and its allowed range, are solver-dependent. Typically, solvers expect a value between 0.0 and 1.0.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"List of attributes useful for optimizers","category":"page"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"TerminationStatus\nTerminationStatusCode\nOPTIMIZE_NOT_CALLED\nOPTIMAL\nINFEASIBLE\nDUAL_INFEASIBLE\nLOCALLY_SOLVED\nLOCALLY_INFEASIBLE\nINFEASIBLE_OR_UNBOUNDED\nALMOST_OPTIMAL\nALMOST_INFEASIBLE\nALMOST_DUAL_INFEASIBLE\nALMOST_LOCALLY_SOLVED\nITERATION_LIMIT\nTIME_LIMIT\nNODE_LIMIT\nSOLUTION_LIMIT\nMEMORY_LIMIT\nOBJECTIVE_LIMIT\nNORM_LIMIT\nOTHER_LIMIT\nSLOW_PROGRESS\nNUMERICAL_ERROR\nINVALID_MODEL\nINVALID_OPTION\nINTERRUPTED\nOTHER_ERROR\nPrimalStatus\nDualStatus\nRawStatusString\nResultCount\nObjectiveValue\nDualObjectiveValue\nObjectiveBound\nRelativeGap\nSolveTimeSec\nSimplexIterations\nBarrierIterations\nNodeCount","category":"page"},{"location":"moi/reference/models/#MathOptInterface.TerminationStatus","page":"Models","title":"MathOptInterface.TerminationStatus","text":"TerminationStatus()\n\nA model attribute for the TerminationStatusCode explaining why the optimizer stopped.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.TerminationStatusCode","page":"Models","title":"MathOptInterface.TerminationStatusCode","text":"TerminationStatusCode\n\nAn Enum of possible values for the TerminationStatus attribute. This attribute is meant to explain the reason why the optimizer stopped executing in the most recent call to optimize!.\n\nValues\n\nPossible values are:\n\nOPTIMIZE_NOT_CALLED: The algorithm has not started.\nOPTIMAL: The algorithm found a globally optimal solution.\nINFEASIBLE: The algorithm concluded that no feasible solution exists.\nDUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem. If, additionally, a feasible (primal) solution is known to exist, this status typically implies that the problem is unbounded, with some technical exceptions.\nLOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, could not find directions for improvement, or otherwise completed its search without global guarantees.\nLOCALLY_INFEASIBLE: The algorithm converged to an infeasible point or otherwise completed its search without finding a feasible solution, without guarantees that no feasible solution exists.\nINFEASIBLE_OR_UNBOUNDED: The algorithm stopped because it decided that the problem is infeasible or unbounded; this occasionally happens during MIP presolve.\nALMOST_OPTIMAL: The algorithm found a globally optimal solution to relaxed tolerances.\nALMOST_INFEASIBLE: The algorithm concluded that no feasible solution exists within relaxed tolerances.\nALMOST_DUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem within relaxed tolerances.\nALMOST_LOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, or could not find directions for improvement within relaxed tolerances.\nITERATION_LIMIT: An iterative algorithm stopped after conducting the maximum number of iterations.\nTIME_LIMIT: The algorithm stopped after a user-specified computation time.\nNODE_LIMIT: A branch-and-bound algorithm stopped because it explored a maximum number of nodes in the branch-and-bound tree.\nSOLUTION_LIMIT: The algorithm stopped because it found the required number of solutions. This is often used in MIPs to get the solver to return the first feasible solution it encounters.\nMEMORY_LIMIT: The algorithm stopped because it ran out of memory.\nOBJECTIVE_LIMIT: The algorithm stopped because it found a solution better than a minimum limit set by the user.\nNORM_LIMIT: The algorithm stopped because the norm of an iterate became too large.\nOTHER_LIMIT: The algorithm stopped due to a limit not covered by one of the _LIMIT_ statuses above.\nSLOW_PROGRESS: The algorithm stopped because it was unable to continue making progress towards the solution.\nNUMERICAL_ERROR: The algorithm stopped because it encountered unrecoverable numerical error.\nINVALID_MODEL: The algorithm stopped because the model is invalid.\nINVALID_OPTION: The algorithm stopped because it was provided an invalid option.\nINTERRUPTED: The algorithm stopped because of an interrupt signal.\nOTHER_ERROR: The algorithm stopped because of an error not covered by one of the statuses defined above.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.OPTIMIZE_NOT_CALLED","page":"Models","title":"MathOptInterface.OPTIMIZE_NOT_CALLED","text":"OPTIMIZE_NOT_CALLED::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nOPTIMIZE_NOT_CALLED: The algorithm has not started.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.OPTIMAL","page":"Models","title":"MathOptInterface.OPTIMAL","text":"OPTIMAL::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nOPTIMAL: The algorithm found a globally optimal solution.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.INFEASIBLE","page":"Models","title":"MathOptInterface.INFEASIBLE","text":"INFEASIBLE::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nINFEASIBLE: The algorithm concluded that no feasible solution exists.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.DUAL_INFEASIBLE","page":"Models","title":"MathOptInterface.DUAL_INFEASIBLE","text":"DUAL_INFEASIBLE::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nDUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem. If, additionally, a feasible (primal) solution is known to exist, this status typically implies that the problem is unbounded, with some technical exceptions.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.LOCALLY_SOLVED","page":"Models","title":"MathOptInterface.LOCALLY_SOLVED","text":"LOCALLY_SOLVED::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nLOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, could not find directions for improvement, or otherwise completed its search without global guarantees.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.LOCALLY_INFEASIBLE","page":"Models","title":"MathOptInterface.LOCALLY_INFEASIBLE","text":"LOCALLY_INFEASIBLE::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nLOCALLY_INFEASIBLE: The algorithm converged to an infeasible point or otherwise completed its search without finding a feasible solution, without guarantees that no feasible solution exists.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.INFEASIBLE_OR_UNBOUNDED","page":"Models","title":"MathOptInterface.INFEASIBLE_OR_UNBOUNDED","text":"INFEASIBLE_OR_UNBOUNDED::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nINFEASIBLE_OR_UNBOUNDED: The algorithm stopped because it decided that the problem is infeasible or unbounded; this occasionally happens during MIP presolve.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.ALMOST_OPTIMAL","page":"Models","title":"MathOptInterface.ALMOST_OPTIMAL","text":"ALMOST_OPTIMAL::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nALMOST_OPTIMAL: The algorithm found a globally optimal solution to relaxed tolerances.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.ALMOST_INFEASIBLE","page":"Models","title":"MathOptInterface.ALMOST_INFEASIBLE","text":"ALMOST_INFEASIBLE::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nALMOST_INFEASIBLE: The algorithm concluded that no feasible solution exists within relaxed tolerances.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.ALMOST_DUAL_INFEASIBLE","page":"Models","title":"MathOptInterface.ALMOST_DUAL_INFEASIBLE","text":"ALMOST_DUAL_INFEASIBLE::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nALMOST_DUAL_INFEASIBLE: The algorithm concluded that no dual bound exists for the problem within relaxed tolerances.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.ALMOST_LOCALLY_SOLVED","page":"Models","title":"MathOptInterface.ALMOST_LOCALLY_SOLVED","text":"ALMOST_LOCALLY_SOLVED::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nALMOST_LOCALLY_SOLVED: The algorithm converged to a stationary point, local optimal solution, or could not find directions for improvement within relaxed tolerances.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.ITERATION_LIMIT","page":"Models","title":"MathOptInterface.ITERATION_LIMIT","text":"ITERATION_LIMIT::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nITERATION_LIMIT: An iterative algorithm stopped after conducting the maximum number of iterations.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.TIME_LIMIT","page":"Models","title":"MathOptInterface.TIME_LIMIT","text":"TIME_LIMIT::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nTIME_LIMIT: The algorithm stopped after a user-specified computation time.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.NODE_LIMIT","page":"Models","title":"MathOptInterface.NODE_LIMIT","text":"NODE_LIMIT::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nNODE_LIMIT: A branch-and-bound algorithm stopped because it explored a maximum number of nodes in the branch-and-bound tree.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.SOLUTION_LIMIT","page":"Models","title":"MathOptInterface.SOLUTION_LIMIT","text":"SOLUTION_LIMIT::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nSOLUTION_LIMIT: The algorithm stopped because it found the required number of solutions. This is often used in MIPs to get the solver to return the first feasible solution it encounters.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.MEMORY_LIMIT","page":"Models","title":"MathOptInterface.MEMORY_LIMIT","text":"MEMORY_LIMIT::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nMEMORY_LIMIT: The algorithm stopped because it ran out of memory.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.OBJECTIVE_LIMIT","page":"Models","title":"MathOptInterface.OBJECTIVE_LIMIT","text":"OBJECTIVE_LIMIT::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nOBJECTIVE_LIMIT: The algorithm stopped because it found a solution better than a minimum limit set by the user.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.NORM_LIMIT","page":"Models","title":"MathOptInterface.NORM_LIMIT","text":"NORM_LIMIT::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nNORM_LIMIT: The algorithm stopped because the norm of an iterate became too large.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.OTHER_LIMIT","page":"Models","title":"MathOptInterface.OTHER_LIMIT","text":"OTHER_LIMIT::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nOTHER_LIMIT: The algorithm stopped due to a limit not covered by one of the _LIMIT_ statuses above.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.SLOW_PROGRESS","page":"Models","title":"MathOptInterface.SLOW_PROGRESS","text":"SLOW_PROGRESS::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nSLOW_PROGRESS: The algorithm stopped because it was unable to continue making progress towards the solution.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.NUMERICAL_ERROR","page":"Models","title":"MathOptInterface.NUMERICAL_ERROR","text":"NUMERICAL_ERROR::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nNUMERICAL_ERROR: The algorithm stopped because it encountered unrecoverable numerical error.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.INVALID_MODEL","page":"Models","title":"MathOptInterface.INVALID_MODEL","text":"INVALID_MODEL::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nINVALID_MODEL: The algorithm stopped because the model is invalid.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.INVALID_OPTION","page":"Models","title":"MathOptInterface.INVALID_OPTION","text":"INVALID_OPTION::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nINVALID_OPTION: The algorithm stopped because it was provided an invalid option.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.INTERRUPTED","page":"Models","title":"MathOptInterface.INTERRUPTED","text":"INTERRUPTED::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nINTERRUPTED: The algorithm stopped because of an interrupt signal.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.OTHER_ERROR","page":"Models","title":"MathOptInterface.OTHER_ERROR","text":"OTHER_ERROR::TerminationStatusCode\n\nAn instance of the TerminationStatusCode enum.\n\nOTHER_ERROR: The algorithm stopped because of an error not covered by one of the statuses defined above.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.PrimalStatus","page":"Models","title":"MathOptInterface.PrimalStatus","text":"PrimalStatus(result_index::Int = 1)\n\nA model attribute for the ResultStatusCode of the primal result result_index. If result_index is omitted, it defaults to 1.\n\nSee ResultCount for information on how the results are ordered.\n\nIf result_index is larger than the value of ResultCount then NO_SOLUTION is returned.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.DualStatus","page":"Models","title":"MathOptInterface.DualStatus","text":"DualStatus(result_index::Int = 1)\n\nA model attribute for the ResultStatusCode of the dual result result_index. If result_index is omitted, it defaults to 1.\n\nSee ResultCount for information on how the results are ordered.\n\nIf result_index is larger than the value of ResultCount then NO_SOLUTION is returned.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.RawStatusString","page":"Models","title":"MathOptInterface.RawStatusString","text":"RawStatusString()\n\nA model attribute for a solver specific string explaining why the optimizer stopped.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ResultCount","page":"Models","title":"MathOptInterface.ResultCount","text":"ResultCount()\n\nA model attribute for the number of results available.\n\nOrder of solutions\n\nA number of attributes contain an index, result_index, which is used to refer to one of the available results. Thus, result_index must be an integer between 1 and the number of available results.\n\nAs a general rule, the first result (result_index=1) is the most important result (e.g., an optimal solution or an infeasibility certificate). Other results will typically be alternate solutions that the solver found during the search for the first result.\n\nIf a (local) optimal solution is available, i.e., TerminationStatus is OPTIMAL or LOCALLY_SOLVED, the first result must correspond to the (locally) optimal solution. Other results may be alternative optimal solutions, or they may be other suboptimal solutions; use ObjectiveValue to distingiush between them.\n\nIf a primal or dual infeasibility certificate is available, i.e., TerminationStatus is INFEASIBLE or DUAL_INFEASIBLE and the corresponding PrimalStatus or DualStatus is INFEASIBILITY_CERTIFICATE, then the first result must be a certificate. Other results may be alternate certificates, or infeasible points.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ObjectiveValue","page":"Models","title":"MathOptInterface.ObjectiveValue","text":"ObjectiveValue(result_index::Int = 1)\n\nA model attribute for the objective value of the primal solution result_index.\n\nIf the solver does not have a primal value for the objective because the result_index is beyond the available solutions (whose number is indicated by the ResultCount attribute), getting this attribute must throw a ResultIndexBoundsError. Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check PrimalStatus before accessing the ObjectiveValue attribute.\n\nSee ResultCount for information on how the results are ordered.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.DualObjectiveValue","page":"Models","title":"MathOptInterface.DualObjectiveValue","text":"DualObjectiveValue(result_index::Int = 1)\n\nA model attribute for the value of the objective function of the dual problem for the result_indexth dual result.\n\nIf the solver does not have a dual value for the objective because the result_index is beyond the available solutions (whose number is indicated by the ResultCount attribute), getting this attribute must throw a ResultIndexBoundsError. Otherwise, if the result is unavailable for another reason (for instance, only a primal solution is available), the result is undefined. Users should first check DualStatus before accessing the DualObjectiveValue attribute.\n\nSee ResultCount for information on how the results are ordered.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ObjectiveBound","page":"Models","title":"MathOptInterface.ObjectiveBound","text":"ObjectiveBound()\n\nA model attribute for the best known bound on the optimal objective value.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.RelativeGap","page":"Models","title":"MathOptInterface.RelativeGap","text":"RelativeGap()\n\nA model attribute for the final relative optimality gap.\n\nwarning: Warning\nThe definition of this gap is solver-dependent. However, most solvers implementing this attribute define the relative gap as some variation of fracb-ff, where b is the best bound and f is the best feasible objective value.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.SolveTimeSec","page":"Models","title":"MathOptInterface.SolveTimeSec","text":"SolveTimeSec()\n\nA model attribute for the total elapsed solution time (in seconds) as reported by the optimizer.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.SimplexIterations","page":"Models","title":"MathOptInterface.SimplexIterations","text":"SimplexIterations()\n\nA model attribute for the cumulative number of simplex iterations during the optimization process.\n\nFor a mixed-integer program (MIP), the return value is the total simplex iterations for all nodes.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.BarrierIterations","page":"Models","title":"MathOptInterface.BarrierIterations","text":"BarrierIterations()\n\nA model attribute for the cumulative number of barrier iterations while solving a problem.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.NodeCount","page":"Models","title":"MathOptInterface.NodeCount","text":"NodeCount()\n\nA model attribute for the total number of branch-and-bound nodes explored while solving a mixed-integer program (MIP).\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#ResultStatusCode","page":"Models","title":"ResultStatusCode","text":"","category":"section"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"ResultStatusCode\nNO_SOLUTION\nFEASIBLE_POINT\nNEARLY_FEASIBLE_POINT\nINFEASIBLE_POINT\nINFEASIBILITY_CERTIFICATE\nNEARLY_INFEASIBILITY_CERTIFICATE\nREDUCTION_CERTIFICATE\nNEARLY_REDUCTION_CERTIFICATE\nUNKNOWN_RESULT_STATUS\nOTHER_RESULT_STATUS","category":"page"},{"location":"moi/reference/models/#MathOptInterface.ResultStatusCode","page":"Models","title":"MathOptInterface.ResultStatusCode","text":"ResultStatusCode\n\nAn Enum of possible values for the PrimalStatus and DualStatus attributes.\n\nThe values indicate how to interpret the result vector.\n\nValues\n\nPossible values are:\n\nNO_SOLUTION: the result vector is empty.\nFEASIBLE_POINT: the result vector is a feasible point.\nNEARLY_FEASIBLE_POINT: the result vector is feasible if some constraint tolerances are relaxed.\nINFEASIBLE_POINT: the result vector is an infeasible point.\nINFEASIBILITY_CERTIFICATE: the result vector is an infeasibility certificate. If the PrimalStatus is INFEASIBILITY_CERTIFICATE, then the primal result vector is a certificate of dual infeasibility. If the DualStatus is INFEASIBILITY_CERTIFICATE, then the dual result vector is a proof of primal infeasibility.\nNEARLY_INFEASIBILITY_CERTIFICATE: the result satisfies a relaxed criterion for a certificate of infeasibility.\nREDUCTION_CERTIFICATE: the result vector is an ill-posed certificate; see this article for details. If the PrimalStatus is REDUCTION_CERTIFICATE, then the primal result vector is a proof that the dual problem is ill-posed. If the DualStatus is REDUCTION_CERTIFICATE, then the dual result vector is a proof that the primal is ill-posed.\nNEARLY_REDUCTION_CERTIFICATE: the result satisfies a relaxed criterion for an ill-posed certificate.\nUNKNOWN_RESULT_STATUS: the result vector contains a solution with an unknown interpretation.\nOTHER_RESULT_STATUS: the result vector contains a solution with an interpretation not covered by one of the statuses defined above\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.NO_SOLUTION","page":"Models","title":"MathOptInterface.NO_SOLUTION","text":"NO_SOLUTION::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nNO_SOLUTION: the result vector is empty.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.FEASIBLE_POINT","page":"Models","title":"MathOptInterface.FEASIBLE_POINT","text":"FEASIBLE_POINT::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nFEASIBLE_POINT: the result vector is a feasible point.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.NEARLY_FEASIBLE_POINT","page":"Models","title":"MathOptInterface.NEARLY_FEASIBLE_POINT","text":"NEARLY_FEASIBLE_POINT::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nNEARLY_FEASIBLE_POINT: the result vector is feasible if some constraint tolerances are relaxed.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.INFEASIBLE_POINT","page":"Models","title":"MathOptInterface.INFEASIBLE_POINT","text":"INFEASIBLE_POINT::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nINFEASIBLE_POINT: the result vector is an infeasible point.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.INFEASIBILITY_CERTIFICATE","page":"Models","title":"MathOptInterface.INFEASIBILITY_CERTIFICATE","text":"INFEASIBILITY_CERTIFICATE::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nINFEASIBILITY_CERTIFICATE: the result vector is an infeasibility certificate. If the PrimalStatus is INFEASIBILITY_CERTIFICATE, then the primal result vector is a certificate of dual infeasibility. If the DualStatus is INFEASIBILITY_CERTIFICATE, then the dual result vector is a proof of primal infeasibility.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.NEARLY_INFEASIBILITY_CERTIFICATE","page":"Models","title":"MathOptInterface.NEARLY_INFEASIBILITY_CERTIFICATE","text":"NEARLY_INFEASIBILITY_CERTIFICATE::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nNEARLY_INFEASIBILITY_CERTIFICATE: the result satisfies a relaxed criterion for a certificate of infeasibility.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.REDUCTION_CERTIFICATE","page":"Models","title":"MathOptInterface.REDUCTION_CERTIFICATE","text":"REDUCTION_CERTIFICATE::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nREDUCTION_CERTIFICATE: the result vector is an ill-posed certificate; see this article for details. If the PrimalStatus is REDUCTION_CERTIFICATE, then the primal result vector is a proof that the dual problem is ill-posed. If the DualStatus is REDUCTION_CERTIFICATE, then the dual result vector is a proof that the primal is ill-posed.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.NEARLY_REDUCTION_CERTIFICATE","page":"Models","title":"MathOptInterface.NEARLY_REDUCTION_CERTIFICATE","text":"NEARLY_REDUCTION_CERTIFICATE::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nNEARLY_REDUCTION_CERTIFICATE: the result satisfies a relaxed criterion for an ill-posed certificate.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.UNKNOWN_RESULT_STATUS","page":"Models","title":"MathOptInterface.UNKNOWN_RESULT_STATUS","text":"UNKNOWN_RESULT_STATUS::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nUNKNOWN_RESULT_STATUS: the result vector contains a solution with an unknown interpretation.\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.OTHER_RESULT_STATUS","page":"Models","title":"MathOptInterface.OTHER_RESULT_STATUS","text":"OTHER_RESULT_STATUS::ResultStatusCode\n\nAn instance of the ResultStatusCode enum.\n\nOTHER_RESULT_STATUS: the result vector contains a solution with an interpretation not covered by one of the statuses defined above\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#Conflict-Status","page":"Models","title":"Conflict Status","text":"","category":"section"},{"location":"moi/reference/models/","page":"Models","title":"Models","text":"compute_conflict!\nConflictStatus\nConstraintConflictStatus\nConflictStatusCode\nConflictParticipationStatusCode\nNOT_IN_CONFLICT\nIN_CONFLICT\nMAYBE_IN_CONFLICT","category":"page"},{"location":"moi/reference/models/#MathOptInterface.compute_conflict!","page":"Models","title":"MathOptInterface.compute_conflict!","text":"compute_conflict!(optimizer::AbstractOptimizer)\n\nComputes a minimal subset of constraints such that the model with the other constraint removed is still infeasible.\n\nSome solvers call a set of conflicting constraints an Irreducible Inconsistent Subsystem (IIS).\n\nSee also ConflictStatus and ConstraintConflictStatus.\n\nNote\n\nIf the model is modified after a call to compute_conflict!, the implementor is not obliged to purge the conflict. Any calls to the above attributes may return values for the original conflict without a warning. Similarly, when modifying the model, the conflict can be discarded.\n\n\n\n\n\n","category":"function"},{"location":"moi/reference/models/#MathOptInterface.ConflictStatus","page":"Models","title":"MathOptInterface.ConflictStatus","text":"ConflictStatus()\n\nA model attribute for the ConflictStatusCode explaining why the conflict refiner stopped when computing the conflict.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ConstraintConflictStatus","page":"Models","title":"MathOptInterface.ConstraintConflictStatus","text":"ConstraintConflictStatus()\n\nA constraint attribute indicating whether the constraint participates in the conflict. Its type is ConflictParticipationStatusCode.\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ConflictStatusCode","page":"Models","title":"MathOptInterface.ConflictStatusCode","text":"ConflictStatusCode\n\nAn Enum of possible values for the ConflictStatus attribute. This attribute is meant to explain the reason why the conflict finder stopped executing in the most recent call to compute_conflict!.\n\nPossible values are:\n\nCOMPUTE_CONFLICT_NOT_CALLED: the function compute_conflict! has not yet been called\nNO_CONFLICT_EXISTS: there is no conflict because the problem is feasible\nNO_CONFLICT_FOUND: the solver could not find a conflict\nCONFLICT_FOUND: at least one conflict could be found\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.ConflictParticipationStatusCode","page":"Models","title":"MathOptInterface.ConflictParticipationStatusCode","text":"ConflictParticipationStatusCode\n\nAn Enum of possible values for the ConstraintConflictStatus attribute. This attribute is meant to indicate whether a given constraint participates or not in the last computed conflict.\n\nValues\n\nPossible values are:\n\nNOT_IN_CONFLICT: the constraint does not participate in the conflict\nIN_CONFLICT: the constraint participates in the conflict\nMAYBE_IN_CONFLICT: the constraint may participate in the conflict, the solver was not able to prove that the constraint can be excluded from the conflict\n\n\n\n\n\n","category":"type"},{"location":"moi/reference/models/#MathOptInterface.NOT_IN_CONFLICT","page":"Models","title":"MathOptInterface.NOT_IN_CONFLICT","text":"NOT_IN_CONFLICT::ConflictParticipationStatusCode\n\nAn instance of the ConflictParticipationStatusCode enum.\n\nNOT_IN_CONFLICT: the constraint does not participate in the conflict\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.IN_CONFLICT","page":"Models","title":"MathOptInterface.IN_CONFLICT","text":"IN_CONFLICT::ConflictParticipationStatusCode\n\nAn instance of the ConflictParticipationStatusCode enum.\n\nIN_CONFLICT: the constraint participates in the conflict\n\n\n\n\n\n","category":"constant"},{"location":"moi/reference/models/#MathOptInterface.MAYBE_IN_CONFLICT","page":"Models","title":"MathOptInterface.MAYBE_IN_CONFLICT","text":"MAYBE_IN_CONFLICT::ConflictParticipationStatusCode\n\nAn instance of the ConflictParticipationStatusCode enum.\n\nMAYBE_IN_CONFLICT: the constraint may participate in the conflict, the solver was not able to prove that the constraint can be excluded from the conflict\n\n\n\n\n\n","category":"constant"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"EditURL = \"https://github.com/jump-dev/MathOptInterface.jl/blob/v1.20.1/docs/src/submodules/Nonlinear/reference.md\"","category":"page"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"CurrentModule = MathOptInterface\nDocTestSetup = quote\n import MathOptInterface as MOI\nend\nDocTestFilters = [r\"MathOptInterface|MOI\"]","category":"page"},{"location":"moi/submodules/Nonlinear/reference/#NonlinearAPI","page":"API Reference","title":"Nonlinear Modeling","text":"","category":"section"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"More information can be found in the Nonlinear section of the manual.","category":"page"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"Nonlinear\nNonlinear.Model","category":"page"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear","page":"API Reference","title":"MathOptInterface.Nonlinear","text":"Nonlinear\n\nwarning: Warning\nThe Nonlinear submodule is experimental. Until this message is removed, breaking changes may be introduced in any minor or patch release of MathOptInterface.\n\n\n\n\n\n","category":"module"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.Model","page":"API Reference","title":"MathOptInterface.Nonlinear.Model","text":"Model()\n\nThe core datastructure for representing a nonlinear optimization problem.\n\nIt has the following fields:\n\nobjective::Union{Nothing,Expression} : holds the nonlinear objective function, if one exists, otherwise nothing.\nexpressions::Vector{Expression} : a vector of expressions in the model.\nconstraints::OrderedDict{ConstraintIndex,Constraint} : a map from ConstraintIndex to the corresponding Constraint. An OrderedDict is used instead of a Vector to support constraint deletion.\nparameters::Vector{Float64} : holds the current values of the parameters.\noperators::OperatorRegistry : stores the operators used in the model.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#nonlinear_api_expressions","page":"API Reference","title":"Expressions","text":"","category":"section"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"Nonlinear.ExpressionIndex\nNonlinear.add_expression","category":"page"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.ExpressionIndex","page":"API Reference","title":"MathOptInterface.Nonlinear.ExpressionIndex","text":"ExpressionIndex\n\nAn index to a nonlinear expression that is returned by add_expression.\n\nGiven data::Model and ex::ExpressionIndex, use data[ex] to retrieve the corresponding Expression.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.add_expression","page":"API Reference","title":"MathOptInterface.Nonlinear.add_expression","text":"add_expression(model::Model, expr)::ExpressionIndex\n\nParse expr into a Expression and add to model. Returns an ExpressionIndex that can be interpolated into other input expressions.\n\nexpr must be a type that is supported by parse_expression.\n\nExamples\n\nmodel = Model()\nx = MOI.VariableIndex(1)\nex = add_expression(model, :($x^2 + 1))\nset_objective(model, :(sqrt($ex)))\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#nonlinear_api_parameters","page":"API Reference","title":"Parameters","text":"","category":"section"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"Nonlinear.ParameterIndex\nNonlinear.add_parameter","category":"page"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.ParameterIndex","page":"API Reference","title":"MathOptInterface.Nonlinear.ParameterIndex","text":"ParameterIndex\n\nAn index to a nonlinear parameter that is returned by add_parameter. Given data::Model and p::ParameterIndex, use data[p] to retrieve the current value of the parameter and data[p] = value to set a new value.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.add_parameter","page":"API Reference","title":"MathOptInterface.Nonlinear.add_parameter","text":"add_parameter(model::Model, value::Float64)::ParameterIndex\n\nAdd a new parameter to model with the default value value. Returns a ParameterIndex that can be interpolated into other input expressions and used to modify the value of the parameter.\n\nExamples\n\nmodel = Model()\nx = MOI.VariableIndex(1)\np = add_parameter(model, 1.2)\nc = add_constraint(model, :($x^2 - $p), MOI.LessThan(0.0))\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#nonlinear_api_objectives","page":"API Reference","title":"Objectives","text":"","category":"section"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"Nonlinear.set_objective","category":"page"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.set_objective","page":"API Reference","title":"MathOptInterface.Nonlinear.set_objective","text":"set_objective(model::Model, obj)::Nothing\n\nParse obj into a Expression and set as the objective function of model.\n\nobj must be a type that is supported by parse_expression.\n\nTo remove the objective, pass nothing.\n\nExamples\n\nmodel = Model()\nx = MOI.VariableIndex(1)\nset_objective(model, :($x^2 + 1))\nset_objective(model, x)\nset_objective(model, nothing)\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#nonlinear_api_constraints","page":"API Reference","title":"Constraints","text":"","category":"section"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"Nonlinear.ConstraintIndex\nNonlinear.add_constraint\nNonlinear.delete","category":"page"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.ConstraintIndex","page":"API Reference","title":"MathOptInterface.Nonlinear.ConstraintIndex","text":"ConstraintIndex\n\nAn index to a nonlinear constraint that is returned by add_constraint.\n\nGiven data::Model and c::ConstraintIndex, use data[c] to retrieve the corresponding Constraint.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.add_constraint","page":"API Reference","title":"MathOptInterface.Nonlinear.add_constraint","text":"add_constraint(\n model::Model,\n func,\n set::Union{\n MOI.GreaterThan{Float64},\n MOI.LessThan{Float64},\n MOI.Interval{Float64},\n MOI.EqualTo{Float64},\n },\n)\n\nParse func and set into a Constraint and add to model. Returns a ConstraintIndex that can be used to delete the constraint or query solution information.\n\nExamples\n\nmodel = Model()\nx = MOI.VariableIndex(1)\nc = add_constraint(model, :($x^2), MOI.LessThan(1.0))\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.delete","page":"API Reference","title":"MathOptInterface.Nonlinear.delete","text":"delete(model::Model, c::ConstraintIndex)::Nothing\n\nDelete the constraint index c from model.\n\nExamples\n\nmodel = Model()\nx = MOI.VariableIndex(1)\nc = add_constraint(model, :($x^2), MOI.LessThan(1.0))\ndelete(model, c)\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#nonlinear_api_operators","page":"API Reference","title":"User-defined operators","text":"","category":"section"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"Nonlinear.OperatorRegistry\nNonlinear.DEFAULT_UNIVARIATE_OPERATORS\nNonlinear.DEFAULT_MULTIVARIATE_OPERATORS\nNonlinear.register_operator\nNonlinear.register_operator_if_needed\nNonlinear.assert_registered\nNonlinear.check_return_type\nNonlinear.eval_univariate_function\nNonlinear.eval_univariate_gradient\nNonlinear.eval_univariate_hessian\nNonlinear.eval_multivariate_function\nNonlinear.eval_multivariate_gradient\nNonlinear.eval_multivariate_hessian\nNonlinear.eval_logic_function\nNonlinear.eval_comparison_function","category":"page"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.OperatorRegistry","page":"API Reference","title":"MathOptInterface.Nonlinear.OperatorRegistry","text":"OperatorRegistry()\n\nCreate a new OperatorRegistry to store and evaluate univariate and multivariate operators.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.DEFAULT_UNIVARIATE_OPERATORS","page":"API Reference","title":"MathOptInterface.Nonlinear.DEFAULT_UNIVARIATE_OPERATORS","text":"DEFAULT_UNIVARIATE_OPERATORS\n\nThe list of univariate operators that are supported by default.\n\nExample\n\njulia> import MathOptInterface as MOI\n\njulia> MOI.Nonlinear.DEFAULT_UNIVARIATE_OPERATORS\n72-element Vector{Symbol}:\n :+\n :-\n :abs\n :sqrt\n :cbrt\n :abs2\n :inv\n :log\n :log10\n :log2\n ⋮\n :airybi\n :airyaiprime\n :airybiprime\n :besselj0\n :besselj1\n :bessely0\n :bessely1\n :erfcx\n :dawson\n\n\n\n\n\n","category":"constant"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.DEFAULT_MULTIVARIATE_OPERATORS","page":"API Reference","title":"MathOptInterface.Nonlinear.DEFAULT_MULTIVARIATE_OPERATORS","text":"DEFAULT_MULTIVARIATE_OPERATORS\n\nThe list of multivariate operators that are supported by default.\n\nExample\n\njulia> import MathOptInterface as MOI\n\njulia> MOI.Nonlinear.DEFAULT_MULTIVARIATE_OPERATORS\n9-element Vector{Symbol}:\n :+\n :-\n :*\n :^\n :/\n :ifelse\n :atan\n :min\n :max\n\n\n\n\n\n","category":"constant"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.register_operator","page":"API Reference","title":"MathOptInterface.Nonlinear.register_operator","text":"register_operator(\n model::Model,\n op::Symbol,\n nargs::Int,\n f::Function,\n [∇f::Function],\n [∇²f::Function],\n)\n\nRegister the user-defined operator op with nargs input arguments in model.\n\nUnivariate functions\n\nf(x::T)::T must be a function that takes a single input argument x and returns the function evaluated at x. If ∇f and ∇²f are not provided, f must support any Real input type T.\n∇f(x::T)::T is a function that takes a single input argument x and returns the first derivative of f with respect to x. If ∇²f is not provided, ∇f must support any Real input type T.\n∇²f(x::T)::T is a function that takes a single input argument x and returns the second derivative of f with respect to x.\n\nMultivariate functions\n\nf(x::T...)::T must be a function that takes a nargs input arguments x and returns the function evaluated at x. If ∇f and ∇²f are not provided, f must support any Real input type T.\n∇f(g::AbstractVector{T}, x::T...)::T is a function that takes a cache vector g of length length(x), and fills each element g[i] with the partial derivative of f with respect to x[i].\n∇²f(H::AbstractMatrix, x::T...)::T is a function that takes a matrix H and fills the lower-triangular components H[i, j] with the Hessian of f with respect to x[i] and x[j] for i >= j.\n\nNotes for multivariate Hessians\n\nH has size(H) == (length(x), length(x)), but you must not access elements H[i, j] for i > j.\nH is dense, but you do not need to fill structural zeros.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.register_operator_if_needed","page":"API Reference","title":"MathOptInterface.Nonlinear.register_operator_if_needed","text":"register_operator_if_needed(\n registry::OperatorRegistry,\n op::Symbol,\n nargs::Int,\n f::Function;\n)\n\nSimilar to register_operator, but this function warns if the function is not registered, and skips silently if it already is.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.assert_registered","page":"API Reference","title":"MathOptInterface.Nonlinear.assert_registered","text":"assert_registered(registry::OperatorRegistry, op::Symbol, nargs::Int)\n\nThrow an error if op is not registered in registry with nargs arguments.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.check_return_type","page":"API Reference","title":"MathOptInterface.Nonlinear.check_return_type","text":"check_return_type(::Type{T}, ret::S) where {T,S}\n\nOverload this method for new types S to throw an informative error if a user-defined function returns the type S instead of T.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.eval_univariate_function","page":"API Reference","title":"MathOptInterface.Nonlinear.eval_univariate_function","text":"eval_univariate_function(\n registry::OperatorRegistry,\n op::Symbol,\n x::T,\n) where {T}\n\nEvaluate the operator op(x)::T, where op is a univariate function in registry.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.eval_univariate_gradient","page":"API Reference","title":"MathOptInterface.Nonlinear.eval_univariate_gradient","text":"eval_univariate_gradient(\n registry::OperatorRegistry,\n op::Symbol,\n x::T,\n) where {T}\n\nEvaluate the first-derivative of the operator op(x)::T, where op is a univariate function in registry.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.eval_univariate_hessian","page":"API Reference","title":"MathOptInterface.Nonlinear.eval_univariate_hessian","text":"eval_univariate_hessian(\n registry::OperatorRegistry,\n op::Symbol,\n x::T,\n) where {T}\n\nEvaluate the second-derivative of the operator op(x)::T, where op is a univariate function in registry.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.eval_multivariate_function","page":"API Reference","title":"MathOptInterface.Nonlinear.eval_multivariate_function","text":"eval_multivariate_function(\n registry::OperatorRegistry,\n op::Symbol,\n x::AbstractVector{T},\n) where {T}\n\nEvaluate the operator op(x)::T, where op is a multivariate function in registry.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.eval_multivariate_gradient","page":"API Reference","title":"MathOptInterface.Nonlinear.eval_multivariate_gradient","text":"eval_multivariate_gradient(\n registry::OperatorRegistry,\n op::Symbol,\n g::AbstractVector{T},\n x::AbstractVector{T},\n) where {T}\n\nEvaluate the gradient of operator g .= ∇op(x), where op is a multivariate function in registry.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.eval_multivariate_hessian","page":"API Reference","title":"MathOptInterface.Nonlinear.eval_multivariate_hessian","text":"eval_multivariate_hessian(\n registry::OperatorRegistry,\n op::Symbol,\n H::AbstractMatrix,\n x::AbstractVector{T},\n) where {T}\n\nEvaluate the Hessian of operator ∇²op(x), where op is a multivariate function in registry.\n\nThe Hessian is stored in the lower-triangular part of the matrix H.\n\nnote: Note\nImplementations of the Hessian operators will not fill structural zeros. Therefore, before calling this function you should pre-populate the matrix H with 0.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.eval_logic_function","page":"API Reference","title":"MathOptInterface.Nonlinear.eval_logic_function","text":"eval_logic_function(\n registry::OperatorRegistry,\n op::Symbol,\n lhs::T,\n rhs::T,\n)::Bool where {T}\n\nEvaluate (lhs op rhs)::Bool, where op is a logic operator in registry.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.eval_comparison_function","page":"API Reference","title":"MathOptInterface.Nonlinear.eval_comparison_function","text":"eval_comparison_function(\n registry::OperatorRegistry,\n op::Symbol,\n lhs::T,\n rhs::T,\n)::Bool where {T}\n\nEvaluate (lhs op rhs)::Bool, where op is a comparison operator in registry.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#Automatic-differentiation-backends","page":"API Reference","title":"Automatic-differentiation backends","text":"","category":"section"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"Nonlinear.Evaluator\nNonlinear.AbstractAutomaticDifferentiation\nNonlinear.ExprGraphOnly\nNonlinear.SparseReverseMode","category":"page"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.Evaluator","page":"API Reference","title":"MathOptInterface.Nonlinear.Evaluator","text":"Evaluator(\n model::Model,\n backend::AbstractAutomaticDifferentiation,\n ordered_variables::Vector{MOI.VariableIndex},\n)\n\nCreate Evaluator, a subtype of MOI.AbstractNLPEvaluator, from Model.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.AbstractAutomaticDifferentiation","page":"API Reference","title":"MathOptInterface.Nonlinear.AbstractAutomaticDifferentiation","text":"AbstractAutomaticDifferentiation\n\nAn abstract type for extending Evaluator.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.ExprGraphOnly","page":"API Reference","title":"MathOptInterface.Nonlinear.ExprGraphOnly","text":"ExprGraphOnly() <: AbstractAutomaticDifferentiation\n\nThe default implementation of AbstractAutomaticDifferentiation. The only supported feature is :ExprGraph.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.SparseReverseMode","page":"API Reference","title":"MathOptInterface.Nonlinear.SparseReverseMode","text":"SparseReverseMode() <: AbstractAutomaticDifferentiation\n\nAn implementation of AbstractAutomaticDifferentiation that uses sparse reverse-mode automatic differentiation to compute derivatives. Supports all features in the MOI nonlinear interface.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#Data-structure","page":"API Reference","title":"Data-structure","text":"","category":"section"},{"location":"moi/submodules/Nonlinear/reference/","page":"API Reference","title":"API Reference","text":"Nonlinear.Node\nNonlinear.NodeType\nNonlinear.Expression\nNonlinear.Constraint\nNonlinear.adjacency_matrix\nNonlinear.parse_expression\nNonlinear.convert_to_expr\nNonlinear.ordinal_index","category":"page"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.Node","page":"API Reference","title":"MathOptInterface.Nonlinear.Node","text":"struct Node\n type::NodeType\n index::Int\n parent::Int\nend\n\nA single node in a nonlinear expression tree. Used by Expression.\n\nSee the MathOptInterface documentation for information on how the nodes and values form an expression tree.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.NodeType","page":"API Reference","title":"MathOptInterface.Nonlinear.NodeType","text":"NodeType\n\nAn enum describing the possible node types. Each Node has a .index field, which should be interpreted as follows:\n\nNODE_CALL_MULTIVARIATE: the index into operators.multivariate_operators\nNODE_CALL_UNIVARIATE: the index into operators.univariate_operators\nNODE_LOGIC: the index into operators.logic_operators\nNODE_COMPARISON: the index into operators.comparison_operators\nNODE_MOI_VARIABLE: the value of MOI.VariableIndex(index) in the user's space of the model.\nNODE_VARIABLE: the 1-based index of the internal vector\nNODE_VALUE: the index into the .values field of Expression\nNODE_PARAMETER: the index into data.parameters\nNODE_SUBEXPRESSION: the index into data.expressions\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.Expression","page":"API Reference","title":"MathOptInterface.Nonlinear.Expression","text":"struct Expression\n nodes::Vector{Node}\n values::Vector{Float64}\nend\n\nThe core type that represents a nonlinear expression. See the MathOptInterface documentation for information on how the nodes and values form an expression tree.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.Constraint","page":"API Reference","title":"MathOptInterface.Nonlinear.Constraint","text":"struct Constraint\n expression::Expression\n set::Union{\n MOI.LessThan{Float64},\n MOI.GreaterThan{Float64},\n MOI.EqualTo{Float64},\n MOI.Interval{Float64},\n }\nend\n\nA type to hold information relating to the nonlinear constraint f(x) in S, where f(x) is defined by .expression, and S is .set.\n\n\n\n\n\n","category":"type"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.adjacency_matrix","page":"API Reference","title":"MathOptInterface.Nonlinear.adjacency_matrix","text":"adjacency_matrix(nodes::Vector{Node})\n\nCompute the sparse adjacency matrix describing the parent-child relationships in nodes.\n\nThe element (i, j) is true if there is an edge from node[j] to node[i]. Since we get a column-oriented matrix, this gives us a fast way to look up the edges leaving any node (i.e., the children).\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.parse_expression","page":"API Reference","title":"MathOptInterface.Nonlinear.parse_expression","text":"parse_expression(data::Model, input)::Expression\n\nParse input into a Expression.\n\n\n\n\n\nparse_expression(\n data::Model,\n expr::Expression,\n input::Any,\n parent_index::Int,\n)::Expression\n\nParse input into a Expression, and add it to expr as a child of expr.nodes[parent_index]. Existing subexpressions and parameters are stored in data.\n\nYou can extend parsing support to new types of objects by overloading this method with a different type on input::Any.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.convert_to_expr","page":"API Reference","title":"MathOptInterface.Nonlinear.convert_to_expr","text":"convert_to_expr(data::Model, expr::Expression)\n\nConvert the Expression expr into a Julia Expr.\n\nsubexpressions are represented by a ExpressionIndex object.\nparameters are represented by a ParameterIndex object.\nvariables are represennted by an MOI.VariableIndex object.\n\n\n\n\n\nconvert_to_expr(\n evaluator::Evaluator,\n expr::Expression;\n moi_output_format::Bool,\n)\n\nConvert the Expression expr into a Julia Expr.\n\nIf moi_output_format = true:\n\nsubexpressions will be converted to Julia Expr and substituted into the output expression.\nthe current value of each parameter will be interpolated into the expression\nvariables will be represented in the form x[MOI.VariableIndex(i)]\n\nIf moi_output_format = false:\n\nsubexpressions will be represented by a ExpressionIndex object.\nparameters will be represented by a ParameterIndex object.\nvariables will be represennted by an MOI.VariableIndex object.\n\nwarning: Warning\nTo use moi_output_format = true, you must have first called MOI.initialize with :ExprGraph as a requested feature.\n\n\n\n\n\n","category":"function"},{"location":"moi/submodules/Nonlinear/reference/#MathOptInterface.Nonlinear.ordinal_index","page":"API Reference","title":"MathOptInterface.Nonlinear.ordinal_index","text":"ordinal_index(evaluator::Evaluator, c::ConstraintIndex)::Int\n\nReturn the 1-indexed value of the constraint index c in evaluator.\n\nExamples\n\nmodel = Model()\nx = MOI.VariableIndex(1)\nc1 = add_constraint(model, :($x^2), MOI.LessThan(1.0))\nc2 = add_constraint(model, :($x^2), MOI.LessThan(1.0))\nevaluator = Evaluator(model)\nMOI.initialize(evaluator, Symbol[])\nordinal_index(evaluator, c2) # Returns 2\ndelete(model, c1)\nevaluator = Evaluator(model)\nMOI.initialize(evaluator, Symbol[])\nordinal_index(model, c2) # Returns 1\n\n\n\n\n\n","category":"function"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"EditURL = \"https://github.com/lanl-ansi/Juniper.jl/blob/62532341586d447f19c7360715333ba62a42bea9/README.md\"","category":"page"},{"location":"packages/Juniper/#Juniper","page":"lanl-ansi/Juniper.jl","title":"Juniper","text":"","category":"section"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"(Image: CI) (Image: codecov) (Image: Documentation)","category":"page"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"Juniper (Jump Nonlinear Integer Program solver) is a solver for mixed-integer nonlinear programs. ","category":"page"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"It is a heuristic which is not guaranteed to find the global optimum. If you need the global optimum, check out Alpine.","category":"page"},{"location":"packages/Juniper/#Installation","page":"lanl-ansi/Juniper.jl","title":"Installation","text":"","category":"section"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"Install Juniper using the Julia package manager:","category":"page"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"import Pkg\nPkg.add(\"JuMP\")","category":"page"},{"location":"packages/Juniper/#Use-with-JuMP","page":"lanl-ansi/Juniper.jl","title":"Use with JuMP","text":"","category":"section"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"Use Juniper with JuMP as follows:","category":"page"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"using JuMP, Juniper, Ipopt\nipopt = optimizer_with_attributes(Ipopt.Optimizer, \"print_level\"=>0)\noptimizer = optimizer_with_attributes(Juniper.Optimizer, \"nl_solver\"=>ipopt)\nmodel = Model(optimizer)\nv = [10, 20, 12, 23, 42]\nw = [12, 45, 12, 22, 21]\n@variable(model, x[1:5], Bin)\n@objective(model, Max, v' * x)\n@constraint(model, sum(w[i]*x[i]^2 for i in 1:5) <= 45)\noptimize!(model)\nprintln(termination_status(model))\nprintln(objective_value(model))\nprintln(value.(x))","category":"page"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"The nl_solver is used by Juniper to solve continuous nonlinear sub-problems while Juniper searches for acceptable assignments to the discrete variables. A common choice is Ipopt, but any optimizer that supports the continuous relaxation of the model may be used.","category":"page"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"To solve problems with more complex nonlinear functions, use the @NLconstraint and @NLobjective JuMP macros.","category":"page"},{"location":"packages/Juniper/#Documentation","page":"lanl-ansi/Juniper.jl","title":"Documentation","text":"","category":"section"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"The online documentation is available at https://lanl-ansi.github.io/Juniper.jl/stable/.","category":"page"},{"location":"packages/Juniper/#Feasibility-pump","page":"lanl-ansi/Juniper.jl","title":"Feasibility pump","text":"","category":"section"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"If Juniper has difficulty finding feasible solutions on your model, try adding a solver that supports integer variables (for example, HiGHS) to run a feasibility pump:","category":"page"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"using JuMP, Juniper, Ipopt, HiGHS\nipopt = optimizer_with_attributes(Ipopt.Optimizer, \"print_level\" => 0)\nhighs = optimizer_with_attributes(HiGHS.Optimizer, \"output_flag\" => false)\nmodel = Model(\n optimizer_with_attributes(\n Juniper.Optimizer,\n \"nl_solver\" => ipopt,\n \"mip_solver\" => highs,\n ),\n)","category":"page"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"The feasibility pump is used at the start of Juniper to find a feasible solution before the branch and bound part starts. For some classes of problems this can be a highly effective pre-processor.","category":"page"},{"location":"packages/Juniper/#Citing-Juniper","page":"lanl-ansi/Juniper.jl","title":"Citing Juniper","text":"","category":"section"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"If you find Juniper useful in your work, we kindly request that you cite the following paper or technical report:","category":"page"},{"location":"packages/Juniper/","page":"lanl-ansi/Juniper.jl","title":"lanl-ansi/Juniper.jl","text":"@inproceedings{juniper,\n Author = {Ole Kröger and Carleton Coffrin and Hassan Hijazi and Harsha Nagarajan},\n Title = {Juniper: An Open-Source Nonlinear Branch-and-Bound Solver in Julia},\n booktitle=\"Integration of Constraint Programming, Artificial Intelligence, and Operations Research\",\n pages=\"377--386\",\n year=\"2018\",\n publisher=\"Springer International Publishing\",\n isbn=\"978-3-319-93031-2\"\n}","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"EditURL = \"diet.jl\"","category":"page"},{"location":"tutorials/linear/diet/#The-diet-problem","page":"The diet problem","title":"The diet problem","text":"","category":"section"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"The purpose of this tutorial is to demonstrate how to incorporate DataFrames into a JuMP model. As an example, we use classic Stigler diet problem.","category":"page"},{"location":"tutorials/linear/diet/#Required-packages","page":"The diet problem","title":"Required packages","text":"","category":"section"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"This tutorial requires the following packages:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"using JuMP\nimport CSV\nimport DataFrames\nimport HiGHS\nimport Test #hide","category":"page"},{"location":"tutorials/linear/diet/#Formulation","page":"The diet problem","title":"Formulation","text":"","category":"section"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"We wish to cook a nutritionally balanced meal by choosing the quantity of each food f to eat from a set of foods F in our kitchen.","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"Each food f has a cost, c_f, as well as a macro-nutrient profile a_mf for each macro-nutrient m in M.","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"Because we care about a nutritionally balanced meal, we set some minimum and maximum limits for each nutrient, which we denote l_m and u_m respectively.","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"Furthermore, because we are optimizers, we seek the minimum cost solution.","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"With a little effort, we can formulate our dinner problem as the following linear program:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"beginaligned\nmin sumlimits_f in F c_f x_f \ntextst l_m le sumlimits_f in F a_mf x_f le u_m forall m in M \n x_f ge 0 forall f in F\nendaligned","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"In the rest of this tutorial, we will create and solve this problem in JuMP, and learn what we should cook for dinner.","category":"page"},{"location":"tutorials/linear/diet/#Data","page":"The diet problem","title":"Data","text":"","category":"section"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"First, we need some data for the problem. For this tutorial, we'll write CSV files to a temporary directory from Julia. If you have existing files, you could change the filenames to point to them instead.","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"dir = mktempdir()","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"The first file is a list of foods with their macro-nutrient profile:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"food_csv_filename = joinpath(dir, \"diet_foods.csv\")\nopen(food_csv_filename, \"w\") do io\n write(\n io,\n \"\"\"\n name,cost,calories,protein,fat,sodium\n hamburger,2.49,410,24,26,730\n chicken,2.89,420,32,10,1190\n hot dog,1.50,560,20,32,1800\n fries,1.89,380,4,19,270\n macaroni,2.09,320,12,10,930\n pizza,1.99,320,15,12,820\n salad,2.49,320,31,12,1230\n milk,0.89,100,8,2.5,125\n ice cream,1.59,330,8,10,180\n \"\"\",\n )\n return\nend\nfoods = CSV.read(food_csv_filename, DataFrames.DataFrame)","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"Here, F is foods.name and c_f is foods.cost. (We're also playing a bit loose the term \"macro-nutrient\" by including calories and sodium.)","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"We also need our minimum and maximum limits:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"nutrient_csv_filename = joinpath(dir, \"diet_nutrient.csv\")\nopen(nutrient_csv_filename, \"w\") do io\n write(\n io,\n \"\"\"\n nutrient,min,max\n calories,1800,2200\n protein,91,\n fat,0,65\n sodium,0,1779\n \"\"\",\n )\n return\nend\nlimits = CSV.read(nutrient_csv_filename, DataFrames.DataFrame)","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"Protein is missing data for the maximum. Let's fix that using coalesce:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"limits.max = coalesce.(limits.max, Inf)\nlimits","category":"page"},{"location":"tutorials/linear/diet/#JuMP-formulation","page":"The diet problem","title":"JuMP formulation","text":"","category":"section"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"Now we're ready to convert our mathematical formulation into a JuMP model.","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"First, create a new JuMP model. Since we have a linear program, we'll use HiGHS as our optimizer:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"model = Model(HiGHS.Optimizer)\nset_silent(model)","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"Next, we create a set of decision variables x, with one element for each row in the DataFrame, and each x has a lower bound of 0:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"@variable(model, x[foods.name] >= 0)","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"To simplify things later on, we store the vector as a new column x in the DataFrame foods. Since x is a DenseAxisArray, we first need to convert it to an Array:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"foods.x = Array(x)","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"Our objective is to minimize the total cost of purchasing food:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"@objective(model, Min, sum(foods.cost .* foods.x));\nnothing #hide","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"For the next component, we need to add a constraint that our total intake of each component is within the limits contained in the limits DataFrame:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"@constraint(\n model,\n [row in eachrow(limits)],\n row.min <= sum(foods[!, row.nutrient] .* foods.x) <= row.max,\n);\nnothing #hide","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"What does our model look like?","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"print(model)","category":"page"},{"location":"tutorials/linear/diet/#Solution","page":"The diet problem","title":"Solution","text":"","category":"section"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"Let's optimize and take a look at the solution:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"optimize!(model)\nTest.@test primal_status(model) == FEASIBLE_POINT #hide\nTest.@test objective_value(model) ≈ 11.8288 atol = 1e-4 #hide\nsolution_summary(model)","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"We found an optimal solution. Let's see what the optimal solution is:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"for row in eachrow(foods)\n println(row.name, \" = \", value(row.x))\nend","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"That's a lot of milk and ice cream, and sadly, we only get 0.6 of a hamburger.","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"We can also use the function Containers.rowtable to easily convert the result into a DataFrame:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"table = Containers.rowtable(value, x; header = [:food, :quantity])\nsolution = DataFrames.DataFrame(table)","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"This makes it easy to perform analyses our solution:","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"filter!(row -> row.quantity > 0.0, solution)","category":"page"},{"location":"tutorials/linear/diet/#Problem-modification","page":"The diet problem","title":"Problem modification","text":"","category":"section"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"JuMP makes it easy to take an existing model and modify it by adding extra constraints. Let's see what happens if we add a constraint that we can buy at most 6 units of milk or ice cream combined.","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"dairy_foods = [\"milk\", \"ice cream\"]\nis_dairy = map(name -> name in dairy_foods, foods.name)\ndairy_constraint = @constraint(model, sum(foods[is_dairy, :x]) <= 6)\noptimize!(model)\nTest.@test termination_status(model) == INFEASIBLE #hide\nTest.@test primal_status(model) == NO_SOLUTION #hide\nsolution_summary(model)","category":"page"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"There exists no feasible solution to our problem. Looks like we're stuck eating ice cream for dinner.","category":"page"},{"location":"tutorials/linear/diet/#Next-steps","page":"The diet problem","title":"Next steps","text":"","category":"section"},{"location":"tutorials/linear/diet/","page":"The diet problem","title":"The diet problem","text":"You can delete a constraint using delete(model, dairy_constraint). Can you add a different constraint to provide a diet with less dairy?\nSome food items (like hamburgers) are discrete. You can use set_integer to force a variable to take integer values. What happens to the solution if you do?","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"EditURL = \"n-queens.jl\"","category":"page"},{"location":"tutorials/linear/n-queens/#N-Queens","page":"N-Queens","title":"N-Queens","text":"","category":"section"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"This tutorial was originally contributed by Matthew Helm and Mathieu Tanneau.","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"The N-Queens problem involves placing N queens on an N x N chessboard such that none of the queens attacks another. In chess, a queen can move vertically, horizontally, and diagonally so there cannot be more than one queen on any given row, column, or diagonal.","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"(Image: Four Queens)","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"Note that none of the queens above are able to attack any other as a result of their careful placement.","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"using JuMP\nimport HiGHS\nimport LinearAlgebra","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"N-Queens","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"N = 8\n\nmodel = Model(HiGHS.Optimizer)\nset_silent(model)","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"Next, let's create an N x N chessboard of binary values. 0 will represent an empty space on the board and 1 will represent a space occupied by one of our queens:","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"@variable(model, x[1:N, 1:N], Bin);\nnothing #hide","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"Now we can add our constraints:","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"There must be exactly one queen in a given row/column","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"for i in 1:N\n @constraint(model, sum(x[i, :]) == 1)\n @constraint(model, sum(x[:, i]) == 1)\nend","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"There can only be one queen on any given diagonal","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"for i in -(N - 1):(N-1)\n @constraint(model, sum(LinearAlgebra.diag(x, i)) <= 1)\n @constraint(model, sum(LinearAlgebra.diag(reverse(x; dims = 1), i)) <= 1)\nend","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"We are ready to put our model to work and see if it is able to find a feasible solution:","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"optimize!(model)","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"We can now review the solution that our model found:","category":"page"},{"location":"tutorials/linear/n-queens/","page":"N-Queens","title":"N-Queens","text":"solution = round.(Int, value.(x))","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"EditURL = \"https://github.com/jump-dev/SDPNAL.jl/blob/00a3fa19f4e1235587948113b0b681da17f4dab5/README.md\"","category":"page"},{"location":"packages/SDPNAL/#SDPNAL.jl","page":"jump-dev/SDPNAL.jl","title":"SDPNAL.jl","text":"","category":"section"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"SDPNAL.jl is wrapper for the SDPNALplus solver.","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"The wrapper has two components:","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"an exported sdpnalplus function that is a thin wrapper on top of the sdpnalplus MATLAB function\nan interface to MathOptInterface","category":"page"},{"location":"packages/SDPNAL/#Affiliation","page":"jump-dev/SDPNAL.jl","title":"Affiliation","text":"","category":"section"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"This wrapper is maintained by the JuMP community and is not an official wrapper of SDPNALplus.","category":"page"},{"location":"packages/SDPNAL/#License","page":"jump-dev/SDPNAL.jl","title":"License","text":"","category":"section"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"SDPNAL.jl is licensed under the MIT License.","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"The underlying solver, SDPNALplus is licensed under the Creative Commons Attribution-ShareAlike 4.0 International Public License.","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"In addition, SDPNAL requires an installation of MATLAB, which is a closed-source commercial product for which you must obtain a license.","category":"page"},{"location":"packages/SDPNAL/#Use-with-JuMP","page":"jump-dev/SDPNAL.jl","title":"Use with JuMP","text":"","category":"section"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"To use SDPNAL with JuMP, do:","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"using JuMP, SDPNAL\nmodel = Model(SDPNAL.Optimizer)\nset_attribute(model, \"printlevel\", 0)","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"Note that, contrary to implementation of other solver-independent interfaces, using SDPNAL from JuMP or MOI fully exploits the particular structures of the SDPNAL interface and does not create superfluous slack variables and equality constraints as discussed in the SDPNAL guide:","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"A new interface is necessary to facilitate the modeling of an SDP problem for SDPNAL+ because of latter’s flexibility to directly accept inequality constraints of the form “l ≤ B(X) ≤ u”, and bound constraints of the form “L ≤ X ≤ U”. The flexibility can significantly simplify the generation of the data in the SDPNAL+ format as compared to what need to be done in CVX or YALMIP to reformulate them as equality constraints through introducing extra variables. In addition, the final number of equality constraints present in the data input to SDPNAL+ can also be substantially fewer than those present in CVX or YALMIP. It is important to note here that the number of equality constraints present in the generated problem data can greatly affect the computational efficiency of the solvers, especially for interior-point based solvers.","category":"page"},{"location":"packages/SDPNAL/#Installation","page":"jump-dev/SDPNAL.jl","title":"Installation","text":"","category":"section"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"First, make sure that you satisfy the requirements of the MATLAB.jl Julia package, and that the SDPNALplus software is installed in your MATLAB™ installation.","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"Then, install SDPNAL.jl using Pkg.add:","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"import Pkg\nPkg.add(\"SDPNAL\")","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"There is a startup.m file at the root of the SDPNAL folder. This adds all subdirectories recursively when MATLAB starts. However, the interface directory contains a .git subdirectory which contains a very large number of files. Because of this, MATLAB crashes if SDPNAL is in its path because the startup.m requests MATLAB to try to parse all the files in the .git folder. To resolve this problem, delete the startup.m file and .git folder, and add the subdirectories manually your toolbox/local/pathdef.m file as follows:","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"function p = pathdef\n\n% (...)\n\np = [...\n%%% BEGIN ENTRIES %%%\n'/path/to/SDPNALv1.0:', ...\n'/path/to/SDPNALv1.0/interface:', ...\n'/path/to/SDPNALv1.0/mexfun:', ...\n'/path/to/SDPNALv1.0/solver:', ...\n'/path/to/SDPNALv1.0/solver_main_default:', ...\n'/path/to/SDPNALv1.0/util:', ...\n% (...)","category":"page"},{"location":"packages/SDPNAL/","page":"jump-dev/SDPNAL.jl","title":"jump-dev/SDPNAL.jl","text":"If you have SDPT3 in addition to SDPNAL in the MATLAB path (that is, the toolbox/local/pathdef.m file) then you might have issues because both solvers define a validate function, and this might make SDPNAL call SDPT3's validate function instead of SDPT3's validate function.","category":"page"},{"location":"should_i_use/#Should-you-use-JuMP?","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"","category":"section"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"JuMP is an algebraic modeling language for mathematical optimization written in the Julia language.","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"This page explains when you should consider using JuMP, and importantly, when you should not use JuMP.","category":"page"},{"location":"should_i_use/#When-should-you-use-JuMP?","page":"Should you use JuMP?","title":"When should you use JuMP?","text":"","category":"section"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"You should use JuMP if you have a constrained optimization problem for which you can formulate using the language of mathematical programming, that is:","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"a set of decision variables\na scalar- or vector-valued objective function\na set of constraints.","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"Key reasons to use JuMP include:","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"User friendliness\nJuMP has syntax that mimics natural mathematical expressions. (See the section on algebraic modeling languages.)\nSolver independence\nJuMP uses a generic solver-independent interface provided by the MathOptInterface package, making it easy to change between a number of open-source and commercial optimization software packages (\"solvers\"). The Supported solvers section contains a table of the currently supported solvers.\nEase of embedding\nJuMP itself is written purely in Julia. Solvers are the only binary dependencies.\nJuMP provides automatic installation of many open-source solvers. This is different to modeling languages in Python which require you to download and install a solver yourself.\nBecause it is embedded in a general-purpose programming language, JuMP makes it easy to solve optimization problems as part of a larger workflow, for example, inside a simulation, behind a web server, or as a subproblem in a decomposition algorithm. As a trade-off, JuMP's syntax is constrained by the syntax and functionality available in Julia.\nJuMP is MPL licensed, meaning that it can be embedded in commercial software that complies with the terms of the license.\nSpeed\nBenchmarking has shown that JuMP can create problems at similar speeds to special-purpose modeling languages such as AMPL.\nJuMP communicates with most solvers in memory, avoiding the need to write intermediary files.\nAccess to advanced algorithmic techniques\nJuMP supports efficient in-memory re-solves of linear programs, which previously required using solver-specific or low-level C++ libraries.\nJuMP provides access to solver-independent and solver-dependent Callbacks.","category":"page"},{"location":"should_i_use/#When-should-you-not-use-JuMP?","page":"Should you use JuMP?","title":"When should you not use JuMP?","text":"","category":"section"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"JuMP supports a broad range of optimization classes. However, there are still some that it doesn't support, or that are better supported by other software packages.","category":"page"},{"location":"should_i_use/#You-want-to-optimize-a-complicated-Julia-function","page":"Should you use JuMP?","title":"You want to optimize a complicated Julia function","text":"","category":"section"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"Packages in Julia compose well. It's common for people to pick two unrelated packages and use them in conjunction to create novel behavior. JuMP isn't one of those packages.","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"If you want to optimize an ordinary differential equation from DifferentialEquations.jl or tune a neural network from Flux.jl, consider using other packages such as:","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"Optim.jl\nOptimization.jl\nNLPModels.jl\nNonconvex.jl","category":"page"},{"location":"should_i_use/#Black-box,-derivative-free,-or-unconstrained-optimization","page":"Should you use JuMP?","title":"Black-box, derivative free, or unconstrained optimization","text":"","category":"section"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"JuMP does support nonlinear programs with constraints and objectives containing user-defined operators. However, the functions must be automatically differentiable, or need to provide explicit derivatives. (See User-defined operators for more information.)","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"If your function is a black-box that is non-differentiable (for example, it is the output of a simulation written in C++), JuMP is not the right tool for the job. This also applies if you want to use a derivative free method.","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"Even if your problem is differentiable, if it is unconstrained there is limited benefit (and downsides in the form of more overhead) to using JuMP over tools which are only concerned with function minimization.","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"Alternatives to consider are:","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"Optim.jl\nOptimization.jl\nNLopt.jl","category":"page"},{"location":"should_i_use/#Optimal-control-problems","page":"Should you use JuMP?","title":"Optimal control problems","text":"","category":"section"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"JuMP supports formulating optimal control problems as large nonlinear programs (see, for example, Optimal control for a Space Shuttle reentry trajectory). However, the nonlinear interface has a number of limitations (for example, the need to write out the dynamics in algebraic form) that mean JuMP might not be the right tool for the job.","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"Alternatives to consider are:","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"CasADi [MATLAB/Python], CasADi.jl\nInfiniteOpt.jl\npyomo.DAE [Python]","category":"page"},{"location":"should_i_use/#Disciplined-convex-programming","page":"Should you use JuMP?","title":"Disciplined convex programming","text":"","category":"section"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"JuMP does not support disciplined convex programming (DCP).","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"Alternatives to consider are:","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"Convex.jl\nCVXPY [Python]\nYALMIP [MATLAB]","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"note: Note\nConvex.jl is also built on MathOptInterface, and shares the same set of underlying solvers. However, you input problems differently, and Convex.jl checks that the problem is DCP.","category":"page"},{"location":"should_i_use/#Stochastic-programming","page":"Should you use JuMP?","title":"Stochastic programming","text":"","category":"section"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"JuMP requires deterministic input data.","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"If you have stochastic input data, consider using a JuMP extension such as:","category":"page"},{"location":"should_i_use/","page":"Should you use JuMP?","title":"Should you use JuMP?","text":"InfiniteOpt.jl\nStochasticPrograms.jl\nSDDP.jl","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"EditURL = \"design_patterns_for_larger_models.jl\"","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Design-patterns-for-larger-models","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"JuMP makes it easy to build and solve optimization models. However, once you start to construct larger models, and especially ones that interact with external data sources or have customizable sets of variables and constraints based on client choices, you may find that your scripts become unwieldy. This tutorial demonstrates a variety of ways in which you can structure larger JuMP models to improve their readability and maintainability.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"tip: Tip\nThis tutorial is more advanced than the other \"Getting started\" tutorials. It's in the \"Getting started\" section to give you an early preview of how JuMP makes it easy to structure larger models. However, if you are new to JuMP you may want to briefly skim the tutorial, and come back to it once you have written a few JuMP models.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Overview","page":"Design patterns for larger models","title":"Overview","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"This tutorial uses explanation-by-example. We're going to start with a simple knapsack model, and then expand it to add various features and structure.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#A-simple-script","page":"Design patterns for larger models","title":"A simple script","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Your first prototype of a JuMP model is probably a script that uses a small set of hard-coded data.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"using JuMP, HiGHS\nprofit = [5, 3, 2, 7, 4]\nweight = [2, 8, 4, 2, 5]\ncapacity = 10\nN = 5\nmodel = Model(HiGHS.Optimizer)\n@variable(model, x[1:N], Bin)\n@objective(model, Max, sum(profit[i] * x[i] for i in 1:N))\n@constraint(model, sum(weight[i] * x[i] for i in 1:N) <= capacity)\noptimize!(model)\nvalue.(x)","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"The benefits of this approach are:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"it is quick to code\nit is quick to make changes.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"The downsides include:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"all variables are global (read Performance tips)\nit is easy to introduce errors, for example, having profit and weight be vectors of different lengths, or not match N\nthe solution, x[i], is hard to interpret without knowing the order in which we provided the data.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Wrap-the-model-in-a-function","page":"Design patterns for larger models","title":"Wrap the model in a function","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"A good next step is to wrap your model in a function. This is useful for a few reasons:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"it removes global variables\nit encapsulates the JuMP model and forces you to clarify your inputs and outputs\nwe can add some error checking.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"function solve_knapsack_1(profit::Vector, weight::Vector, capacity::Real)\n if length(profit) != length(weight)\n throw(DimensionMismatch(\"profit and weight are different sizes\"))\n end\n N = length(weight)\n model = Model(HiGHS.Optimizer)\n @variable(model, x[1:N], Bin)\n @objective(model, Max, sum(profit[i] * x[i] for i in 1:N))\n @constraint(model, sum(weight[i] * x[i] for i in 1:N) <= capacity)\n optimize!(model)\n return value.(x)\nend\n\nsolve_knapsack_1([5, 3, 2, 7, 4], [2, 8, 4, 2, 5], 10)","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Create-better-data-structures","page":"Design patterns for larger models","title":"Create better data structures","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Although we can check for errors like mis-matched vector lengths, if you start to develop models with a lot of data, keeping track of vectors and lengths and indices is fragile and a common source of bugs. A good solution is to use Julia's type system to create an abstraction over your data.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"For example, we can create a struct that represents a single object, with a constructor that lets us validate assumptions on the input data:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"struct KnapsackObject\n profit::Float64\n weight::Float64\n function KnapsackObject(profit::Float64, weight::Float64)\n if weight < 0\n throw(DomainError(\"Weight of object cannot be negative\"))\n end\n return new(profit, weight)\n end\nend","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"as well as a struct that holds a dictionary of objects and the knapsack's capacity:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"struct KnapsackData\n objects::Dict{String,KnapsackObject}\n capacity::Float64\nend","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Here's what our data might look like now:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"objects = Dict(\n \"apple\" => KnapsackObject(5.0, 2.0),\n \"banana\" => KnapsackObject(3.0, 8.0),\n \"cherry\" => KnapsackObject(2.0, 4.0),\n \"date\" => KnapsackObject(7.0, 2.0),\n \"eggplant\" => KnapsackObject(4.0, 5.0),\n)\ndata = KnapsackData(objects, 10.0)","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"If you want, you can add custom printing to make it easier to visualize:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"function Base.show(io::IO, data::KnapsackData)\n println(io, \"A knapsack with capacity $(data.capacity) and possible items:\")\n for (k, v) in data.objects\n println(\n io,\n \" $(rpad(k, 8)) : profit = $(v.profit), weight = $(v.weight)\",\n )\n end\n return\nend\n\ndata","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Then, we can re-write our solve_knapsack function to take our KnapsackData as input:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"function solve_knapsack_2(data::KnapsackData)\n model = Model(HiGHS.Optimizer)\n @variable(model, x[keys(data.objects)], Bin)\n @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects))\n @constraint(\n model,\n sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity,\n )\n optimize!(model)\n return value.(x)\nend\n\nsolve_knapsack_2(data)","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Read-in-data-from-files","page":"Design patterns for larger models","title":"Read in data from files","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Having a data structure is a good step. But it is still annoying that we have to hard-code the data into Julia. A good next step is to separate the data into an external file format; JSON is a common choice.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"json_data = \"\"\"\n{\n \"objects\": {\n \"apple\": {\"profit\": 5.0, \"weight\": 2.0},\n \"banana\": {\"profit\": 3.0, \"weight\": 8.0},\n \"cherry\": {\"profit\": 2.0, \"weight\": 4.0},\n \"date\": {\"profit\": 7.0, \"weight\": 2.0},\n \"eggplant\": {\"profit\": 4.0, \"weight\": 5.0}\n },\n \"capacity\": 10.0\n}\n\"\"\"\ntemp_dir = mktempdir()\nknapsack_json_filename = joinpath(temp_dir, \"knapsack.json\")\n# Instead of writing a new file here you could replace `knapsack_json_filename`\n# with the path to a local file.\nwrite(knapsack_json_filename, json_data);\nnothing #hide","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Now let's write a function that reads this file and builds a KnapsackData object:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"import JSON\n\nfunction read_data(filename)\n d = JSON.parsefile(filename)\n return KnapsackData(\n Dict(\n k => KnapsackObject(v[\"profit\"], v[\"weight\"]) for\n (k, v) in d[\"objects\"]\n ),\n d[\"capacity\"],\n )\nend\n\ndata = read_data(knapsack_json_filename)","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Add-options-via-if-else","page":"Design patterns for larger models","title":"Add options via if-else","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"At this point, we have data in a file format which we can load and solve a single problem. For many users, this might be sufficient. However, at some point you may be asked to add features like \"but what if we want to take more than one of a particular item?\"","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"If this is the first time that you've been asked to add a feature, adding options via if-else statements is a good approach. For example, we might write:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"function solve_knapsack_3(data::KnapsackData; binary_knapsack::Bool)\n model = Model(HiGHS.Optimizer)\n if binary_knapsack\n @variable(model, x[keys(data.objects)], Bin)\n else\n @variable(model, x[keys(data.objects)] >= 0, Int)\n end\n @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects))\n @constraint(\n model,\n sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity,\n )\n optimize!(model)\n return value.(x)\nend","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Now we can solve the binary knapsack:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"solve_knapsack_3(data; binary_knapsack = true)","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"And an integer knapsack where we can take more than one copy of each item:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"solve_knapsack_3(data; binary_knapsack = false)","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Add-configuration-options-via-dispatch","page":"Design patterns for larger models","title":"Add configuration options via dispatch","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"If you get repeated requests to add different options, you'll quickly find yourself in a mess of different flags and if-else statements. It's hard to write, hard to read, and hard to ensure you haven't introduced any bugs. A good solution is to use Julia's type dispatch to control the configuration of the model. The easiest way to explain this is by example.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"First, start by defining a new abstract type, as well as new subtypes for each of our options. These types are going to control the configuration of the knapsack model.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"abstract type AbstractConfiguration end\n\nstruct BinaryKnapsackConfig <: AbstractConfiguration end\n\nstruct IntegerKnapsackConfig <: AbstractConfiguration end","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Then, we rewrite our solve_knapsack function to take a config argument, and we introduce an add_knapsack_variables function to abstract the creation of our variables.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"function solve_knapsack_4(data::KnapsackData, config::AbstractConfiguration)\n model = Model(HiGHS.Optimizer)\n x = add_knapsack_variables(model, data, config)\n @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects))\n @constraint(\n model,\n sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity,\n )\n optimize!(model)\n return value.(x)\nend","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"For the binary knapsack problem, add_knapsack_variables looks like this:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"function add_knapsack_variables(\n model::Model,\n data::KnapsackData,\n ::BinaryKnapsackConfig,\n)\n return @variable(model, x[keys(data.objects)], Bin)\nend","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"For the integer knapsack problem, add_knapsack_variables looks like this:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"function add_knapsack_variables(\n model::Model,\n data::KnapsackData,\n ::IntegerKnapsackConfig,\n)\n return @variable(model, x[keys(data.objects)] >= 0, Int)\nend","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Now we can solve the binary knapsack:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"solve_knapsack_4(data, BinaryKnapsackConfig())","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"and the integer knapsack problem:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"solve_knapsack_4(data, IntegerKnapsackConfig())","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"The main benefit of the dispatch approach is that you can quickly add new options without needing to modify the existing code. For example:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"struct UpperBoundedKnapsackConfig <: AbstractConfiguration\n limit::Int\nend\n\nfunction add_knapsack_variables(\n model::Model,\n data::KnapsackData,\n config::UpperBoundedKnapsackConfig,\n)\n return @variable(model, 0 <= x[keys(data.objects)] <= config.limit, Int)\nend\n\nsolve_knapsack_4(data, UpperBoundedKnapsackConfig(3))","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Generalize-constraints-and-objectives","page":"Design patterns for larger models","title":"Generalize constraints and objectives","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"It's easy to extend the dispatch approach to constraints and objectives as well. The key points to notice in the next two functions are that:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"we can access registered variables via model[:x]\nwe can define generic functions which accept any AbstractConfiguration as a configuration argument. That means we can implement a single method and have it apply to multiple configuration types.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"function add_knapsack_constraints(\n model::Model,\n data::KnapsackData,\n ::AbstractConfiguration,\n)\n x = model[:x]\n @constraint(\n model,\n capacity_constraint,\n sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity,\n )\n return\nend\n\nfunction add_knapsack_objective(\n model::Model,\n data::KnapsackData,\n ::AbstractConfiguration,\n)\n x = model[:x]\n @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects))\n return\nend\n\nfunction solve_knapsack_5(data::KnapsackData, config::AbstractConfiguration)\n model = Model(HiGHS.Optimizer)\n add_knapsack_variables(model, data, config)\n add_knapsack_constraints(model, data, config)\n add_knapsack_objective(model, data, config)\n optimize!(model)\n return value.(model[:x])\nend\n\nsolve_knapsack_5(data, BinaryKnapsackConfig())","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Remove-solver-dependence,-add-error-checks","page":"Design patterns for larger models","title":"Remove solver dependence, add error checks","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Compared to where we started, our knapsack model is now significantly different. We've wrapped it in a function, defined some data types, and introduced configuration options to control the variables and constraints that get added. There are a few other steps we can do to further improve things:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"remove the dependence on HiGHS\nadd checks that we found an optimal solution\nadd a helper function to avoid the need to explicitly construct the data.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"function solve_knapsack_6(\n optimizer,\n data::KnapsackData,\n config::AbstractConfiguration,\n)\n model = Model(optimizer)\n add_knapsack_variables(model, data, config)\n add_knapsack_constraints(model, data, config)\n add_knapsack_objective(model, data, config)\n optimize!(model)\n if termination_status(model) != OPTIMAL\n @warn(\"Model not solved to optimality\")\n return nothing\n end\n return value.(model[:x])\nend\n\nfunction solve_knapsack_6(\n optimizer,\n data::String,\n config::AbstractConfiguration,\n)\n return solve_knapsack_6(optimizer, read_data(data), config)\nend\n\nsolution = solve_knapsack_6(\n HiGHS.Optimizer,\n knapsack_json_filename,\n BinaryKnapsackConfig(),\n)","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Create-a-module","page":"Design patterns for larger models","title":"Create a module","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Now we're ready to expose our model to the wider world. That might be as part of a larger Julia project that we're contributing to, or as a stand-alone script that we can run on-demand. In either case, it's good practice to wrap everything in a module. This further encapsulates our code into a single namespace, and we can add documentation in the form of docstrings.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Some good rules to follow when creating a module are:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"use import in a module instead of using to make it clear which functions are from which packages\nuse _ to start function and type names that are considered private\nadd docstrings to all public variables and functions.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"module KnapsackModel\n\nimport JuMP\nimport JSON\n\nstruct _KnapsackObject\n profit::Float64\n weight::Float64\n function _KnapsackObject(profit::Float64, weight::Float64)\n if weight < 0\n throw(DomainError(\"Weight of object cannot be negative\"))\n end\n return new(profit, weight)\n end\nend\n\nstruct _KnapsackData\n objects::Dict{String,_KnapsackObject}\n capacity::Float64\nend\n\nfunction _read_data(filename)\n d = JSON.parsefile(filename)\n return _KnapsackData(\n Dict(\n k => _KnapsackObject(v[\"profit\"], v[\"weight\"]) for\n (k, v) in d[\"objects\"]\n ),\n d[\"capacity\"],\n )\nend\n\nabstract type _AbstractConfiguration end\n\n\"\"\"\n BinaryKnapsackConfig()\n\nCreate a binary knapsack problem where each object can be taken 0 or 1 times.\n\"\"\"\nstruct BinaryKnapsackConfig <: _AbstractConfiguration end\n\n\"\"\"\n IntegerKnapsackConfig()\n\nCreate an integer knapsack problem where each object can be taken any number of\ntimes.\n\"\"\"\nstruct IntegerKnapsackConfig <: _AbstractConfiguration end\n\nfunction _add_knapsack_variables(\n model::JuMP.Model,\n data::_KnapsackData,\n ::BinaryKnapsackConfig,\n)\n return JuMP.@variable(model, x[keys(data.objects)], Bin)\nend\n\nfunction _add_knapsack_variables(\n model::JuMP.Model,\n data::_KnapsackData,\n ::IntegerKnapsackConfig,\n)\n return JuMP.@variable(model, x[keys(data.objects)] >= 0, Int)\nend\n\nfunction _add_knapsack_constraints(\n model::JuMP.Model,\n data::_KnapsackData,\n ::_AbstractConfiguration,\n)\n x = model[:x]\n JuMP.@constraint(\n model,\n capacity_constraint,\n sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity,\n )\n return\nend\n\nfunction _add_knapsack_objective(\n model::JuMP.Model,\n data::_KnapsackData,\n ::_AbstractConfiguration,\n)\n x = model[:x]\n JuMP.@objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects))\n return\nend\n\nfunction _solve_knapsack(\n optimizer,\n data::_KnapsackData,\n config::_AbstractConfiguration,\n)\n model = JuMP.Model(optimizer)\n _add_knapsack_variables(model, data, config)\n _add_knapsack_constraints(model, data, config)\n _add_knapsack_objective(model, data, config)\n JuMP.optimize!(model)\n if JuMP.termination_status(model) != JuMP.OPTIMAL\n @warn(\"Model not solved to optimality\")\n return nothing\n end\n return JuMP.value.(model[:x])\nend\n\n\"\"\"\n solve_knapsack(\n optimizer,\n knapsack_json_filename::String,\n config::_AbstractConfiguration,\n )\n\nSolve the knapsack problem and return the optimal primal solution\n\n# Arguments\n\n * `optimizer` : an object that can be passed to `JuMP.Model` to construct a new\n JuMP model.\n * `knapsack_json_filename` : the filename of a JSON file containing the data for the\n problem.\n * `config` : an object to control the type of knapsack model constructed.\n Valid options are:\n * `BinaryKnapsackConfig()`\n * `IntegerKnapsackConfig()`\n\n# Returns\n\n * If an optimal solution exists: a `JuMP.DenseAxisArray` that maps the `String`\n name of each object to the number of objects to pack into the knapsack.\n * Otherwise, `nothing`, indicating that the problem does not have an optimal\n solution.\n\n# Examples\n\n```julia\nsolution = solve_knapsack(\n HiGHS.Optimizer,\n \"path/to/data.json\",\n BinaryKnapsackConfig(),\n)\n```\n\n```julia\nsolution = solve_knapsack(\n MOI.OptimizerWithAttributes(HiGHS.Optimizer, \"output_flag\" => false),\n \"path/to/data.json\",\n IntegerKnapsackConfig(),\n)\n```\n\"\"\"\nfunction solve_knapsack(\n optimizer,\n knapsack_json_filename::String,\n config::_AbstractConfiguration,\n)\n data = _read_data(knapsack_json_filename)\n return _solve_knapsack(optimizer, data, config)\nend\n\nend","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Finally, you can call your model:","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"import .KnapsackModel\n\nKnapsackModel.solve_knapsack(\n HiGHS.Optimizer,\n knapsack_json_filename,\n KnapsackModel.BinaryKnapsackConfig(),\n)","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"note: Note\nThe . in .KnapsackModel denotes that it is a submodule and not a separate package that we installed with Pkg.add. If you put the KnapsackModel in a separate file, load it with:include(\"path/to/KnapsackModel.jl\")\nimport .KnapsackModel","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Add-tests","page":"Design patterns for larger models","title":"Add tests","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"As a final step, you should add tests for your model. This often means testing on a small problem for which you can work out the optimal solution by hand. The Julia standard library Test has good unit-testing functionality.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"import .KnapsackModel\nusing Test\n\n@testset \"KnapsackModel\" begin\n @testset \"feasible_binary_knapsack\" begin\n x = KnapsackModel.solve_knapsack(\n HiGHS.Optimizer,\n knapsack_json_filename,\n KnapsackModel.BinaryKnapsackConfig(),\n )\n @test isapprox(x[\"apple\"], 1, atol = 1e-5)\n @test isapprox(x[\"banana\"], 0, atol = 1e-5)\n @test isapprox(x[\"cherry\"], 0, atol = 1e-5)\n @test isapprox(x[\"date\"], 1, atol = 1e-5)\n @test isapprox(x[\"eggplant\"], 1, atol = 1e-5)\n end\n @testset \"feasible_integer_knapsack\" begin\n x = KnapsackModel.solve_knapsack(\n HiGHS.Optimizer,\n knapsack_json_filename,\n KnapsackModel.IntegerKnapsackConfig(),\n )\n @test isapprox(x[\"apple\"], 0, atol = 1e-5)\n @test isapprox(x[\"banana\"], 0, atol = 1e-5)\n @test isapprox(x[\"cherry\"], 0, atol = 1e-5)\n @test isapprox(x[\"date\"], 5, atol = 1e-5)\n @test isapprox(x[\"eggplant\"], 0, atol = 1e-5)\n end\n @testset \"infeasible_binary_knapsack\" begin\n dir = mktempdir()\n infeasible_filename = joinpath(dir, \"infeasible.json\")\n write(\n infeasible_filename,\n \"\"\"{\n \"objects\": {\n \"apple\": {\"profit\": 5.0, \"weight\": 2.0},\n \"banana\": {\"profit\": 3.0, \"weight\": 8.0},\n \"cherry\": {\"profit\": 2.0, \"weight\": 4.0},\n \"date\": {\"profit\": 7.0, \"weight\": 2.0},\n \"eggplant\": {\"profit\": 4.0, \"weight\": 5.0}\n },\n \"capacity\": -10.0\n }\"\"\",\n )\n x = KnapsackModel.solve_knapsack(\n HiGHS.Optimizer,\n infeasible_filename,\n KnapsackModel.BinaryKnapsackConfig(),\n )\n @test x === nothing\n end\nend","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"tip: Tip\nPlace these tests in a separate file test_knapsack_model.jl so that you can run the tests by adding include(\"test_knapsack_model.jl\") to any file where needed.","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/#Next-steps","page":"Design patterns for larger models","title":"Next steps","text":"","category":"section"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"We've only briefly scratched the surface of ways to create and structure large JuMP models, so consider this tutorial a starting point, rather than a comprehensive list of all the possible ways to structure JuMP models. If you are embarking on a large project that uses JuMP, a good next step is to look at ways people have written large JuMP projects \"in the wild.\"","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"Here are some good examples (all co-incidentally related to energy):","category":"page"},{"location":"tutorials/getting_started/design_patterns_for_larger_models/","page":"Design patterns for larger models","title":"Design patterns for larger models","text":"AnyMOD.jl\nJuMP-dev 2021 talk\nsource code\nPowerModels.jl\nJuMP-dev 2021 talk\nsource code\nPowerSimulations.jl\nJuliaCon 2021 talk\nsource code\nUnitCommitment.jl\nJuMP-dev 2021 talk\nsource code","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"EditURL = \"multi.jl\"","category":"page"},{"location":"tutorials/linear/multi/#The-multi-commodity-flow-problem","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"","category":"section"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"This tutorial was originally contributed by Louis Luangkesorn.","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"This tutorial is a JuMP implementation of the multi-commodity transportation model described in AMPL: A Modeling Language for Mathematical Programming, by R. Fourer, D.M. Gay and B.W. Kernighan.","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"The purpose of this tutorial is to demonstrate creating a JuMP model from an SQLite database.","category":"page"},{"location":"tutorials/linear/multi/#Required-packages","page":"The multi-commodity flow problem","title":"Required packages","text":"","category":"section"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"This tutorial uses the following packages","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"using JuMP\nimport DataFrames\nimport HiGHS\nimport SQLite\nimport Tables\n\nconst DBInterface = SQLite.DBInterface","category":"page"},{"location":"tutorials/linear/multi/#Formulation","page":"The multi-commodity flow problem","title":"Formulation","text":"","category":"section"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"The multi-commondity flow problem is a simple extension of The transportation problem to multiple types of products. Briefly, we start with the formulation of the transportation problem:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"beginaligned\nmin sum_i in O j in D c_ij x_ij \nst sum_j in D x_i j le s_i forall i in O \n sum_i in O x_i j = d_j forall j in D \n x_i j ge 0 forall i in O j in D\nendaligned","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"but introduce a set of products P, resulting in:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"beginaligned\nmin sum_i in O j in D k in P c_ijk x_ijk \nst sum_j in D x_i j k le s_ik forall i in O k in P \n sum_i in O x_i j k = d_jk forall j in D k in P \n x_i jk ge 0 forall i in O j in D k in P \n sum_k in P x_i j k le u_ij forall i in O j in D\nendaligned","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"Note that the last constraint is new; it says that there is a maximum quantity of goods (of any type) that can be transported from origin i to destination j.","category":"page"},{"location":"tutorials/linear/multi/#Data","page":"The multi-commodity flow problem","title":"Data","text":"","category":"section"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"For the purpose of this tutorial, the JuMP repository contains an example database called multi.sqlite.","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"filename = joinpath(@__DIR__, \"multi.sqlite\");\nnothing #hide","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"To run locally, download multi.sqlite and update filename appropriately.","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"Load the database using SQLite.DB:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"db = SQLite.DB(filename)","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"A quick way to see the schema of the database is via SQLite.tables:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"SQLite.tables(db)","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"We interact with the database by executing queries, and then piping the results to an appropriate table. One example is a DataFrame:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"DBInterface.execute(db, \"SELECT * FROM locations\") |> DataFrames.DataFrame","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"But other table types are supported, such as Tables.rowtable:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"DBInterface.execute(db, \"SELECT * FROM locations\") |> Tables.rowtable","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"A rowtable is a Vector of NamedTuples.","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"You can construct more complicated SQL queries:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"origins =\n DBInterface.execute(\n db,\n \"SELECT location FROM locations WHERE type = \\\"origin\\\"\",\n ) |> Tables.rowtable","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"But for our purpose, we just want the list of strings:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"origins = map(y -> y.location, origins)","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"We can compose these two operations to get a list of destinations:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"destinations =\n DBInterface.execute(\n db,\n \"SELECT location FROM locations WHERE type = \\\"destination\\\"\",\n ) |>\n Tables.rowtable |>\n x -> map(y -> y.location, x)","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"And a list of products from our products table:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"products =\n DBInterface.execute(db, \"SELECT product FROM products\") |>\n Tables.rowtable |>\n x -> map(y -> y.product, x)","category":"page"},{"location":"tutorials/linear/multi/#JuMP-formulation","page":"The multi-commodity flow problem","title":"JuMP formulation","text":"","category":"section"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"We start by creating a model and our decision variables:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"model = Model(HiGHS.Optimizer)\nset_silent(model)\n@variable(model, x[origins, destinations, products] >= 0)","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"One approach when working with databases is to extract all of the data into a Julia datastructure. For example, let's pull the cost table into a DataFrame and then construct our objective by iterating over the rows of the DataFrame:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"cost = DBInterface.execute(db, \"SELECT * FROM cost\") |> DataFrames.DataFrame\n@objective(\n model,\n Max,\n sum(r.cost * x[r.origin, r.destination, r.product] for r in eachrow(cost)),\n);\nnothing #hide","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"If we don't want to use a DataFrame, we can use a Tables.rowtable instead:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"supply = DBInterface.execute(db, \"SELECT * FROM supply\") |> Tables.rowtable\nfor r in supply\n @constraint(model, sum(x[r.origin, :, r.product]) <= r.supply)\nend","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"Another approach is to execute the query, and then to iterate through the rows of the query using Tables.rows:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"demand = DBInterface.execute(db, \"SELECT * FROM demand\")\nfor r in Tables.rows(demand)\n @constraint(model, sum(x[:, r.destination, r.product]) == r.demand)\nend","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"warning: Warning\nIterating through the rows of a query result works by incrementing a cursor inside the database. As a consequence, you cannot call Tables.rows twice on the same query result.","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"The SQLite queries can be arbitrarily complex. For example, here's a query which builds every possible origin-destination pair:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"od_pairs = DBInterface.execute(\n db,\n \"\"\"\n SELECT a.location as 'origin',\n b.location as 'destination'\n FROM locations a\n INNER JOIN locations b\n ON a.type = 'origin' AND b.type = 'destination'\n \"\"\",\n)","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"With a constraint that we cannot send more than 625 units between each pair:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"for r in Tables.rows(od_pairs)\n @constraint(model, sum(x[r.origin, r.destination, :]) <= 625)\nend","category":"page"},{"location":"tutorials/linear/multi/#Solution","page":"The multi-commodity flow problem","title":"Solution","text":"","category":"section"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"Finally, we can optimize the model:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"optimize!(model)\nsolution_summary(model)","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"and print the solution:","category":"page"},{"location":"tutorials/linear/multi/","page":"The multi-commodity flow problem","title":"The multi-commodity flow problem","text":"begin\n println(\" \", join(products, ' '))\n for o in origins, d in destinations\n v = lpad.([round(Int, value(x[o, d, p])) for p in products], 5)\n println(o, \" \", d, \" \", join(replace.(v, \" 0\" => \" . \"), \" \"))\n end\nend","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"EditURL = \"finance.jl\"","category":"page"},{"location":"tutorials/linear/finance/#Financial-modeling-problems","page":"Financial modeling problems","title":"Financial modeling problems","text":"","category":"section"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"This tutorial was originally contributed by Arpit Bhatia.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Optimization models play an increasingly important role in financial decisions. Many computational finance problems can be solved efficiently using modern optimization techniques.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"In this tutorial we will discuss two such examples taken from the book Optimization Methods in Finance.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"This tutorial uses the following packages","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"using JuMP\nimport HiGHS","category":"page"},{"location":"tutorials/linear/finance/#Short-term-financing","page":"Financial modeling problems","title":"Short-term financing","text":"","category":"section"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Corporations routinely face the problem of financing short term cash commitments such as the following:","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Month Jan Feb Mar Apr May Jun\nNet Cash Flow -150 -100 200 -200 50 300","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Net cash flow requirements are given in thousands of dollars. The company has the following sources of funds:","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"A line of credit of up to $100K at an interest rate of 1% per month,\nIn any one of the first three months, it can issue 90-day commercial paper bearing a total interest of 2% for the 3-month period,\nExcess funds can be invested at an interest rate of 0.3% per month.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Our task is to find out the most economical way to use these 3 sources such that we end up with the most amount of money at the end of June.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"We model this problem in the following manner:","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"We will use the following decision variables:","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"the amount u_i drawn from the line of credit in month i\nthe amount v_i of commercial paper issued in month i\nthe excess funds w_i in month i","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Here we have three types of constraints:","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"for every month, cash inflow = cash outflow for each month\nupper bounds on u_i\nnonnegativity of the decision variables u_i, v_i and w_i.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Our objective will be to simply maximize the company's wealth in June, which say we represent with the variable m.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"financing = Model(HiGHS.Optimizer)\n\n@variables(financing, begin\n 0 <= u[1:5] <= 100\n 0 <= v[1:3]\n 0 <= w[1:5]\n m\nend)\n\n@objective(financing, Max, m)\n\n@constraints(\n financing,\n begin\n u[1] + v[1] - w[1] == 150 # January\n u[2] + v[2] - w[2] - 1.01u[1] + 1.003w[1] == 100 # February\n u[3] + v[3] - w[3] - 1.01u[2] + 1.003w[2] == -200 # March\n u[4] - w[4] - 1.02v[1] - 1.01u[3] + 1.003w[3] == 200 # April\n u[5] - w[5] - 1.02v[2] - 1.01u[4] + 1.003w[4] == -50 # May\n -m - 1.02v[3] - 1.01u[5] + 1.003w[5] == -300 # June\n end\n)\n\noptimize!(financing)\n\nobjective_value(financing)","category":"page"},{"location":"tutorials/linear/finance/#Combinatorial-auctions","page":"Financial modeling problems","title":"Combinatorial auctions","text":"","category":"section"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"In many auctions, the value that a bidder has for a set of items may not be the sum of the values that he has for individual items.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Examples are equity trading, electricity markets, pollution right auctions and auctions for airport landing slots.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"To take this into account, combinatorial auctions allow the bidders to submit bids on combinations of items.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Let M=12 ldots m be the set of items that the auctioneer has to sell. A bid is a pair B_j=left(S_j p_jright) where S_j subseteq M is a nonempty set of items and p_j is the price offer for this set.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"Suppose that the auctioneer has received n bids B_1 B_2 ldots B_n The goal of this problem is to help an auctioneer determine the winners in order to maximize his revenue.","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"We model this problem by taking a decision variable y_j for every bid. We add a constraint that each item i is sold at most once. This gives us the following model:","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"beginaligned\nmax sum_i=1^n p_j y_j \ntext st sum_j i in S_j y_j leq 1 forall i=12 ldots m \n y_j in01 forall j in12 ldots n\nendaligned","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"bid_values = [6 3 12 12 8 16]\nbid_items = [[1], [2], [3 4], [1 3], [2 4], [1 3 4]]\n\nauction = Model(HiGHS.Optimizer)\n@variable(auction, y[1:6], Bin)\n@objective(auction, Max, sum(y' .* bid_values))\nfor i in 1:6\n @constraint(auction, sum(y[j] for j in 1:6 if i in bid_items[j]) <= 1)\nend\n\noptimize!(auction)\n\nobjective_value(auction)","category":"page"},{"location":"tutorials/linear/finance/","page":"Financial modeling problems","title":"Financial modeling problems","text":"value.(y)","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"EditURL = \"https://github.com/GAMS-dev/GAMS.jl/blob/c5dee9f929e9d2f4433ae09fa92b8d872c9c43e0/README.md\"","category":"page"},{"location":"packages/GAMS/#GAMS.jl","page":"GAMS-dev/GAMS.jl","title":"GAMS.jl","text":"","category":"section"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"GAMS.jl provides a MathOptInterface Optimizer to solve JuMP models using GAMS.","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"GAMS comes with dozens of supported solvers. Among them are: ALPHAECP, ANTIGONE, BARON, CBC, CONOPT, CPLEX, DICOPT, GUROBI, IPOPT, KNITRO, LINDO, LINDOGLOBAL, MINOS, MOSEK, NLPEC, PATH, QUADMINOS, SBB, SHOT, SCIP, SNOPT, SOPLEX, XPRESS. Find a complete list here.","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"GAMS.jl supports the following JuMP features:","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"linear, quadratic and nonlinear (convex and non-convex) objective and constraints\ncontinuous, binary, integer, semi-continuous and semi-integer variables\nSOS1 and SOS2 sets\ncomplementarity constraints","category":"page"},{"location":"packages/GAMS/#Installation","page":"GAMS-dev/GAMS.jl","title":"Installation","text":"","category":"section"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"Download GAMS and obtain a GAMS license. Please note that GAMS also offers a free community license.\n(optional) Add the GAMS system directory to the PATH variable in order to find GAMS automatically.\nInstall GAMS.jl using the Julia package manager:\nusing Pkg\nPkg.add(\"GAMS\")","category":"page"},{"location":"packages/GAMS/#Usage","page":"GAMS-dev/GAMS.jl","title":"Usage","text":"","category":"section"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"Using GAMS as optimizer for your JuMP model:","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"using GAMS, JuMP\nmodel = Model(GAMS.Optimizer)","category":"page"},{"location":"packages/GAMS/#GAMS-System","page":"GAMS-dev/GAMS.jl","title":"GAMS System","text":"","category":"section"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"If the GAMS system directory has been added to the PATH variable (you can check this with print(ENV[\"PATH\"])), GAMS.jl will find it automatically. Otherwise, or if you like to switch between systems, the system directory can be specified by (one of the following):","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"set_optimizer_attribute(model, \"SysDir\", \"\")\nset_optimizer_attribute(model, GAMS.SysDir(), \"\")","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"Analogously, you can specify a working directory with \"WorkDir\" or GAMS.WorkDir(). If no working directory has been set, GAMS.jl will create a temporary one.","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"If you want to use the same GAMS workspace (same system and working directory) for multiple models, you can create a GAMSWorkspace first with either of the following","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"ws = GAMS.GAMSWorkspace()\nws = GAMS.GAMSWorkspace(\"\")\nws = GAMS.GAMSWorkspace(\"\", \"\")","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"and then pass it to your models:","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"model = Model(() -> GAMS.Optimizer(ws))","category":"page"},{"location":"packages/GAMS/#GAMS-Options","page":"GAMS-dev/GAMS.jl","title":"GAMS Options","text":"","category":"section"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"GAMS command line options can be specified by","category":"page"},{"location":"packages/GAMS/","page":"GAMS-dev/GAMS.jl","title":"GAMS-dev/GAMS.jl","text":"set_optimizer_attribute(model, \"