Skip to content

Commit bb80481

Browse files
odowmlubin
authored andcommitted
new modification syntax (#388)
* Begin refactor from `modifyconstraint(m, c, set)` to `set!(m, ConstraintSet(), c, set)`. This patch currently errors as it needs to be implemented for src/Utilities/model.jl, but I'm not sure where to start. * Update Model (#389) Updates from @blegat * Resolve ambiguity errors relating to canset and set! ConstraintSet. * Fixes to get tests passing * Refactor modfiyconstraint!(m,c,f) to set!(m,ConstraintFunction(),c,f) * Refactor modifyconstraint! to modify! and canmodifycontraint to canmodify. These functions now only apply to AbstractFunctionModification changes. * Fix definition of canmodify. This patch does not pass tests due to problems with canmodify in the bridge code. * Add broken tests * Add unit tests for setting ConstraintSet * Refactor modifyobjective! to modify!(m, ObjectiveFunction, change). * Add more tests for modifications * Begin documentation of problem modificatino * Add more modification tests * Add test for modifying constraint function. There is a broken test and one commented out as MockOptimizer fails getting the constraint function. * Fix bridges tests * Fix set! ConstraintFunction for mock optimizer * Add more tests for default fallbacks * Add more documentation, tests for MultirowChange, and fix associated bug. * Refactor transformconstraint! to transform!. Currently this is not tested as transform! is not implemented in MockOptimizer * Update modification docs with comments from @mlubin * Address method ambiguity * Refactor canset and set! to reduce method ambiguities * Fix typo * Fix cantransform * Address comments by @blegat * Re-enable transform test * Address comments by @mlubin
1 parent e9bf3f1 commit bb80481

26 files changed

+1111
-255
lines changed

docs/src/apimanual.md

Lines changed: 200 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,11 +412,210 @@ primal_variable_result = MOI.get(optimizer, MOI.VariablePrimal(), x)
412412
@show primal_variable_result
413413
```
414414

415+
## Problem modification
416+
417+
In addition to adding and deleting constraints and variables, MathOptInterface
418+
supports modifying, in-place, coefficients in the constraints and the objective
419+
function of a model. These modifications can be grouped into two categories:
420+
modifications which replace the set of function of a constraint with a new set
421+
or function; and modifications which change, in-place, a component of a
422+
function.
423+
424+
In the following, we detail the various ways this can be
425+
achieved. Readers should note that some solvers will not support problem
426+
modification.
427+
428+
### Replacements
429+
430+
First, we discuss how to replace the set or function of a constraint with a new
431+
instance of the same type.
432+
433+
#### The set of a constraint
434+
435+
Given a constraint of type `F`-in-`S` (see [Constraints by function-set pairs](@ref)
436+
above for an explanation), we can modify parameters (but not the type) of the
437+
set `S` by replacing it with a new instance of the same type. For example,
438+
given the variable bound ``x \\le 1``:
439+
```julia
440+
c = addconstraint(m, SingleVariable(x), LessThan(1.0))
441+
```
442+
we can modify the set so that the bound now ``x \\le 2`` as follows:
443+
```julia
444+
set!(m, ConstraintSet(), c, LessThan(2.0))
445+
```
446+
where `m` is our [`ModelLike`](@ref) model. However, the following will fail as
447+
the new set (`GreaterThan`) is of a different type to the original set
448+
(`LessThan`):
449+
```julia
450+
set!(m, ConstraintSet(), c, GreaterThan(2.0)) # errors
451+
```
452+
If our constraint is an affine inequality, then this corresponds to modifying
453+
the right-hand side of a constraint in linear programming.
454+
455+
In some special cases, solvers may support efficiently changing the set of a
456+
constraint (for example, from [`LessThan`](@ref) to [`GreaterThan`](@ref)). For
457+
these cases, MathOptInterface provides the [`transform!`](@ref) method. For
458+
example, instead of the error we observed above, the following will work:
459+
```julia
460+
c2 = transform!(m, c, GreaterThan(1.0))
461+
```
462+
The [`transform!`](@ref) function returns a new constraint index, and the old
463+
constraint index (i.e., `c`) is no longer valid:
464+
```julia
465+
isvalid(m, c) # false
466+
isvalid(m, c2) # true
467+
```
468+
Also note that [`transform!`](@ref) cannot be called with a set of the same
469+
type; [`set!`](@ref) should be used instead.
470+
471+
#### The function of a constraint
472+
473+
Given a constraint of type `F`-in-`S` (see [Constraints by function-set pairs](@ref)
474+
above for an explanation), it is also possible to modify the function of type
475+
`F` by replacing it with a new instance of the same type. For example, given the
476+
variable bound ``x \\le 1``:
477+
```julia
478+
c = addconstraint(m, SingleVariable(x), LessThan(1.0))
479+
```
480+
we can modify the function so that the bound now ``y \\le 1`` as follows:
481+
```julia
482+
set!(m, ConstraintFunction(), c, SingleVariable(y))
483+
```
484+
where `m` is our [`ModelLike`](@ref) model. However, the following will fail as
485+
the new function is of a different type to the original function:
486+
```julia
487+
set!(m, ConstraintFunction(), c,
488+
ScalarAffineFunction([ScalarAffineTerm(1.0, x)], 0.0)
489+
)
490+
```
491+
492+
### In-place modification
493+
494+
The second type of problem modifications allow the user to modify, in-place, the
495+
coefficients of a function. Currently, four modifications are supported by
496+
MathOptInterface. They are:
497+
1. change the constant term in a scalar function;
498+
2. change the constant term in a vector function;
499+
3. change the affine coefficients in a scalar function; and
500+
4. change the affine coefficients in a vector function.
501+
502+
To distinguish between the replacement of the function with a new instance
503+
(described above) and the modification of an existing function, the in-place
504+
modifications use the [`modify!`](@ref) method:
505+
```julia
506+
modify!(model, index, change::AbstractFunctionModification)
507+
```
508+
[`modify!`](@ref) takes three arguments. The first is the [`ModelLike`](@ref)
509+
model `model`, the second is the constraint index, and the third is an instance
510+
of an [`AbstractFunctionModification`](@ref).
511+
512+
We now detail each of these four in-place modifications.
513+
514+
#### Constant term in a scalar function
515+
516+
MathOptInterface supports is the ability to modify the constant term within a
517+
[`ScalarAffineFunction`](@ref) and a [`ScalarQuadraticFunction`](@ref) using the
518+
[`ScalarConstantChange`](@ref) subtype of
519+
[`AbstractFunctionModification`](@ref). This includes the objective function, as
520+
well as the function in a function-pair constraint.
521+
522+
For example, consider a problem `m` with the objective ``\\max 1.0x + 0.0``:
523+
```julia
524+
set!(m,
525+
ObjectiveFunction{ScalarAffineFunction{Float64}}(),
526+
ScalarAffineFunction([ScalarAffineTerm(1.0, x)], 0.0)
527+
)
528+
```
529+
We can modify the constant term in the objective function as follows:
530+
```julia
531+
modify!(m,
532+
ObjectiveFunction{ScalarAffineFunction{Float64}}(),
533+
ScalarConstantChange(1.0)
534+
)
535+
```
536+
The objective function will now be ``\\max 1.0x + 1.0``.
537+
538+
#### Constant terms in a vector function
539+
540+
We can modify the constant terms in a [`VectorAffineFunction`](@ref) or a
541+
[`VectorQuadraticFunction`](@ref) using the [`VectorConstantChange`](@ref)
542+
subtype of [`AbstractFunctionModification`](@ref).
543+
544+
For example, consider a model with the following
545+
`VectorAffineFunction`-in-`Nonpositives` constraint:
546+
```julia
547+
c = addconstraint!(m,
548+
VectorAffineFunction([
549+
VectorAffineTerm(1, ScalarAffineTerm(1.0, x)),
550+
VectorAffineTerm(1, ScalarAffineTerm(2.0, y))
551+
],
552+
[0.0, 0.0]
553+
),
554+
Nonpositives(2)
555+
)
556+
```
557+
We can modify the constant vector in the [`VectorAffineFunction`](@ref) from
558+
`[0.0, 0.0]` to `[1.0, 2.0]` as follows:
559+
```julia
560+
modify!(m, c, VectorConstantChange([1.0, 2.0])
561+
)
562+
```
563+
The constraints are now ``1.0x + 1.0 \\le 0.0`` and ``2.0y + 2.0 \\le 0.0``.
564+
565+
#### Affine coefficients in a scalar function
566+
567+
In addition to modifying the constant terms in a function, we can also modify
568+
the affine variable coefficients in an [`ScalarAffineFunction`](@ref) or a
569+
[`ScalarQuadraticFunction`](@ref) using the [`ScalarCoefficientChange`](@ref)
570+
subtype of [`AbstractFunctionModification`](@ref).
571+
572+
For example, given the constraint ``1.0x <= 1.0``:
573+
```julia
574+
c = addconstraint!(m,
575+
ScalarAffineFunction([ScalarAffineTerm(1.0, x)], 0.0),
576+
LessThan(1.0)
577+
)
578+
```
579+
we can modify the coefficient of the `x` variable so that the constraint becomes
580+
``2.0x <= 1.0`` as follows:
581+
```julia
582+
modify!(m, c, ScalarCoefficientChange(x, 2.0))
583+
```
584+
585+
[`ScalarCoefficientChange`](@ref) can also be used to modify the objective
586+
function by passing an instance of [`ObjectiveFunction`](@ref) instead of the
587+
constraint index `c` as we saw above.
588+
589+
#### Affine coefficients in a vector function
590+
591+
Finally, the last modification supported by MathOptInterface is the ability to
592+
modify the affine coefficients of a single variable in a
593+
[`VectorAffineFunction`](@ref) or a [`VectorQuadraticFunction`](@ref) using the
594+
[`MultirowChange`](@ref) subtype of [`AbstractFunctionModification`](@ref).
595+
596+
For example, given the constraint ``Ax \\in \\mathbb{R}^2_+``, where
597+
``A = [1.0, 2.0]^\\top``:
598+
```julia
599+
c = addconstraint!(m,
600+
VectorAffineFunction([
601+
VectorAffineTerm(1, ScalarAffineTerm(1.0, x)),
602+
VectorAffineTerm(1, ScalarAffineTerm(2.0, x))
603+
],
604+
[0.0, 0.0]
605+
),
606+
Nonnegatives(2)
607+
)
608+
```
609+
we can modify the coefficients of the `x` variable so that the `A` matrix
610+
becomes ``A = [3.0, 4.0]^\\top`` as follows:
611+
```julia
612+
modify!(m, c, MultirowChange(x, [3.0, 4.0]))
613+
```
614+
415615
## Advanced
416616

417617
### Duals
418618

419-
420619
Conic duality is the starting point for MOI's duality conventions. When all functions are affine (or coordinate projections), and all constraint sets are closed convex cones, the model may be called a conic optimization problem.
421620
For conic-form minimization problems, the primal is:
422621

@@ -529,9 +728,6 @@ If the set ``C_i`` of the section [Duals](@ref) is one of these three cones,
529728
then the rows of the matrix ``A_i`` corresponding to off-diagonal entries are twice the value of the `coefficients` field in the `VectorAffineFunction` for the corresponding rows.
530729
See [`PositiveSemidefiniteConeTriangle`](@ref MathOptInterface.PositiveSemidefiniteConeTriangle) for details.
531730

532-
### Modifying a model
533-
534-
[Explain `modifyconstraint!` and `modifyobjective!`.]
535731

536732
### Constraint bridges
537733

docs/src/apireference.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,10 @@ isvalid(::ModelLike,::ConstraintIndex)
163163
canaddconstraint
164164
addconstraint!
165165
addconstraints!
166-
modifyconstraint!
167-
canmodifyconstraint
168-
transformconstraint!
169-
cantransformconstraint
166+
modify!
167+
canmodify
168+
transform!
169+
cantransform
170170
supportsconstraint
171171
```
172172

@@ -209,6 +209,7 @@ output_dimension
209209

210210
List of function modifications.
211211
```@docs
212+
AbstractFunctionModification
212213
ScalarConstantChange
213214
VectorConstantChange
214215
ScalarCoefficientChange
@@ -261,8 +262,8 @@ dimension
261262
Functions for modifying objective functions. Use `ObjectiveFunction` and `ObjectiveSense` to set and query the objective function.
262263

263264
```@docs
264-
modifyobjective!
265-
canmodifyobjective
265+
modify!
266+
canmodify
266267
```
267268

268269
## Nonlinear programming (NLP)

perf/cachingoptimizer.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ MOIU.resetoptimizer!(caching_optimizer) # detach optimizer
1414
v = MOI.addvariables!(caching_optimizer, 2)
1515
cf = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([0.0, 0.0], v), 0.0)
1616
c = MOI.addconstraint!(caching_optimizer, cf, MOI.Interval(-Inf, 1.0))
17-
@btime MOI.modifyconstraint!($caching_optimizer, $c, $(MOI.Interval(0.0, 2.0)))
17+
@btime MOI.set!($caching_optimizer, $(MOI.ConstraintSet()), $c, $(MOI.Interval(0.0, 2.0)))
1818
MOIU.attachoptimizer!(caching_optimizer)
19-
@btime MOI.modifyconstraint!($caching_optimizer, $c, $(MOI.Interval(0.0, 2.0)))
20-
@btime MOI.modifyconstraint!($(caching_optimizer.model_cache), $c, $(MOI.Interval(0.0, 2.0)))
21-
@btime MOI.modifyconstraint!($(caching_optimizer.optimizer), $(caching_optimizer.model_to_optimizer_map[c]), $(MOI.Interval(0.0, 2.0)))
19+
@btime MOI.set!($caching_optimizer, $(MOI.ConstraintSet()), $c, $(MOI.Interval(0.0, 2.0)))
20+
@btime MOI.set!($(caching_optimizer.model_cache), $(MOI.ConstraintSet()), $c, $(MOI.Interval(0.0, 2.0)))
21+
@btime MOI.set!($(caching_optimizer.optimizer), $(MOI.ConstraintSet()), $(caching_optimizer.model_to_optimizer_map[c]), $(MOI.Interval(0.0, 2.0)))

src/Bridges/bridgeoptimizer.jl

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -209,25 +209,51 @@ function MOI.addconstraint!(b::AbstractBridgeOptimizer, f::MOI.AbstractFunction,
209209
MOI.addconstraint!(b.model, f, s)
210210
end
211211
end
212-
function MOI.canmodifyconstraint(b::AbstractBridgeOptimizer, ci::CI, change)
213-
if isbridged(b, typeof(ci))
214-
MOI.canmodifyconstraint(b.bridged, ci, change) && MOI.canmodifyconstraint(b, MOIB.bridge(b, ci), change)
212+
function MOI.canmodify(b::AbstractBridgeOptimizer, ::Type{CI{F, S}}, ::Type{Chg}) where {F, S, Chg<:MOI.AbstractFunctionModification}
213+
if isbridged(b, CI{F, S})
214+
MOI.canmodify(b.bridged, CI{F, S}, Chg) && MOI.canmodify(b, MOIB.bridgetype(b, F, S), Chg)
215215
else
216-
MOI.canmodifyconstraint(b.model, ci, change)
216+
MOI.canmodify(b.model, CI{F, S}, Chg)
217217
end
218218
end
219-
function MOI.modifyconstraint!(b::AbstractBridgeOptimizer, ci::CI, change)
219+
function MOI.modify!(b::AbstractBridgeOptimizer, ci::CI, change)
220220
if isbridged(b, typeof(ci))
221-
MOI.modifyconstraint!(b, bridge(b, ci), change)
222-
MOI.modifyconstraint!(b.bridged, ci, change)
221+
MOI.modify!(b, bridge(b, ci), change)
222+
MOI.modify!(b.bridged, ci, change)
223+
else
224+
MOI.modify!(b.model, ci, change)
225+
end
226+
end
227+
228+
function MOI.canset(b::AbstractBridgeOptimizer, attr::Union{MOI.ConstraintFunction,MOI.ConstraintSet}, ::Type{CI{F, S}}) where {F, S}
229+
if isbridged(b, CI{F, S})
230+
MOI.canset(b.bridged, attr, CI{F, S}) && MOI.canset(b, attr, MOIB.bridgetype(b, F, S))
231+
else
232+
MOI.canset(b.model, attr, CI{F, S})
233+
end
234+
end
235+
236+
function MOI.set!(b::AbstractBridgeOptimizer, ::MOI.ConstraintSet, constraint_index::CI{F,S}, set::S) where {F,S}
237+
if isbridged(b, typeof(constraint_index))
238+
MOI.set!(b, MOI.ConstraintSet(), bridge(b, constraint_index), set)
239+
MOI.set!(b.bridged, MOI.ConstraintSet(), constraint_index, set)
240+
else
241+
MOI.set!(b.model, MOI.ConstraintSet(), constraint_index, set)
242+
end
243+
end
244+
245+
function MOI.set!(b::AbstractBridgeOptimizer, ::MOI.ConstraintFunction, constraint_index::CI{F,S}, func::F) where {F,S}
246+
if isbridged(b, typeof(constraint_index))
247+
MOI.set!(b, MOI.ConstraintFunction(), bridge(b, constraint_index), func)
248+
MOI.set!(b.bridged, MOI.ConstraintFunction(), constraint_index, func)
223249
else
224-
MOI.modifyconstraint!(b.model, ci, change)
250+
MOI.set!(b.model, MOI.ConstraintFunction(), constraint_index, func)
225251
end
226252
end
227253

228254
# Objective
229-
MOI.canmodifyobjective(b::AbstractBridgeOptimizer, ::Type{M}) where M<:MOI.AbstractFunctionModification = MOI.canmodifyobjective(b.model, M)
230-
MOI.modifyobjective!(b::AbstractBridgeOptimizer, change::MOI.AbstractFunctionModification) = MOI.modifyobjective!(b.model, change)
255+
MOI.canmodify(b::AbstractBridgeOptimizer, obj::MOI.ObjectiveFunction, ::Type{M}) where M<:MOI.AbstractFunctionModification = MOI.canmodify(b.model, obj, M)
256+
MOI.modify!(b::AbstractBridgeOptimizer, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) = MOI.modify!(b.model, obj, change)
231257

232258
# Variables
233259
MOI.canaddvariable(b::AbstractBridgeOptimizer) = MOI.canaddvariable(b.model)

src/Bridges/detbridge.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ end
141141
MOI.canget(model::MOI.ModelLike, a::MOI.ConstraintDual, ::Type{<:LogDetBridge}) = false
142142

143143
# Constraints
144-
MOI.canmodifyconstraint(model::MOI.ModelLike, c::LogDetBridge, change) = false
144+
MOI.canmodify(model::MOI.ModelLike, ::Type{<:LogDetBridge}, change) = false
145+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintSet, ::Type{<:LogDetBridge}) = false
146+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintFunction, ::Type{<:LogDetBridge}) = false
145147

146148
"""
147149
RootDetBridge{T}
@@ -207,4 +209,6 @@ end
207209
MOI.canget(model::MOI.ModelLike, ::MOI.ConstraintDual, ::Type{<:RootDetBridge}) = false
208210

209211
# Constraints
210-
MOI.canmodifyconstraint(model::MOI.ModelLike, c::RootDetBridge, change) = false
212+
MOI.canmodify(model::MOI.ModelLike, ::Type{<:RootDetBridge}, ::Type{<:MOI.AbstractFunctionModification}) = false
213+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintSet, ::Type{<:RootDetBridge}) = false
214+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintFunction, ::Type{<:RootDetBridge}) = false

src/Bridges/geomeanbridge.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,6 @@ MOI.canget(model::MOI.ModelLike, a::MOI.ConstraintDual, ::Type{<:GeoMeanBridge})
138138
#end
139139

140140
# Constraints
141-
MOI.canmodifyconstraint(model::MOI.ModelLike, c::GeoMeanBridge, change) = false
141+
MOI.canmodify(model::MOI.ModelLike, ::Type{<:GeoMeanBridge}, ::Type{<:MOI.AbstractFunctionModification}) = false
142+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintSet, ::Type{<:GeoMeanBridge}) = false
143+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintFunction, ::Type{<:GeoMeanBridge}) = false

src/Bridges/intervalbridge.jl

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,20 @@ function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::SplitIntervalBr
5151
end
5252

5353
# Constraints
54-
MOI.canmodifyconstraint(model::MOI.ModelLike, c::SplitIntervalBridge, change) = true
55-
function MOI.modifyconstraint!(model::MOI.ModelLike, c::SplitIntervalBridge, change::Union{MOI.ScalarAffineFunction, MOI.AbstractFunctionModification})
56-
MOI.modifyconstraint!(model, c.lower, change)
57-
MOI.modifyconstraint!(model, c.upper, change)
58-
end
59-
function MOI.modifyconstraint!(model::MOI.ModelLike, c::SplitIntervalBridge, change::MOI.Interval)
60-
MOI.modifyconstraint!(model, c.lower, MOI.GreaterThan(change.lower))
61-
MOI.modifyconstraint!(model, c.upper, MOI.LessThan(change.upper))
54+
MOI.canmodify(model::MOI.ModelLike, ::Type{<:SplitIntervalBridge}, ::Type{<:MOI.AbstractFunctionModification}) = true
55+
function MOI.modify!(model::MOI.ModelLike, c::SplitIntervalBridge, change::MOI.AbstractFunctionModification)
56+
MOI.modify!(model, c.lower, change)
57+
MOI.modify!(model, c.upper, change)
58+
end
59+
60+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintFunction, ::Type{<:SplitIntervalBridge}) = true
61+
function MOI.set!(model::MOI.ModelLike, ::MOI.ConstraintFunction, c::SplitIntervalBridge, func::MOI.ScalarAffineFunction)
62+
MOI.set!(model, MOI.ConstraintFunction(), c.lower, func)
63+
MOI.set!(model, MOI.ConstraintFunction(), c.upper, func)
64+
end
65+
66+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintSet, ::Type{<:SplitIntervalBridge}) = true
67+
function MOI.set!(model::MOI.ModelLike, ::MOI.ConstraintSet, c::SplitIntervalBridge, change::MOI.Interval)
68+
MOI.set!(model, MOI.ConstraintSet(), c.lower, MOI.GreaterThan(change.lower))
69+
MOI.set!(model, MOI.ConstraintSet(), c.upper, MOI.LessThan(change.upper))
6270
end

src/Bridges/rsocbridge.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,6 @@ function MOI.canget(model::MOI.ModelLike, a::MOI.ConstraintDual, ::Type{RSOCBrid
6565
end
6666
MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual, c::RSOCBridge) = _get(model, attr, c)
6767

68-
# Constraints
69-
MOI.canmodifyconstraint(model::MOI.ModelLike, c::RSOCBridge, change) = false
68+
MOI.canmodify(model::MOI.ModelLike, ::Type{<:RSOCBridge}, ::Type{<:MOI.AbstractFunctionModification}) = false
69+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintSet, ::Type{<:RSOCBridge}) = false
70+
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintFunction, ::Type{<:RSOCBridge}) = false

0 commit comments

Comments
 (0)