-
Notifications
You must be signed in to change notification settings - Fork 92
RFC: new modification syntax #388
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
Conversation
`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.
Do we now disallow a set or function being passed to |
Yes. |
src/Utilities/mockoptimizer.jl
Outdated
function MOI.modifyconstraint!(mock::MockOptimizer, c::CI{F,S}, set::S) where {F<:MOI.AbstractFunction, S<:MOI.AbstractSet} | ||
MOI.modifyconstraint!(mock.inner_model, xor_index(c), set) | ||
MOI.canset(mock::MockOptimizer, ::MOI.ConstraintSet, c::CI{F,S}, set::Type{S}) where {F<:MOI.AbstractFunction, S<:MOI.AbstractSet} = true | ||
function MOI.set!(mock::MockOptimizer, ::MOI.ConstraintSet, c::CI{F,S}, set::S) where {F<:MOI.AbstractFunction, S<:MOI.AbstractSet} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already handled by line 94
src/constraints.jl
Outdated
set!(model, ConstraintSet(), c, NonPositives) # Error | ||
``` | ||
""" | ||
function set!(model::ModelLike, cs::ConstraintSet, c::ConstraintIndex{F,S1}, s::S2) where F where S1 where S2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue of having a specific method like this is that it prevents models to handle it with a more general method as this would lead to ambiguity (this affects mockoptimizer, bridges, ...)
We could instead call something like seterror
in line 234.
The default seterror
method would give the ArgumentError currently at line 234 and we would add this method as a seterror
method.
Why don't we use a different signature for |
Codecov Report
@@ Coverage Diff @@
## master #388 +/- ##
==========================================
+ Coverage 96.35% 96.51% +0.15%
==========================================
Files 39 40 +1
Lines 4913 5676 +763
==========================================
+ Hits 4734 5478 +744
- Misses 179 198 +19
Continue to review full report at Codecov.
|
canmodify. These functions now only apply to AbstractFunctionModification changes.
problems with canmodify in the bridge code.
src/Bridges/bridgeoptimizer.jl
Outdated
if isbridged(b, C) | ||
MOI.canmodify(b.bridged, C, Chg) | ||
# TODO(@blegat): how should the following be implemented? | ||
# && MOI.canmodify(b, MOIB.bridge(b, ci), Chg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, @blegat new question. Is this necessary and in what use-case? It makes tests like
https://github.com/JuliaOpt/MathOptInterface.jl/pull/388/files#diff-5d9f4af23e8ec976db542b6b9af239c4R135
break, but everything else passes. There are also things like
https://github.com/JuliaOpt/MathOptInterface.jl/pull/388/files#diff-91d11cfb910f6ae0036ae89cac114d3eR144
Are they necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it checks that the bridge supports the modification. Currently, only the interval bridge supports modification
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed the second part to the question. If they are necessary, how do we implement this using only type information...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need bridgetype
:)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have pushed a fix
src/Bridges/bridgeoptimizer.jl
Outdated
if isbridged(b, C) | ||
MOI.canset(b.bridged, MOI.ConstraintSet(), C) | ||
# TODO(@blegat) is this necessary? How do I do it without types | ||
# && MOI.canset(b, MOIB.bridge(b, ci), change) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto here.
src/Bridges/bridgeoptimizer.jl
Outdated
if isbridged(b, C) | ||
MOI.canset(b.bridged, MOI.ConstraintFunction(), C) | ||
# TODO(@blegat) is this necessary? How do I do it without types | ||
# && MOI.canset(b, MOIB.bridge(b, ci), change) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here.
and one commented out as MockOptimizer fails getting the constraint function.
src/Test/UnitTests/modifications.jl
Outdated
) | ||
@test MOI.canget(model, MOI.ConstraintFunction(), typeof(c)) | ||
foo = MOI.get(model, MOI.ConstraintFunction(), c) | ||
@test_broken foo == MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(2.0, x)], 0.0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@blegat another mystery. The VariableIndex
in foo
is VariableIndex(12345679)
instead of VariableIndex(1)
, but I couldn't work out where the missing xor
is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed by 1ad6f61
associated bug.
tested as transform! is not implemented in MockOptimizer
src/Bridges/bridgeoptimizer.jl
Outdated
end | ||
end | ||
function MOI.set!(b::AbstractBridgeOptimizer, ::MOI.ConstraintFunction, constraint_index::CI, func) | ||
# Note: we also under-type this function to avoid ambiguity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by this comment ? Why don't you just define set!(..., attr::Union{ConstraintFunction, ConstraintSet}, ...)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a hard time getting rid of method ambiguities since there are quite a few abstract subtypes of ModelLike floating around. With the Union there is an ambiguity with set!(::ModelLike, ::ConstraintFunction, ...)
. as the first argument is more specific but the second is less specific. I've typed this now and it seems to all work.
docs/src/apimanual.md
Outdated
set!(m, ConstraintSet(), c, GreaterThan(2.0)) # errors | ||
``` | ||
If our constraint is an affine inequality, then this corresponds to modifying | ||
the right hand-side of a constraint in linear programming. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: right-hand side
docs/src/apimanual.md
Outdated
Also note that `transform!` cannot be called with a set of the same type; | ||
[`set!`](@ref) should be used instead. | ||
|
||
### Modify the function of a constraint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of "modify" here could be a bit confusing given that this is discussing replacing the function rather than using an AbstractFunctionModification
. It maybe be worth restructuring a bit to clarify, e.g., by grouping all the AbstractFunctionModifications
together somehow.
docs/src/apimanual.md
Outdated
|
||
### MultirowChange | ||
|
||
Finally, the last modification supports by MathOptInterface is the ability to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: supported
docs/src/apimanual.md
Outdated
### MultirowChange | ||
|
||
Finally, the last modification supports by MathOptInterface is the ability to | ||
modify the affine coefficient of a single variable in a VectorAffine or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
affine coefficients
docs/src/apimanual.md
Outdated
### MultirowChange | ||
|
||
Finally, the last modification supports by MathOptInterface is the ability to | ||
modify the affine coefficient of a single variable in a VectorAffine or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit clearer to say more explicitly "in a VectorAffineFunction
or a VectorQuadraticFunction
".
src/Utilities/universalfallback.jl
Outdated
function MOI.canset(uf::UniversalFallback, ::MOI.ConstraintSet, ::Type{C}) where C <: CI | ||
MOI.canset(uf.model, MOI.ConstraintSet(), C) | ||
end | ||
function MOI.set!(uf::UniversalFallback, ::MOI.ConstraintSet, ci::CI{F,S}, set::S) where F where S |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think where {F,S}
is more idiomatic than where F where S
, but correct me if I'm wrong.
src/constraints.jl
Outdated
function canmodifyconstraint end | ||
canmodifyconstraint(model::ModelLike, c::ConstraintIndex, change) = false | ||
canset(model::ModelLike, ::ConstraintSet, type_of_constraint_set) = false | ||
# Note: the above method is deliberately under-typed to avoid method ambiguities |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an example of which method ambiguity is being avoided, in case we refactor later and it's no longer necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
src/constraints.jl
Outdated
modifyconstraint!(model, c, Interval(0, 5)) | ||
modifyconstraint!(model, c, NonPositives) # Error | ||
set!(model, ConstraintSet(), c, Interval(0, 5)) | ||
set!(model, ConstraintSet(), c, NonPositives) # Error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nonpositives
is the type. Maybe use a scalar set instead so it's a bit more clear, e.g., LessThan(3)
.
src/constraints.jl
Outdated
cantransformconstraint(model, c, GreaterThan(0.0)) # true | ||
cantransformconstraint(model, c, ZeroOne()) # false | ||
cantransform(model, c, GreaterThan(0.0)) # true | ||
cantransform(model, c, ZeroOne()) # false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is false for all current solvers, but MOI allows you to implement a solver where this could work. Maybe clarify that this is expected behavior but not a hard rule.
src/modifications.jl
Outdated
|
||
## Objective Function | ||
|
||
canmodify(model::ModelLike, ::ObjectiveFunction, ::Type{M})::Bool where M<:AbstractFunctionModification |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing code formatting?
src/Bridges/bridgeoptimizer.jl
Outdated
end | ||
end | ||
|
||
function MOI.canset(b::AbstractBridgeOptimizer, attr::MOI.ConstraintSet, ::Type{CI{F, S}}) where {F, S} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be merged with the ConstraintFunction
or not finally ? I thought you said in your comment that it was updated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is ambiguous with calls like
https://github.com/JuliaOpt/MathOptInterface.jl/blob/3ffd5ab6851260d84aa1d85ff4f2cda11b3ad870/src/constraints.jl#L52
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #388 (comment)
src/Utilities/universalfallback.jl
Outdated
MOI.canmodify(uf::UniversalFallback, ci::Type{<:CI}, change) = MOI.canmodify(uf.model, ci, change) | ||
MOI.modify!(uf::UniversalFallback, ci::CI, change) = MOI.modify!(uf.model, ci, change) | ||
|
||
function MOI.canset(uf::UniversalFallback, ::MOI.ConstraintSet, ::Type{C}) where C <: CI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, do we really need 4 functions instead of two ?
src/constraints.jl
Outdated
``` | ||
""" | ||
function set!(model::ModelLike, ::ConstraintSet, constraint_index, set) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't define set!
and canset
methods with so specific types.
We should do set!(model::ModelLike, attr::AbstractConstraintAttribute, ci::CI, value) = default_set!(model, attr, ci, value)
and then in the methods of default_set!
we can do any typing we like without risk of ambiguity
There are still a few unresolved tick-boxes in the description of this PR, but they can be probably be addressed in future PRs. |
src/attributes.jl
Outdated
@@ -527,13 +578,35 @@ It is guaranteed to be equivalent but not necessarily identical to the function | |||
""" | |||
struct ConstraintFunction <: AbstractConstraintAttribute end | |||
|
|||
function hidden_set!(model::ModelLike, ::ConstraintFunction, constraint_index::ConstraintIndex, func::AbstractFunction) | |||
if func_type(constraint_index) != typeof(func) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be done with multiple dispatch,
hidden_set!(..., ::ConstraintFunction, ::CI{F}, ::F)
-> same constraint set
hidden_set!(..., ::ConstraintFunction, ::CI, ::AbstractFunction)
-> different constraint set
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah good point. This is a hold-over from before
src/attributes.jl
Outdated
""" | ||
ConstraintSet() | ||
|
||
Return the `AbstractSet` object used to define the constraint. | ||
""" | ||
struct ConstraintSet <: AbstractConstraintAttribute end | ||
|
||
function hidden_set!(model::ModelLike, ::ConstraintSet, constraint_index::ConstraintIndex, set::AbstractSet) | ||
if set_type(constraint_index) != typeof(set) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, here, it is cleaner to do that using multiple dispatch
src/Utilities/cachingoptimizer.jl
Outdated
end | ||
|
||
# This function avoids duplicating code, but allows us to strongly type the | ||
# arguments of the set! functions below. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't get that immediately, mentioning the 4th argument explicitly in the comment would be clearer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, the new API is a lot better :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just a couple additional minor comments.
docs/src/apimanual.md
Outdated
) | ||
``` | ||
|
||
### In-place Modifications |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Inconsistent capitalization of words in section titles. (Also "Problem Modification" at the top).
test/model.jl
Outdated
@@ -123,3 +123,28 @@ end | |||
@test MOI.dimension(s) == 1 | |||
|
|||
end | |||
|
|||
struct Func388 <: MOI.AbstractScalarFunction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: use a more descriptive name or add a link to the PR if we need to reference the discussion. 388 isn't too meaningful on its own.
Overview
This pull request changes the modification syntax in MOI.
The biggest change is to refactor
modifyconstraint!(m, c::ConstraintIndex{F,S}, set::S)
toset!(m, ::ConstraintSet, c::ConstraintIndex{F,S}, set::S)
. (A similar refactor is done withConstraintFunction
.) This plays much nicer with theget
/set!
paradigm.In addition,
modifyconstraint!
methods that implementAbstractFunctionModification
is refactored tomodify!
andtransformconstraint!
is refactored intotransform!
.This PR also adds tests and documentation.
Changes
See #360 for discussion. Proposed changes:
set!(m::ModelLike, ::ConstraintFunction, c::ConstraintIndex{F,S}, new_function::F)
set!(m::ModelLike, ::ConstraintSet, c::ConstraintIndex{F,S}, new_set::S)
@blegat, how do I make the change forConstraintSet
inmodel.jl
? There is quite a few levels of in-direction and the macro and I don't know what to touch.modify!(m::ModelLike, c::ConstraintIndex{F,S}, change::AbstractFunctionModification)
(Note: this is slightly different to that proposed in Proposal: change API for modifications #360.)
modify!(m::ModelLike, ::ObjectiveFunction, change::AbstractFunctionModification)
transformconstraint!
->transform!
Tests
Given that we say the constant should be part of the set, what F-in-S pair makes sense to test?
Not tested. See below
Blockers / future PR
cantransform
still takes the instance of the constraint index of an argument rather than the type. This is needed because there is a default fallback andcandelete
also takes the instance rather than the type (see candelete is inconsistent with canget #218).transform!
is not tested as it is not implemented inMockOptimizer
.transform!
only applies to constraint sets. Should it apply to constraint functions for consistency? I'm not sure if it makes sense to change the function to a different type of function without deleting the constraint though...These points can probably be addressed by a future PR that changes
candelete
andcantransform
at the same time and adds tests. This PR is already quite large.