Skip to content

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

Merged
merged 27 commits into from
Jun 20, 2018
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3f7be94
Begin refactor from `modifyconstraint(m, c, set)` to
odow Jun 14, 2018
98f82af
Update Model (#389)
blegat Jun 14, 2018
49d118b
Resolve ambiguity errors relating to canset and set! ConstraintSet.
odow Jun 14, 2018
82dc333
Fixes to get tests passing
odow Jun 14, 2018
bf5bcc4
Refactor modfiyconstraint!(m,c,f) to set!(m,ConstraintFunction(),c,f)
odow Jun 15, 2018
8822416
Refactor modifyconstraint! to modify! and canmodifycontraint to
odow Jun 15, 2018
35b46e2
Fix definition of canmodify. This patch does not pass tests due to
odow Jun 15, 2018
136b03e
Add broken tests
odow Jun 15, 2018
0c25804
Add unit tests for setting ConstraintSet
odow Jun 15, 2018
db7b687
Refactor modifyobjective! to modify!(m, ObjectiveFunction, change).
odow Jun 15, 2018
8097841
Add more tests for modifications
odow Jun 15, 2018
bf46749
Begin documentation of problem modificatino
odow Jun 15, 2018
32c0b5a
Add more modification tests
odow Jun 15, 2018
b8fb3bf
Add test for modifying constraint function. There is a broken test
odow Jun 16, 2018
0e48a2a
Fix bridges tests
blegat Jun 16, 2018
1ad6f61
Fix set! ConstraintFunction for mock optimizer
blegat Jun 16, 2018
1e4479c
Add more tests for default fallbacks
odow Jun 16, 2018
1e91624
Add more documentation, tests for MultirowChange, and fix
odow Jun 17, 2018
300d57e
Refactor transformconstraint! to transform!. Currently this is not
odow Jun 17, 2018
8c02953
Update modification docs with comments from @mlubin
odow Jun 17, 2018
3ffd5ab
Address method ambiguity
odow Jun 17, 2018
ec5b12a
Refactor canset and set! to reduce method ambiguities
odow Jun 18, 2018
b4c1ba8
Fix typo
odow Jun 19, 2018
cc41670
Fix cantransform
odow Jun 19, 2018
8c9e53c
Address comments by @blegat
odow Jun 19, 2018
f55f777
Re-enable transform test
odow Jun 19, 2018
622f009
Address comments by @mlubin
odow Jun 19, 2018
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
204 changes: 200 additions & 4 deletions docs/src/apimanual.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,11 +412,210 @@ primal_variable_result = MOI.get(optimizer, MOI.VariablePrimal(), x)
@show primal_variable_result
```

## Problem Modification

In addition to adding and deleting constraints and variables, MathOptInterface
supports modifying, in-place, coefficients in the constraints and the objective
function of a model. These modifications can be grouped into two categories:
modifications which replace the set of function of a constraint with a new set
or function; and modifications which change, in-place, a component of a
function.

In the following, we detail the various ways this can be
achieved. Readers should note that some solvers will not support problem
modification.

### Replacements

First, we discuss how to replace the set or function of a constraint with a new
instance of the same type.

#### The set of a constraint

Given a constraint of type `F`-in-`S` (see [Constraints by function-set pairs](@ref)
above for an explanation), we can modify parameters (but not the type) of the
set `S` by replacing it with a new instance of the same type. For example,
given the variable bound ``x \\le 1``:
```julia
c = addconstraint(m, SingleVariable(x), LessThan(1.0))
```
we can modify the set so that the bound now ``x \\le 2`` as follows:
```julia
set!(m, ConstraintSet(), c, LessThan(2.0))
```
where `m` is our [`ModelLike`](@ref) model. However, the following will fail as
the new set (`GreaterThan`) is of a different type to the original set
(`LessThan`):
```julia
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.

In some special cases, solvers may support efficiently changing the set of a
constraint (for example, from [`LessThan`](@ref) to [`GreaterThan`](@ref)). For
these cases, MathOptInterface provides the [`transform!`](@ref) method. For
example, instead of the error we observed above, the following will work:
```julia
c2 = transform!(m, c, GreaterThan(1.0))
```
The [`transform!`](@ref) function returns a new constraint index, and the old
constraint index (i.e., `c`) is no longer valid:
```julia
isvalid(m, c) # false
isvalid(m, c2) # true
```
Also note that [`transform!`](@ref) cannot be called with a set of the same
type; [`set!`](@ref) should be used instead.

#### The function of a constraint

Given a constraint of type `F`-in-`S` (see [Constraints by function-set pairs](@ref)
above for an explanation), it is also possible to modify the function of type
`F` by replacing it with a new instance of the same type. For example, given the
variable bound ``x \\le 1``:
```julia
c = addconstraint(m, SingleVariable(x), LessThan(1.0))
```
we can modify the function so that the bound now ``y \\le 1`` as follows:
```julia
set!(m, ConstraintFunction(), c, SingleVariable(y))
```
where `m` is our [`ModelLike`](@ref) model. However, the following will fail as
the new function is of a different type to the original function:
```julia
set!(m, ConstraintFunction(), c,
ScalarAffineFunction([ScalarAffineTerm(1.0, x)], 0.0)
)
```

### In-place Modifications
Copy link
Member

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).


