From 84511a40ef001203a4cf2fb133bc7546c10cfd3e Mon Sep 17 00:00:00 2001 From: Miles Lubin Date: Tue, 22 Mar 2016 21:06:46 -0600 Subject: [PATCH] fix for parsing changes on julia 0.5 --- src/callbacks.jl | 24 +++--- src/macros.jl | 171 +++++++++++++++++++++++----------------- src/nlp.jl | 18 ++++- src/nlpmacros.jl | 3 + src/parseExpr_staged.jl | 14 ++-- test/macros.jl | 21 +++-- 6 files changed, 142 insertions(+), 109 deletions(-) diff --git a/src/callbacks.jl b/src/callbacks.jl index a6a45aa4384..ee3dccb872a 100644 --- a/src/callbacks.jl +++ b/src/callbacks.jl @@ -165,13 +165,13 @@ export addLazyConstraint, @addLazyConstraint macro addLazyConstraint(cbdata, x) cbdata = esc(cbdata) - if (x.head != :comparison) - error("Expected comparison operator in constraint $x") + if VERSION < v"0.5.0-dev+3231" + x = comparison_to_call(x) end - if length(x.args) == 3 # simple comparison - lhs = :($(x.args[1]) - $(x.args[3])) # move everything to the lhs + if isexpr(x, :call) && length(x.args) == 3 # simple comparison + lhs = :($(x.args[2]) - $(x.args[3])) # move everything to the lhs newaff, parsecode = parseExprToplevel(lhs, :aff) - sense, vectorized = _canonicalize_sense(x.args[2]) + sense, vectorized = _canonicalize_sense(x.args[1]) vectorized && error("Cannot add vectorized constraint in lazy callback") quote aff = zero(AffExpr) @@ -180,7 +180,7 @@ macro addLazyConstraint(cbdata, x) addLazyConstraint($cbdata, constr) end else - error("Syntax error (ranged constraints not permitted in callbacks)") + error("Syntax error in addLazyConstraint, expected one-sided comparison.") end end @@ -202,13 +202,13 @@ export addUserCut, @addUserCut macro addUserCut(cbdata, x) cbdata = esc(cbdata) - if (x.head != :comparison) - error("Expected comparison operator in constraint $x") + if VERSION < v"0.5.0-dev+3231" + x = comparison_to_call(x) end - if length(x.args) == 3 # simple comparison - lhs = :($(x.args[1]) - $(x.args[3])) # move everything to the lhs + if isexpr(x, :call) && length(x.args) == 3 # simple comparison + lhs = :($(x.args[2]) - $(x.args[3])) # move everything to the lhs newaff, parsecode = parseExprToplevel(lhs, :aff) - sense, vectorized = _canonicalize_sense(x.args[2]) + sense, vectorized = _canonicalize_sense(x.args[1]) vectorized && error("Cannot add vectorized constraint in cut callback") quote aff = zero(AffExpr) @@ -217,7 +217,7 @@ macro addUserCut(cbdata, x) addUserCut($cbdata, constr) end else - error("Syntax error (ranged constraints not permitted in callbacks)") + error("Syntax error in addUserCut, expected one-sided comparison.") end end diff --git a/src/macros.jl b/src/macros.jl index b4a9de20311..932d337e82c 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -8,6 +8,18 @@ using Base.Meta issum(s::Symbol) = (s == :sum) || (s == :∑) || (s == :Σ) isprod(s::Symbol) = (s == :prod) || (s == :∏) +# for backward compatibility with 0.4 +function comparison_to_call(ex) + if isexpr(ex,:comparison) && length(ex.args) == 3 + return Expr(:call,ex.args[2],ex.args[1],ex.args[3]) + else + return ex + end +end + +# we don't support older versions of 0.5 due to parsing changes +v"0.5-" < VERSION < v"0.5.0-dev+3231" && error("Current Julia version is a prerelease of 0.5 which is no longer supported by JuMP. Please update to a newer build of 0.5.") + include("parseExpr_staged.jl") ############################################################################### @@ -299,18 +311,20 @@ macro addConstraint(args...) (x.head == :block) && error("Code block passed as constraint. Perhaps you meant to use addConstraints instead?") - (x.head != :comparison) && - error("in @addConstraint ($(string(x))): expected comparison operator (<=, >=, or ==).") + if VERSION < v"0.5.0-dev+3231" + x = comparison_to_call(x) + end # Strategy: build up the code for non-macro addconstraint, and if needed # we will wrap in loops to assign to the ConstraintRefs crefflag = isa(c,Expr) refcall, idxvars, idxsets, idxpairs, condition = buildrefsets(c) # Build the constraint - if length(x.args) == 3 + if isexpr(x, :call) # Simple comparison - move everything to the LHS - (sense,vectorized) = _canonicalize_sense(x.args[2]) - lhs = :($(x.args[1]) - $(x.args[3])) + @assert length(x.args) == 3 + (sense,vectorized) = _canonicalize_sense(x.args[1]) + lhs = :($(x.args[2]) - $(x.args[3])) addconstr = (vectorized ? :addVectorizedConstraint : :addConstraint) newaff, parsecode = parseExprToplevel(lhs, :q) constraintcall = :($addconstr($m, constructconstraint!($newaff,$(quot(sense))))) @@ -323,7 +337,7 @@ macro addConstraint(args...) $parsecode $(refcall) = $constraintcall end - elseif length(x.args) == 5 + elseif isexpr(x, :comparison) # Ranged row (lsign,lvectorized) = _canonicalize_sense(x.args[2]) (rsign,rvectorized) = _canonicalize_sense(x.args[4]) @@ -392,14 +406,14 @@ macro addSDPConstraint(m, x) (x.head == :block) && error("Code block passed as constraint.") - (x.head != :comparison) && - error("in @addSDPConstraint ($(string(x))): expected comparison operator (<=, or >=).") - - length(x.args) == 3 || error("in @addSDPConstraint ($(string(x))): constraints must be in one of the following forms:\n" * + if VERSION < v"0.5.0-dev+3231" + x = comparison_to_call(x) + end + isexpr(x,:call) && length(x.args) == 3 || error("in @addSDPConstraint ($(string(x))): constraints must be in one of the following forms:\n" * " expr1 <= expr2\n" * " expr1 >= expr2\n") # Build the constraint # Simple comparison - move everything to the LHS - sense = x.args[2] + sense = x.args[1] if sense == :⪰ sense = :(>=) elseif sense == :⪯ @@ -408,9 +422,9 @@ macro addSDPConstraint(m, x) sense,_ = _canonicalize_sense(sense) lhs = :() if sense == :(>=) - lhs = :($(x.args[1]) - $(x.args[3])) + lhs = :($(x.args[2]) - $(x.args[3])) elseif sense == :(<=) - lhs = :($(x.args[3]) - $(x.args[1])) + lhs = :($(x.args[3]) - $(x.args[2])) else error("Invalid sense $sense in SDP constraint") end @@ -427,15 +441,16 @@ end macro LinearConstraint(x) (x.head == :block) && error("Code block passed as constraint. Perhaps you meant to use @LinearConstraints instead?") - (x.head != :comparison) && - error("in @LinearConstraint ($(string(x))): expected comparison operator (<=, >=, or ==).") - if length(x.args) == 3 - (sense,vectorized) = _canonicalize_sense(x.args[2]) + if VERSION < v"0.5.0-dev+3231" + x = comparison_to_call(x) + end + if isexpr(x, :call) && length(x.args) == 3 + (sense,vectorized) = _canonicalize_sense(x.args[1]) # Simple comparison - move everything to the LHS vectorized && error("in @LinearConstraint ($(string(x))): Cannot add vectorized constraints") - lhs = :($(x.args[1]) - $(x.args[3])) + lhs = :($(x.args[2]) - $(x.args[3])) return quote newaff = @Expression($(esc(lhs))) c = constructconstraint!(newaff,$(quot(sense))) @@ -443,7 +458,7 @@ macro LinearConstraint(x) error("Constraint in @LinearConstraint is really a $(typeof(c))") c end - elseif length(x.args) == 5 + elseif isexpr(x, :comparison) # Ranged row (lsense,lvectorized) = _canonicalize_sense(x.args[2]) (rsense,rvectorized) = _canonicalize_sense(x.args[4]) @@ -477,22 +492,23 @@ end macro QuadConstraint(x) (x.head == :block) && error("Code block passed as constraint. Perhaps you meant to use @QuadConstraints instead?") - (x.head != :comparison) && - error("in @QuadConstraint ($(string(x))): expected comparison operator (<=, >=, or ==).") - if length(x.args) == 3 - (sense,vectorized) = _canonicalize_sense(x.args[2]) + if VERSION < v"0.5.0-dev+3231" + x = comparison_to_call(x) + end + if isexpr(x, :call) && length(x.args) == 3 + (sense,vectorized) = _canonicalize_sense(x.args[1]) # Simple comparison - move everything to the LHS vectorized && error("in @QuadConstraint ($(string(x))): Cannot add vectorized constraints") - lhs = :($(x.args[1]) - $(x.args[3])) + lhs = :($(x.args[2]) - $(x.args[3])) return quote newaff = @Expression($(esc(lhs))) q = constructconstraint!(newaff,$(quot(sense))) isa(q, QuadConstraint) || error("Constraint in @QuadConstraint is really a $(typeof(q))") q end - elseif length(x.args) == 5 + elseif isexpr(x, :comparison) error("Ranged quadratic constraints are not allowed") else # Unknown @@ -505,22 +521,23 @@ end macro SOCConstraint(x) (x.head == :block) && error("Code block passed as constraint. Perhaps you meant to use @SOCConstraints instead?") - (x.head != :comparison) && - error("in @SOCConstraint ($(string(x))): expected comparison operator (<=, >=, or ==).") - if length(x.args) == 3 - (sense,vectorized) = _canonicalize_sense(x.args[2]) + if VERSION < v"0.5.0-dev+3231" + x = comparison_to_call(x) + end + if isexpr(x, :call) && length(x.args) == 3 + (sense,vectorized) = _canonicalize_sense(x.args[1]) # Simple comparison - move everything to the LHS vectorized && error("in @SOCConstraint ($(string(x))): Cannot add vectorized constraints") - lhs = :($(x.args[1]) - $(x.args[3])) + lhs = :($(x.args[2]) - $(x.args[3])) return quote newaff = @Expression($(esc(lhs))) q = constructconstraint!(newaff,$(quot(sense))) isa(q, SOCConstraint) || error("Constraint in @SOCConstraint is really a $(typeof(q))") q end - elseif length(x.args) == 5 + elseif isexpr(x, :comparison) error("Ranged second-order cone constraints are not allowed") else # Unknown @@ -715,42 +732,44 @@ macro defVar(args...) gottype = false # Identify the variable bounds. Five (legal) possibilities are "x >= lb", # "x <= ub", "lb <= x <= ub", "x == val", or just plain "x" - if isexpr(x,:comparison) - # We have some bounds + if VERSION < v"0.5.0-dev+3231" + x = comparison_to_call(x) + end + if isexpr(x,:comparison) # two-sided if x.args[2] == :>= || x.args[2] == :≥ - if length(x.args) == 5 - # ub >= x >= lb - x.args[4] == :>= || x.args[4] == :≥ || error("Invalid variable bounds") - var = x.args[3] - lb = esc_nonconstant(x.args[5]) - ub = esc_nonconstant(x.args[1]) - else - # x >= lb - var = x.args[1] - @assert length(x.args) == 3 - lb = esc_nonconstant(x.args[3]) - ub = Inf - end + # ub >= x >= lb + x.args[4] == :>= || x.args[4] == :≥ || error("Invalid variable bounds") + var = x.args[3] + lb = esc_nonconstant(x.args[5]) + ub = esc_nonconstant(x.args[1]) elseif x.args[2] == :<= || x.args[2] == :≤ - if length(x.args) == 5 - # lb <= x <= u - var = x.args[3] - (x.args[4] != :<= && x.args[4] != :≤) && - error("in @defVar ($var): expected <= operator after variable name.") - lb = esc_nonconstant(x.args[1]) - ub = esc_nonconstant(x.args[5]) - else - # x <= ub - var = x.args[1] - # NB: May also be lb <= x, which we do not support - # We handle this later in the macro - @assert length(x.args) == 3 - ub = esc_nonconstant(x.args[3]) - lb = -Inf - end - elseif x.args[2] == :(==) + # lb <= x <= u + var = x.args[3] + (x.args[4] != :<= && x.args[4] != :≤) && + error("in @defVar ($var): expected <= operator after variable name.") + lb = esc_nonconstant(x.args[1]) + ub = esc_nonconstant(x.args[5]) + else + error("in @defVar ($(string(x))): use the form lb <= ... <= ub.") + end + elseif isexpr(x,:call) + if x.args[1] == :>= || x.args[1] == :≥ + # x >= lb + var = x.args[2] + @assert length(x.args) == 3 + lb = esc_nonconstant(x.args[3]) + ub = Inf + elseif x.args[1] == :<= || x.args[1] == :≤ + # x <= ub + var = x.args[2] + # NB: May also be lb <= x, which we do not support + # We handle this later in the macro + @assert length(x.args) == 3 + ub = esc_nonconstant(x.args[3]) + lb = -Inf + elseif x.args[1] == :(==) # fixed variable - var = x.args[1] + var = x.args[2] @assert length(x.args) == 3 lb = esc(x.args[3]) ub = esc(x.args[3]) @@ -758,7 +777,7 @@ macro defVar(args...) t = :Fixed else # Its a comparsion, but not using <= ... <= - error("in @defVar ($(string(x))): use the form lb <= ... <= ub.") + error("in @defVar: unexpected syntax $(string(x)).") end else # No bounds provided - free variable @@ -961,16 +980,17 @@ macro addNLConstraint(m, x, extra...) c = length(extra) == 1 ? x : nothing x = length(extra) == 1 ? extra[1] : x - (x.head != :comparison) && - error("in @addNLConstraint ($(string(x))): expected comparison operator (<=, >=, or ==).") + if VERSION < v"0.5.0-dev+3231" + x = comparison_to_call(x) + end # Strategy: build up the code for non-macro addconstraint, and if needed # we will wrap in loops to assign to the ConstraintRefs refcall, idxvars, idxsets, idxpairs, condition = buildrefsets(c) # Build the constraint - if length(x.args) == 3 + if isexpr(x, :call) # one-sided constraint # Simple comparison - move everything to the LHS - op = x.args[2] + op = x.args[1] if op == :(==) lb = 0.0 ub = 0.0 @@ -983,13 +1003,13 @@ macro addNLConstraint(m, x, extra...) else error("in @addNLConstraint ($(string(x))): expected comparison operator (<=, >=, or ==).") end - lhs = :($(x.args[1]) - $(x.args[3])) + lhs = :($(x.args[2]) - $(x.args[3])) code = quote c = NonlinearConstraint(@processNLExpr($m, $(esc(lhs))), $lb, $ub) push!($m.nlpdata.nlconstr, c) $(refcall) = ConstraintRef{NonlinearConstraint}($m, length($m.nlpdata.nlconstr)) end - elseif length(x.args) == 5 + elseif isexpr(x, :comparison) # ranged row if (x.args[2] != :<= && x.args[2] != :≤) || (x.args[4] != :<= && x.args[4] != :≤) error("in @addNLConstraint ($(string(x))): only ranged rows of the form lb <= expr <= ub are supported.") @@ -1067,10 +1087,13 @@ end # syntax is @defNLParam(m, p[i=1] == 2i) macro defNLParam(m, ex) m = esc(m) - @assert isexpr(ex, :comparison) + if VERSION < v"0.5.0-dev+3231" + ex = comparison_to_call(ex) + end + @assert isexpr(ex, :call) @assert length(ex.args) == 3 - @assert ex.args[2] == :(==) - c = ex.args[1] + @assert ex.args[1] == :(==) + c = ex.args[2] x = ex.args[3] refcall, idxvars, idxsets, idxpairs, condition = buildrefsets(c) diff --git a/src/nlp.jl b/src/nlp.jl index 223d330aa08..4958ffceb99 100644 --- a/src/nlp.jl +++ b/src/nlp.jl @@ -1109,12 +1109,20 @@ function MathProgBase.constr_expr(d::JuMPNLPEvaluator,i::Integer) if sense(constr) == :range return Expr(:comparison, constr.lb, :(<=), ex, :(<=), constr.ub) else - return Expr(:comparison, ex, sense(constr), rhs(constr)) + if VERSION > v"0.5-" + return Expr(:call, sense(constr), ex, rhs(constr)) + else + return Expr(:comparison, ex, sense(constr), rhs(constr)) + end end elseif i > nlin && i <= nlin + nquad i -= nlin qconstr = d.m.quadconstr[i] - return Expr(:comparison, quadToExpr(qconstr.terms, true), qconstr.sense, 0) + if VERSION > v"0.5-" + return Expr(:call, qconstr.sense, quadToExpr(qconstr.terms, true), 0) + else + return Expr(:comparison, quadToExpr(qconstr.terms, true), qconstr.sense, 0) + end else i -= nlin + nquad # for now, don't pass simplified expressions @@ -1125,7 +1133,11 @@ function MathProgBase.constr_expr(d::JuMPNLPEvaluator,i::Integer) if sense(constr) == :range return Expr(:comparison, constr.lb, :(<=), julia_expr, :(<=), constr.ub) else - return Expr(:comparison, julia_expr, sense(constr), rhs(constr)) + if VERSION > v"0.5-" + return Expr(:call, sense(constr), julia_expr, rhs(constr)) + else + return Expr(:comparison, julia_expr, sense(constr), rhs(constr)) + end end end end diff --git a/src/nlpmacros.jl b/src/nlpmacros.jl index 56769efa7e4..dbded35ef7e 100644 --- a/src/nlpmacros.jl +++ b/src/nlpmacros.jl @@ -39,6 +39,9 @@ function parseNLExpr(m, x, tapevar, parent, values) if haskey(operator_to_id,x.args[1]) # fast compile-time lookup operatorid = operator_to_id[x.args[1]] push!(block.args, :(push!($tapevar, NodeData(CALL, $operatorid, $parent)))) + elseif haskey(comparison_operator_to_id,x.args[1]) + operatorid = comparison_operator_to_id[x.args[1]] + push!(block.args, :(push!($tapevar, NodeData(COMPARISON, $operatorid, $parent)))) else # could be user defined opname = quot(x.args[1]) errorstring = "Unrecognized function $opname used in nonlinear expression." diff --git a/src/parseExpr_staged.jl b/src/parseExpr_staged.jl index a15060affe0..ada026f7b20 100644 --- a/src/parseExpr_staged.jl +++ b/src/parseExpr_staged.jl @@ -3,18 +3,16 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -# `a in b` is a comparison after JuliaLang/julia#13078 -const in_is_compare = VERSION >= v"0.5.0-dev+901" - function tryParseIdxSet(arg::Expr) - if arg.head === :(=) || (!in_is_compare && arg.head === :in) + # :in appears as arg.head only prior to 0.5 + if arg.head === :(=) || arg.head === :in @assert length(arg.args) == 2 return true, arg.args[1], arg.args[2] - elseif (in_is_compare && arg.head === :comparison && - length(arg.args) == 3 && arg.args[2] === :in) - return true, arg.args[1], arg.args[3] + elseif isexpr(arg, :call) && arg.args[1] === :in + return true, arg.args[2], arg.args[3] + else + return false, nothing, nothing end - return false, nothing, nothing end function parseIdxSet(arg::Expr) diff --git a/test/macros.jl b/test/macros.jl index 411849a4da1..7a6b63d7a92 100644 --- a/test/macros.jl +++ b/test/macros.jl @@ -11,8 +11,6 @@ const geq = JuMP.repl[:geq] const eq = JuMP.repl[:eq] const Vert = JuMP.repl[:Vert] const sub2 = JuMP.repl[:sub2] -# `a in b` is a comparison after JuliaLang/julia#13078 -const in_is_compare = VERSION >= v"0.5.0-dev+901" facts("[macros] Check Julia expression parsing") do sumexpr = :(sum{x[i,j] * y[i,j], i = 1:N, j in 1:M; i != j}) @@ -22,12 +20,11 @@ facts("[macros] Check Julia expression parsing") do @fact sumexpr.args[2].head --> :parameters @fact sumexpr.args[3] --> :(x[i,j] * y[i,j]) @fact sumexpr.args[4].head --> :(=) - if in_is_compare - @fact sumexpr.args[5].head --> :comparison - @fact length(sumexpr.args[5].args) --> 3 - @fact sumexpr.args[5].args[2] --> :in - else + if VERSION < v"0.5.0-dev+3231" @fact sumexpr.args[5].head --> :in + else + @fact sumexpr.args[5].head --> :call + @fact sumexpr.args[5].args[1] --> :in end sumexpr = :(sum{x[i,j] * y[i,j], i in 1:N, j = 1:M}) @@ -35,13 +32,13 @@ facts("[macros] Check Julia expression parsing") do @fact length(sumexpr.args) --> 4 @fact sumexpr.args[1] --> :sum @fact sumexpr.args[2] --> :(x[i,j] * y[i,j]) - if in_is_compare - @fact sumexpr.args[3].head --> :comparison - @fact length(sumexpr.args[3].args) --> 3 - @fact sumexpr.args[3].args[2] --> :in - else + if VERSION < v"0.5.0-dev+3231" @fact sumexpr.args[3].head --> :in + else + @fact sumexpr.args[3].head --> :call + @fact sumexpr.args[3].args[1] --> :in end + @fact sumexpr.args[4].head --> :(=) end