diff --git a/src/constraints.jl b/src/constraints.jl index 9c7533cf2b8..5e12a40a803 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -147,13 +147,44 @@ end # Abstract base type for all constraint types abstract type AbstractConstraint end +""" + jump_function(constraint::JuMP.AbstractConstraint) + +Return the function of the constraint `constraint` in the function-in-set form +as a `JuMP.AbstractJuMPScalar` or `Vector{JuMP.AbstractJuMPScalar}`. +""" +function jump_function end + +""" + moi_function(constraint::JuMP.AbstractConstraint) + +Return the function of the constraint `constraint` in the function-in-set form +as a `MathOptInterface.AbstractFunction`. +""" +function moi_function(constraint::JuMP.AbstractConstraint) + return moi_function(jump_function(constraint)) +end + +""" + 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`. +""" +function moi_set end + struct ScalarConstraint{F <: AbstractJuMPScalar, S <: MOI.AbstractScalarSet} <: AbstractConstraint func::F set::S end -moi_function_and_set(c::ScalarConstraint) = (moi_function(c.func), c.set) +jump_function(constraint::ScalarConstraint) = constraint.func +moi_set(constraint::ScalarConstraint) = constraint.set shape(::ScalarConstraint) = ScalarShape() function constraint_object(ref::ConstraintRef{Model, MOICON{FuncType, SetType}}) where {FuncType <: MOI.AbstractScalarFunction, SetType <: MOI.AbstractScalarSet} @@ -175,7 +206,8 @@ function VectorConstraint(func::Vector{<:AbstractJuMPScalar}, VectorConstraint(func, set, VectorShape()) end -moi_function_and_set(c::VectorConstraint) = (moi_function(c.func), c.set) +jump_function(constraint::VectorConstraint) = constraint.func +moi_set(constraint::VectorConstraint) = constraint.set shape(c::VectorConstraint) = c.shape function constraint_object(ref::ConstraintRef{Model, MOICON{FuncType, SetType}}) where {FuncType <: MOI.AbstractVectorFunction, SetType <: MOI.AbstractVectorSet} @@ -191,7 +223,8 @@ end Add a constraint `c` to `Model m` and sets its name. """ function add_constraint(m::Model, c::AbstractConstraint, name::String="") - f, s = moi_function_and_set(c) + f = moi_function(c) + s = moi_set(c) if !MOI.supports_constraint(m.moi_backend, typeof(f), typeof(s)) if m.moi_backend isa MOI.Bridges.LazyBridgeOptimizer bridge_message = " and there are no bridges that can reformulate it into supported constraints." diff --git a/src/print.jl b/src/print.jl index ac7b4609bd4..c2362274212 100644 --- a/src/print.jl +++ b/src/print.jl @@ -272,13 +272,31 @@ end ## Constraints #------------------------------------------------------------------------ -function Base.show(io::IO, ref::ConstraintRef{Model}) +## Notes for extensions +# For a `ConstraintRef{ModelType, IndexType}` where `ModelType` is not +# `JuMP.Model` or `IndexType` is not `MathOptInterface.ConstraintIndex`, the +# methods `JuMP.name` and `JuMP.constraint_object` should be implemented for +# printing to work. If the `AbstractConstraint` returned by `constraint_object` +# is not `JuMP.ScalarConstraint` nor `JuMP.VectorConstraint`, then either +# `JuMP.jump_function` or `JuMP.function_string` and either `JuMP.moi_set` or +# `JuMP.in_set_string` should be implemented. +function Base.show(io::IO, ref::ConstraintRef) print(io, constraint_string(REPLMode, name(ref), constraint_object(ref))) end -function Base.show(io::IO, ::MIME"text/latex", ref::ConstraintRef{Model}) +function Base.show(io::IO, ::MIME"text/latex", ref::ConstraintRef) print(io, constraint_string(IJuliaMode, name(ref), constraint_object(ref))) end +""" + function_string(print_mode::Type{<:JuMP.PrintMode}, + func::Union{JuMP.AbstractJuMPScalar, + Vector{<:JuMP.AbstractJuMPScalar}}) + +Return a `String` representing the function `func` using print mode +`print_mode`. +""" +function function_string end + function function_string(print_mode, variable::AbstractVariableRef) return var_string(print_mode, variable) end @@ -304,6 +322,17 @@ function function_string(print_mode, quad_vector::Vector{<:GenericQuadExpr}) return "[" * join(quad_string.(print_mode, quad_vector), ", ") * "]" end +""" + function_string(print_mode::{<:JuMP.PrintMode}, + constraint::JuMP.AbstractConstraint) + +Return a `String` representing the function of the constraint `constraint` +using print mode `print_mode`. +""" +function function_string(print_mode, constraint::AbstractConstraint) + return function_string(print_mode, jump_function(constraint)) +end + function in_set_string(print_mode, set::MOI.LessThan) return string(math_symbol(print_mode, :leq), " ", set.upper) end @@ -325,15 +354,34 @@ end # TODO: Convert back to JuMP types for sets like PSDCone. # TODO: Consider fancy latex names for some sets. They're currently printed as # regular text in math mode which looks a bit awkward. +""" + in_set_string(print_mode::Type{<:JuMP.PrintMode}, + set::Union{JuMP.AbstractJuMPScalar, + Vector{<:JuMP.AbstractJuMPScalar}}) + +Return a `String` representing the membership to the set `set` using print mode +`print_mode`. +""" function in_set_string(print_mode, set::MOI.AbstractSet) return string(math_symbol(print_mode, :in), " ", set) end +""" + in_set_string(print_mode::Type{<:JuMP.PrintMode}, + constraint::JuMP.AbstractConstraint) + +Return a `String` representing the membership to the set of the constraint +`constraint` using print mode `print_mode`. +""" +function in_set_string(print_mode, constraint::AbstractConstraint) + return in_set_string(print_mode, moi_set(constraint)) +end + # constraint_object is a JuMP constraint object like AffExprConstraint. # Assumes a .func and .set member. function constraint_string(print_mode, constraint_name, constraint_object) - func_str = function_string(print_mode, constraint_object.func) - in_set_str = in_set_string(print_mode, constraint_object.set) + func_str = function_string(print_mode, constraint_object) + in_set_str = in_set_string(print_mode, constraint_object) constraint_without_name = func_str * " " * in_set_str if print_mode == IJuliaMode constraint_without_name = wrap_in_inline_math_mode(constraint_without_name) diff --git a/src/sets.jl b/src/sets.jl index 134e69e43af..5c32b460466 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1,12 +1,5 @@ abstract type AbstractVectorSet end -""" - moi_set(s::AbstractVectorSet, dim::Int) - -Returns the MOI set of dimension `dim` corresponding to the JuMP set `s`. -""" -function moi_set end - # Used in `@constraint model f in s` function build_constraint(_error::Function, f::AbstractVector, s::AbstractVectorSet) diff --git a/test/print.jl b/test/print.jl index 557ba242ad9..26137801c1a 100644 --- a/test/print.jl +++ b/test/print.jl @@ -38,6 +38,43 @@ Base.:(*)(α::Float64, u::UnitNumber) = UnitNumber(α * u.α) Base.abs(u::UnitNumber) = UnitNumber(abs(u.α)) Base.isless(u::UnitNumber, v::UnitNumber) = isless(u.α, v.α) +# Used to test extensibility of JuMP printing for `JuMP.AbstractConstraint` +struct CustomConstraint{S <: JuMP.AbstractShape} <: JuMP.AbstractConstraint + function_str::String + in_set_str::String + shape::S +end +function JuMP.function_string(print_mode, + constraint::CustomConstraint) + return constraint.function_str +end +function JuMP.in_set_string(print_mode, + constraint::CustomConstraint) + return constraint.in_set_str +end +struct CustomIndex + value::Int +end +function JuMP.add_constraint(model::JuMP.Model, constraint::CustomConstraint, + name::String) + if !haskey(model.ext, :custom) + model.ext[:custom_constraints] = CustomConstraint[] + model.ext[:custom_names] = String[] + end + constraints = model.ext[:custom_constraints] + push!(constraints, constraint) + push!(model.ext[:custom_names], name) + return JuMP.ConstraintRef(model, CustomIndex(length(constraints)), + constraint.shape) +end +function JuMP.constraint_object(cref::JuMP.ConstraintRef{JuMP.Model, + CustomIndex}) + return cref.model.ext[:custom_constraints][cref.index.value] +end +function JuMP.name(cref::JuMP.ConstraintRef{JuMP.Model, CustomIndex}) + return cref.model.ext[:custom_names][cref.index.value] +end + @testset "Printing" begin @testset "expressions" begin @@ -151,6 +188,18 @@ Base.isless(u::UnitNumber, v::UnitNumber) = isless(u.α, v.α) io_test(REPLMode, constr, "(subexpression[1] - parameter[1]) - 0.0 $le 0") io_test(IJuliaMode, constr, "(subexpression_{1} - parameter_{1}) - 0.0 \\leq 0") end + + @testset "Custom constraint" begin + model = Model() + function test_constraint(function_str, in_set_str, name) + constraint = CustomConstraint(function_str, in_set_str, + JuMP.ScalarShape()) + cref = JuMP.add_constraint(model, constraint, name) + @test string(cref) == "$name : $function_str $in_set_str" + end + test_constraint("fun", "set", "name") + test_constraint("a", "b", "c") + end end function printing_test(ModelType::Type{<:JuMP.AbstractModel})