From 1d2b7530747d4b7797b66a3eb2e33f046c8a5a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 5 Nov 2018 19:12:20 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20Improve=20show=20extensibility?= =?UTF-8?q?=20for=20AbstratConstraint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constraints.jl | 24 ++++++++++++++++++++++-- src/print.jl | 44 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/constraints.jl b/src/constraints.jl index 9c7533cf2b8..c5058e85d14 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -147,13 +147,32 @@ end # Abstract base type for all constraint types abstract type AbstractConstraint end +""" + function_object(constraint::AbstractConstraint) + +Return the function of the constraint `constraint` in the function-in-set form. +""" +function function_object end + +""" + set_object(constraint::AbstractConstraint) + +Return the set of the constraint `constraint` in the function-in-set form. +""" +function set_object end + +function moi_function_and_set(c::ScalarConstraint) + return (moi_function(function_object(constraint)), set_object(constraint)) +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) +function_object(constraint::ScalarConstraint) = c.func +set_object(constraint::ScalarConstraint) = c.set shape(::ScalarConstraint) = ScalarShape() function constraint_object(ref::ConstraintRef{Model, MOICON{FuncType, SetType}}) where {FuncType <: MOI.AbstractScalarFunction, SetType <: MOI.AbstractScalarSet} @@ -175,7 +194,8 @@ function VectorConstraint(func::Vector{<:AbstractJuMPScalar}, VectorConstraint(func, set, VectorShape()) end -moi_function_and_set(c::VectorConstraint) = (moi_function(c.func), c.set) +function_object(constraint::VectorConstraint) = c.func +set_object(constraint::VectorConstraint) = c.set shape(c::VectorConstraint) = c.shape function constraint_object(ref::ConstraintRef{Model, MOICON{FuncType, SetType}}) where {FuncType <: MOI.AbstractVectorFunction, SetType <: MOI.AbstractVectorSet} diff --git a/src/print.jl b/src/print.jl index ac7b4609bd4..3b97ab2abfb 100644 --- a/src/print.jl +++ b/src/print.jl @@ -272,13 +272,23 @@ end ## Constraints #------------------------------------------------------------------------ -function Base.show(io::IO, ref::ConstraintRef{Model}) +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::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 +314,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::PrintMode, constraint::AbstractConstraint) + return function_string(print_mode, function_object(constraint)) +end + function in_set_string(print_mode, set::MOI.LessThan) return string(math_symbol(print_mode, :leq), " ", set.upper) end @@ -325,10 +346,29 @@ 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::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::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, set_object(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) From 3fe8bfd92659c539b27f62d70fba955b9af7e818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 5 Nov 2018 19:52:04 +0100 Subject: [PATCH 2/4] Add test --- src/constraints.jl | 41 +++++++++++++++++++++++++------------- src/print.jl | 4 ++-- src/sets.jl | 7 ------- test/print.jl | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 23 deletions(-) diff --git a/src/constraints.jl b/src/constraints.jl index c5058e85d14..5e12a40a803 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -148,31 +148,43 @@ end abstract type AbstractConstraint end """ - function_object(constraint::AbstractConstraint) + jump_function(constraint::JuMP.AbstractConstraint) -Return the function of the constraint `constraint` in the function-in-set form. +Return the function of the constraint `constraint` in the function-in-set form +as a `JuMP.AbstractJuMPScalar` or `Vector{JuMP.AbstractJuMPScalar}`. """ -function function_object end +function jump_function end """ - set_object(constraint::AbstractConstraint) + moi_function(constraint::JuMP.AbstractConstraint) -Return the set of the constraint `constraint` in the function-in-set form. +Return the function of the constraint `constraint` in the function-in-set form +as a `MathOptInterface.AbstractFunction`. """ -function set_object end - -function moi_function_and_set(c::ScalarConstraint) - return (moi_function(function_object(constraint)), set_object(constraint)) +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 -function_object(constraint::ScalarConstraint) = c.func -set_object(constraint::ScalarConstraint) = 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} @@ -194,8 +206,8 @@ function VectorConstraint(func::Vector{<:AbstractJuMPScalar}, VectorConstraint(func, set, VectorShape()) end -function_object(constraint::VectorConstraint) = c.func -set_object(constraint::VectorConstraint) = 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} @@ -211,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 3b97ab2abfb..eb39b0c5468 100644 --- a/src/print.jl +++ b/src/print.jl @@ -322,7 +322,7 @@ Return a `String` representing the function of the constraint `constraint` using print mode `print_mode`. """ function function_string(print_mode::PrintMode, constraint::AbstractConstraint) - return function_string(print_mode, function_object(constraint)) + return function_string(print_mode, jump_function(constraint)) end function in_set_string(print_mode, set::MOI.LessThan) @@ -366,7 +366,7 @@ 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, set_object(constraint)) + return in_set_string(print_mode, moi_set(constraint)) end # constraint_object is a JuMP constraint object like AffExprConstraint. 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..3818fa135dc 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::JuMP.PrintMode, + constraint::CustomConstraint) + return constraint.function_str +end +function JuMP.in_set_string(print_mode::JuMP.PrintMode, + 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) + @show string(cref) + end + test_constraint("fun", "set", "name") + test_constraint("a", "b", "c") + end end function printing_test(ModelType::Type{<:JuMP.AbstractModel}) From e401193dd1399831c19f037d4aa573d976375c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 5 Nov 2018 20:01:13 +0100 Subject: [PATCH 3/4] Fix tests --- src/print.jl | 14 +++++++------- test/print.jl | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/print.jl b/src/print.jl index eb39b0c5468..1c39437a9a0 100644 --- a/src/print.jl +++ b/src/print.jl @@ -280,7 +280,7 @@ function Base.show(io::IO, ::MIME"text/latex", ref::ConstraintRef) end """ - function_string(print_mode::JuMP.PrintMode, + function_string(print_mode::Type{<:JuMP.PrintMode}, func::Union{JuMP.AbstractJuMPScalar, Vector{<:JuMP.AbstractJuMPScalar}}) @@ -315,13 +315,13 @@ function function_string(print_mode, quad_vector::Vector{<:GenericQuadExpr}) end """ - function_string(print_mode::JuMP.PrintMode, + 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::PrintMode, constraint::AbstractConstraint) +function function_string(print_mode, constraint::AbstractConstraint) return function_string(print_mode, jump_function(constraint)) end @@ -347,7 +347,7 @@ end # 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::JuMP.PrintMode, + in_set_string(print_mode::Type{<:JuMP.PrintMode}, set::Union{JuMP.AbstractJuMPScalar, Vector{<:JuMP.AbstractJuMPScalar}}) @@ -359,7 +359,7 @@ function in_set_string(print_mode, set::MOI.AbstractSet) end """ - in_set_string(print_mode::JuMP.PrintMode, + in_set_string(print_mode::Type{<:JuMP.PrintMode}, constraint::JuMP.AbstractConstraint) Return a `String` representing the membership to the set of the constraint @@ -372,8 +372,8 @@ 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/test/print.jl b/test/print.jl index 3818fa135dc..26137801c1a 100644 --- a/test/print.jl +++ b/test/print.jl @@ -44,11 +44,11 @@ struct CustomConstraint{S <: JuMP.AbstractShape} <: JuMP.AbstractConstraint in_set_str::String shape::S end -function JuMP.function_string(print_mode::JuMP.PrintMode, +function JuMP.function_string(print_mode, constraint::CustomConstraint) return constraint.function_str end -function JuMP.in_set_string(print_mode::JuMP.PrintMode, +function JuMP.in_set_string(print_mode, constraint::CustomConstraint) return constraint.in_set_str end @@ -195,7 +195,7 @@ end constraint = CustomConstraint(function_str, in_set_str, JuMP.ScalarShape()) cref = JuMP.add_constraint(model, constraint, name) - @show string(cref) + @test string(cref) == "$name : $function_str $in_set_str" end test_constraint("fun", "set", "name") test_constraint("a", "b", "c") From 5b46dda6423629e6b834b93661cd2a8a1d0cadf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 6 Nov 2018 14:53:57 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9D=20Document=20how=20to=20extend?= =?UTF-8?q?=20ConstraintRef=20printing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/print.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/print.jl b/src/print.jl index 1c39437a9a0..c2362274212 100644 --- a/src/print.jl +++ b/src/print.jl @@ -272,6 +272,14 @@ end ## Constraints #------------------------------------------------------------------------ +## 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