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

Add POI.Parameters #40

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
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
20 changes: 20 additions & 0 deletions docs/src/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,31 @@ To change a given parameter's value, access its `ConstraintIndex` and set it to
MOI.set(optimizer, MOI.ConstraintSet(), cy, POI.Parameter(2.0))
```

as an alternative one can also use the custom MOI attribute `ParameterValue`

```julia
MOI.set(optimizer, POI.ParameterValue(), y, 2.0)
```

### Retrieving the dual of a parameter

Given an optimized model, one can calculate the dual associated to a parameters, **as long as it is an additive term in the constraints or objective**.
One can do so by getting the `MOI.ConstraintDual` attribute of the paraameter's `MOI.ConstraintIndex`:

```julia
MOI.get(optimizer, MOI.ConstraintDual, cy)
```

as an alternative one can also use the custom MOI attribute `ParameterValue`

```julia
MOI.get(optimizer, POI.ParameterDual(), y)
```

## API

```@docs
POI.Parameter
POI.Parameters
POI.ParametricOptimizer
```
45 changes: 43 additions & 2 deletions src/ParametricOptInterface.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module ParametricOptInterface

using MathOptInterface
import Base.copy

const MOI = MathOptInterface
const MOIU = MathOptInterface.Utilities
const DD = MOIU.DoubleDicts
Expand All @@ -9,7 +11,7 @@ const PARAMETER_INDEX_THRESHOLD = 1_000_000_000_000_000_000
const SUPPORTED_SETS = [MOI.LessThan{Float64}, MOI.EqualTo{Float64}, MOI.GreaterThan{Float64}]

"""
Parameter(Float64)
Parameter <: MOI.AbstractScalarSet

The `Parameter` structure stores the numerical value associated to a given parameter.
# Example:
Expand All @@ -22,6 +24,25 @@ struct Parameter <: MOI.AbstractScalarSet
val::Float64
end

"""
Parameters <: MOI.AbstractVectorSet

The `Parameters` structure stores the numerical values associated to parameters.
# Example:
```julia-repl
julia> ParametricOptInterface.Parameters(ones(2))
ParametricOptInterface.Parameters([1.0, 1.0], 2)
```
"""
struct Parameters <: MOI.AbstractVectorSet
vals::Vector{Float64}
dimension::Int
function Parameters(vals::Vector{Float64})
return new(vals, length(vals))
end
end
Base.copy(set::Parameters) = Parameters(copy(set.vals))

"""
ParametricOptimizer{T, OT <: MOI.ModelLike} <: MOI.AbstractOptimizer

Expand Down Expand Up @@ -308,6 +329,26 @@ function MOI.add_constrained_variable(model::ParametricOptimizer, set::Parameter
return p, cp
end

"""
MOI.add_constrained_variables(model::ParametricOptimizer, set::Parameters)

Adds multiple parameters, that is, variables parameterized to the value provided in `set`, to the `model` specified.
The `model` must be a `ParametricOptInterface.ParametricOptimizer` model.
Returns vector of MOI.VariableIndex of the parameterized variables and the vector of MOI.ConstraintIndex associated.
"""
function MOI.add_constrained_variables(model::ParametricOptimizer, set::Parameters)
variables = Vector{MOI.VariableIndex}(undef, set.dimension)
constraints = Vector{MOI.ConstraintIndex{MOI.SingleVariable, Parameter}}(undef, set.dimension)
for (i, val) in enumerate(set.vals)
next_parameter_index!(model)
variables[i] = MOI.VariableIndex(model.last_parameter_index_added)
model.parameters[variables[i]] = val
constraints[i] = MOI.ConstraintIndex{MOI.SingleVariable, Parameter}(model.last_parameter_index_added)
update_number_of_parameters!(model)
end
return variables, constraints
end

function MOI.add_constraint(model::ParametricOptimizer, f::MOI.SingleVariable, set::MOI.AbstractScalarSet)
if is_parameter_in_model(model, f.variable)
error("Cannot constrain a parameter")
Expand Down Expand Up @@ -372,7 +413,7 @@ Sets the parameter to a given value, using its `MOI.VariableIndex` as reference.

#Example:
```julia-repl
julia> MOI.set(model, ParameterValue(), w, 2.0)
julia> MOI.set(model, POI.ParameterValue(), w, 2.0)
2.0
```
"""
Expand Down
56 changes: 56 additions & 0 deletions test/jump_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,60 @@ end
@test_broken isapprox.(value(x[1]), 0.0, atol = ATOL)
@test_broken isapprox.(value(x[2]), 2.0, atol = ATOL)
@test_broken isapprox.(value(y), 2.0, atol = ATOL)
end

@testset "JuMP direct model - Variable in POI.Parameters" begin
optimizer = POI.ParametricOptimizer(GLPK.Optimizer())

model = direct_model(optimizer)

@variable(model, x[i=1:2] >= 0)

@variable(model, y[i=1:3] in POI.Parameters(zeros(3)))

@constraint(model, 2*x[1] + x[2] + y[1] <= 4)
@constraint(model, 1*x[1] + 2*x[2] + y[3] <= 4)

@objective(model, Max, 4*x[1] + 3*x[2] + y[2])

optimize!(model)

@test isapprox.(value(x[1]), 4.0/3.0, atol = ATOL)
@test isapprox.(value(x[2]), 4.0/3.0, atol = ATOL)
@test isapprox.(value(y[1]), 0, atol = ATOL)

# ===== Set parameter value =====
MOI.set(model, POI.ParameterValue(), y[1], 2.0)
optimize!(model)

@test isapprox.(value(x[1]), 0.0, atol = ATOL)
@test isapprox.(value(x[2]), 2.0, atol = ATOL)
@test isapprox.(value(y[1]), 2.0, atol = ATOL)
end

@testset "JuMP model - Variable in POI.Parameters" begin
model = Model(() -> POI.ParametricOptimizer(GLPK.Optimizer()))

@variable(model, x[i=1:2] >= 0)

@variable(model, y[i=1:3] in POI.Parameters(zeros(3)))

@constraint(model, 2*x[1] + x[2] + y[1] <= 4)
@constraint(model, 1*x[1] + 2*x[2] + y[3] <= 4)

@objective(model, Max, 4*x[1] + 3*x[2] + y[2])

@test_broken optimize!(model)

@test_broken isapprox.(value(x[1]), 4.0/3.0, atol = ATOL)
@test_broken isapprox.(value(x[2]), 4.0/3.0, atol = ATOL)
@test_broken isapprox.(value(y[1]), 0, atol = ATOL)

# ===== Set parameter value =====
@test_broken MOI.set(model, POI.ParameterValue(), y[1], 2.0)
@test_broken optimize!(model)

@test_broken isapprox.(value(x[1]), 0.0, atol = ATOL)
@test_broken isapprox.(value(x[2]), 2.0, atol = ATOL)
@test_broken isapprox.(value(y[1]), 2.0, atol = ATOL)
end
92 changes: 92 additions & 0 deletions test/production_problem_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,98 @@

end

@testset "Production Problem with Parameters as VectorSet" begin
optimizer = POI.ParametricOptimizer(GLPK.Optimizer())

c = [4.0, 3.0]
A1 = [2.0, 1.0, 1.0]
A2 = [1.0, 2.0, 1.0]
b1 = 4.0
b2 = 4.0

x = MOI.add_variables(optimizer, length(c))

@test typeof(x[1]) == MOI.VariableIndex

vars, cons = MOI.add_constrained_variables(optimizer, POI.Parameters(zeros(3)))
w, y, z = vars
cw, cy, cz = cons

@test MOI.get(optimizer, MOI.VariablePrimal(), w) == 0

for x_i in x
MOI.add_constraint(optimizer, MOI.SingleVariable(x_i), MOI.GreaterThan(0.0))
end

cons1 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(A1, [x[1], x[2], y]), 0.0)
MOI.add_constraint(optimizer, cons1, MOI.LessThan(b1))

cons2 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(A2, [x[1], x[2], z]), 0.0)
MOI.add_constraint(optimizer, cons2, MOI.LessThan(b2))

@test cons1.terms[1].coefficient == 2
@test POI.is_parameter_in_model(optimizer, cons2.terms[3].variable_index)

obj_func = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([c[1], c[2], 3.0], [x[1], x[2], w]), 0.0)
MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), obj_func)
MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE)

MOI.optimize!(optimizer)

MOI.get(optimizer, MOI.TerminationStatus())

MOI.get(optimizer, MOI.PrimalStatus())

@test isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 28/3, atol = ATOL)
@test isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 4/3, atol = ATOL)
@test isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 4/3, atol = ATOL)

@test isapprox(MOI.get(optimizer, MOI.ConstraintDual(), cy), -5/3, atol = ATOL)
@test isapprox(MOI.get(optimizer, MOI.ConstraintDual(), cz), -2/3, atol = ATOL)
@test isapprox(MOI.get(optimizer, MOI.ConstraintDual(), cw), 3.0, atol = ATOL)

MOI.get(optimizer, MOI.VariablePrimal(), w)
MOI.get(optimizer, MOI.VariablePrimal(), y)
MOI.get(optimizer, MOI.VariablePrimal(), z)

MOI.set(optimizer, MOI.ConstraintSet(), cw, POI.Parameter(2.0))
MOI.set(optimizer, MOI.ConstraintSet(), cy, POI.Parameter(1.0))
MOI.set(optimizer, MOI.ConstraintSet(), cz, POI.Parameter(1.0))

MOI.optimize!(optimizer)

@test MOI.get(optimizer, MOI.VariablePrimal(), w) == 2.0
@test MOI.get(optimizer, MOI.VariablePrimal(), y) == 1.0
@test MOI.get(optimizer, MOI.VariablePrimal(), z) == 1.0

@test isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 13.0, atol = ATOL)
@test MOI.get.(optimizer, MOI.VariablePrimal(), x) == [1.0, 1.0]

@test isapprox(MOI.get(optimizer, MOI.ConstraintDual(), cy), -5/3, atol = ATOL)
@test isapprox(MOI.get(optimizer, MOI.ConstraintDual(), cz), -2/3, atol = ATOL)
@test isapprox(MOI.get(optimizer, MOI.ConstraintDual(), cw), 3.0, atol = ATOL)

MOI.set(optimizer, MOI.ConstraintSet(), cw, POI.Parameter(0.0))

MOI.optimize!(optimizer)

@test MOI.get(optimizer, MOI.VariablePrimal(), w) == 0.0
@test MOI.get(optimizer, MOI.VariablePrimal(), y) == 1.0
@test MOI.get(optimizer, MOI.VariablePrimal(), z) == 1.0
@test isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 7, atol = ATOL)
@test MOI.get.(optimizer, MOI.VariablePrimal(), x) == [1.0, 1.0]


MOI.set(optimizer, MOI.ConstraintSet(), cy, POI.Parameter(-5.0))
MOI.optimize!(optimizer)
@test isapprox.(MOI.get(optimizer, MOI.ObjectiveValue()), 12.0, atol = ATOL)
@test MOI.get.(optimizer, MOI.VariablePrimal(), x) == [3.0, 0.0]

@test isapprox(MOI.get(optimizer, MOI.ConstraintDual(), cy), 0, atol = ATOL)
@test isapprox(MOI.get(optimizer, MOI.ConstraintDual(), cz), -4, atol = ATOL)

end


@testset "Production Problem variation on parameters for duals" begin
optimizer = POI.ParametricOptimizer(GLPK.Optimizer())
Expand Down