Skip to content
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

[docs] expand MatrixOfConstraints example #2474

Merged
merged 2 commits into from
Apr 10, 2024
Merged
Changes from all commits
Commits
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
207 changes: 165 additions & 42 deletions docs/src/submodules/Utilities/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,34 +379,156 @@ julia> f
## Utilities.MatrixOfConstraints

The constraints of [`Utilities.Model`](@ref) are stored as a vector of tuples
of function and set in a `Utilities.VectorOfConstraints`. Other representations
can be used by parameterizing the type [`Utilities.GenericModel`](@ref)
(resp. [`Utilities.GenericOptimizer`](@ref)). For instance, if all
non-`VariableIndex` constraints are affine, the coefficients of all the
constraints can be stored in a single sparse matrix using
[`Utilities.MatrixOfConstraints`](@ref). The constraints storage can even be
customized up to a point where it exactly matches the storage of the solver of
interest, in which case [`copy_to`](@ref) can be implemented for the solver by
calling [`copy_to`](@ref) to this custom model.

For instance, [Clp](https://github.com/jump-dev/Clp.jl) defines the following
of function and set in a [`Utilities.VectorOfConstraints`](@ref).

Other representations can be used by parameterizing the type
[`Utilities.GenericModel`](@ref) (resp. [`Utilities.GenericOptimizer`](@ref)).

For example, if all non-`VariableIndex` constraints are affine, the coefficients
of all the constraints can be stored in a single sparse matrix using
[`Utilities.MatrixOfConstraints`](@ref).

The constraints storage can even be customized up to a point where it exactly
matches the storage of the solver of interest, in which case [`copy_to`](@ref)
can be implemented for the solver by calling [`copy_to`](@ref) to this custom
model.

For example, [Clp.jl](https://github.com/jump-dev/Clp.jl) defines the following
model:
```julia
MOI.Utilities.@product_of_scalar_sets(LP, MOI.EqualTo{T}, MOI.LessThan{T}, MOI.GreaterThan{T})
const Model = MOI.Utilities.GenericModel{
Float64,
MOI.Utilities.MatrixOfConstraints{
Float64,
MOI.Utilities.MutableSparseMatrixCSC{Float64,Cint,MOI.Utilities.ZeroBasedIndexing},
MOI.Utilities.Hyperrectangle{Float64},
LP{Float64},
},
}
```jldoctest matrixofconstraints
julia> MOI.Utilities.@product_of_sets(
SupportedSets,
MOI.EqualTo{T},
MOI.LessThan{T},
MOI.GreaterThan{T},
MOI.Interval{T},
);

julia> const OptimizerCache = MOI.Utilities.GenericModel{
Float64,
MOI.Utilities.ObjectiveContainer{Float64},
MOI.Utilities.VariablesContainer{Float64},
MOI.Utilities.MatrixOfConstraints{
Float64,
MOI.Utilities.MutableSparseMatrixCSC{
# The data type of the coefficients
Float64,
# The data type of the variable indices
Cint,
# Can also be MOI.Utilities.OneBasedIndexing
MOI.Utilities.ZeroBasedIndexing,
},
MOI.Utilities.Hyperrectangle{Float64},
SupportedSets{Float64},
},
};
```
Given the input model:
```jldoctest matrixofconstraints
julia> src = MOI.Utilities.Model{Float64}();

julia> MOI.Utilities.loadfromstring!(
src,
"""
variables: x, y, z
maxobjective: x + 2.0 * y + -3.1 * z
x + y <= 1.0
2.0 * y >= 3.0
-4.0 * x + z == 5.0
x in Interval(0.0, 1.0)
y <= 10.0
z == 5.0
""",
)
```

The [`copy_to`](@ref) operation can now be implemented as follows:
We can construct a new cached model and copy `src` to it:
```jldoctest matrixofconstraints
julia> dest = OptimizerCache();

julia> index_map = MOI.copy_to(dest, src);
```

From `dest`, we can access the `A` matrix in sparse matrix form:
```jldoctest matrixofconstraints
julia> A = dest.constraints.coefficients;

julia> A.n
3

julia> A.m
3

julia> A.colptr
4-element Vector{Int32}:
0
1
3
5

julia> A.rowval
5-element Vector{Int32}:
0
1
2
0
1

julia> A.nzval
5-element Vector{Float64}:
1.0
1.0
2.0
-4.0
1.0
```
The lower and upper row bounds:
```jldoctest matrixofconstraints
julia> row_bounds = dest.constraints.constants;

julia> row_bounds.lower
3-element Vector{Float64}:
5.0
-Inf
3.0

julia> row_bounds.upper
3-element Vector{Float64}:
5.0
1.0
Inf
```
The lower and upper variable bounds:
```jldoctest matrixofconstraints
julia> dest.variables.lower
3-element Vector{Float64}:
5.0
-Inf
0.0

julia> dest.variables.upper
3-element Vector{Float64}:
5.0
10.0
1.0
```
Because of larger variations between solvers, the objective can be queried using
the standard MOI methods:
```jldoctest matrixofconstraints
julia> MOI.get(dest, MOI.ObjectiveSense())
MAX_SENSE::OptimizationSense = 1

julia> F = MOI.get(dest, MOI.ObjectiveFunctionType())
MathOptInterface.ScalarAffineFunction{Float64}

julia> F = MOI.get(dest, MOI.ObjectiveFunction{F}())
0.0 + 1.0 MOI.VariableIndex(3) + 2.0 MOI.VariableIndex(2) - 3.1 MOI.VariableIndex(1)
```

Thus, Clp.jl implements [`copy_to`](@ref) methods similar to the following:
```julia
function _copy_to(dest::Optimizer, src::Model)
# This method copies from the cache to the `Clp.Optimizer` object.
function MOI.copy_to(dest::Optimizer, src::OptimizerCache)
@assert MOI.is_empty(dest)
A = src.constraints.coefficients
row_bounds = src.constraints.constants
Expand All @@ -423,35 +545,36 @@ function _copy_to(dest::Optimizer, src::Model)
row_bounds.lower,
row_bounds.upper,
)
# Set objective sense and constant (omitted)
return
end

function MOI.copy_to(dest::Optimizer, src::Model)
odow marked this conversation as resolved.
Show resolved Hide resolved
_copy_to(dest, src)
return MOI.Utilities.identity_index_map(src)
end

function MOI.copy_to(
dest::Optimizer,
src::MOI.Utilities.UniversalFallback{Model},
)
# Copy attributes from `src` to `dest` and error in case any unsupported
# constraints or attributes are set in `UniversalFallback`.
return MOI.copy_to(dest, src.model)
# This method copies from an arbitrary model to the optimizer, by the
# intermediate `OptimizerCache` representation.
function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
cache = OptimizerCache()
index_map = MOI.copy_to(cache, src)
MOI.copy_to(dest, cache)
return index_map
end

# This is a special method that gets called in some cases when `OptimizerCache`
# is used as the backing data structure in a `MOI.Utilities.CachingOptimizer`.
# It is needed for performance, but not correctness.
function MOI.copy_to(
dest::Optimizer,
src::MOI.ModelLike,
src::MOI.Utilities.UniversalFallback{OptimizerCache},
)
model = Model()
index_map = MOI.copy_to(model, src)
_copy_to(dest, model)
return index_map
MOI.Utilities.throw_unsupported(src)
return MOI.copy_to(dest, src.model)
end
```

!!! tip
For other examples of [`Utilities.MatrixOfConstraints`](@ref), see:
* [Cbc.jl](https://github.com/jump-dev/Cbc.jl)
* [ECOS.jl](https://github.com/jump-dev/ECOS.jl)
* [SCS.jl](https://github.com/jump-dev/SCS.jl)

## ModelFilter

Utilities provides [`Utilities.ModelFilter`](@ref) as a useful tool to copy a
Expand Down
Loading