diff --git a/docs/make.jl b/docs/make.jl index 3d102f1b69e..52f06ba2c23 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -215,7 +215,7 @@ function sort_by_api_fn((key, type)) deprecated_methods = [ :add_nonlinear_constraint, :add_nonlinear_expression, - :add_nnonlinear_parameter, + :add_nonlinear_parameter, :all_nonlinear_constraints, :get_optimizer_attribute, :nonlinear_constraint_string, @@ -226,7 +226,9 @@ function sort_by_api_fn((key, type)) :register, :set_nonlinear_dual_start_value, :set_nonlinear_objective, + :set_normalized_coefficients, :set_optimizer_attribute, + :set_optimizer_attributes, :set_value, :NonlinearConstraintIndex, :NonlinearConstraintRef, diff --git a/src/JuMP.jl b/src/JuMP.jl index 895b5618f57..5d0764f4dcc 100644 --- a/src/JuMP.jl +++ b/src/JuMP.jl @@ -50,6 +50,17 @@ include("shapes.jl") ModelMode An enum to describe the state of the CachingOptimizer inside a JuMP model. + +See also: [`mode`](@ref). + +## Values + +Possible values are: + + * [`AUTOMATIC`]: `moi_backend` field holds a CachingOptimizer in AUTOMATIC mode. + * [`MANUAL`]: `moi_backend` field holds a CachingOptimizer in MANUAL mode. + * [`DIRECT`]: `moi_backend` field holds an AbstractOptimizer. No extra copy of + the model is stored. The `moi_backend` must support `add_constraint` etc. """ @enum(ModelMode, AUTOMATIC, MANUAL, DIRECT) @@ -78,6 +89,13 @@ abstract type AbstractModel end Return the return type of [`value`](@ref) for variables of that model. It defaults to `Float64` if it is not implemented. + +## Example + +```jldoctest +julia> value_type(GenericModel{BigFloat}) +BigFloat +``` """ value_type(::Type{<:AbstractModel}) = Float64 @@ -559,8 +577,16 @@ end """ mode(model::GenericModel) -Return the [`ModelMode`](@ref) ([`DIRECT`](@ref), [`AUTOMATIC`](@ref), or -[`MANUAL`](@ref)) of `model`. +Return the [`ModelMode`](@ref) of `model`. + +## Example + +```jldoctest +julia> model = Model(); + +julia> mode(model) +AUTOMATIC::ModelMode = 0 +``` """ function mode(model::GenericModel) # The type of `backend(model)` is not type-stable, so we use a function @@ -606,6 +632,22 @@ 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. + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> bridge_constraints(model) +true + +julia> model = Model(Ipopt.Optimizer; add_bridges = false); + +julia> bridge_constraints(model) +false +``` """ function bridge_constraints(model::GenericModel) # The type of `backend(model)` is not type-stable, so we use a function @@ -835,6 +877,32 @@ Empty the model, that is, remove all variables, constraints and model attributes but not optimizer attributes. Always return the argument. Note: removes extensions data. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> isempty(model) +false + +julia> empty!(model) +A JuMP Model +Feasibility problem with: +Variables: 0 +Model mode: AUTOMATIC +CachingOptimizer state: NO_OPTIMIZER +Solver name: No optimizer attached. + +julia> print(model) +Feasibility +Subject to + +julia> isempty(model) +true +``` """ function Base.empty!(model::GenericModel)::GenericModel # The method changes the Model object to, basically, the state it was when @@ -858,9 +926,23 @@ end """ 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. +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. + +## Example + +```jldoctest +julia> model = Model(); + +julia> isempty(model) +true + +julia> @variable(model, x[1:2]); + +julia> isempty(model) +false +``` """ function Base.isempty(model::GenericModel) return MOI.is_empty(model.moi_backend) && @@ -877,11 +959,25 @@ end 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`. +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`. + +See also: [`unregister`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> object_dictionary(model) +Dict{Symbol, Any} with 1 entry: + :x => VariableRef[x[1], x[2]] +``` """ object_dictionary(model::GenericModel) = model.obj_dict @@ -1074,6 +1170,17 @@ end owner_model(s::AbstractJuMPScalar) Return the model owning the scalar `s`. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> owner_model(x) === model +true +``` """ function owner_model end diff --git a/src/aff_expr.jl b/src/aff_expr.jl index 5c4182b6fce..e0edcdf6ec1 100644 --- a/src/aff_expr.jl +++ b/src/aff_expr.jl @@ -116,6 +116,25 @@ An expression type representing an affine expression of the form: * `.constant`: the constant `c` in the expression. * `.terms`: an `OrderedDict`, with keys of `VarType` and values of `CoefType` describing the sparse vector `a`. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> expr = x[2] + 3.0 * x[1] + 4.0 +x[2] + 3 x[1] + 4 + +julia> expr.constant +4.0 + +julia> expr.terms +OrderedCollections.OrderedDict{VariableRef, Float64} with 2 entries: + x[2] => 1.0 + x[1] => 3.0 +``` """ mutable struct GenericAffExpr{CoefType,VarType} <: AbstractJuMPScalar constant::CoefType @@ -254,14 +273,46 @@ Base.:(==)(x::GenericAffExpr, y::Number) = isempty(x.terms) && x.constant == y coefficient(a::GenericAffExpr{C,V}, v::V) where {C,V} Return the coefficient associated with variable `v` in the affine expression `a`. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> expr = 2.0 * x + 1.0; + +julia> coefficient(expr, x) +2.0 +``` """ coefficient(a::GenericAffExpr{C,V}, v::V) where {C,V} = get(a.terms, v, zero(C)) + coefficient(::GenericAffExpr{C,V}, ::V, ::V) where {C,V} = zero(C) """ drop_zeros!(expr::GenericAffExpr) Remove terms in the affine expression with `0` coefficients. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> expr = x[1] + x[2]; + +julia> add_to_expression!(expr, -1.0, x[1]) +0 x[1] + x[2] + +julia> drop_zeros!(expr) + +julia> expr +x[2] +``` """ function drop_zeros!(expr::GenericAffExpr) _drop_zeros!(expr.terms) @@ -361,9 +412,22 @@ function value(var_value::Function, ex::GenericAffExpr{T,V}) where {T,V} end """ - constant(aff::GenericAffExpr{C, V})::C + constant(aff::GenericAffExpr{C,V})::C Return the constant of the affine expression. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> aff = 2.0 * x + 3.0; + +julia> constant(aff) +3.0 +``` """ constant(aff::GenericAffExpr) = aff.constant @@ -378,7 +442,7 @@ struct LinearTermIterator{GAE<:GenericAffExpr} end """ - linear_terms(aff::GenericAffExpr{C, V}) + 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. @@ -413,17 +477,43 @@ end """ 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 +Updates `expression` in-place to `expression + (*)(terms...)`. + +This is typically much more efficient than `expression += (*)(terms...)` because +it avoids the temorary allocation of the right-hand side term. + +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`. +## When to implement + 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. +when: + + 1. they can be implemented efficiently + 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. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x) +x + +julia> expr = 2 + x +x + 2 + +julia> add_to_expression!(expr, 3, x) +4 x + 2 + +julia> expr +4 x + 2 +``` """ function add_to_expression! end @@ -643,11 +733,26 @@ function MOI.ScalarAffineFunction( end """ - moi_function(x) + moi_function(x::AbstractJuMPScalar) + moi_function(x::AbstractArray{<:AbstractJuMPScalar}) Given a JuMP object `x`, return the MathOptInterface equivalent. See also: [`jump_function`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> f = 2.0 * x + 1.0 +2 x + 1 + +julia> moi_function(f) +1.0 + 2.0 MOI.VariableIndex(1) +``` """ function moi_function end @@ -678,24 +783,54 @@ end Given a JuMP object type `T`, return the MathOptInterface equivalent. See also: [`jump_function_type`](@ref). + +## Example + +```jldoctest +julia> moi_function_type(AffExpr) +MathOptInterface.ScalarAffineFunction{Float64} +``` """ function moi_function_type end """ - jump_function(x) + jump_function(model::AbstractModel, x::MOI.AbstractFunction) Given an MathOptInterface object `x`, return the JuMP equivalent. See also: [`moi_function`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> f = 2.0 * index(x) + 1.0 +1.0 + 2.0 MOI.VariableIndex(1) + +julia> jump_function(model, f) +2 x + 1 +``` """ function jump_function end """ - jump_function_type(::Type{T}) where {T} + jump_function_type(model::AbstractModel, ::Type{T}) where {T} Given an MathOptInterface object type `T`, return the JuMP equivalent. See also: [`moi_function_type`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> jump_function_type(model, MOI.ScalarAffineFunction{Float64}) +AffExpr (alias for GenericAffExpr{Float64, GenericVariableRef{Float64}}) +``` """ function jump_function_type end diff --git a/src/constraints.jl b/src/constraints.jl index 5f903c569b3..57d22236d11 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -37,12 +37,29 @@ Return the index of the constraint that corresponds to `cr` in the MOI backend. index(cr::ConstraintRef) = cr.index """ - struct ConstraintNotOwned{C <: ConstraintRef} <: Exception + struct ConstraintNotOwned{C<:ConstraintRef} <: Exception constraint_ref::C end -The constraint `constraint_ref` was used in a model different to -`owner_model(constraint_ref)`. +An error thrown when the constraint `constraint_ref` was used in a model +different to `owner_model(constraint_ref)`. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> @constraint(model, c, x >= 0) +c : x ≥ 0 + +julia> model_new = Model(); + +julia> MOI.get(model_new, MOI.ConstraintName(), c) +ERROR: ConstraintNotOwned{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.GreaterThan{Float64}}, ScalarShape}}(c : x ≥ 0) +Stacktrace: +[...] """ struct ConstraintNotOwned{C<:ConstraintRef} <: Exception constraint_ref::C @@ -74,10 +91,30 @@ Base.broadcastable(con_ref::ConstraintRef) = Ref(con_ref) 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`. +If no dual start value has been set, `dual_start_value` will return `nothing`. See also [`set_dual_start_value`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x, start = 2.0); + +julia> @constraint(model, c, [2x] in Nonnegatives()) +c : [2 x] ∈ MathOptInterface.Nonnegatives(1) + +julia> set_dual_start_value(c, [0.0]) + +julia> dual_start_value(c) +1-element Vector{Float64}: + 0.0 + +julia> set_dual_start_value(c, nothing) + +julia> dual_start_value(c) +``` """ function dual_start_value( con_ref::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex}, @@ -109,9 +146,32 @@ end 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`. +`con_ref` to `value`. + +To remove a dual start value set it to `nothing`. See also [`dual_start_value`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x, start = 2.0); + +julia> @constraint(model, c, [2x] in Nonnegatives()) +c : [2 x] ∈ MathOptInterface.Nonnegatives(1) + +julia> set_dual_start_value(c, [0.0]) + +julia> dual_start_value(c) +1-element Vector{Float64}: + 0.0 + +julia> set_dual_start_value(c, nothing) + +julia> dual_start_value(c) +``` """ function set_dual_start_value( con_ref::ConstraintRef{ @@ -132,6 +192,7 @@ function set_dual_start_value( ) return end + function set_dual_start_value( con_ref::ConstraintRef{ <:AbstractModel, @@ -164,10 +225,32 @@ end set_start_value(con_ref::ConstraintRef, value) Set the primal start value ([`MOI.ConstraintPrimalStart`](@ref)) of the -constraint `con_ref` to `value`. To remove a primal start value set it to -`nothing`. +constraint `con_ref` to `value`. + +To remove a primal start value set it to `nothing`. See also [`start_value`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x, start = 2.0); + +julia> @constraint(model, c, [2x] in Nonnegatives()) +c : [2 x] ∈ MathOptInterface.Nonnegatives(1) + +julia> set_start_value(c, [4.0]) + +julia> start_value(c) +1-element Vector{Float64}: + 4.0 + +julia> set_start_value(c, nothing) + +julia> start_value(c) +``` """ function set_start_value( con_ref::ConstraintRef{ @@ -224,10 +307,30 @@ end Return the primal start value ([`MOI.ConstraintPrimalStart`](@ref)) of the constraint `con_ref`. -Note: If no primal start value has been set, `start_value` will return -`nothing`. +If no primal start value has been set, `start_value` will return `nothing`. See also [`set_start_value`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x, start = 2.0); + +julia> @constraint(model, c, [2x] in Nonnegatives()) +c : [2 x] ∈ MathOptInterface.Nonnegatives(1) + +julia> set_start_value(c, [4.0]) + +julia> start_value(c) +1-element Vector{Float64}: + 4.0 + +julia> set_start_value(c, nothing) + +julia> start_value(c) +``` """ function start_value( con_ref::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex}, @@ -242,6 +345,20 @@ end name(con_ref::ConstraintRef) Get a constraint's name attribute. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> @constraint(model, c, [2x] in Nonnegatives()) +c : [2 x] ∈ MathOptInterface.Nonnegatives(1) + +julia> name(c) +"c" +``` """ function name( con_ref::ConstraintRef{<:AbstractModel,C}, @@ -264,6 +381,25 @@ end set_name(con_ref::ConstraintRef, s::AbstractString) Set a constraint's name attribute. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> @constraint(model, c, [2x] in Nonnegatives()) +c : [2 x] ∈ MathOptInterface.Nonnegatives(1) + +julia> set_name(c, "my_constraint") + +julia> name(c) +"my_constraint" + +julia> c +my_constraint : [2 x] ∈ MathOptInterface.Nonnegatives(1) +``` """ function set_name( con_ref::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex}, @@ -273,28 +409,25 @@ function set_name( end """ - constraint_by_name(model::AbstractModel, - name::String)::Union{ConstraintRef, Nothing} + constraint_by_name(model::AbstractModel, name::String, [F, S])::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 (that is, without `F` and `S`), the exact return type -of the constraint index cannot be inferred. - -```jldoctest objective_function; filter = r"Stacktrace:.*"s +if no constraint has this name attribute. + +Throws an error if several constraints have `name` as their name attribute. + +If `F` and `S` are provided, this method addititionally 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. + +Providing `F` and `S` is recommended if you know the type of the function and +set since its returned type can be inferred while for the method above (that is, +without `F` and `S`), the exact return type of the constraint index cannot be +inferred. + +## Example + +```jldoctest julia> model = Model(); julia> @variable(model, x) @@ -328,22 +461,25 @@ end function constraint_by_name( model::GenericModel, name::String, - F::Type{<:MOI.AbstractFunction}, - S::Type{<:MOI.AbstractSet}, -) + ::Type{F}, + ::Type{S}, +) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} index = MOI.get(backend(model), MOI.ConstraintIndex{F,S}, name) if index isa Nothing return nothing - else - return constraint_ref_with_index(model, index) end + return constraint_ref_with_index(model, index) end + function constraint_by_name( model::GenericModel, name::String, - F::Type{<:Union{ScalarType,Vector{ScalarType}}}, - S::Type, -) where {ScalarType<:AbstractJuMPScalar} + ::Type{F}, + ::Type{S}, +) where { + F<:Union{AbstractJuMPScalar,Vector{<:AbstractJuMPScalar}}, + S<:MOI.AbstractSet, +} return constraint_by_name(model, name, moi_function_type(F), S) end @@ -419,20 +555,49 @@ end 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, that is, when `isconcretetype(eltype(con_refs))`. These -may be more efficient than repeatedly calling the single constraint delete -method. +the same concrete type. These methods may be more efficient than repeatedly +calling the single constraint `delete` method. See also: [`unregister`](@ref) + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:3]); + +julia> @constraint(model, c, 2 * x .<= 1) +3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}: + c : 2 x[1] ≤ 1 + c : 2 x[2] ≤ 1 + c : 2 x[3] ≤ 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: +[...] +``` """ function delete( model::GenericModel, con_refs::Vector{<:ConstraintRef{<:AbstractModel}}, ) if any(c -> model !== c.model, con_refs) - error("A constraint reference you are trying to delete does not" * " - belong to the model.") + error( + "A constraint reference you are trying to delete does not " * + "belong to the model.", + ) end model.is_model_dirty = true MOI.delete(backend(model), index.(con_refs)) @@ -577,17 +742,95 @@ moi_set(constraint::AbstractConstraint) = constraint.set """ constraint_object(con_ref::ConstraintRef) -Return the underlying constraint data for the constraint referenced by `ref`. +Return the underlying constraint data for the constraint referenced by `con_ref`. + +## Example + +A scalar constraint: +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> @constraint(model, c, 2x <= 1) +c : 2 x ≤ 1 + +julia> object = constraint_object(c) +ScalarConstraint{AffExpr, MathOptInterface.LessThan{Float64}}(2 x, MathOptInterface.LessThan{Float64}(1.0)) + +julia> typeof(object) +ScalarConstraint{AffExpr, MathOptInterface.LessThan{Float64}} + +julia> object.func +2 x + +julia> object.set +MathOptInterface.LessThan{Float64}(1.0) +``` + +A vector constraint: +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:3]); + +julia> @constraint(model, c, x in SecondOrderCone()) +c : [x[1], x[2], x[3]] ∈ MathOptInterface.SecondOrderCone(3) + +julia> object = constraint_object(c) +VectorConstraint{VariableRef, MathOptInterface.SecondOrderCone, VectorShape}(VariableRef[x[1], x[2], x[3]], MathOptInterface.SecondOrderCone(3), VectorShape()) + +julia> typeof(object) +VectorConstraint{VariableRef, MathOptInterface.SecondOrderCone, VectorShape} + +julia> object.func +3-element Vector{VariableRef}: + x[1] + x[2] + x[3] + +julia> object.set +MathOptInterface.SecondOrderCone(3) +``` """ function constraint_object end """ 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. +The data for a scalar constraint. + See also the [documentation](@ref Constraints) on JuMP's representation of constraints for more background. + +## Fields + + * `.func`: field contains a JuMP object representing the function + * `.set`: field contains the MOI set + +## Example + +A scalar constraint: +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> @constraint(model, c, 2x <= 1) +c : 2 x ≤ 1 + +julia> object = constraint_object(c) +ScalarConstraint{AffExpr, MathOptInterface.LessThan{Float64}}(2 x, MathOptInterface.LessThan{Float64}(1.0)) + +julia> typeof(object) +ScalarConstraint{AffExpr, MathOptInterface.LessThan{Float64}} + +julia> object.func +2 x + +julia> object.set +MathOptInterface.LessThan{Float64}(1.0) +``` """ struct ScalarConstraint{ F<:Union{Number,AbstractJuMPScalar}, @@ -598,6 +841,7 @@ struct ScalarConstraint{ end reshape_set(set::MOI.AbstractScalarSet, ::ScalarShape) = set + shape(::ScalarConstraint) = ScalarShape() function constraint_object( @@ -618,12 +862,47 @@ end """ 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`](@ref) matching the form in which -the constraint was constructed (for example, by using matrices or flat vectors). +The data for a vector constraint. + See also the [documentation](@ref Constraints) on JuMP's representation of constraints. + +## Fields + + * `func`: field contains a JuMP object representing the function + * `set`: field contains the MOI set. + * `shape`: field contains an [`AbstractShape`](@ref) matching the form in which + the constraint was constructed (for example, by using matrices or flat + vectors). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:3]); + +julia> @constraint(model, c, x in SecondOrderCone()) +c : [x[1], x[2], x[3]] ∈ MathOptInterface.SecondOrderCone(3) + +julia> object = constraint_object(c) +VectorConstraint{VariableRef, MathOptInterface.SecondOrderCone, VectorShape}(VariableRef[x[1], x[2], x[3]], MathOptInterface.SecondOrderCone(3), VectorShape()) + +julia> typeof(object) +VectorConstraint{VariableRef, MathOptInterface.SecondOrderCone, VectorShape} + +julia> object.func +3-element Vector{VariableRef}: + x[1] + x[2] + x[3] + +julia> object.set +MathOptInterface.SecondOrderCone(3) + +julia> object.shape +VectorShape() +``` """ struct VectorConstraint{ F<:Union{Number,AbstractJuMPScalar}, @@ -659,7 +938,9 @@ function VectorConstraint( end reshape_set(set::MOI.AbstractVectorSet, ::VectorShape) = set + shape(con::VectorConstraint) = con.shape + function constraint_object( con_ref::ConstraintRef{ <:AbstractModel, @@ -671,6 +952,7 @@ function constraint_object( s = MOI.get(model, MOI.ConstraintSet(), con_ref)::SetType return VectorConstraint(jump_function(model, f), s, con_ref.shape) end + function check_belongs_to_model(con::VectorConstraint, model) for func in con.func check_belongs_to_model(func, model) @@ -696,9 +978,14 @@ function _moi_add_constraint( end """ - add_constraint(model::GenericModel, con::AbstractConstraint, name::String="") + add_constraint( + model::GenericModel, + con::AbstractConstraint, + name::String= "", + ) -Add a constraint `con` to `Model model` and sets its name. +This method should only be implemented by developers creating JuMP extensions. +It should never be called by users of JuMP. """ function add_constraint( model::GenericModel, @@ -1007,6 +1294,31 @@ Return `true` if the solver has a dual solution in result index `result` available to query, otherwise return `false`. See also [`dual`](@ref), [`shadow_price`](@ref), and [`result_count`](@ref). + +## Example + +```jldoctest +julia> import HiGHS + +julia> model = Model(HiGHS.Optimizer); + +julia> set_silent(model) + +julia> @variable(model, x); + +julia> @constraint(model, c, x <= 1) +c : x ≤ 1 + +julia> @objective(model, Max, 2 * x + 1); + +julia> has_duals(model) +false + +julia> optimize!(model) + +julia> has_duals(model) +true +``` """ function has_duals(model::GenericModel; result::Int = 1) return dual_status(model; result = result) != MOI.NO_SOLUTION @@ -1018,9 +1330,34 @@ end 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. +Use [`has_duals`](@ref) to check if a result exists before asking for values. See also: [`result_count`](@ref), [`shadow_price`](@ref). + +## Example + +```jldoctest +julia> import HiGHS + +julia> model = Model(HiGHS.Optimizer); + +julia> set_silent(model) + +julia> @variable(model, x); + +julia> @constraint(model, c, x <= 1) +c : x ≤ 1 + +julia> @objective(model, Max, 2 * x + 1); + +julia> optimize!(model) + +julia> has_duals(model) +true + +julia> dual(c) +-2.0 +```` """ function dual( con_ref::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex}; @@ -1046,13 +1383,22 @@ end Return the change in the objective from an infinitesimal relaxation of the constraint. -This value is computed from [`dual`](@ref) and can be queried only when +The shadow price is computed from [`dual`](@ref) 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. +(not `FEASIBILITY_SENSE`). See also [`reduced_cost`](@ref JuMP.reduced_cost). +## Comparison to `dual` + +The shadow prices differ at most in sign from the `dual` value depending on the +objective sense. The differences are summarized in the table: + +| | `Min` | `Max` | +| ----------- | ----- | ----- | +| `f(x) <= b` | `+1` | `-1` | +| `f(x) >= b` | `-1` | `+1` | + ## Notes - The function simply translates signs from `dual` and does not validate @@ -1063,6 +1409,31 @@ See also [`reduced_cost`](@ref JuMP.reduced_cost). 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. + +## Example + +```jldoctest +julia> import HiGHS + +julia> model = Model(HiGHS.Optimizer); + +julia> set_silent(model) + +julia> @variable(model, x); + +julia> @constraint(model, c, x <= 1) +c : x ≤ 1 + +julia> @objective(model, Max, 2 * x + 1); + +julia> optimize!(model) + +julia> has_duals(model) +true + +julia> shadow_price(c) +2.0 +``` """ function shadow_price( con_ref::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex}, diff --git a/src/copy.jl b/src/copy.jl index dce575827dc..ae8c9c8aa15 100644 --- a/src/copy.jl +++ b/src/copy.jl @@ -11,6 +11,9 @@ data of the new model `new_model`. A method should be added for any JuMP extension storing data in the `ext` field. +This method should only be implemented by developers creating JuMP extensions. +It should never be called by users of JuMP. + !!! 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 diff --git a/src/lp_matrix_data.jl b/src/lp_matrix_data.jl index 22f22a55bba..7736d84abbf 100644 --- a/src/lp_matrix_data.jl +++ b/src/lp_matrix_data.jl @@ -32,8 +32,8 @@ Given a JuMP model of a linear program, return an [`LPMatrixData{T}`](@ref) struct storing data for an equivalent linear program in the form: ```math \\begin{aligned} -\\min & c^\\top x + c_0\\ - & b_l \\le A x \\le b_u \\ +\\min & c^\\top x + c_0 \\\\ + & b_l \\le A x \\le b_u \\\\ & x_l \\le x \\le x_u \\end{aligned} ``` @@ -69,6 +69,53 @@ The struct returned by [`lp_matrix_data`](@ref) has the fields: The models supported by [`lp_matrix_data`](@ref) are intentionally limited to linear programs. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2] >= 0); + +julia> @constraint(model, x[1] + 2 * x[2] <= 1); + +julia> @objective(model, Max, x[2]); + +julia> data = lp_matrix_data(model); + +julia> data.A +1×2 SparseArrays.SparseMatrixCSC{Float64, Int64} with 2 stored entries: + 1.0 2.0 + +julia> data.b_lower +1-element Vector{Float64}: + -Inf + +julia> data.b_upper +1-element Vector{Float64}: + 1.0 + +julia> data.x_lower +2-element Vector{Float64}: + 0.0 + 0.0 + +julia> data.x_upper +2-element Vector{Float64}: + Inf + Inf + +julia> data.c +2-element Vector{Float64}: + 0.0 + 1.0 + +julia> data.c_offset +0.0 + +julia> data.sense +MAX_SENSE::OptimizationSense = 1 +``` """ function lp_matrix_data(model::GenericModel{T}) where {T} variables = all_variables(model) diff --git a/src/macros/@constraint.jl b/src/macros/@constraint.jl index 375a42a0ccf..87d1e47726b 100644 --- a/src/macros/@constraint.jl +++ b/src/macros/@constraint.jl @@ -31,7 +31,7 @@ The expression `expr` may be one of following forms: when `z` is `1` * `!z --> {expr}`, which defines an indicator constraint that activates - when `z` is `0` + when `z` is `0` * `z <--> {expr}`, which defines a reified constraint @@ -57,6 +57,46 @@ which are not listed here. attribute. Passing `set_string_name = false` can improve performance. Other keyword arguments may be supported by JuMP extensions. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:3]); + +julia> @variable(model, z, Bin); + +julia> @constraint(model, x in SecondOrderCone()) +[x[1], x[2], x[3]] ∈ MathOptInterface.SecondOrderCone(3) + +julia> @constraint(model, [i in 1:3], x[i] == i) +3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}: + x[1] = 1 + x[2] = 2 + x[3] = 3 + +julia> @constraint(model, x .== [1, 2, 3]) +3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}: + x[1] = 1 + x[2] = 2 + x[3] = 3 + +julia> @constraint(model, con_name, 1 <= x[1] + x[2] <= 3) +con_name : x[1] + x[2] ∈ [1, 3] + +julia> @constraint(model, con_perp[i in 1:3], x[i] - 1 ⟂ x[i]) +3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Complements}, VectorShape}}: + con_perp[1] : [x[1] - 1, x[1]] ∈ MathOptInterface.Complements(2) + con_perp[2] : [x[2] - 1, x[2]] ∈ MathOptInterface.Complements(2) + con_perp[3] : [x[3] - 1, x[3]] ∈ MathOptInterface.Complements(2) + +julia> @constraint(model, z --> {x[1] >= 0}) +z --> {x[1] ≥ 0} + +julia> @constraint(model, !z --> {2 * x[2] <= 3}) +!z --> {2 x[2] ≤ 3} +``` """ macro constraint(input_args...) error_fn = Containers.build_error_fn(:constraint, input_args, __source__) @@ -205,6 +245,17 @@ julia> @variable(model, x); julia> @build_constraint(2x >= 1) ScalarConstraint{AffExpr, MathOptInterface.GreaterThan{Float64}}(2 x, MathOptInterface.GreaterThan{Float64}(1.0)) ``` + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> @build_constraint(x .>= 0) +2-element Vector{ScalarConstraint{AffExpr, MathOptInterface.GreaterThan{Float64}}}: + ScalarConstraint{AffExpr, MathOptInterface.GreaterThan{Float64}}(x[1], MathOptInterface.GreaterThan{Float64}(-0.0)) + ScalarConstraint{AffExpr, MathOptInterface.GreaterThan{Float64}}(x[2], MathOptInterface.GreaterThan{Float64}(-0.0)) +``` """ macro build_constraint(arg) error_fn = Containers.build_error_fn(:build_constraint, (arg,), __source__) @@ -778,7 +829,12 @@ function build_constraint(error_fn::Function, f::AbstractVector, set::Zeros) return build_constraint(error_fn, f, MOI.Zeros(length(f))) end -# Generic fallback. +""" + build_constraint(error_fn::Function, func, set, args...; kwargs...) + +This method should only be implemented by developers creating JuMP extensions. +It should never be called by users of JuMP. +""" function build_constraint(error_fn::Function, func, set, args...; kwargs...) arg_str = join(args, ", ") arg_str = isempty(arg_str) ? "" : ", " * arg_str diff --git a/src/nlp.jl b/src/nlp.jl index 64f87b75a08..3d5e2f55c43 100644 --- a/src/nlp.jl +++ b/src/nlp.jl @@ -553,11 +553,6 @@ function add_nonlinear_constraint(model::Model, ex::Expr) return ConstraintRef(model, c, ScalarShape()) end -""" - is_valid(model::Model, c::NonlinearConstraintRef) - -Return `true` if `c` refers to a valid nonlinear constraint in `model`. -""" function is_valid(model::Model, c::NonlinearConstraintRef) if model !== c.model return false @@ -567,11 +562,6 @@ function is_valid(model::Model, c::NonlinearConstraintRef) return MOI.is_valid(nlp, index) end -""" - delete(model::Model, c::NonlinearConstraintRef) - -Delete the nonlinear constraint `c` from `model`. -""" function delete(model::Model, c::NonlinearConstraintRef) nlp = nonlinear_model(model; force = true)::MOI.Nonlinear.Model index = MOI.Nonlinear.ConstraintIndex(c.index.value) @@ -658,11 +648,6 @@ end ### Nonlinear dual solutions ### -""" - dual(c::NonlinearConstraintRef) - -Return the dual of the nonlinear constraint `c`. -""" function dual(c::NonlinearConstraintRef) _init_NLP(c.model) evaluator = diff --git a/src/operators.jl b/src/operators.jl index 9e18da55c64..25cd8f14c79 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -270,6 +270,9 @@ 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. + +This method should only be implemented by developers creating JuMP extensions. +It should never be called by users of JuMP. """ operator_warn(::AbstractModel) = nothing diff --git a/src/optimizer_interface.jl b/src/optimizer_interface.jl index b0b03ea94cf..f487b5bfb64 100644 --- a/src/optimizer_interface.jl +++ b/src/optimizer_interface.jl @@ -158,6 +158,24 @@ Takes precedence over any other attribute controlling verbosity and requires the solver to produce no output. See also: [`unset_silent`](@ref). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> set_silent(model) + +julia> get_attribute(model, MOI.Silent()) +true + +julia> unset_silent(model) + +julia> get_attribute(model, MOI.Silent()) +false +``` """ function set_silent(model::GenericModel) return MOI.set(model, MOI.Silent(), true) @@ -170,6 +188,24 @@ Neutralize the effect of the `set_silent` function and let the solver attributes control the verbosity. See also: [`set_silent`](@ref). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> set_silent(model) + +julia> get_attribute(model, MOI.Silent()) +true + +julia> unset_silent(model) + +julia> get_attribute(model, MOI.Silent()) +false +``` """ function unset_silent(model::GenericModel) return MOI.set(model, MOI.Silent(), false) @@ -184,6 +220,25 @@ Can be unset using [`unset_time_limit_sec`](@ref) or with `limit` set to `nothing`. See also: [`unset_time_limit_sec`](@ref), [`time_limit_sec`](@ref). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> time_limit_sec(model) + +julia> set_time_limit_sec(model, 60.0) + +julia> time_limit_sec(model) +60.0 + +julia> unset_time_limit_sec(model) + +julia> time_limit_sec(model) +``` """ function set_time_limit_sec(model::GenericModel, limit::Real) return MOI.set(model, MOI.TimeLimitSec(), convert(Float64, limit)) @@ -199,6 +254,25 @@ end Unset the time limit of the solver. See also: [`set_time_limit_sec`](@ref), [`time_limit_sec`](@ref). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> time_limit_sec(model) + +julia> set_time_limit_sec(model, 60.0) + +julia> time_limit_sec(model) +60.0 + +julia> unset_time_limit_sec(model) + +julia> time_limit_sec(model) +``` """ function unset_time_limit_sec(model::GenericModel) return MOI.set(model, MOI.TimeLimitSec(), nothing) @@ -212,6 +286,25 @@ Return the time limit (in seconds) of the `model`. Returns `nothing` if unset. See also: [`set_time_limit_sec`](@ref), [`unset_time_limit_sec`](@ref). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> time_limit_sec(model) + +julia> set_time_limit_sec(model, 60.0) + +julia> time_limit_sec(model) +60.0 + +julia> unset_time_limit_sec(model) + +julia> time_limit_sec(model) +``` """ function time_limit_sec(model::GenericModel) return MOI.get(model, MOI.TimeLimitSec()) @@ -221,7 +314,7 @@ function _try_get_solver_name(model_like) try return MOI.get(model_like, MOI.SolverName())::String catch ex - if isa(ex, ArgumentError) + if isa(ex, ArgumentError) || isa(ex, MOI.GetAttributeNotAllowed) return "SolverName() attribute not implemented by the optimizer." else rethrow(ex) @@ -232,13 +325,35 @@ end """ solver_name(model::GenericModel) -If available, returns the `SolverName` property of the underlying optimizer. +If available, returns the [`MOI.SolverName`](@ref) property of the underlying +optimizer. -Returns `"No optimizer attached"` in `AUTOMATIC` or `MANUAL` modes when no +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. + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> solver_name(model) +"Ipopt" + +julia> model = Model(); + +julia> solver_name(model) +"No optimizer attached." + +julia> model = Model(MOI.FileFormats.MPS.Model); + +julia> solver_name(model) +"SolverName() attribute not implemented by the optimizer." +``` """ function solver_name(model::GenericModel) if mode(model) != DIRECT && MOIU.state(backend(model)) == MOIU.NO_OPTIMIZER @@ -496,6 +611,17 @@ end Return a [`MOI.TerminationStatusCode`](@ref) describing why the solver stopped (that is, the [`MOI.TerminationStatus`](@ref) attribute). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> termination_status(model) +OPTIMIZE_NOT_CALLED::TerminationStatusCode = 0 +``` """ function termination_status(model::GenericModel) return MOI.get(model, MOI.TerminationStatus())::MOI.TerminationStatusCode @@ -513,6 +639,17 @@ end Return the number of results available to query after a call to [`optimize!`](@ref). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> result_count(model) +0 +``` """ function result_count(model::GenericModel)::Int if termination_status(model) == MOI.OPTIMIZE_NOT_CALLED @@ -525,7 +662,18 @@ end raw_status(model::GenericModel) Return the reason why the solver stopped in its own words (that is, the -MathOptInterface model attribute `RawStatusString`). +MathOptInterface model attribute [`MOI.RawStatusString`](@ref)). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> raw_status(model) +"optimize not called" +``` """ function raw_status(model::GenericModel) if MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED @@ -552,6 +700,17 @@ primal solution of the solver (that is, the [`MOI.PrimalStatus`](@ref) attribute associated with the result index `result`. See also: [`result_count`](@ref). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> primal_status(model; result = 2) +NO_SOLUTION::ResultStatusCode = 0 +``` """ function primal_status(model::GenericModel; result::Int = 1) return MOI.get(model, MOI.PrimalStatus(result))::MOI.ResultStatusCode @@ -565,6 +724,17 @@ dual solution of the solver (that is, the [`MOI.DualStatus`](@ref) attribute) associated with the result index `result`. See also: [`result_count`](@ref). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> dual_status(model; result = 2) +NO_SOLUTION::ResultStatusCode = 0 +``` """ function dual_status(model::GenericModel; result::Int = 1) return MOI.get(model, MOI.DualStatus(result))::MOI.ResultStatusCode @@ -598,6 +768,17 @@ If `dual`, additionally check that an optimal dual solution is available. If this function returns `false`, use [`termination_status`](@ref), [`result_count`](@ref), [`primal_status`](@ref) and [`dual_status`](@ref) to understand what solutions are available (if any). + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> is_solved_and_feasible(model) +false +``` """ function is_solved_and_feasible( model::GenericModel; @@ -630,10 +811,26 @@ end """ 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. +If available, returns the solve time in wall-clock seconds reported by the +solver (the [`MOI.SolveTimeSec`](@ref) attribute). + +Throws a `MOI.GetAttributeNotAllowed` error if the attribute is not implemented +by the solver. + +## Example + +```jldoctest; filter=r"[0-9].+" +julia> import HiGHS + +julia> model = Model(HiGHS.Optimizer); + +julia> set_silent(model) + +julia> optimize!(model) + +julia> solve_time(model) +1.0488089174032211e-5 +``` """ function solve_time(model::GenericModel) return MOI.get(model, MOI.SolveTimeSec()) @@ -642,10 +839,26 @@ end """ simplex_iterations(model::GenericModel) -Gets the cumulative number of simplex iterations during the most-recent -optimization. +If available, returns the cumulative number of simplex iterations during the +most-recent optimization (the [`MOI.SimplexIterations`](@ref) attribute). -Solvers must implement `MOI.SimplexIterations()` to use this function. +Throws a `MOI.GetAttributeNotAllowed` error if the attribute is not implemented +by the solver. + +## Example + +```jldoctest; filter=r"[0-9].+" +julia> import HiGHS + +julia> model = Model(HiGHS.Optimizer); + +julia> set_silent(model) + +julia> optimize!(model) + +julia> simplex_iterations(model) +0 +``` """ function simplex_iterations(model::GenericModel) return MOI.get(model, MOI.SimplexIterations()) @@ -654,10 +867,26 @@ end """ barrier_iterations(model::GenericModel) -Gets the cumulative number of barrier iterations during the most recent -optimization. +If available, returns the cumulative number of barrier iterations during the +most-recent optimization (the [`MOI.BarrierIterations`](@ref) attribute). + +Throws a `MOI.GetAttributeNotAllowed` error if the attribute is not implemented +by the solver. -Solvers must implement `MOI.BarrierIterations()` to use this function. +## Example + +```jldoctest; filter=r"[0-9].+" +julia> import HiGHS + +julia> model = Model(HiGHS.Optimizer); + +julia> set_silent(model) + +julia> optimize!(model) + +julia> barrier_iterations(model) +0 +``` """ function barrier_iterations(model::GenericModel) return MOI.get(model, MOI.BarrierIterations()) @@ -666,10 +895,27 @@ end """ node_count(model::GenericModel) -Gets the total number of branch-and-bound nodes explored during the most recent -optimization in a Mixed Integer Program. +If available, returns the total number of branch-and-bound nodes explored during +the most recent optimization in a Mixed Integer Program (the +[`MOI.NodeCount`](@ref) attribute). + +Throws a `MOI.GetAttributeNotAllowed` error if the attribute is not implemented +by the solver. + +## Example + +```jldoctest; filter=r"[0-9].+" +julia> import HiGHS + +julia> model = Model(HiGHS.Optimizer); + +julia> set_silent(model) + +julia> optimize!(model) -Solvers must implement `MOI.NodeCount()` to use this function. +julia> node_count(model) +0 +``` """ function node_count(model::GenericModel) return MOI.get(model, MOI.NodeCount()) @@ -687,15 +933,42 @@ end """ struct OptimizeNotCalled <: Exception end -A result attribute cannot be queried before [`optimize!`](@ref) is called. +An error thrown when a result attribute cannot be queried before +[`optimize!`](@ref) is called. + +## Example + +```jldoctest +julia> import Ipopt + +julia> model = Model(Ipopt.Optimizer); + +julia> objective_value(model) +ERROR: OptimizeNotCalled() +Stacktrace: +[...] +``` """ struct OptimizeNotCalled <: Exception end """ struct NoOptimizer <: Exception end -No optimizer is set. The optimizer can be provided to the [`Model`](@ref) -constructor or by calling [`set_optimizer`](@ref). +An error thrown when no optimizer is set and one is required. + +The optimizer can be provided to the [`Model`](@ref) constructor or by calling +[`set_optimizer`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> optimize!(model) +ERROR: NoOptimizer() +Stacktrace: +[...] +``` """ struct NoOptimizer <: Exception end diff --git a/src/print.jl b/src/print.jl index 564566d7a26..e181466a771 100644 --- a/src/print.jl +++ b/src/print.jl @@ -179,6 +179,15 @@ _plural(n) = isone(n) ? "" : "s" Return the [`MOI.Name`](@ref) attribute of `model`'s [`backend`](@ref), or a default if empty. + +## Example + +```jldoctest +julia> model = Model(); + +julia> name(model) +"A JuMP Model" +``` """ name(model::AbstractModel) = "An Abstract JuMP Model" @@ -277,6 +286,9 @@ function show_backend_summary(io::IO, model::GenericModel) catch "unknown" end + if name == "SolverName() attribute not implemented by the optimizer." + name = "unknown" + end print(io, "Solver name: ", name) return end @@ -600,6 +612,17 @@ end anonymous_name(::MIME, x::AbstractVariableRef) The name to use for an anonymous variable `x` when printing. + +## Example + +```jldoctest +julia> model = Model(); + +julia> x = @variable(model); + +julia> anonymous_name(MIME("text/plain"), x) +"_[1]" +``` """ anonymous_name(::Any, x::AbstractVariableRef) = "anon" @@ -880,7 +903,8 @@ end constraint_string( mode::MIME, ref::ConstraintRef; - in_math_mode::Bool = false) + in_math_mode::Bool = false, + ) Return a string representation of the constraint `ref`, given the `mode`. """ diff --git a/src/quad_expr.jl b/src/quad_expr.jl index 3cc582a6a65..bdada8b9150 100644 --- a/src/quad_expr.jl +++ b/src/quad_expr.jl @@ -19,6 +19,21 @@ UnorderedPair(a::T, b::T) A wrapper type used by [`GenericQuadExpr`](@ref) with fields `.a` and `.b`. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> expr = 2.0 * x[1] * x[2] +2 x[1]*x[2] + +julia> expr.terms +OrderedCollections.OrderedDict{UnorderedPair{VariableRef}, Float64} with 1 entry: + UnorderedPair{VariableRef}(x[1], x[2]) => 2.0 +``` """ struct UnorderedPair{T} a::T @@ -45,6 +60,25 @@ An expression type representing an quadratic expression of the form: expression. * `.terms`: an `OrderedDict`, with keys of `UnorderedPair{VarType}` and values of `CoefType`, describing the sparse list of terms `q`. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> expr = 2.0 * x[1]^2 + x[1] * x[2] + 3.0 * x[1] + 4.0 +2 x[1]² + x[1]*x[2] + 3 x[1] + 4 + +julia> expr.aff +3 x[1] + 4 + +julia> expr.terms +OrderedCollections.OrderedDict{UnorderedPair{VariableRef}, Float64} with 2 entries: + UnorderedPair{VariableRef}(x[1], x[1]) => 2.0 + UnorderedPair{VariableRef}(x[1], x[2]) => 1.0 +``` """ mutable struct GenericQuadExpr{CoefType,VarType} <: AbstractJuMPScalar aff::GenericAffExpr{CoefType,VarType} @@ -157,11 +191,31 @@ end Base.:(==)(x::GenericQuadExpr, y::Number) = isempty(x.terms) && x.aff == y """ - coefficient(a::GenericAffExpr{C,V}, v1::V, v2::V) where {C,V} + coefficient(a::GenericQuadExpr{C,V}, v1::V, v2::V) where {C,V} -Return the coefficient associated with the term `v1 * v2` in the quadratic expression `a`. +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)`. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> expr = 2.0 * x[1] * x[2]; + +julia> coefficient(expr, x[1], x[2]) +2.0 + +julia> coefficient(expr, x[2], x[1]) +2.0 + +julia> coefficient(expr, x[1], x[1]) +0.0 +``` """ function coefficient(q::GenericQuadExpr{C,V}, v1::V, v2::V) where {C,V} return get(q.terms, UnorderedPair(v1, v2), zero(C)) @@ -170,7 +224,21 @@ end """ coefficient(a::GenericQuadExpr{C,V}, v::V) where {C,V} -Return the coefficient associated with variable `v` in the affine component of `a`. +Return the coefficient associated with variable `v` in the affine component of +`a`. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> expr = 2.0 * x^2 + 3.0 * x; + +julia> coefficient(expr, x) +3.0 +``` """ coefficient(q::GenericQuadExpr{C,V}, v::V) where {C,V} = coefficient(q.aff, v) @@ -178,6 +246,24 @@ coefficient(q::GenericQuadExpr{C,V}, v::V) where {C,V} = coefficient(q.aff, v) drop_zeros!(expr::GenericQuadExpr) Remove terms in the quadratic expression with `0` coefficients. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> expr = x[1]^2 + x[2]^2; + +julia> add_to_expression!(expr, -1.0, x[1], x[1]) +0 x[1]² + x[2]² + +julia> drop_zeros!(expr) + +julia> expr +x[2]² +``` """ function drop_zeros!(expr::GenericQuadExpr) drop_zeros!(expr.aff) @@ -264,14 +350,27 @@ function _map_quad( end """ - constant(aff::GenericQuadExpr{C, V})::C + constant(quad::GenericQuadExpr{C,V})::C Return the constant of the quadratic expression. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> quad = 2.0 * x^2 + 3.0; + +julia> constant(quad) +3.0 +``` """ constant(quad::GenericQuadExpr) = constant(quad.aff) """ - linear_terms(quad::GenericQuadExpr{C, V}) + linear_terms(quad::GenericQuadExpr{C,V}) Provides an iterator over tuples `(coefficient::C, variable::V)` in the linear part of the quadratic expression. @@ -289,7 +388,7 @@ struct QuadTermIterator{GQE<:GenericQuadExpr} end """ - quad_terms(quad::GenericQuadExpr{C, V}) + 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. diff --git a/src/sd.jl b/src/sd.jl index 703e9053f08..6bd967c55ad 100644 --- a/src/sd.jl +++ b/src/sd.jl @@ -299,23 +299,6 @@ function _vectorize_variables(error_fn::Function, matrix::Matrix) return vectorize(matrix, SymmetricMatrixShape(n)) end -""" - build_variable(error_fn::Function, variables, ::SymmetricMatrixSpace) - -Return a `VariablesConstrainedOnCreation` of shape [`SymmetricMatrixShape`](@ref) -creating variables in `MOI.Reals`, that is, "free" variables unless they are -constrained after their creation. - -This function is used by the [`@variable`](@ref) macro as follows: -```jldoctest -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] -``` -""" function build_variable( error_fn::Function, variables::Matrix{<:AbstractVariable}, @@ -331,23 +314,6 @@ function build_variable( ) end -""" - build_variable(error_fn::Function, variables, ::SkewSymmetricMatrixSpace) - -Return a `VariablesConstrainedOnCreation` of shape [`SkewSymmetricMatrixShape`](@ref) -creating variables in `MOI.Reals`, that is, "free" variables unless they are -constrained after their creation. - -This function is used by the [`@variable`](@ref) macro as follows: -```jldoctest -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 -``` -""" function build_variable( error_fn::Function, variables::Matrix{<:AbstractVariable}, @@ -363,23 +329,6 @@ function build_variable( ) end -""" - build_variable(error_fn::Function, variables, ::HermitianMatrixSpace) - -Return a `VariablesConstrainedOnCreation` of shape [`HermitianMatrixShape`](@ref) -creating variables in `MOI.Reals`, that is, "free" variables unless they are -constrained after their creation. - -This function is used by the [`@variable`](@ref) macro as follows: -```jldoctest -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]) -``` -""" function build_variable( error_fn::Function, variables::Matrix{<:AbstractVariable}, @@ -397,22 +346,6 @@ function build_variable( ) end -""" - build_variable(error_fn::Function, variables, ::PSDCone) - -Return a `VariablesConstrainedOnCreation` of shape [`SymmetricMatrixShape`](@ref) -constraining the variables to be positive semidefinite. - -This function is used by the [`@variable`](@ref) macro as follows: -```jldoctest -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] -``` -""" function build_variable( error_fn::Function, variables::Matrix{<:AbstractVariable}, @@ -439,8 +372,8 @@ end ::PSDCone, ) where {V<:AbstractJuMPScalar,M<:AbstractMatrix{V}} -Return a `VectorConstraint` of shape [`SymmetricMatrixShape`](@ref) constraining -the matrix `Q` to be positive semidefinite. +Return a [`VectorConstraint`](@ref) of shape [`SymmetricMatrixShape`](@ref) +constraining the matrix `Q` to be positive semidefinite. This function is used by the [`@constraint`](@ref) macros as follows: ```jldoctest @@ -484,27 +417,6 @@ function build_constraint( ) end -""" - build_constraint( - error_fn::Function, - Q::AbstractMatrix{<:AbstractJuMPScalar}, - ::PSDCone, - ) - -Return a `VectorConstraint` of shape [`SquareMatrixShape`](@ref) constraining -the matrix `Q` to be symmetric and positive semidefinite. - -This function is used by the [`@constraint`](@ref) macro as follows: -```jldoctest -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() -``` -""" function build_constraint( error_fn::Function, Q::AbstractMatrix{<:AbstractJuMPScalar}, @@ -644,29 +556,6 @@ function build_variable( ) end -""" - build_constraint( - error_fn::Function, - Q::LinearAlgebra.Hermitian{V,M}, - ::HermitianPSDCone, - ) where {V<:AbstractJuMPScalar,M<:AbstractMatrix{V}} - -Return a `VectorConstraint` of shape [`HermitianMatrixShape`](@ref) constraining -the matrix `Q` to be Hermitian positive semidefinite. - -This function is used by the [`@constraint`](@ref) macros as follows: -```jldoctest -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() -``` -""" function build_constraint( ::Function, Q::LinearAlgebra.Hermitian{V,M}, diff --git a/src/sets.jl b/src/sets.jl index 7e1e0d5c838..20504baabfc 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -41,23 +41,6 @@ function build_constraint( return build_constraint(error_fn, func, moi_set(set, length(func))) end -""" - build_constraint( - error_fn::Function, - f::AbstractVector{<:AbstractJuMPScalar}, - ::Nonnegatives, - extra::Union{MOI.AbstractVectorSet,AbstractVectorSet}, - ) - -A helper method that re-writes -```julia -@constraint(model, X >= Y, extra) -``` -into -```julia -@constraint(model, X - Y in extra) -``` -""" function build_constraint( error_fn::Function, f::AbstractVector{<:AbstractJuMPScalar}, @@ -67,23 +50,6 @@ function build_constraint( return build_constraint(error_fn, f, extra) end -""" - build_constraint( - error_fn::Function, - f::AbstractVector{<:AbstractJuMPScalar}, - ::Nonpositives, - extra::Union{MOI.AbstractVectorSet,AbstractVectorSet}, - ) - -A helper method that re-writes -```julia -@constraint(model, Y <= X, extra) -``` -into -```julia -@constraint(model, X - Y in extra) -``` -""" function build_constraint( error_fn::Function, f::AbstractVector{<:AbstractJuMPScalar}, @@ -195,6 +161,23 @@ to element `x[i]`. If not provided, the `weights` vector defaults to `weights[i] = i`. This is a shortcut for the [`MOI.SOS1`](@ref) set. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:3] in SOS1([4.1, 3.2, 5.0])) +3-element Vector{VariableRef}: + x[1] + x[2] + x[3] + +julia> print(model) +Feasibility +Subject to + [x[1], x[2], x[3]] ∈ MathOptInterface.SOS1{Float64}([4.1, 3.2, 5.0]) +``` """ struct SOS1{T} <: AbstractVectorSet weights::Vector{T} @@ -230,6 +213,23 @@ to element `x[i]`. If not provided, the `weights` vector defaults to `weights[i] = i`. This is a shortcut for the [`MOI.SOS2`](@ref) set. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:3] in SOS2([4.1, 3.2, 5.0])) +3-element Vector{VariableRef}: + x[1] + x[2] + x[3] + +julia> print(model) +Feasibility +Subject to + [x[1], x[2], x[3]] ∈ MathOptInterface.SOS2{Float64}([4.1, 3.2, 5.0]) +``` """ struct SOS2{T} <: AbstractVectorSet weights::Vector{T} diff --git a/src/variables.jl b/src/variables.jl index ddd73f9a6b1..76d4a354f1f 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -381,10 +381,24 @@ end """ coefficient(v1::GenericVariableRef{T}, v2::GenericVariableRef{T}) where {T} -Return `one(T)` if `v1 == v2`, and `zero(T)` otherwise. +Return `one(T)` if `v1 == v2` and `zero(T)` otherwise. This is a fallback for other [`coefficient`](@ref) methods to simplify code in which the expression may be a single variable. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> coefficient(x[1], x[1]) +1.0 + +julia> coefficient(x[1], x[2]) +0.0 +``` """ function coefficient( v1::GenericVariableRef{T}, @@ -1731,12 +1745,38 @@ end """ start_value(v::GenericVariableRef) -Return the start value (MOI attribute `VariablePrimalStart`) of the variable -`v`. +Return the start value ([`MOI.VariablePrimalStart`](@ref)) of the variable `v`. Note: `VariablePrimalStart`s are sometimes called "MIP-starts" or "warmstarts". -See also [`set_start_value`](@ref). +See also: [`has_start_value`](@ref), [`set_start_value`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x, start = 1.5); + +julia> @variable(model, y); + +julia> has_start_value(x) +true + +julia> has_start_value(y) +false + +julia> start_value(x) +1.5 + +julia> set_start_value(y, 2.0) + +julia> has_start_value(y) +true + +julia> start_value(y) +2.0 +``` """ function start_value(v::GenericVariableRef{T})::Union{Nothing,T} where {T} return MOI.get(owner_model(v), MOI.VariablePrimalStart(), v) @@ -1745,9 +1785,36 @@ end """ has_start_value(variable::AbstractVariableRef) -Return `true` if the variable has a start value set otherwise return `false`. +Return `true` if the variable has a start value set, otherwise return `false`. + +See also: [`start_value`](@ref), [`set_start_value`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x, start = 1.5); + +julia> @variable(model, y); -See also [`set_start_value`](@ref). +julia> has_start_value(x) +true + +julia> has_start_value(y) +false + +julia> start_value(x) +1.5 + +julia> set_start_value(y, 2.0) + +julia> has_start_value(y) +true + +julia> start_value(y) +2.0 +``` """ has_start_value(v::AbstractVariableRef)::Bool = start_value(v) !== nothing @@ -1757,14 +1824,46 @@ _convert_if_something(::Type, ::Nothing) = nothing """ set_start_value(variable::GenericVariableRef, value::Union{Real,Nothing}) -Set the start value (MOI attribute `VariablePrimalStart`) of the `variable` to +Set the start value ([`MOI.VariablePrimalStart`](@ref)) of the `variable` to `value`. Pass `nothing` to unset the start value. Note: `VariablePrimalStart`s are sometimes called "MIP-starts" or "warmstarts". -See also [`start_value`](@ref). +See also: [`has_start_value`](@ref), [`start_value`](@ref). + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x, start = 1.5); + +julia> @variable(model, y); + +julia> has_start_value(x) +true + +julia> has_start_value(y) +false + +julia> start_value(x) +1.5 + +julia> set_start_value(x, nothing) + +julia> has_start_value(x) +false + +julia> set_start_value(y, 2.0) + +julia> has_start_value(y) +true + +julia> start_value(y) +2.0 +``` """ function set_start_value( variable::GenericVariableRef{T}, @@ -1809,15 +1908,41 @@ Return `true` if the solver has a primal solution in result index `result` available to query, otherwise return `false`. See also [`value`](@ref) and [`result_count`](@ref). + +## Example + +```jldoctest +julia> import HiGHS + +julia> model = Model(HiGHS.Optimizer); + +julia> set_silent(model) + +julia> @variable(model, x); + +julia> @constraint(model, c, x <= 1) +c : x ≤ 1 + +julia> @objective(model, Max, 2 * x + 1); + +julia> has_values(model) +false + +julia> optimize!(model) + +julia> has_values(model) +true +``` """ function has_values(model::GenericModel; result::Int = 1) return primal_status(model; result = result) != MOI.NO_SOLUTION end """ - add_variable(m::GenericModel, v::AbstractVariable, name::String="") + add_variable(m::GenericModel, v::AbstractVariable, name::String = "") -Add a variable `v` to `Model m` and sets its name. +This method should only be implemented by developers creating JuMP extensions. +It should never be called by users of JuMP. """ function add_variable end @@ -2223,10 +2348,35 @@ end 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). +One interpretation of the reduced cost is that it is the change in the objective +from an infinitesimal relaxation of the variable bounds. + +This method is equivalent to querying the shadow price of the active variable +bound (if one exists and is active). See also: [`shadow_price`](@ref). + +## Example + +```jldoctest +julia> import HiGHS + +julia> model = Model(HiGHS.Optimizer); + +julia> set_silent(model) + +julia> @variable(model, x <= 1); + +julia> @objective(model, Max, 2 * x + 1); + +julia> optimize!(model) + +julia> has_duals(model) +true + +julia> reduced_cost(x) +2.0 +``` """ function reduced_cost(x::GenericVariableRef{T})::T where {T} model = owner_model(x)