The second type of problem modifications allow the user to modify, in-place, the
coefficients of a function. Currently, four modifications are supported by
MathOptInterface. They are:
1. change the constant term in a scalar function;
2. change the constant term in a vector function;
3. change the affine coefficients in a scalar function; and
4. change the affine coefficients in a vector function.

To distinguish between the replacement of the function with a new instance
(described above) and the modification of an existing function, the in-place
modifications use the [`modify!`](@ref) method:
```julia
modify!(model, index, change::AbstractFunctionModification)
```
[`modify!`](@ref) takes three arguments. The first is the [`ModelLike`](@ref)
model `model`, the second is the constraint index, and the third is an instance
of an [`AbstractFunctionModification`](@ref).

We now detail each of these four in-place modifications.

#### Constant term in a scalar function

MathOptInterface supports is the ability to modify the constant term within a
[`ScalarAffineFunction`](@ref) and a [`ScalarQuadraticFunction`](@ref) using the
[`ScalarConstantChange`](@ref) subtype of
[`AbstractFunctionModification`](@ref). This includes the objective function, as
well as the function in a function-pair constraint.

For example, consider a problem `m` with the objective ``\\max 1.0x + 0.0``:
```julia
set!(m,
ObjectiveFunction{ScalarAffineFunction{Float64}}(),
ScalarAffineFunction([ScalarAffineTerm(1.0, x)], 0.0)
)
```
We can modify the constant term in the objective function as follows:
```julia
modify!(m,
ObjectiveFunction{ScalarAffineFunction{Float64}}(),
ScalarConstantChange(1.0)
)
```
The objective function will now be ``\\max 1.0x + 1.0``.

#### Constant terms in a vector function

We can modify the constant terms in a [`VectorAffineFunction`](@ref) or a
[`VectorQuadraticFunction`](@ref) using the [`VectorConstantChange`](@ref)
subtype of [`AbstractFunctionModification`](@ref).

For example, consider a model with the following
`VectorAffineFunction`-in-`Nonpositives` constraint:
```julia
c = addconstraint!(m,
VectorAffineFunction([
VectorAffineTerm(1, ScalarAffineTerm(1.0, x)),
VectorAffineTerm(1, ScalarAffineTerm(2.0, y))
],
[0.0, 0.0]
),
Nonpositives(2)
)
```
We can modify the constant vector in the [`VectorAffineFunction`](@ref) from
`[0.0, 0.0]` to `[1.0, 2.0]` as follows:
```julia
modify!(m, c, VectorConstantChange([1.0, 2.0])
)
```
The constraints are now ``1.0x + 1.0 \\le 0.0`` and ``2.0y + 2.0 \\le 0.0``.

#### Affine coefficients in a scalar function

In addition to modifying the constant terms in a function, we can also modify
the affine variable coefficients in an [`ScalarAffineFunction`](@ref) or a
[`ScalarQuadraticFunction`](@ref) using the [`ScalarCoefficientChange`](@ref)
subtype of [`AbstractFunctionModification`](@ref).

For example, given the constraint ``1.0x <= 1.0``:
```julia
c = addconstraint!(m,
ScalarAffineFunction([ScalarAffineTerm(1.0, x)], 0.0),
LessThan(1.0)
)
```
we can modify the coefficient of the `x` variable so that the constraint becomes
``2.0x <= 1.0`` as follows:
```julia
modify!(m, c, ScalarCoefficientChange(x, 2.0))
```

