Skip to content

Commit

Permalink
Remove _constraint_macro function in macros/@constraint.jl
Browse files Browse the repository at this point in the history
  • Loading branch information
odow committed Dec 10, 2023
1 parent 19818ef commit 45ba5a2
Showing 1 changed file with 88 additions and 123 deletions.
211 changes: 88 additions & 123 deletions src/macros/@constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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...)
Expand Down

0 comments on commit 45ba5a2

Please sign in to comment.