From 45ba5a28e31cd4bf9d4ba53c9d3fabb4bb6c2a60 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 11 Dec 2023 10:35:49 +1300 Subject: [PATCH] Remove _constraint_macro function in macros/@constraint.jl --- src/macros/@constraint.jl | 211 ++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 123 deletions(-) diff --git a/src/macros/@constraint.jl b/src/macros/@constraint.jl index 0a1780763bf..bc898801c7e 100644 --- a/src/macros/@constraint.jl +++ b/src/macros/@constraint.jl @@ -78,125 +78,8 @@ enable this syntax by defining extensions of `build_constraint(error_fn, func, set, my_arg; kwargs...)`. This produces the user syntax: `@constraint(model, ref[...], expr, my_arg, kwargs...)`. """ -macro constraint(args...) - return _constraint_macro(args, :constraint, parse_constraint, __source__) -end - -""" - @constraints(model, args...) - -Adds groups of constraints at once, in the same fashion as the -[`@constraint`](@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, w); - -julia> @variable(model, x); - -julia> @variable(model, y); - -julia> @variable(model, z[1:3]); - -julia> @constraints(model, begin - x >= 1 - y - w <= 2 - sum_to_one[i=1:3], z[i] + y == 1 - end); - -julia> print(model) -Feasibility -Subject to - sum_to_one[1] : y + z[1] = 1 - sum_to_one[2] : y + z[2] = 1 - sum_to_one[3] : y + z[3] = 1 - x ≥ 1 - -w + y ≤ 2 -``` -""" -macro constraints(model, block) - return _plural_macro_code(model, block, Symbol("@constraint")) -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(arg) - function error_fn(str...) - return _macro_error(:build_constraint, (arg,), __source__, str...) - end - if arg isa Symbol - error_fn( - "Incomplete constraint specification $arg. " * - "Are you missing a comparison (<=, >=, or ==)?", - ) - end - _, parse_code, build_call = parse_constraint(error_fn, arg) - return quote - $parse_code - $build_call - end -end - -""" - _constraint_macro( - args, - macro_name::Symbol, - parse_fn::Function, - source::LineNumberNode, - ) - -Returns the code for the macro `@constraint args...` of syntax -```julia -@constraint(model, con, extra_arg, kwargs...) # single constraint -@constraint(model, ref, con, extra_arg, kwargs...) # group of constraints -``` - -The expression `con` is parsed by `parse_fn` which returns a `build_constraint` -call code that, when executed, returns an `AbstractConstraint`. The macro -keyword arguments (except the `container` keyword argument which is used to -determine the container type) are added to the `build_constraint` call. The -`extra_arg` is added as terminal positional argument to the `build_constraint` -call along with any keyword arguments (apart from `container` and `base_name`). -The returned value of this call is passed to `add_constraint` which returns a -constraint reference. - -`source` is a `LineNumberNode` that should refer to the line that the macro was -called from in the user's code. One way of generating this is via the hidden -variable `__source__`. -""" -function _constraint_macro( - input_args, - macro_name::Symbol, - parse_fn::Function, - source::LineNumberNode, -) - error_fn(str...) = _macro_error(macro_name, input_args, source, str...) +macro constraint(input_args...) + error_fn(str...) = _macro_error(:constraint, input_args, __source__, str...) args, kwargs, container = Containers._extract_kw_args(input_args) if length(args) < 2 && !isempty(kwargs) error_fn( @@ -206,7 +89,7 @@ function _constraint_macro( elseif length(args) < 2 error_fn("Not enough arguments") elseif Meta.isexpr(args[2], :block) - error_fn("Invalid syntax. Did you mean to use `@$(macro_name)s`?") + error_fn("Invalid syntax. Did you mean to use `@constraints`?") end model, y, extra = esc(args[1]), args[2], args[3:end] # Determine if a reference/container argument was given by the user @@ -237,7 +120,7 @@ function _constraint_macro( "different name for the index.", ) end - is_vectorized, parse_code, build_call = parse_fn(error_fn, x) + is_vectorized, parse_code, build_call = parse_constraint(error_fn, x) _add_positional_args(build_call, extra) _add_kw_args(build_call, kwargs; exclude = [:base_name, :set_string_name]) base_name = _get_kwarg_value( @@ -250,7 +133,7 @@ function _constraint_macro( :set_string_name; default = :(set_string_names_on_creation($model)), ) - name_expr = Expr(:if, set_name_flag, _name_call(base_name, index_vars), "") + name_expr = :($set_name_flag ? $(_name_call(base_name, index_vars)) : "") code = if is_vectorized quote $parse_code @@ -273,12 +156,87 @@ function _constraint_macro( return _finalize_macro( model, Containers.container_code(index_vars, indices, code, container), - source; + __source__; register_name = Containers._get_name(c), wrap_let = true, ) end +""" + @constraints(model, args...) + +Adds groups of constraints at once, in the same fashion as the +[`@constraint`](@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, w); + +julia> @variable(model, x); + +julia> @variable(model, y); + +julia> @variable(model, z[1:3]); + +julia> @constraints(model, begin + x >= 1 + y - w <= 2 + sum_to_one[i=1:3], z[i] + y == 1 + end); + +julia> print(model) +Feasibility +Subject to + sum_to_one[1] : y + z[1] = 1 + sum_to_one[2] : y + z[2] = 1 + sum_to_one[3] : y + z[3] = 1 + x ≥ 1 + -w + y ≤ 2 +``` +""" +macro constraints(model, block) + return _plural_macro_code(model, block, Symbol("@constraint")) +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(arg) + function error_fn(str...) + return _macro_error(:build_constraint, (arg,), __source__, str...) + end + _, parse_code, build_call = parse_constraint(error_fn, arg) + return quote + $parse_code + $build_call + end +end + """ parse_constraint(error_fn::Function, expr::Expr) @@ -320,6 +278,13 @@ function parse_constraint(error_fn::Function, expr::Expr) return parse_constraint_head(error_fn, Val(expr.head), expr.args...) end +function parse_constraint(error_fn::Function, arg) + return error_fn( + "Incomplete constraint specification $arg. Are you missing a " * + "comparison (<=, >=, or ==)?", + ) +end + """ parse_constraint_head(error_fn::Function, ::Val{head}, args...)