@@ -142,39 +142,106 @@ x[2]_b
142
142
143
143
## Extend [ ` @constraint ` ] ( @ref )
144
144
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.
153
147
154
- Adding methods to these functions is the recommended way to extend the
155
- [ ` @constraint ` ] ( @ref ) macro.
148
+ ### Parse
156
149
157
- ### Adding ` parse_constraint ` methods
150
+ To extend the [ ` @constraint ` ] ( @ref ) macro at parse time, implement one of the
151
+ following methods:
158
152
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.
161
161
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
167
166
168
- ### Adding ` add_constraint ` methods
167
+ julia> const MutableArithmetics = JuMP._MA;
169
168
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
171
184
172
- ### Adding an extra positional argument
185
+ julia> @constraint(model, x + x := 1.0)
186
+ Rewriting := as ==
187
+ 2 x = 1.0
188
+ ```
173
189
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 ) :
178
245
``` jldoctest
179
246
julia> model = Model(); @variable(model, x);
180
247
@@ -194,9 +261,15 @@ julia> function JuMP.build_constraint(
194
261
julia> @constraint(model, my_con, x == 0, MyConstrType, d = 2)
195
262
my_con : x ≤ 2.0
196
263
```
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.
200
273
201
274
### Shapes
202
275
0 commit comments