From fa3919f0217e7b91925cc571feb5dd2211518037 Mon Sep 17 00:00:00 2001 From: Miles Lubin Date: Sun, 29 Jul 2018 10:41:47 -0400 Subject: [PATCH] First pass of printing for MOI constraints. (#1389) * First pass of printing for MOI constraints. * remove old TODO * style pass --- src/JuMP.jl | 1 + src/nlp.jl | 24 +-- src/print.jl | 414 ++++++++++++++++++++++++++++++---------------- test/old/print.jl | 41 ----- test/print.jl | 144 +++++++++++++++- test/variable.jl | 4 +- 6 files changed, 425 insertions(+), 203 deletions(-) diff --git a/src/JuMP.jl b/src/JuMP.jl index 45df94f9fe0..c0f58e3cd38 100644 --- a/src/JuMP.jl +++ b/src/JuMP.jl @@ -281,6 +281,7 @@ Base.isempty(::AbstractJuMPScalar) = false ########################################################################## # Constraint # Holds the index of a constraint in a Model. +# TODO: Rename "m" field (breaks style guidelines). struct ConstraintRef{M<:AbstractModel,C} m::M index::C diff --git a/src/nlp.jl b/src/nlp.jl index caa0fa08342..3e82aaaff0d 100644 --- a/src/nlp.jl +++ b/src/nlp.jl @@ -362,7 +362,7 @@ function MOI.initialize!(d::NLPEvaluator, requested_features::Vector{Symbol}) d.subexpressions_as_julia_expressions = Array{Any}(undef,length(subexpr)) for k in d.subexpression_order ex = d.subexpressions[k] - d.subexpressions_as_julia_expressions[k] = tapeToExpr(d.m, 1, nldata.nlexpr[k].nd, ex.adj, ex.const_values, d.parameter_values, d.subexpressions_as_julia_expressions, nldata.user_operators, true, true) + d.subexpressions_as_julia_expressions[k] = tape_to_expr(d.m, 1, nldata.nlexpr[k].nd, ex.adj, ex.const_values, d.parameter_values, d.subexpressions_as_julia_expressions, nldata.user_operators, true, true) end end @@ -837,7 +837,7 @@ mutable struct VariablePrintWrapper v::VariableRef mode end -Base.show(io::IO,v::VariablePrintWrapper) = print(io,var_str(v.mode,v.v)) +Base.show(io::IO,v::VariablePrintWrapper) = print(io,var_string(v.mode,v.v)) mutable struct ParameterPrintWrapper idx::Int mode @@ -862,7 +862,7 @@ function Base.show(io::IO,s::SubexpressionPrintWrapper) end # we splat in the subexpressions (for now) -function tapeToExpr(m::Model, k, nd::Vector{NodeData}, adj, const_values, parameter_values, subexpressions::Vector{Any},user_operators::Derivatives.UserOperatorRegistry, generic_variable_names::Bool, splat_subexpressions::Bool, print_mode=REPLMode) +function tape_to_expr(m::Model, k, nd::Vector{NodeData}, adj, const_values, parameter_values, subexpressions::Vector{Any},user_operators::Derivatives.UserOperatorRegistry, generic_variable_names::Bool, splat_subexpressions::Bool, print_mode=REPLMode) children_arr = rowvals(adj) @@ -909,7 +909,7 @@ function tapeToExpr(m::Model, k, nd::Vector{NodeData}, adj, const_values, parame end ex = Expr(:call,opsymbol) for cidx in children_idx - push!(ex.args, tapeToExpr(m, children_arr[cidx], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) + push!(ex.args, tape_to_expr(m, children_arr[cidx], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) end return ex elseif nod.nodetype == CALLUNIVAR @@ -926,7 +926,7 @@ function tapeToExpr(m::Model, k, nd::Vector{NodeData}, adj, const_values, parame end @assert opsymbol != :error cidx = first(nzrange(adj,k)) - return Expr(:call,opsymbol,tapeToExpr(m, children_arr[cidx], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) + return Expr(:call,opsymbol,tape_to_expr(m, children_arr[cidx], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) elseif nod.nodetype == COMPARISON op = nod.index opsymbol = comparison_operators[op] @@ -934,22 +934,22 @@ function tapeToExpr(m::Model, k, nd::Vector{NodeData}, adj, const_values, parame if length(children_idx) > 2 ex = Expr(:comparison) for cidx in children_idx - push!(ex.args, tapeToExpr(m, children_arr[cidx], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) + push!(ex.args, tape_to_expr(m, children_arr[cidx], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) push!(ex.args, opsymbol) end pop!(ex.args) else ex = Expr(:call, opsymbol) - push!(ex.args, tapeToExpr(m, children_arr[children_idx[1]], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) - push!(ex.args, tapeToExpr(m, children_arr[children_idx[2]], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) + push!(ex.args, tape_to_expr(m, children_arr[children_idx[1]], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) + push!(ex.args, tape_to_expr(m, children_arr[children_idx[2]], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode)) end return ex elseif nod.nodetype == LOGIC op = nod.index opsymbol = logic_operators[op] children_idx = nzrange(adj,k) - lhs = tapeToExpr(m, children_arr[first(children_idx)], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode) - rhs = tapeToExpr(m, children_arr[last(children_idx)], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode) + lhs = tape_to_expr(m, children_arr[first(children_idx)], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode) + rhs = tape_to_expr(m, children_arr[last(children_idx)], nd, adj, const_values, parameter_values, subexpressions, user_operators, generic_variable_names, splat_subexpressions, print_mode) return Expr(opsymbol, lhs, rhs) end error() @@ -961,7 +961,7 @@ end function MOI.objective_expr(d::NLPEvaluator) if d.has_nlobj ex = d.objective - return tapeToExpr(d.m, 1, d.m.nlpdata.nlobj.nd, ex.adj, ex.const_values, d.parameter_values, d.subexpressions_as_julia_expressions,d.m.nlpdata.user_operators, true, true) + return tape_to_expr(d.m, 1, d.m.nlpdata.nlobj.nd, ex.adj, ex.const_values, d.parameter_values, d.subexpressions_as_julia_expressions,d.m.nlpdata.user_operators, true, true) else error("No nonlinear objective present") end @@ -970,7 +970,7 @@ end function MOI.constraint_expr(d::NLPEvaluator,i::Integer) ex = d.constraints[i] constr = d.m.nlpdata.nlconstr[i] - julia_expr = tapeToExpr(d.m, 1, constr.terms.nd, ex.adj, ex.const_values, d.parameter_values, d.subexpressions_as_julia_expressions,d.m.nlpdata.user_operators, true, true) + julia_expr = tape_to_expr(d.m, 1, constr.terms.nd, ex.adj, ex.const_values, d.parameter_values, d.subexpressions_as_julia_expressions,d.m.nlpdata.user_operators, true, true) if sense(constr) == :range return Expr(:comparison, constr.lb, :(<=), julia_expr, :(<=), constr.ub) else diff --git a/src/print.jl b/src/print.jl index efe35be3758..30157934457 100644 --- a/src/print.jl +++ b/src/print.jl @@ -28,97 +28,136 @@ abstract type REPLMode <: PrintMode end abstract type IJuliaMode <: PrintMode end # Whether something is zero or not for the purposes of printing it -# oneunit is useful e.g. if coef is a Unitful quantity -iszeroforprinting(coef) = abs(coef) < 1e-10 * oneunit(coef) -# Whether something is one or not for the purposes of printing it -isoneforprinting(coef) = iszeroforprinting(abs(coef) - oneunit(coef)) -str_sign(coef) = coef < zero(coef) ? " - " : " + " - -# List of indices available for variable printing -const DIMS = ["i","j","k","l","m","n"] +# oneunit is useful e.g. if coef is a Unitful quantity. +is_zero_for_printing(coef) = abs(coef) < 1e-10 * oneunit(coef) +# Whether something is one or not for the purposes of printing it. +is_one_for_printing(coef) = is_zero_for_printing(abs(coef) - oneunit(coef)) +sign_string(coef) = coef < zero(coef) ? " - " : " + " # Helper function that rounds carefully for the purposes of printing # e.g. 5.3 => 5.3 # 1.0 => 1 -function str_round(f::Float64) +function string_round(f::Float64) iszero(f) && return "0" # strip sign off zero str = string(f) length(str) >= 2 && str[end-1:end] == ".0" ? str[1:end-2] : str end -str_round(f) = string(f) - -# TODO: get rid of this! This is only a helper, and should be Base.values -# (and probably live there, as well) -_values(x::Array) = x -_values(x) = Base.values(x) +string_round(f) = string(f) # REPL-specific symbols # Anything here: https://en.wikipedia.org/wiki/Windows-1252 # should probably work fine on Windows -const repl = Dict{Symbol,String}( - :leq => (Compat.Sys.iswindows() ? "<=" : "≤"), - :geq => (Compat.Sys.iswindows() ? ">=" : "≥"), - :eq => (Compat.Sys.iswindows() ? "==" : "="), - :times => "*", - :sq => "²", - :ind_open => "[", - :ind_close => "]", - :for_all => (Compat.Sys.iswindows() ? "for all" : "∀"), - :in => (Compat.Sys.iswindows() ? "in" : "∈"), - :open_set => "{", - :dots => (Compat.Sys.iswindows() ? ".." : "…"), - :close_set => "}", - :union => (Compat.Sys.iswindows() ? "or" : "∪"), - :infty => (Compat.Sys.iswindows() ? "Inf" : "∞"), - :open_rng => "[", - :close_rng => "]", - :integer => "integer", - :succeq0 => " is semidefinite", - :Vert => (Compat.Sys.iswindows() ? "||" : "‖"), - :sub2 => (Compat.Sys.iswindows() ? "_2" : "₂")) - -# IJulia-specific symbols -const ijulia = Dict{Symbol,String}( - :leq => "\\leq", - :geq => "\\geq", - :eq => "=", - :times => "\\times ", - :sq => "^2", - :ind_open => "_{", - :ind_close => "}", - :for_all => "\\quad\\forall", - :in => "\\in", - :open_set => "\\{", - :dots => "\\dots", - :close_set => "\\}", - :union => "\\cup", - :infty => "\\infty", - :open_rng => "\\[", - :close_rng => "\\]", - :integer => "\\in \\mathbb{Z}", - :succeq0 => "\\succeq 0", - :Vert => "\\Vert", - :sub2 => "_2") - -const PrintSymbols = Dict{Symbol,String} - -# If not already mathmode, then wrap in MathJax start/close tags -math(s,mathmode) = mathmode ? s : "\$\$ $s \$\$" +function math_symbol(::Type{REPLMode}, name::Symbol) + if name == :leq + return Compat.Sys.iswindows() ? "<=" : "≤" + elseif name == :geq + return Compat.Sys.iswindows() ? ">=" : "≥" + elseif name == :eq + return Compat.Sys.iswindows() ? "==" : "=" + elseif name == :times + return "*" + elseif name == :sq + return "²" + elseif name == :ind_open + return "[" + elseif name == :ind_close + return "]" + elseif name == :for_all + return Compat.Sys.iswindows() ? "for all" : "∀" + elseif name == :in + return Compat.Sys.iswindows() ? "in" : "∈" + elseif name == :open_set + return "{" + elseif name == :dots + return Compat.Sys.iswindows() ? ".." : "…" + elseif name == :close_set + return "}" + elseif name == :union + return Compat.Sys.iswindows() ? "or" : "∪" + elseif name == :infty + return Compat.Sys.iswindows() ? "Inf" : "∞" + elseif name == :open_rng + return "[" + elseif name == :close_rng + return "]" + elseif name == :integer + return "integer" + elseif name == :succeq0 + return " is semidefinite" + elseif name == :Vert + return Compat.Sys.iswindows() ? "||" : "‖" + elseif name == :sub2 + return Compat.Sys.iswindows() ? "_2" : "₂" + else + error("Internal error: Unrecognized symbol $name.") + end +end + +# IJulia-specific symbols. +function math_symbol(::Type{IJuliaMode}, name::Symbol) + if name == :leq + return "\\leq" + elseif name == :geq + return "\\geq" + elseif name == :eq + return "=" + elseif name == :times + return "\\times " + elseif name == :sq + return "^2" + elseif name == :ind_open + return "_{" + elseif name == :ind_close + return "}" + elseif name == :for_all + return "\\quad\\forall" + elseif name == :in + return "\\in" + elseif name == :open_set + return "\\{" + elseif name == :dots + return "\\dots" + elseif name == :close_set + return "\\}" + elseif name == :union + return "\\cup" + elseif name == :infty + return "\\infty" + elseif name == :open_rng + return "\\[" + elseif name == :close_rng + return "\\]" + elseif name == :integer + return "\\in \\mathbb{Z}" + elseif name == :succeq0 + return "\\succeq 0" + elseif name == :Vert + return "\\Vert" + elseif name == :sub2 + return "_2" + else + error("Internal error: Unrecognized symbol $name.") + end +end + +wrap_in_math_mode(str) = "\$\$ $str \$\$" +wrap_in_inline_math_mode(str) = "\$ $str \$" #------------------------------------------------------------------------ ## Model #------------------------------------------------------------------------ -function Base.show(io::IO, m::Model) # TODO temporary +function Base.show(io::IO, model::Model) # TODO(#1180) temporary print(io, "A JuMP Model") end #------------------------------------------------------------------------ ## VariableRef #------------------------------------------------------------------------ -Base.show(io::IO, v::AbstractVariableRef) = print(io, var_str(REPLMode,v)) -Base.show(io::IO, ::MIME"text/latex", v::AbstractVariableRef) = - print(io, var_str(IJuliaMode,v,mathmode=false)) -function var_str(::Type{REPLMode}, v::AbstractVariableRef; mathmode=true) +Base.show(io::IO, v::AbstractVariableRef) = print(io, var_string(REPLMode, v)) +function Base.show(io::IO, ::MIME"text/latex", v::AbstractVariableRef) + print(io, wrap_in_math_mode(var_string(IJuliaMode, v))) +end +function var_string(::Type{REPLMode}, v::AbstractVariableRef) var_name = name(v) if !isempty(var_name) return var_name @@ -126,84 +165,86 @@ function var_str(::Type{REPLMode}, v::AbstractVariableRef; mathmode=true) return "noname" end end -function var_str(::Type{IJuliaMode}, v::AbstractVariableRef; mathmode=true) +function var_string(::Type{IJuliaMode}, v::AbstractVariableRef) var_name = name(v) if !isempty(var_name) # TODO: This is wrong if variable name constains extra "]" - return math(replace(replace(var_name,"[","_{",1),"]","}"), mathmode) + return replace(replace(var_name, "[", "_{", 1), "]", "}") else - return math("noname", mathmode) + return "noname" end end -Base.show(io::IO, a::GenericAffExpr) = print(io, aff_str(REPLMode,a)) -Base.show(io::IO, ::MIME"text/latex", a::GenericAffExpr) = - print(io, math(aff_str(IJuliaMode,a),false)) -# Generic string converter, called by mode-specific handlers -function aff_str(mode, a::GenericAffExpr{C, V}, show_constant=true) where {C, V} +Base.show(io::IO, a::GenericAffExpr) = print(io, aff_string(REPLMode,a)) +function Base.show(io::IO, ::MIME"text/latex", a::GenericAffExpr) + print(io, wrap_in_math_mode(aff_string(IJuliaMode, a))) +end + +function aff_string(mode, a::GenericAffExpr, show_constant=true) # If the expression is empty, return the constant (or 0) if length(linearterms(a)) == 0 - return show_constant ? str_round(a.constant) : "0" + return show_constant ? string_round(a.constant) : "0" end - term_str = Array{String}(undef,2*length(linearterms(a))) + term_str = Array{String}(undef, 2 * length(linearterms(a))) elm = 1 # For each non-zero for this model for (coef, var) in linearterms(a) - iszeroforprinting(coef) && continue # e.g. x - x + is_zero_for_printing(coef) && continue # e.g. x - x - pre = isoneforprinting(coef) ? "" : str_round(abs(coef)) * " " + pre = is_one_for_printing(coef) ? "" : string_round(abs(coef)) * " " - term_str[2*elm-1] = str_sign(coef) - term_str[2*elm ] = string(pre, var_str(mode, var)) + term_str[2 * elm - 1] = sign_string(coef) + term_str[2 * elm] = string(pre, var_string(mode, var)) elm += 1 end if elm == 1 # Will happen with cancellation of all terms # We should just return the constant, if its desired - return show_constant ? str_round(a.constant) : "0" + return show_constant ? string_round(a.constant) : "0" else # Correction for very first term - don't want a " + "/" - " term_str[1] = (term_str[1] == " - ") ? "-" : "" - ret = join(term_str[1:2*(elm-1)]) - if !iszeroforprinting(a.constant) && show_constant - ret = string(ret, str_sign(a.constant), str_round(abs(a.constant))) + ret = join(term_str[1 : 2 * (elm - 1)]) + if !is_zero_for_printing(a.constant) && show_constant + ret = string(ret, sign_string(a.constant), + string_round(abs(a.constant))) end return ret end end -# Precompile for faster boot times -Base.precompile(aff_str, (Type{JuMP.REPLMode}, AffExpr, Bool)) -Base.precompile(aff_str, (Type{JuMP.IJuliaMode}, AffExpr, Bool)) -Base.precompile(aff_str, (Type{JuMP.REPLMode}, AffExpr)) -Base.precompile(aff_str, (Type{JuMP.IJuliaMode}, AffExpr)) - #------------------------------------------------------------------------ ## GenericQuadExpr #------------------------------------------------------------------------ -Base.show(io::IO, q::GenericQuadExpr) = print(io, quad_str(REPLMode,q)) -Base.show(io::IO, ::MIME"text/latex", q::GenericQuadExpr) = - print(io, quad_str(IJuliaMode,q,mathmode=false)) -# Generic string converter, called by mode-specific handlers -function quad_str(mode, q::GenericQuadExpr, sym) - length(quadterms(q)) == 0 && return aff_str(mode,q.aff) +Base.show(io::IO, q::GenericQuadExpr) = print(io, quad_string(REPLMode,q)) +function Base.show(io::IO, ::MIME"text/latex", q::GenericQuadExpr) + print(io, wrap_in_math_mode(quad_string(IJuliaMode, q))) +end + +function quad_string(mode, q::GenericQuadExpr) + length(quadterms(q)) == 0 && return aff_string(mode, q.aff) # Odd terms are +/i, even terms are the variables/coeffs - term_str = Array{String}(undef,2*length(quadterms(q))) + term_str = Array{String}(undef, 2 * length(quadterms(q))) elm = 1 if length(term_str) > 0 for (coef, var1, var2) in quadterms(q) - iszeroforprinting(coef) && continue # e.g. x - x + is_zero_for_printing(coef) && continue # e.g. x - x - pre = isoneforprinting(coef) ? "" : str_round(abs(coef)) * " " + pre = is_one_for_printing(coef) ? "" : string_round(abs(coef)) * " " - x = var_str(mode,var1) - y = var_str(mode,var2) + x = var_string(mode,var1) + y = var_string(mode,var2) - term_str[2*elm-1] = str_sign(coef) - term_str[2*elm ] = "$pre$x" * (x == y ? sym[:sq] : "$(sym[:times])$y") + term_str[2 * elm - 1] = sign_string(coef) + term_str[2 * elm] = "$pre$x" + if x == y + term_str[2 * elm] *= math_symbol(mode, :sq) + else + term_str[2 * elm] *= string(math_symbol(mode, :times), y) + end if elm == 1 # Correction for first term as there is no space # between - and variable coefficient/name @@ -212,67 +253,154 @@ function quad_str(mode, q::GenericQuadExpr, sym) elm += 1 end end - ret = join(term_str[1:2*(elm-1)]) + ret = join(term_str[1 : 2 * (elm - 1)]) - aff_string = aff_str(mode, q.aff) - if aff_string == "0" + aff_str = aff_string(mode, q.aff) + if aff_str == "0" return ret else - if aff_string[1] == '-' - return string(ret, " - ", aff_string[2:end]) + if aff_str[1] == '-' + return string(ret, " - ", aff_str[2 : end]) else - return string(ret, " + ", aff_string) + return string(ret, " + ", aff_str) end end end -# Handlers to use correct symbols -quad_str(::Type{REPLMode}, q::GenericQuadExpr) = - quad_str(REPLMode, q, repl) -quad_str(::Type{IJuliaMode}, q::GenericQuadExpr; mathmode=true) = - math(quad_str(IJuliaMode, q, ijulia), mathmode) #------------------------------------------------------------------------ -## NonlinearExprData +## Constraints #------------------------------------------------------------------------ -#Base.show(io::IO, c::NonlinearExprData) = print(io, expr_str(REPLMode, c)) -#Base.show(io::IO, ::MIME"text/latex", c::NonlinearExprData) = -# print(io, expr_str(IJuliaMode, c)) -function expr_str(m::Model, mode, c::NonlinearExprData) - return string(tapeToExpr(m, 1, c.nd, adjmat(c.nd), c.const_values, [], [], m.nlpdata.user_operators, false, false, mode)) + +function Base.show(io::IO, ref::ConstraintRef{Model}) + constraint_object = constraintobject(ref) + constraint_name = name(ref) + print(io, constraint_string(REPLMode, constraint_name, constraint_object)) +end +function Base.show(io::IO, ::MIME"text/latex", ref::ConstraintRef{Model}) + constraint_object = constraintobject(ref) + constraint_name = name(ref) + print(io, constraint_string(IJuliaMode, constraint_name, constraint_object)) +end + +function function_string(print_mode, variable::AbstractVariableRef) + return var_string(print_mode, variable) end -# TODO: Print SingleVariableConstraint, VectorOfVariablesConstraint, AffExprConstraint, VectorAffExprConstraint, QuadExprConstraint +function function_string(print_mode, + variable_vector::Vector{<:AbstractVariableRef}) + return "[" * join(var_string.(print_mode, variable_vector), ", ") * "]" +end +function function_string(print_mode, aff::GenericAffExpr) + return aff_string(print_mode, aff) +end + +function function_string(print_mode, aff_vector::Vector{<:GenericAffExpr}) + return "[" * join(aff_string.(print_mode, aff_vector), ", ") * "]" +end + +function function_string(print_mode, quad::GenericQuadExpr) + return quad_string(print_mode, quad) +end + +function function_string(print_mode, quad_vector::Vector{<:GenericQuadExpr}) + return "[" * join(quad_string.(print_mode, quad_vector), ", ") * "]" +end + +function in_set_string(print_mode, set::MOI.LessThan) + return string(math_symbol(print_mode, :leq), " ", set.upper) +end + +function in_set_string(print_mode, set::MOI.GreaterThan) + return string(math_symbol(print_mode, :geq), " ", set.lower) +end + +function in_set_string(print_mode, set::MOI.EqualTo) + return string(math_symbol(print_mode, :eq), " ", set.value) +end + +function in_set_string(print_mode, set::MOI.Interval) + return string(math_symbol(print_mode, :in), " ", + math_symbol(print_mode, :open_rng), set.lower, ", ", + set.upper, math_symbol(print_mode, :close_rng)) +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. +function in_set_string(print_mode, set::MOI.AbstractSet) + return string(math_symbol(print_mode, :in), " ", set) +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) + constraint_without_name = func_str * " " * in_set_str + if print_mode == IJuliaMode + constraint_without_name = wrap_in_inline_math_mode(constraint_without_name) + end + if isempty(constraint_name) + return constraint_without_name + else + return constraint_name * " : " * constraint_without_name + end +end + +#------------------------------------------------------------------------ +## NonlinearExprData +#------------------------------------------------------------------------ +function nl_expr_string(model::Model, mode, c::NonlinearExprData) + return string(tape_to_expr(model, 1, c.nd, adjmat(c.nd), c.const_values, [], + [], model.nlpdata.user_operators, false, false, + mode)) +end #------------------------------------------------------------------------ ## NonlinearConstraint #------------------------------------------------------------------------ -Base.show(io::IO, c::NonlinearConstraint) = print(io, con_str(REPLMode,c)) -Base.show(io::IO, ::MIME"text/latex", c::NonlinearConstraint) = - print(io, con_str(IJuliaMode,c,mathmode=false)) -# Generic string converter, called by mode-specific handlers -function con_str(m::Model, mode, c::NonlinearConstraint, sym) +const NonlinearConstraintRef = ConstraintRef{Model, NonlinearConstraintIndex} + +function Base.show(io::IO, c::NonlinearConstraintRef) + print(io, nl_constraint_string(c.m, REPLMode, + c.m.nlpdata.nlconstr[c.index.value])) +end + +function Base.show(io::IO, ::MIME"text/latex", c::NonlinearConstraintRef) + constraint = c.m.nlpdata.nlconstr[c.index.value] + print(io, wrap_in_math_mode(nl_constraint_string(c.m, IJuliaMode, + constraint))) +end + +# TODO: Printing is inconsistent between regular constraints and nonlinear +# constraints because nonlinear constraints don't have names. +function nl_constraint_string(model::Model, mode, c::NonlinearConstraint) s = sense(c) - nl = expr_str(m, mode, c.terms) + nl = nl_expr_string(model, mode, c.terms) if s == :range - out_str = "$(str_round(c.lb)) $(sym[:leq]) $nl $(sym[:leq]) $(str_round(c.ub))" + out_str = "$(string_round(c.lb)) " * math_symbol(mode, :leq) * " $nl " * + math_symbol(mode, :leq) * " " * string_round(c.ub) else - rel = s == :<= ? sym[:leq] : (s == :>= ? sym[:geq] : sym[:eq]) - out_str = string(nl," ",rel," ",str_round(rhs(c))) + if s == :<= + rel = math_symbol(mode, :leq) + elseif s == :>= + rel = math_symbol(mode, :geq) + else + rel = math_symbol(mode, :eq) + end + out_str = string(nl," ",rel," ",string_round(rhs(c))) end - out_str + return out_str end -# Handlers to use correct symbols -# con_str(m::Model, ::Type{REPLMode}, c::GenericRangeConstraint; args...) = -# con_str(m, REPLMode, c, repl) -# con_str(m::Model, ::Type{IJuliaMode}, c::GenericRangeConstraint; mathmode=true) = -# math(con_str(m, IJuliaMode, c, ijulia), mathmode) - -# TODO: Print ConstraintRef #------------------------------------------------------------------------ ## Nonlinear expression/parameter reference #------------------------------------------------------------------------ -Base.show(io::IO, ex::NonlinearExpression) = Base.show(io, "Reference to nonlinear expression #$(ex.index)") -Base.show(io::IO, p::NonlinearParameter) = Base.show(io, "Reference to nonlinear parameter #$(p.index)") +function Base.show(io::IO, ex::NonlinearExpression) + Base.show(io, "Reference to nonlinear expression #$(ex.index)") +end +function Base.show(io::IO, p::NonlinearParameter) + Base.show(io, "Reference to nonlinear parameter #$(p.index)") +end diff --git a/test/old/print.jl b/test/old/print.jl index 60d0095e1d5..4c77e13cc71 100644 --- a/test/old/print.jl +++ b/test/old/print.jl @@ -441,47 +441,6 @@ end * 1 linear constraint * 1 variable Solver is default solver""", repl=:show) - - #------------------------------------------------------------------ - - mod_3 = Model() - - @variable(mod_3, y[1:5]) - @NLparameter(mod_3, p == 10) - @NLexpression(mod_3, ex, y[2]) - @NLconstraint(mod_3, y[1]*y[2] == 1) - @NLconstraint(mod_3, y[3]*y[4] == 1) - @NLconstraint(mod_3, y[5]*y[1] - ex == 1) - - @NLobjective(mod_3, Min, y[1]*y[3] - p) - - io_test(REPLMode, p, "\"Reference to nonlinear parameter #1\"") - io_test(REPLMode, ex, "\"Reference to nonlinear expression #1\"") - - io_test(REPLMode, mod_3, """ - Min y[1] * y[3] - parameter[1] - Subject to - y[1] * y[2] - 1.0 $eq 0 - y[3] * y[4] - 1.0 $eq 0 - (y[5] * y[1] - subexpression[1]) - 1.0 $eq 0 - y[i] $fa i $inset {1,2,3,4,5} - subexpression[1]: y[2] - """, repl=:print) - io_test(REPLMode, mod_3, """ - Minimization problem with: - * 0 linear constraints - * 3 nonlinear constraints - * 5 variables - Solver is default solver""", repl=:show) - io_test(IJuliaMode, mod_3, """ - \\begin{alignat*}{1}\\min\\quad & y_{1} * y_{3} - parameter_{1}\\\\ - \\text{Subject to} \\quad & y_{1} * y_{2} - 1.0 = 0\\\\ - & y_{3} * y_{4} - 1.0 = 0\\\\ - & (y_{5} * y_{1} - subexpression_{1}) - 1.0 = 0\\\\ - & y_{i} \\quad\\forall i \\in \\{1,2,3,4,5\\}\\\\ - subexpression_{1} = \\quad &y_{2}\\\\ - \\end{alignat*} - """, repl=:print) end @testset "changing variable categories" begin diff --git a/test/print.jl b/test/print.jl index 01a343887f2..5623dde4661 100644 --- a/test/print.jl +++ b/test/print.jl @@ -14,7 +14,6 @@ using JuMP using Compat using Compat.Test import JuMP.REPLMode, JuMP.IJuliaMode -import JuMP.repl, JuMP.ijulia # Helper function to test IO methods work correctly function io_test(mode, obj, exp_str; repl=:both) @@ -44,7 +43,8 @@ Base.isless(u::UnitNumber, v::UnitNumber) = isless(u.α, v.α) @testset "expressions" begin # Most of the expression logic is well covered by test/operator.jl # This is really just to check IJulia printing for expressions - le, ge = repl[:leq], repl[:geq] + le = JuMP.math_symbol(REPLMode, :leq) + ge = JuMP.math_symbol(REPLMode, :geq) #------------------------------------------------------------------ mod = Model() @@ -71,8 +71,10 @@ Base.isless(u::UnitNumber, v::UnitNumber) = isless(u.α, v.α) io_test(IJuliaMode, ex, "y_{2,2}\\times x_{1} + y_{2,2}\\times x_{2} + 3 x_{1} + 3 x_{2}") ex = @expression(mod, (x[1]+x[2])*(y[2,2]+3.0) + z^2 - 1) - io_test(REPLMode, ex, "x[1]*y[2,2] + x[2]*y[2,2] + z$(repl[:sq]) + 3 x[1] + 3 x[2] - 1") - io_test(IJuliaMode, ex, "x_{1}\\times y_{2,2} + x_{2}\\times y_{2,2} + z$(ijulia[:sq]) + 3 x_{1} + 3 x_{2} - 1") + repl_sq = JuMP.math_symbol(REPLMode, :sq) + io_test(REPLMode, ex, "x[1]*y[2,2] + x[2]*y[2,2] + z$repl_sq + 3 x[1] + 3 x[2] - 1") + ijulia_sq = JuMP.math_symbol(IJuliaMode, :sq) + io_test(IJuliaMode, ex, "x_{1}\\times y_{2,2} + x_{2}\\times y_{2,2} + z$ijulia_sq + 3 x_{1} + 3 x_{2} - 1") ex = @expression(mod, -z*x[1] - x[1]*z + x[1]*x[2] + 0*z^2) io_test(REPLMode, ex, "-2 x[1]*z + x[1]*x[2]") @@ -169,4 +171,138 @@ Base.isless(u::UnitNumber, v::UnitNumber) = isless(u.α, v.α) io_test(REPLMode, quad, "UnitNumber(2.0) x² + UnitNumber(0.0)") io_test(IJuliaMode, quad, "UnitNumber(2.0) x^2 + UnitNumber(0.0)") end + + @testset "SingleVariable constraints" begin + ge = JuMP.math_symbol(REPLMode, :geq) + in_sym = JuMP.math_symbol(REPLMode, :in) + model = Model() + @variable(model, x >= 10) + zero_one = @constraint(model, x in MathOptInterface.ZeroOne()) + + io_test(REPLMode, JuMP.LowerBoundRef(x), "x $ge 10.0") + io_test(REPLMode, zero_one, "x $in_sym MathOptInterface.ZeroOne()") + # TODO: Test in IJulia mode and do nice printing for {0, 1}. + end + + @testset "VectorOfVariable constraints" begin + ge = JuMP.math_symbol(REPLMode, :geq) + in_sym = JuMP.math_symbol(REPLMode, :in) + model = Model() + @variable(model, x) + @variable(model, y) + zero_constr = @constraint(model, [x, y] in MathOptInterface.Zeros(2)) + + io_test(REPLMode, zero_constr, + "[x, y] $in_sym MathOptInterface.Zeros(2)") + # TODO: Test in IJulia mode and do nice printing for Zeros(). + end + + @testset "Scalar AffExpr constraints" begin + le = JuMP.math_symbol(REPLMode, :leq) + ge = JuMP.math_symbol(REPLMode, :geq) + eq = JuMP.math_symbol(REPLMode, :eq) + in_sym = JuMP.math_symbol(REPLMode, :in) + + model = Model() + @variable(model, x) + @constraint(model, linear_le, x + 0 <= 1) + @constraint(model, linear_ge, x + 0 >= 1) + @constraint(model, linear_eq, x + 0 == 1) + @constraint(model, linear_range, -1 <= x + 0 <= 1) + linear_noname = @constraint(model, x + 0 <= 1) + + io_test(REPLMode, linear_le, "linear_le : x $le 1.0") + io_test(REPLMode, linear_eq, "linear_eq : x $eq 1.0") + io_test(REPLMode, linear_range, "linear_range : x $in_sym [-1.0, 1.0]") + io_test(REPLMode, linear_noname, "x $le 1.0") + + # io_test doesn't work here because constraints print with a mix of math + # and non-math. + @test sprint(show, "text/latex", linear_le) == + "linear_le : \$ x \\leq 1.0 \$" + @test sprint(show, "text/latex", linear_ge) == + "linear_ge : \$ x \\geq 1.0 \$" + @test sprint(show, "text/latex", linear_eq) == + "linear_eq : \$ x = 1.0 \$" + @test sprint(show, "text/latex", linear_range) == + "linear_range : \$ x \\in \\[-1.0, 1.0\\] \$" + @test sprint(show, "text/latex", linear_noname) == "\$ x \\leq 1.0 \$" + end + + @testset "Vector AffExpr constraints" begin + in_sym = JuMP.math_symbol(REPLMode, :in) + + model = Model() + @variable(model, x) + @constraint(model, soc_constr, [x - 1, x + 1] in SecondOrderCone()) + + io_test(REPLMode, soc_constr, "soc_constr : " * + "[x - 1, x + 1] $in_sym MathOptInterface.SecondOrderCone(2)") + + # TODO: Test in IJulia mode. + end + + @testset "Scalar QuadExpr constraints" begin + in_sym = JuMP.math_symbol(REPLMode, :in) + le = JuMP.math_symbol(REPLMode, :leq) + sq = JuMP.math_symbol(REPLMode, :sq) + + model = Model() + @variable(model, x) + quad_constr = @constraint(model, 2x^2 <= 1) + + io_test(REPLMode, quad_constr, "2 x$sq $le 1.0") + # TODO: Test in IJulia mode. + end + + @testset "Nonlinear expressions" begin + model = Model() + @variable(model, x) + expr = @NLexpression(model, x + 1) + io_test(REPLMode, expr, "\"Reference to nonlinear expression #1\"") + end + + @testset "Nonlinear parameters" begin + model = Model() + @NLparameter(model, param == 1.0) + io_test(REPLMode, param, "\"Reference to nonlinear parameter #1\"") + end + + @testset "Nonlinear constraints" begin + le = JuMP.math_symbol(REPLMode, :leq) + ge = JuMP.math_symbol(REPLMode, :geq) + eq = JuMP.math_symbol(REPLMode, :eq) + + model = Model() + @variable(model, x) + constr_le = @NLconstraint(model, sin(x) <= 1) + constr_ge = @NLconstraint(model, sin(x) >= 1) + constr_eq = @NLconstraint(model, sin(x) == 1) + constr_range = @NLconstraint(model, 0 <= sin(x) <= 1) + + io_test(REPLMode, constr_le, "sin(x) - 1.0 $le 0") + io_test(REPLMode, constr_ge, "sin(x) - 1.0 $ge 0") + io_test(REPLMode, constr_eq, "sin(x) - 1.0 $eq 0") + # Note: This is inconsistent with the "x in [-1, 1]" printing for + # regular constraints. + io_test(REPLMode, constr_range, "0 $le sin(x) $le 1") + + io_test(IJuliaMode, constr_le, "sin(x) - 1.0 \\leq 0") + io_test(IJuliaMode, constr_ge, "sin(x) - 1.0 \\geq 0") + io_test(IJuliaMode, constr_eq, "sin(x) - 1.0 = 0") + io_test(IJuliaMode, constr_range, "0 \\leq sin(x) \\leq 1") + end + + @testset "Nonlinear constraints with embedded parameters/expressions" begin + le = JuMP.math_symbol(REPLMode, :leq) + + model = Model() + @variable(model, x) + expr = @NLexpression(model, x + 1) + @NLparameter(model, param == 1.0) + + constr = @NLconstraint(model, expr - param <= 0) + 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 end diff --git a/test/variable.jl b/test/variable.jl index 5adeb88db8b..9b67d4a7668 100644 --- a/test/variable.jl +++ b/test/variable.jl @@ -11,15 +11,13 @@ # Testing for VariableRef ############################################################################# using JuMP -import JuMP.repl using Compat using Compat.Test function variables_test(ModelType::Type{<:JuMP.AbstractModel}, VariableRefType::Type{<:JuMP.AbstractVariableRef}) AffExprType = JuMP.GenericAffExpr{Float64, VariableRefType} - @testset "constructors" begin - # Constructors + @testset "Constructors" begin mcon = ModelType() @testset "No bound" begin