[`ScalarCoefficientChange`](@ref) can also be used to modify the objective
function by passing an instance of [`ObjectiveFunction`](@ref) instead of the
constraint index `c` as we saw above.

#### Affine coefficients in a vector function

Finally, the last modification supported by MathOptInterface is the ability to
modify the affine coefficients of a single variable in a
[`VectorAffineFunction`](@ref) or a [`VectorQuadraticFunction`](@ref) using the
[`MultirowChange`](@ref) subtype of [`AbstractFunctionModification`](@ref).

For example, given the constraint ``Ax \\in \\mathbb{R}^2_+``, where
``A = [1.0, 2.0]^\\top``:
```julia
c = addconstraint!(m,
VectorAffineFunction([
VectorAffineTerm(1, ScalarAffineTerm(1.0, x)),
VectorAffineTerm(1, ScalarAffineTerm(2.0, x))
],
[0.0, 0.0]
),
Nonnegatives(2)
)
```
we can modify the coefficients of the `x` variable so that the `A` matrix
becomes ``A = [3.0, 4.0]^\\top`` as follows:
```julia
modify!(m, c, MultirowChange(x, [3.0, 4.0]))
```

## Advanced

### Duals


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.
For conic-form minimization problems, the primal is:

Expand Down Expand Up @@ -529,9 +728,6 @@ If the set ``C_i`` of the section [Duals](@ref) is one of these three cones,
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.
See [`PositiveSemidefiniteConeTriangle`](@ref MathOptInterface.PositiveSemidefiniteConeTriangle) for details.

### Modifying a model

[Explain `modifyconstraint!` and `modifyobjective!`.]

### Constraint bridges

Expand Down
13 changes: 7 additions & 6 deletions docs/src/apireference.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,10 @@ isvalid(::ModelLike,::ConstraintIndex)
canaddconstraint
addconstraint!
addconstraints!
modifyconstraint!
canmodifyconstraint
transformconstraint!
cantransformconstraint
modify!
canmodify
transform!
cantransform
supportsconstraint
```

Expand Down Expand Up @@ -203,6 +203,7 @@ VectorQuadraticFunction

List of function modifications.
```@docs
AbstractFunctionModification
ScalarConstantChange
VectorConstantChange
ScalarCoefficientChange
Expand Down Expand Up @@ -255,8 +256,8 @@ dimension
Functions for modifying objective functions. Use `ObjectiveFunction` and `ObjectiveSense` to set and query the objective function.

```@docs
modifyobjective!
canmodifyobjective
modify!
canmodify
```

