Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c4afbda
Create manipulating_expressions.md
dourouc05 Aug 24, 2021
87259d4
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
308852e
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
0bcec0e
Update make.jl
dourouc05 Aug 24, 2021
0c53f5f
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
536fd98
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
2a3c4b3
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
477f22a
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
f8c1d1d
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
4cfb8a1
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
67be665
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
ec3ef24
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
5332c05
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
eb86709
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
2394697
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
85f4f5a
Add docs for scalarize.
dourouc05 Aug 24, 2021
d6987b8
Add documentation for eachscalar.
dourouc05 Aug 24, 2021
9bcb684
Update reference.md
dourouc05 Aug 24, 2021
2317e35
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
fa5cbe7
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
0c53e5e
Update manipulating_expressions.md
dourouc05 Aug 24, 2021
89bdf20
Update manipulating_expressions.md
dourouc05 Aug 25, 2021
9088d17
Update manipulating_expressions.md
dourouc05 Aug 25, 2021
743df9b
Update manipulating_expressions.md
dourouc05 Aug 25, 2021
8b98048
Update manipulating_expressions.md
dourouc05 Aug 25, 2021
470ff4a
Update manipulating_expressions.md
dourouc05 Aug 27, 2021
76e5e7d
Update manipulating_expressions.md
dourouc05 Aug 27, 2021
af5e20c
Update manipulating_expressions.md
odow Aug 27, 2021
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
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ makedocs(
"tutorials/implementing.md",
"tutorials/mathprogbase.md",
"tutorials/bridging_constraint.md",
"tutorials/manipulating_expressions.md",
],
"Manual" => [
"manual/standard_form.md",
Expand Down
2 changes: 2 additions & 0 deletions docs/src/submodules/Utilities/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ The following functions can be used to manipulate functions with basic algebra:

```@docs
Utilities.scalar_type
Utilities.scalarize
Utilities.eachscalar
Utilities.promote_operation
Utilities.operate
Utilities.operate!
Expand Down
142 changes: 142 additions & 0 deletions docs/src/tutorials/manipulating_expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
```@meta
CurrentModule = MathOptInterface
DocTestSetup = quote
using MathOptInterface
const MOI = MathOptInterface
end
DocTestFilters = [r"MathOptInterface|MOI"]
```

# Manipulating expressions

This guide highlights a syntactically appealing way to build expressions at the
MOI level, but also to look at their contents. It may be especially useful
when writing models or bridge code.

## Creating functions

This section details the ways to create functions with MathOptInterface.

### Creating scalar affine functions

The simplest scalar function is simply a variable:

```jldoctest expr; setup=:(model = MOI.Utilities.CachingOptimizer(MOI.Utilities.Model{Float64}(), MOI.Utilities.AUTOMATIC); )
julia> x = MOI.add_variable(model) # Create the variable x
MathOptInterface.VariableIndex(1)
julia> f1 = MOI.SingleVariable(x) # A function with the single variable x
MathOptInterface.SingleVariable(MathOptInterface.VariableIndex(1))
```

This type of function is extremely simple; to express more complex functions,
other types must be used. For instance, a [`ScalarAffineFunction`](@ref) is a
sum of linear terms (a factor times a variable) and a constant. Such an object
can be built using the standard constructor:

```jldoctest expr
julia> f2 = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1, x)], 2) # x + 2
MathOptInterface.ScalarAffineFunction{Int64}(MathOptInterface.ScalarAffineTerm{Int64}[ScalarAffineTerm{Int64}(1, VariableIndex(1))], 2)
```

However, you can also use operators to build the same scalar function:

```jldoctest expr
julia> f2 = MOI.SingleVariable(x) + 2 # x + 2
MathOptInterface.ScalarAffineFunction{Int64}(MathOptInterface.ScalarAffineTerm{Int64}[ScalarAffineTerm{Int64}(1, VariableIndex(1))], 2)
```

### Creating scalar quadratic functions

Scalar quadratic functions are stored in [`ScalarQuadraticFunction`](@ref)
objects, in a way that is highly similar to scalar affine functions. You can
obtain a quadratic function as a product of affine functions:

```jldoctest expr
julia> f3 = 1 * MOI.SingleVariable(x) * MOI.SingleVariable(x) # x²
MathOptInterface.ScalarQuadraticFunction{Int64}(MathOptInterface.ScalarQuadraticTerm{Int64}[ScalarQuadraticTerm{Int64}(2, VariableIndex(1), VariableIndex(1))], MathOptInterface.ScalarAffineTerm{Int64}[], 0)
julia> f4 = f2 * f2 # (x + 2)²
MathOptInterface.ScalarQuadraticFunction{Int64}(MathOptInterface.ScalarQuadraticTerm{Int64}[ScalarQuadraticTerm{Int64}(2, VariableIndex(1), VariableIndex(1))], MathOptInterface.ScalarAffineTerm{Int64}[ScalarAffineTerm{Int64}(2, VariableIndex(1)), ScalarAffineTerm{Int64}(2, VariableIndex(1))], 4)
julia> f4 = f2^2 # (x + 2)² too
MathOptInterface.ScalarQuadraticFunction{Int64}(MathOptInterface.ScalarQuadraticTerm{Int64}[ScalarQuadraticTerm{Int64}(2, VariableIndex(1), VariableIndex(1))], MathOptInterface.ScalarAffineTerm{Int64}[ScalarAffineTerm{Int64}(2, VariableIndex(1)), ScalarAffineTerm{Int64}(2, VariableIndex(1))], 4)
```

### Creating vector functions

A vector function is a function with several values, irrespective of the number
of input variables. Similarly to scalar functions, there are three main types
of vector functions: [`VectorOfVariables`](@ref),
[`VectorAffineFunction`](@ref), and [`VectorQuadraticFunction`](@ref).

The easiest way to create a vector function is to stack several scalar
functions using [`Utilities.vectorize`](@ref). It takes a vector as input,
and the generated vector function (of the most appropriate type) has each
dimension corresponding to a dimension of the vector.

```jldoctest expr
julia> f5 = MOI.Utilities.vectorize([f2, 2 * f2])
MathOptInterface.VectorAffineFunction{Int64}(MathOptInterface.VectorAffineTerm{Int64}[VectorAffineTerm{Int64}(1, ScalarAffineTerm{Int64}(1, VariableIndex(1))), VectorAffineTerm{Int64}(2, ScalarAffineTerm{Int64}(2, VariableIndex(1)))], [2, 4])
```

!!! warning
[`Utilities.vectorize`](@ref) only takes a vector of similar scalar
functions: you cannot mix [`SingleVariable`](@ref) and
[`ScalarAffineFunction`](@ref), for instance. In practice, it means that
`Utilities.vectorize([f1, f2])` does not work; you should rather use
`Utilities.vectorize([1 * f1, f2])` instead to only have
[`ScalarAffineFunction`](@ref) objects.

## Canonicalizing functions

In more advanced use cases, you might need to ensure that a function is
"canonical". Functions are stored as an array of terms, but there is no check
that these terms are redundant: a [`ScalarAffineFunction`](@ref) object might
have two terms with the same variable, like `x + x + 1`. These terms could be
merged without changing the semantics of the function: `2x + 1`.

Working with these objects might be cumbersome. Canonicalization helps maintain
redundancy to zero.

[`Utilities.is_canonical`](@ref) checks whether a function is already in its
canonical form:

```jldoctest expr
julia> MOI.Utilities.is_canonical(f2 + f2) # (x + 2) + (x + 2) is stored as x + x + 4
false
```

[`Utilities.canonical`](@ref) returns the equivalent canonical version of the
function:

```jldoctest expr
julia> MOI.Utilities.canonical(f2 + f2) # Returns 2x + 4
MathOptInterface.ScalarAffineFunction{Int64}(MathOptInterface.ScalarAffineTerm{Int64}[ScalarAffineTerm{Int64}(2, VariableIndex(1))], 4)
```

## Exploring functions

At some point, you might need to dig into a function, for instance to map it
into solver constructs.

### Vector functions

[`Utilities.scalarize`](@ref) returns a vector of scalar functions from a
vector function:

```jldoctest expr
julia> MOI.Utilities.scalarize(f5) # Returns a vector [f2, 2 * f2].
2-element Array{MathOptInterface.ScalarAffineFunction{Int64},1}:
MathOptInterface.ScalarAffineFunction{Int64}(MathOptInterface.ScalarAffineTerm{Int64}[ScalarAffineTerm{Int64}(1, VariableIndex(1))], 2)
MathOptInterface.ScalarAffineFunction{Int64}(MathOptInterface.ScalarAffineTerm{Int64}[ScalarAffineTerm{Int64}(2, VariableIndex(1))], 4)
```

!!! note
[`Utilities.eachscalar`](@ref) returns an iterator on the dimensions, which
serves the same purpose as [`Utilities.scalarize`](@ref).

[`output_dimension`](@ref) returns the number of dimensions of the
output of a function:

```jldoctest expr
julia> MOI.output_dimension(f5)
2
```
39 changes: 37 additions & 2 deletions src/Utilities/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,20 @@ function ScalarFunctionIterator(f::MOI.VectorQuadraticFunction)
)
end

"""
eachscalar(f::MOI.AbstractVectorFunction)

Returns an iterator for the scalar components of the vector function.

See also [`scalarize`](@ref).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised this worked. I thought it had to be [`Utilities.scalarize`](@ref)

"""
eachscalar(f::MOI.AbstractVectorFunction) = ScalarFunctionIterator(f)

"""
eachscalar(f::MOI.AbstractVector)

Returns an iterator for the scalar components of the vector.
"""
eachscalar(f::AbstractVector) = f

function Base.iterate(it::ScalarFunctionIterator, state = 1)
Expand Down Expand Up @@ -3067,12 +3080,26 @@ function operate(
return MOI.VectorQuadraticFunction(quadratic_terms, affine_terms, constant)
end

# Similar to `eachscalar` but faster, see
# https://github.com/jump-dev/MathOptInterface.jl/issues/418
"""
scalarize(func::MOI.VectorOfVariables, ignore_constants::Bool = false)

Returns a vector of scalar functions making up the vector function in the form
of a `Vector{MOI.SingleVariable}`.

See also [`eachscalar`](@ref).
"""
function scalarize(f::MOI.VectorOfVariables, ignore_constants::Bool = false)
return MOI.SingleVariable.(f.variables)
end

"""
scalarize(func::MOI.VectorAffineFunction{T}, ignore_constants::Bool = false)

Returns a vector of scalar functions making up the vector function in the form
of a `Vector{MOI.ScalarAffineFunction{T}}`.

See also [`eachscalar`](@ref).
"""
function scalarize(
f::MOI.VectorAffineFunction{T},
ignore_constants::Bool = false,
Expand All @@ -3092,6 +3119,14 @@ function scalarize(
return functions
end

"""
scalarize(func::MOI.VectorQuadraticFunction{T}, ignore_constants::Bool = false)

Returns a vector of scalar functions making up the vector function in the form
of a `Vector{MOI.ScalarQuadraticFunction{T}}`.

See also [`eachscalar`](@ref).
"""
function scalarize(
f::MOI.VectorQuadraticFunction{T},
ignore_constants::Bool = false,
Expand Down