Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove _constraint_macro function in macros/@constraint.jl #3615

Merged
merged 1 commit into from
Dec 10, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading