Skip to content

Commit 86da6f3

Browse files
authored
[breaking] refactor the parse_constraint methods (#2736)
1 parent 5afb189 commit 86da6f3

File tree

7 files changed

+387
-215
lines changed

7 files changed

+387
-215
lines changed

docs/src/developers/extensions.md

+101-28
Original file line numberDiff line numberDiff line change
@@ -142,39 +142,106 @@ x[2]_b
142142

143143
## Extend [`@constraint`](@ref)
144144

145-
The [`@constraint`](@ref) macro always calls the same three functions:
146-
* `parse_constraint`: is called at parsing time, it parses the constraint
147-
expression and returns a [`build_constraint`](@ref) call expression;
148-
* [`build_constraint`](@ref): given the functions and sets involved in the
149-
constraints, it returns a `AbstractConstraint`;
150-
* [`add_constraint`](@ref): given the model, the `AbstractConstraint`
151-
constructed in [`build_constraint`](@ref) and the constraint name, it stores
152-
them in the model and returns a `ConstraintRef`.
145+
The [`@constraint`](@ref) macro has three steps that can be intercepted and
146+
extended: parse time, build time, and add time.
153147

154-
Adding methods to these functions is the recommended way to extend the
155-
[`@constraint`](@ref) macro.
148+
### Parse
156149

157-
### Adding `parse_constraint` methods
150+
To extend the [`@constraint`](@ref) macro at parse time, implement one of the
151+
following methods:
158152

159-
Work in progress.
160-
### Adding `build_constraint` methods
153+
* [`parse_constraint_head`](@ref)
154+
* [`parse_constraint_call`](@ref)
155+
156+
!!! warning
157+
Extending the constraint macro at parse time is an advanced operation and
158+
has the potential to interfere with existing JuMP syntax. Please discuss
159+
with the [developer chatroom](https://gitter.im/JuliaOpt/jump-dev) before
160+
publishing any code that implements these methods.
161161

162-
There are typically two choices when creating a [`build_constraint`](@ref)
163-
method, either return an `AbstractConstraint` already supported by the
164-
model, i.e. `ScalarConstraint` or `VectorConstraint`, or a custom
165-
`AbstractConstraint` with a corresponding [`add_constraint`](@ref) method (see
166-
[Adding `add_constraint` methods](@ref)).
162+
[`parse_constraint_head`](@ref) should be implemented to intercept an expression
163+
based on the `.head` field of `Base.Expr`. For example:
164+
```jldoctest
165+
julia> using JuMP
167166
168-
### Adding `add_constraint` methods
167+
julia> const MutableArithmetics = JuMP._MA;
169168
170-
Work in progress.
169+
julia> model = Model(); @variable(model, x);
170+
171+
julia> function JuMP.parse_constraint_head(
172+
_error::Function,
173+
::Val{:(:=)},
174+
lhs,
175+
rhs,
176+
)
177+
println("Rewriting := as ==")
178+
new_lhs, parse_code = MutableArithmetics.rewrite(lhs)
179+
build_code = :(
180+
build_constraint($(_error), $(new_lhs), MOI.EqualTo($(rhs)))
181+
)
182+
return false, parse_code, build_code
183+
end
171184
172-
### Adding an extra positional argument
185+
julia> @constraint(model, x + x := 1.0)
186+
Rewriting := as ==
187+
2 x = 1.0
188+
```
173189

174-
We can also extend `@constraint` to handle additional positional arguments that
175-
effectively "tag" a particular constraint type and/or pass along additional
176-
information that we may want. For example, we can make a `MyConstrType` that
177-
modifies affine equalities:
190+
[`parse_constraint_call`](@ref) should be implemented to intercept an expression
191+
of the form `Expr(:call, op, args...)`. For example:
192+
```jldoctest
193+
julia> using JuMP
194+
195+
julia> const MutableArithmetics = JuMP._MA;
196+
197+
julia> model = Model(); @variable(model, x);
198+
199+
julia> function JuMP.parse_constraint_call(
200+
_error::Function,
201+
is_vectorized::Bool,
202+
::Val{:my_equal_to},
203+
lhs,
204+
rhs,
205+
)
206+
println("Rewriting my_equal_to to ==")
207+
new_lhs, parse_code = MutableArithmetics.rewrite(lhs)
208+
build_code = if is_vectorized
209+
:(build_constraint($(_error), $(new_lhs), MOI.EqualTo($(rhs)))
210+
)
211+
else
212+
:(build_constraint.($(_error), $(new_lhs), MOI.EqualTo($(rhs))))
213+
end
214+
return parse_code, build_code
215+
end
216+
217+
julia> @constraint(model, my_equal_to(x + x, 1.0))
218+
Rewriting my_equal_to to ==
219+
2 x = 1.0
220+
```
221+
222+
!!! tip
223+
When parsing a constraint you can recurse into sub-constraint (e.g., the
224+
`{expr}` in `z => {x <= 1}`) by calling [`parse_constraint`](@ref).
225+
226+
### Build
227+
228+
To extend the [`@constraint`](@ref) macro at build time, implement a new
229+
[`build_constraint`](@ref) method.
230+
231+
This may mean implementing a method for a specific function or set created at
232+
parse time, or it may mean implementing a method which handles additional
233+
positional arguments.
234+
235+
[`build_constraint`](@ref) must return an [`AbstractConstraint`](@ref), which
236+
can either be an [`AbstractConstraint`](@ref) already supported by JuMP, e.g., `ScalarConstraint` or `VectorConstraint`, or a custom
237+
[`AbstractConstraint`](@ref) with a corresponding [`add_constraint`](@ref)
238+
method (see [Add](@ref extension_add_constraint)).
239+
240+
!!! tip
241+
The easiest way to extend [`@constraint`](@ref) is via an additional
242+
positional argument to [`build_constraint`](@ref).
243+
244+
Here is an example of adding extra arguments to [`build_constraint`](@ref):
178245
```jldoctest
179246
julia> model = Model(); @variable(model, x);
180247
@@ -194,9 +261,15 @@ julia> function JuMP.build_constraint(
194261
julia> @constraint(model, my_con, x == 0, MyConstrType, d = 2)
195262
my_con : x ≤ 2.0
196263
```
197-
Note that only a single positional argument can be given to a particular
198-
constraint. Extensions that seek to pass multiple arguments (e.g., `Foo` and
199-
`Bar`) should combine them into one argument type (e.g., `FooBar`).
264+
265+
!!! note
266+
Only a single positional argument can be given to a particular constraint.
267+
Extensions that seek to pass multiple arguments (e.g., `Foo` and `Bar`)
268+
should combine them into one argument type (e.g., `FooBar`).
269+
270+
### [Add](@id extension_add_constraint)
271+
272+
Work in progress.
200273

201274
### Shapes
202275

docs/src/reference/extensions.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ build_variable
2323
```@docs
2424
build_constraint
2525
add_constraint
26-
sense_to_set
2726
AbstractShape
2827
shape
2928
reshape_vector
@@ -33,4 +32,8 @@ ScalarShape
3332
VectorShape
3433
SquareMatrixShape
3534
SymmetricMatrixShape
35+
operator_to_set
36+
parse_constraint
37+
parse_constraint_head
38+
parse_constraint_call
3639
```

src/complement.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function _build_complements_constraint(
6363
return errorf("second term must be a variable.")
6464
end
6565

66-
function parse_one_operator_constraint(
66+
function parse_constraint_call(
6767
errorf::Function,
6868
::Bool,
6969
::Union{Val{:complements},Val{:⟂}},

src/deprecate.jl

+4
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ function value(x, f::Function)
1818
@warn("`value(x, f::Function)` is deprecated. Use `value(f, x)` instead.")
1919
return value(f, x)
2020
end
21+
22+
@deprecate sense_to_set operator_to_set
23+
@deprecate parse_one_operator_constraint parse_constraint_call
24+
@deprecate parse_constraint_expr parse_constraint

src/indicator.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function _indicator_variable_set(_error::Function, expr::Expr)
3030
return expr, MOI.Indicator{MOI.ACTIVATE_ON_ONE}
3131
end
3232
end
33-
function parse_one_operator_constraint(
33+
function parse_constraint_call(
3434
_error::Function,
3535
vectorized::Bool,
3636
::Union{Val{:(=>)},Val{:⇒}},
@@ -45,7 +45,7 @@ function parse_one_operator_constraint(
4545
end
4646
rhs_con = rhs.args[1]
4747
rhs_vectorized, rhs_parsecode, rhs_buildcall =
48-
parse_constraint_expr(_error, rhs_con)
48+
parse_constraint(_error, rhs_con)
4949
if vectorized != rhs_vectorized
5050
_error("Inconsistent use of `.` in symbols to indicate vectorization.")
5151
end

0 commit comments

Comments
 (0)