## Nonlinear programming (NLP)
Expand Down
8 changes: 4 additions & 4 deletions perf/cachingoptimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ MOIU.resetoptimizer!(caching_optimizer) # detach optimizer
v = MOI.addvariables!(caching_optimizer, 2)
cf = MOI.ScalarAffineFunction(v, [0.0, 0.0], 0.0)
c = MOI.addconstraint!(caching_optimizer, cf, MOI.Interval(-Inf, 1.0))
@btime MOI.modifyconstraint!($caching_optimizer, $c, $(MOI.Interval(0.0, 2.0)))
@btime MOI.set!($caching_optimizer, $(MOI.ConstraintSet()), $c, $(MOI.Interval(0.0, 2.0)))
MOIU.attachoptimizer!(caching_optimizer)
@btime MOI.modifyconstraint!($caching_optimizer, $c, $(MOI.Interval(0.0, 2.0)))
@btime MOI.modifyconstraint!($(caching_optimizer.model_cache), $c, $(MOI.Interval(0.0, 2.0)))
@btime MOI.modifyconstraint!($(caching_optimizer.optimizer), $(caching_optimizer.model_to_optimizer_map[c]), $(MOI.Interval(0.0, 2.0)))
@btime MOI.set!($caching_optimizer, $(MOI.ConstraintSet()), $c, $(MOI.Interval(0.0, 2.0)))
@btime MOI.set!($(caching_optimizer.model_cache), $(MOI.ConstraintSet()), $c, $(MOI.Interval(0.0, 2.0)))
@btime MOI.set!($(caching_optimizer.optimizer), $(MOI.ConstraintSet()), $(caching_optimizer.model_to_optimizer_map[c]), $(MOI.Interval(0.0, 2.0)))
46 changes: 36 additions & 10 deletions src/Bridges/bridgeoptimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -209,25 +209,51 @@ function MOI.addconstraint!(b::AbstractBridgeOptimizer, f::MOI.AbstractFunction,
MOI.addconstraint!(b.model, f, s)
end
end
function MOI.canmodifyconstraint(b::AbstractBridgeOptimizer, ci::CI, change)
if isbridged(b, typeof(ci))
MOI.canmodifyconstraint(b.bridged, ci, change) && MOI.canmodifyconstraint(b, MOIB.bridge(b, ci), change)
function MOI.canmodify(b::AbstractBridgeOptimizer, ::Type{CI{F, S}}, ::Type{Chg}) where {F, S, Chg<:MOI.AbstractFunctionModification}
if isbridged(b, CI{F, S})
MOI.canmodify(b.bridged, CI{F, S}, Chg) && MOI.canmodify(b, MOIB.bridgetype(b, F, S), Chg)
else
MOI.canmodifyconstraint(b.model, ci, change)
MOI.canmodify(b.model, CI{F, S}, Chg)
end
end
function MOI.modifyconstraint!(b::AbstractBridgeOptimizer, ci::CI, change)
function MOI.modify!(b::AbstractBridgeOptimizer, ci::CI, change)
if isbridged(b, typeof(ci))
MOI.modifyconstraint!(b, bridge(b, ci), change)
MOI.modifyconstraint!(b.bridged, ci, change)
MOI.modify!(b, bridge(b, ci), change)
MOI.modify!(b.bridged, ci, change)
else
MOI.modify!(b.model, ci, change)
end
end

function MOI.canset(b::AbstractBridgeOptimizer, attr::Union{MOI.ConstraintFunction,MOI.ConstraintSet}, ::Type{CI{F, S}}) where {F, S}
if isbridged(b, CI{F, S})
MOI.canset(b.bridged, attr, CI{F, S}) && MOI.canset(b, attr, MOIB.bridgetype(b, F, S))
else
MOI.canset(b.model, attr, CI{F, S})
end
end

function MOI.set!(b::AbstractBridgeOptimizer, ::MOI.ConstraintSet, constraint_index::CI{F,S}, set::S) where {F,S}
if isbridged(b, typeof(constraint_index))
MOI.set!(b, MOI.ConstraintSet(), bridge(b, constraint_index), set)
MOI.set!(b.bridged, MOI.ConstraintSet(), constraint_index, set)
else
MOI.set!(b.model, MOI.ConstraintSet(), constraint_index, set)
end
end

function MOI.set!(b::AbstractBridgeOptimizer, ::MOI.ConstraintFunction, constraint_index::CI{F,S}, func::F) where {F,S}
if isbridged(b, typeof(constraint_index))
MOI.set!(b, MOI.ConstraintFunction(), bridge(b, constraint_index), func)
MOI.set!(b.bridged, MOI.ConstraintFunction(), constraint_index, func)
else
MOI.modifyconstraint!(b.model, ci, change)
MOI.set!(b.model, MOI.ConstraintFunction(), constraint_index, func)
end
end

# Objective
MOI.canmodifyobjective(b::AbstractBridgeOptimizer, ::Type{M}) where M<:MOI.AbstractFunctionModification = MOI.canmodifyobjective(b.model, M)
MOI.modifyobjective!(b::AbstractBridgeOptimizer, change::MOI.AbstractFunctionModification) = MOI.modifyobjective!(b.model, change)
MOI.canmodify(b::AbstractBridgeOptimizer, obj::MOI.ObjectiveFunction, ::Type{M}) where M<:MOI.AbstractFunctionModification = MOI.canmodify(b.model, obj, M)
MOI.modify!(b::AbstractBridgeOptimizer, obj::MOI.ObjectiveFunction, change::MOI.AbstractFunctionModification) = MOI.modify!(b.model, obj, change)

# Variables
MOI.canaddvariable(b::AbstractBridgeOptimizer) = MOI.canaddvariable(b.model)
Expand Down
8 changes: 6 additions & 2 deletions src/Bridges/detbridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ end
MOI.canget(model::MOI.ModelLike, a::MOI.ConstraintDual, ::Type{<:LogDetBridge}) = false

# Constraints
MOI.canmodifyconstraint(model::MOI.ModelLike, c::LogDetBridge, change) = false
MOI.canmodify(model::MOI.ModelLike, ::Type{<:LogDetBridge}, change) = false
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintSet, ::Type{<:LogDetBridge}) = false
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintFunction, ::Type{<:LogDetBridge}) = false

