Skip to content

Commit

Permalink
Merge pull request jump-dev#1584 from JuliaOpt/bl/abstractconprint
Browse files Browse the repository at this point in the history
Improve show extensibility for AbstractConstraint
  • Loading branch information
blegat authored Nov 7, 2018
2 parents 3b9dacc + 5b46dda commit 6daac7a
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 14 deletions.
39 changes: 36 additions & 3 deletions src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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}
Expand All @@ -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."
Expand Down
56 changes: 52 additions & 4 deletions src/print.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
7 changes: 0 additions & 7 deletions src/sets.jl
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
49 changes: 49 additions & 0 deletions test/print.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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})
Expand Down

0 comments on commit 6daac7a

Please sign in to comment.