From 293af025d2c27021d679a58270abf30423fa8cd0 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 8 Dec 2023 08:55:14 +1300 Subject: [PATCH] Refactor plural macro generation code (#3603) --- src/macros.jl | 501 ++++++++++++++++++++++++-------------------------- 1 file changed, 241 insertions(+), 260 deletions(-) diff --git a/src/macros.jl b/src/macros.jl index 2f710e9e8d8..e3edb8bbc1d 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -1221,6 +1221,41 @@ function _wrap_let(model, code) return code end +function _plural_macro_code(model, block, macro_sym) + if !Meta.isexpr(block, :block) + error( + "Invalid syntax for $(macro_sym)s. The second argument must be a " * + "`begin end` block. For example:\n" * + "```julia\n$(macro_sym)s(model, begin\n # ... lines here ...\nend)\n```.", + ) + end + @assert block.args[1] isa LineNumberNode + last_line = block.args[1] + code = Expr(:tuple) + jump_macro = Expr(:., JuMP, QuoteNode(macro_sym)) + for arg in block.args + if arg isa LineNumberNode + last_line = arg + elseif Meta.isexpr(arg, :tuple) # Line with commas. + macro_call = Expr(:macrocall, jump_macro, last_line, model) + # Because of the precedence of "=", Keyword arguments have to appear + # like: `x, (start = 10, lower_bound = 5)` + for ex in arg.args + if isexpr(ex, :tuple) # embedded tuple + append!(macro_call.args, ex.args) + else + push!(macro_call.args, ex) + end + end + push!(code.args, esc(macro_call)) + else # Stand-alone symbol or expression. + macro_call = Expr(:macrocall, jump_macro, last_line, model, arg) + push!(code.args, esc(macro_call)) + end + end + return code +end + """ _constraint_macro( args, macro_name::Symbol, parsefun::Function, source::LineNumberNode @@ -1445,126 +1480,6 @@ macro constraint(args...) end """ - @build_constraint(constraint_expr) - -Constructs a `ScalarConstraint` or `VectorConstraint` using the same -machinery as [`@constraint`](@ref) but without adding the constraint to a model. - -Constraints using broadcast operators like `x .<= 1` are also supported and will -create arrays of `ScalarConstraint` or `VectorConstraint`. - -## Example - -```jldoctest -julia> model = Model(); - -julia> @variable(model, x); - -julia> @build_constraint(2x >= 1) -ScalarConstraint{AffExpr, MathOptInterface.GreaterThan{Float64}}(2 x, MathOptInterface.GreaterThan{Float64}(1.0)) -``` -""" -macro build_constraint(constraint_expr) - function _error(str...) - return _macro_error( - :build_constraint, - (constraint_expr,), - __source__, - str..., - ) - end - - if isa(constraint_expr, Symbol) - _error( - "Incomplete constraint specification $constraint_expr. " * - "Are you missing a comparison (<=, >=, or ==)?", - ) - end - - is_vectorized, parse_code, build_call = - parse_constraint(_error, constraint_expr) - result_variable = gensym() - code = quote - $parse_code - $result_variable = $build_call - end - - return code -end - -_add_JuMP_prefix(s::Symbol) = Expr(:., JuMP, :($(QuoteNode(s)))) - -function _pluralize_macro(mac, sym) - error_message = - "Invalid syntax for @$mac. The second argument must be a `begin end` " * - "block. For example:\n" * - "```julia\n@$mac(model, begin\n # ... lines here ...\nend)\n```." - @eval begin - macro $mac(m, x) - if !(isa(x, Expr) && x.head == :block) - # We do a weird string interpolation here so that it gets - # interpolated at compile time, not run-time. - error($error_message) - end - @assert isa(x.args[1], LineNumberNode) - lastline = x.args[1] - code = Expr(:tuple) - for it in x.args - if isa(it, LineNumberNode) - lastline = it - elseif isexpr(it, :tuple) # line with commas - args = [] - # Keyword arguments have to appear like: - # x, (start = 10, lower_bound = 5) - # because of the precedence of "=". - for ex in it.args - if isexpr(ex, :tuple) # embedded tuple - append!(args, ex.args) - else - push!(args, ex) - end - end - macro_call = esc( - Expr( - :macrocall, - $(_add_JuMP_prefix(sym)), - lastline, - m, - args..., - ), - ) - push!(code.args, macro_call) - else # stand-alone symbol or expression - macro_call = esc( - Expr( - :macrocall, - $(_add_JuMP_prefix(sym)), - lastline, - m, - it, - ), - ) - push!(code.args, macro_call) - end - end - return code - end - end -end - -for (mac, sym) in [ - (:NLparameters, Symbol("@NLparameter")), - (:constraints, Symbol("@constraint")), - (:NLconstraints, Symbol("@NLconstraint")), - (:variables, Symbol("@variable")), - (:expressions, Symbol("@expression")), - (:NLexpressions, Symbol("@NLexpression")), -] - _pluralize_macro(mac, sym) -end - -# Doc strings for the auto-generated macro pluralizations -@doc """ @constraints(model, args...) Adds groups of constraints at once, in the same fashion as the @@ -1603,106 +1518,19 @@ Subject to x ≥ 1 -w + y ≤ 2 ``` -""" :(@constraints) - -@doc """ - @variables(model, args...) - -Adds multiple variables to model at once, in the same fashion as the -[`@variable`](@ref) macro. - -The model must be the first argument, and multiple variables can be added on -multiple lines wrapped in a `begin ... end` block. - -The macro returns a tuple containing the variables that were defined. - -## Example - -```jldoctest -julia> model = Model(); - -julia> @variables(model, begin - x - y[i = 1:2] >= 0, (start = i) - z, Bin, (start = 0, base_name = "Z") - end) -(x, VariableRef[y[1], y[2]], Z) -``` - -!!! note - Keyword arguments must be contained within parentheses (refer to the example - above). -""" :(@variables) - -@doc """ - @expressions(model, args...) - -Adds multiple expressions to model at once, in the same fashion as the -[`@expression`](@ref) macro. - -The model must be the first argument, and multiple expressions can be added on -multiple lines wrapped in a `begin ... end` block. - -The macro returns a tuple containing the expressions that were defined. - -## Example - -```jldoctest -julia> model = Model(); - -julia> @variable(model, x); - -julia> @variable(model, y); - -julia> @variable(model, z[1:2]); - -julia> a = [4, 5]; - -julia> @expressions(model, begin - my_expr, x^2 + y^2 - my_expr_1[i = 1:2], a[i] - z[i] - end) -(x² + y², AffExpr[-z[1] + 4, -z[2] + 5]) -``` -""" :(@expressions) - -@doc """ - @NLparameters(model, args...) - -Create and return multiple nonlinear parameters attached to model `model`, in -the same fashion as [`@NLparameter`](@ref) macro. - -The model must be the first argument, and multiple parameters can be added on -multiple lines wrapped in a `begin ... end` block. Distinct parameters need to -be placed on separate lines as in the following example. - -The macro returns a tuple containing the parameters that were defined. - -## Example - -```jldoctest -julia> model = Model(); - -julia> @NLparameters(model, begin - x == 10 - b == 156 - end); - -julia> value(x) -10.0 -``` - """ :(@NLparameters) - -@doc """ - @NLconstraints(model, args...) +""" +macro constraints(model, block) + return _plural_macro_code(model, block, Symbol("@constraint")) +end -Adds multiple nonlinear constraints to model at once, in the same fashion as -the [`@NLconstraint`](@ref) macro. +""" + @build_constraint(constraint_expr) -The model must be the first argument, and multiple constraints can be added on -multiple lines wrapped in a `begin ... end` block. +Constructs a `ScalarConstraint` or `VectorConstraint` using the same +machinery as [`@constraint`](@ref) but without adding the constraint to a model. -The macro returns a tuple containing the constraints that were defined. +Constraints using broadcast operators like `x .<= 1` are also supported and will +create arrays of `ScalarConstraint` or `VectorConstraint`. ## Example @@ -1711,53 +1539,37 @@ julia> model = Model(); julia> @variable(model, x); -julia> @variable(model, y); - -julia> @variable(model, t); - -julia> @variable(model, z[1:2]); - -julia> a = [4, 5]; - -julia> @NLconstraints(model, begin - t >= sqrt(x^2 + y^2) - [i = 1:2], z[i] <= log(a[i]) - end) -((t - sqrt(x ^ 2.0 + y ^ 2.0)) - 0.0 ≥ 0, NonlinearConstraintRef{ScalarShape}[(z[1] - log(4.0)) - 0.0 ≤ 0, (z[2] - log(5.0)) - 0.0 ≤ 0]) +julia> @build_constraint(2x >= 1) +ScalarConstraint{AffExpr, MathOptInterface.GreaterThan{Float64}}(2 x, MathOptInterface.GreaterThan{Float64}(1.0)) ``` -""" :(@NLconstraints) - -@doc """ - @NLexpressions(model, args...) - -Adds multiple nonlinear expressions to model at once, in the same fashion as the -[`@NLexpression`](@ref) macro. - -The model must be the first argument, and multiple expressions can be added on -multiple lines wrapped in a `begin ... end` block. - -The macro returns a tuple containing the expressions that were defined. - -## Example - -```jldoctest -julia> model = Model(); - -julia> @variable(model, x); - -julia> @variable(model, y); +""" +macro build_constraint(constraint_expr) + function _error(str...) + return _macro_error( + :build_constraint, + (constraint_expr,), + __source__, + str..., + ) + end -julia> @variable(model, z[1:2]); + if isa(constraint_expr, Symbol) + _error( + "Incomplete constraint specification $constraint_expr. " * + "Are you missing a comparison (<=, >=, or ==)?", + ) + end -julia> a = [4, 5]; + is_vectorized, parse_code, build_call = + parse_constraint(_error, constraint_expr) + result_variable = gensym() + code = quote + $parse_code + $result_variable = $build_call + end -julia> @NLexpressions(model, begin - my_expr, sqrt(x^2 + y^2) - my_expr_1[i = 1:2], log(a[i]) - z[i] - end) -(subexpression[1]: sqrt(x ^ 2.0 + y ^ 2.0), NonlinearExpression[subexpression[2]: log(4.0) - z[1], subexpression[3]: log(5.0) - z[2]]) -``` -""" :(@NLexpressions) + return code +end """ _moi_sense(_error::Function, sense) @@ -1968,6 +1780,41 @@ macro expression(args...) return _finalize_macro(m, macro_code, __source__) end +""" + @expressions(model, args...) + +Adds multiple expressions to model at once, in the same fashion as the +[`@expression`](@ref) macro. + +The model must be the first argument, and multiple expressions can be added on +multiple lines wrapped in a `begin ... end` block. + +The macro returns a tuple containing the expressions that were defined. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> @variable(model, y); + +julia> @variable(model, z[1:2]); + +julia> a = [4, 5]; + +julia> @expressions(model, begin + my_expr, x^2 + y^2 + my_expr_1[i = 1:2], a[i] - z[i] + end) +(x² + y², AffExpr[-z[1] + 4, -z[2] + 5]) +``` +""" +macro expressions(model, block) + return _plural_macro_code(model, block, Symbol("@expression")) +end + _esc_non_constant(x::Number) = x _esc_non_constant(x::Expr) = isexpr(x, :quote) ? x : esc(x) _esc_non_constant(x) = esc(x) @@ -3052,6 +2899,38 @@ macro variable(args...) return _finalize_macro(model, macro_code, __source__) end +""" + @variables(model, args...) + +Adds multiple variables to model at once, in the same fashion as the +[`@variable`](@ref) macro. + +The model must be the first argument, and multiple variables can be added on +multiple lines wrapped in a `begin ... end` block. + +The macro returns a tuple containing the variables that were defined. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variables(model, begin + x + y[i = 1:2] >= 0, (start = i) + z, Bin, (start = 0, base_name = "Z") + end) +(x, VariableRef[y[1], y[2]], Z) +``` + +!!! note + Keyword arguments must be contained within parentheses (refer to the example + above). +""" +macro variables(model, block) + return _plural_macro_code(model, block, Symbol("@variable")) +end + """ @NLobjective(model, sense, expression) @@ -3160,6 +3039,43 @@ macro NLconstraint(m, x, args...) return _finalize_macro(esc_m, macro_code, __source__) end +""" + @NLconstraints(model, args...) + +Adds multiple nonlinear constraints to model at once, in the same fashion as +the [`@NLconstraint`](@ref) macro. + +The model must be the first argument, and multiple constraints can be added on +multiple lines wrapped in a `begin ... end` block. + +The macro returns a tuple containing the constraints that were defined. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> @variable(model, y); + +julia> @variable(model, t); + +julia> @variable(model, z[1:2]); + +julia> a = [4, 5]; + +julia> @NLconstraints(model, begin + t >= sqrt(x^2 + y^2) + [i = 1:2], z[i] <= log(a[i]) + end) +((t - sqrt(x ^ 2.0 + y ^ 2.0)) - 0.0 ≥ 0, NonlinearConstraintRef{ScalarShape}[(z[1] - log(4.0)) - 0.0 ≤ 0, (z[2] - log(5.0)) - 0.0 ≤ 0]) +``` +""" +macro NLconstraints(model, block) + return _plural_macro_code(model, block, Symbol("@NLconstraint")) +end + """ @NLexpression(args...) @@ -3243,6 +3159,41 @@ macro NLexpression(args...) return _finalize_macro(esc_m, macro_code, __source__) end +""" + @NLexpressions(model, args...) + +Adds multiple nonlinear expressions to model at once, in the same fashion as the +[`@NLexpression`](@ref) macro. + +The model must be the first argument, and multiple expressions can be added on +multiple lines wrapped in a `begin ... end` block. + +The macro returns a tuple containing the expressions that were defined. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> @variable(model, y); + +julia> @variable(model, z[1:2]); + +julia> a = [4, 5]; + +julia> @NLexpressions(model, begin + my_expr, sqrt(x^2 + y^2) + my_expr_1[i = 1:2], log(a[i]) - z[i] + end) +(subexpression[1]: sqrt(x ^ 2.0 + y ^ 2.0), NonlinearExpression[subexpression[2]: log(4.0) - z[1], subexpression[3]: log(5.0) - z[2]]) +``` +""" +macro NLexpressions(model, block) + return _plural_macro_code(model, block, Symbol("@NLexpression")) +end + """ @NLparameter(model, param == value) @@ -3389,3 +3340,33 @@ macro NLparameter(model, args...) end return _finalize_macro(esc_m, macro_code, __source__) end + +""" + @NLparameters(model, args...) + +Create and return multiple nonlinear parameters attached to model `model`, in +the same fashion as [`@NLparameter`](@ref) macro. + +The model must be the first argument, and multiple parameters can be added on +multiple lines wrapped in a `begin ... end` block. Distinct parameters need to +be placed on separate lines as in the following example. + +The macro returns a tuple containing the parameters that were defined. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @NLparameters(model, begin + x == 10 + b == 156 + end); + +julia> value(x) +10.0 +``` +""" +macro NLparameters(model, block) + return _plural_macro_code(model, block, Symbol("@NLparameter")) +end