"""
RootDetBridge{T}
Expand Down Expand Up @@ -207,4 +209,6 @@ end
MOI.canget(model::MOI.ModelLike, ::MOI.ConstraintDual, ::Type{<:RootDetBridge}) = false

# Constraints
MOI.canmodifyconstraint(model::MOI.ModelLike, c::RootDetBridge, change) = false
MOI.canmodify(model::MOI.ModelLike, ::Type{<:RootDetBridge}, ::Type{<:MOI.AbstractFunctionModification}) = false
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintSet, ::Type{<:RootDetBridge}) = false
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintFunction, ::Type{<:RootDetBridge}) = false
4 changes: 3 additions & 1 deletion src/Bridges/geomeanbridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,6 @@ MOI.canget(model::MOI.ModelLike, a::MOI.ConstraintDual, ::Type{<:GeoMeanBridge})
#end

# Constraints
MOI.canmodifyconstraint(model::MOI.ModelLike, c::GeoMeanBridge, change) = false
MOI.canmodify(model::MOI.ModelLike, ::Type{<:GeoMeanBridge}, ::Type{<:MOI.AbstractFunctionModification}) = false
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintSet, ::Type{<:GeoMeanBridge}) = false
MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintFunction, ::Type{<:GeoMeanBridge}) = false
24 changes: 16 additions & 8 deletions src/Bridges/intervalbridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,20 @@ function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::SplitIntervalBr
end

# Constraints
MOI.canmodifyconstraint(model::MOI.ModelLike, c::SplitIntervalBridge, change) = true
function MOI.modifyconstraint!(model::MOI.ModelLike, c::SplitIntervalBridge, change::Union{MOI.ScalarAffineFunction, MOI.AbstractFunctionModification})
MOI.modifyconstraint!(model, c.lower, change)
MOI.modifyconstraint!(model, c.upper, change)
end
function MOI.modifyconstraint!(model::MOI.ModelLike, c::SplitIntervalBridge, change::MOI.Interval)
MOI.modifyconstraint!(model, c.lower, MOI.GreaterThan(change.lower))
MOI.modifyconstraint!(model, c.upper, MOI.LessThan(change.upper))
MOI.canmodify(model::MOI.ModelLike, ::Type{<:SplitIntervalBridge}, ::Type{<:MOI.AbstractFunctionModification}) = true
function MOI.modify!(model::MOI.ModelLike, c::SplitIntervalBridge, change::MOI.AbstractFunctionModification)
MOI.modify!(model, c.lower, change)
MOI.modify!(model, c.upper, change)
end

MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintFunction, ::Type{<:SplitIntervalBridge}) = true
function MOI.set!(model::MOI.ModelLike, ::MOI.ConstraintFunction, c::SplitIntervalBridge, func::MOI.ScalarAffineFunction)
MOI.set!(model, MOI.ConstraintFunction(), c.lower, func)
MOI.set!(model, MOI.ConstraintFunction(), c.upper, func)
end

MOI.canset(model::MOI.ModelLike, ::MOI.ConstraintSet, ::Type{<:SplitIntervalBridge}) = true
function MOI.set!(model::MOI.ModelLike, ::MOI.ConstraintSet, c::SplitIntervalBridge, change::MOI.Interval)
MOI.set!(model, MOI.ConstraintSet(), c.lower, MOI.GreaterThan(change.lower))
MOI.set!(model, MOI.ConstraintSet(), c.upper, MOI.LessThan(change.upper))
end
5 changes: 3 additions & 2 deletions src/Bridges/rsocbridge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,6 @@ function MOI.canget(model::MOI.ModelLike, a::MOI.ConstraintDual, ::Type{RSOCBrid
end
MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual, c::RSOCBridge) = _get(model, attr, c)

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