Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[compat]
BinaryProvider = "0.3, 0.4, 0.5"
MathOptInterface = "~0.9.5"
MathOptInterface = "~0.10.3"
MathProgBase = "~0.5.0, ~0.6, ~0.7"
julia = "1"

Expand Down
75 changes: 42 additions & 33 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
Dict{Symbol, Any}(:verbose => true), sense, objconstant,
constrconstant, modcache, warmstartcache, rowranges)
for (key, value) in kwargs
MOI.set(optimizer, MOI.RawParameter(key), value)
MOI.set(optimizer, MOI.RawOptimizerAttribute(key), value)
end
return optimizer
end
Expand Down Expand Up @@ -109,7 +109,9 @@ end

function MOI.set(model::Optimizer, attr::MOI.TimeLimitSec, ::Nothing)
delete!(model.settings, :time_limit)
OSQP.update_settings!(model.inner, time_limit=0.0)
if !MOI.is_empty(model)
OSQP.update_settings!(model.inner, time_limit=0.0)
end
return
end

Expand All @@ -125,7 +127,6 @@ function MOI.empty!(optimizer::Optimizer)
optimizer.inner = OSQP.Model()
optimizer.hasresults = false
optimizer.results = OSQP.Results()
optimizer.is_empty = true
optimizer.sense = MOI.MIN_SENSE # model parameter, so needs to be reset
optimizer.objconstant = 0.
optimizer.constrconstant = Float64[]
Expand All @@ -135,10 +136,9 @@ function MOI.empty!(optimizer::Optimizer)
optimizer
end

MOI.is_empty(optimizer::Optimizer) = optimizer.is_empty
MOI.is_empty(optimizer::Optimizer) = optimizer.inner.isempty

function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; copy_names=false)
copy_names && error("Copying names is not supported.")
function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
MOI.empty!(dest)
idxmap = MOIU.IndexMap(dest, src)
assign_constraint_row_ranges!(dest.rowranges, idxmap, src)
Expand All @@ -153,8 +153,7 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; copy_names=false)
dest.warmstartcache = WarmStartCache{Float64}(size(A, 2), size(A, 1))
processprimalstart!(dest.warmstartcache.x, src, idxmap)
processdualstart!(dest.warmstartcache.y, src, idxmap, dest.rowranges)
dest.is_empty = false
idxmap
return idxmap
end

"""
Expand All @@ -167,7 +166,7 @@ function MOIU.IndexMap(dest::Optimizer, src::MOI.ModelLike)
idxmap[vis_src[i]] = VI(i)
end
i = 0
for (F, S) in MOI.get(src, MOI.ListOfConstraints())
for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent())
MOI.supports_constraint(dest, F, S) || throw(MOI.UnsupportedConstraint{F, S}())
cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F, S}())
for ci in cis_src
Expand All @@ -180,7 +179,7 @@ end

function assign_constraint_row_ranges!(rowranges::Dict{Int, UnitRange{Int}}, idxmap::MOIU.IndexMap, src::MOI.ModelLike)
startrow = 1
for (F, S) in MOI.get(src, MOI.ListOfConstraints())
for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent())
cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F, S}())
for ci_src in cis_src
set = MOI.get(src, MOI.ConstraintSet(), ci_src)
Expand Down Expand Up @@ -216,8 +215,8 @@ function processobjective(src::MOI.ModelLike, idxmap)
c = faffine.constant
elseif function_type == MOI.ScalarQuadraticFunction{Float64}
fquadratic = MOI.get(src, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}())
I = [Int(idxmap[term.variable_index_1].value) for term in fquadratic.quadratic_terms]
J = [Int(idxmap[term.variable_index_2].value) for term in fquadratic.quadratic_terms]
I = [Int(idxmap[term.variable_1].value) for term in fquadratic.quadratic_terms]
J = [Int(idxmap[term.variable_2].value) for term in fquadratic.quadratic_terms]
V = [term.coefficient for term in fquadratic.quadratic_terms]
upper_triangularize!(I, J, V)
P = sparse(I, J, V, n, n)
Expand All @@ -243,7 +242,7 @@ function processlinearterms!(q, terms::Vector{<:MOI.ScalarAffineTerm}, idxmapfun
q .= 0
end
for term in terms
var = term.variable_index
var = term.variable
coeff = term.coefficient
q[idxmapfun(var).value] += coeff
end
Expand Down Expand Up @@ -272,7 +271,7 @@ function processconstraints(src::MOI.ModelLike, idxmap, rowranges::Dict{Int, Uni
I = Int[]
J = Int[]
V = Float64[]
for (F, S) in MOI.get(src, MOI.ListOfConstraints())
for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent())
processconstraints!((I, J, V), bounds, constant, src, idxmap, rowranges, F, S)
end
l .-= constant
Expand Down Expand Up @@ -311,7 +310,7 @@ end
function processlinearpart!(triplets::SparseTriplets, f::MOI.ScalarAffineFunction, row::Int, idxmap)
(I, J, V) = triplets
for term in f.terms
var = term.variable_index
var = term.variable
coeff = term.coefficient
col = idxmap[var].value
push!(I, row)
Expand All @@ -324,7 +323,7 @@ function processlinearpart!(triplets::SparseTriplets, f::MOI.VectorAffineFunctio
(I, J, V) = triplets
for term in f.terms
row = rows[term.output_index]
var = term.scalar_term.variable_index
var = term.scalar_term.variable
coeff = term.scalar_term.coefficient
col = idxmap[var].value
push!(I, row)
Expand Down Expand Up @@ -371,7 +370,7 @@ function processprimalstart!(x, src::MOI.ModelLike, idxmap)
end

function processdualstart!(y, src::MOI.ModelLike, idxmap, rowranges::Dict{Int, UnitRange{Int}})
for (F, S) in MOI.get(src, MOI.ListOfConstraints())
for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent())
has_dual_start = false
for attr in MOI.get(src, MOI.ListOfConstraintAttributesSet{F, S}())
if attr isa MOI.ConstraintDualStart
Expand Down Expand Up @@ -436,10 +435,10 @@ end # module

using .OSQPSettings

_symbol(param::MOI.RawParameter) = Symbol(param.name)
_symbol(param::MOI.RawOptimizerAttribute) = Symbol(param.name)
_symbol(a::OSQPAttribute) = Symbol(a)
OSQPSettings.isupdatable(param::MOI.RawParameter) = _contains(OSQP.UPDATABLE_SETTINGS, _symbol(param))
function MOI.set(optimizer::Optimizer, a::Union{OSQPAttribute, MOI.RawParameter}, value)
OSQPSettings.isupdatable(param::MOI.RawOptimizerAttribute) = _contains(OSQP.UPDATABLE_SETTINGS, _symbol(param))
function MOI.set(optimizer::Optimizer, a::Union{OSQPAttribute, MOI.RawOptimizerAttribute}, value)
(isupdatable(a) || MOI.is_empty(optimizer)) || throw(MOI.SetAttributeNotAllowed(a))
setting = _symbol(a)
optimizer.settings[setting] = value
Expand All @@ -448,7 +447,7 @@ function MOI.set(optimizer::Optimizer, a::Union{OSQPAttribute, MOI.RawParameter}
end
end

function MOI.get(optimizer::Optimizer, a::Union{OSQPAttribute, MOI.RawParameter})
function MOI.get(optimizer::Optimizer, a::Union{OSQPAttribute, MOI.RawOptimizerAttribute})
return optimizer.settings[_symbol(a)]
end

Expand Down Expand Up @@ -486,8 +485,8 @@ function MOI.set(optimizer::Optimizer, a::MOI.ObjectiveFunction{Quadratic}, obj:
cache = optimizer.modcache
cache.P[:] = 0
for term in obj.quadratic_terms
row = term.variable_index_1.value
col = term.variable_index_2.value
row = term.variable_1.value
col = term.variable_2.value
coeff = term.coefficient
row > col && ((row, col) = (col, row)) # upper triangle only
if !(CartesianIndex(row, col) in cache.P.cartesian_indices_set)
Expand All @@ -514,7 +513,7 @@ function check_has_results(optimizer::Optimizer)
end

# Since these aren't explicitly returned by OSQP, I feel like it would be better to have a fallback method compute these:
function MOI.get(optimizer::Optimizer, a::MOI.SolveTime)
function MOI.get(optimizer::Optimizer, ::MOI.SolveTimeSec)
check_has_results(optimizer)
return optimizer.results.info.run_time
end
Expand Down Expand Up @@ -549,18 +548,19 @@ function MOI.get(optimizer::Optimizer, ::MOI.TerminationStatus)
end

function MOI.get(optimizer::Optimizer, a::MOI.PrimalStatus)
if a.N > MOI.get(optimizer, MOI.ResultCount())
if a.result_index > MOI.get(optimizer, MOI.ResultCount())
return MOI.NO_SOLUTION
end
osqpstatus = optimizer.results.info.status
if osqpstatus == :Unsolved
return MOI.NO_SOLUTION
elseif osqpstatus == :Primal_infeasible
return MOI.INFEASIBILITY_CERTIFICATE
# FIXME is it `NO_SOLUTION` (e.g. `NaN`s) or `INFEASIBLE_POINT` (e.g. current primal solution that we know is infeasible with the dual certificate)
return MOI.NO_SOLUTION
elseif osqpstatus == :Solved
return MOI.FEASIBLE_POINT
elseif osqpstatus == :Primal_infeasible_inaccurate
return MOI.NEARLY_INFEASIBILITY_CERTIFICATE
return MOI.UNKNOWN_RESULT_STATUS
elseif osqpstatus == :Dual_infeasible
return MOI.INFEASIBILITY_CERTIFICATE
else # :Interrupted, :Max_iter_reached, :Solved_inaccurate, :Non_convex (TODO: good idea? use OSQP.SOLUTION_PRESENT?)
Expand All @@ -569,14 +569,15 @@ function MOI.get(optimizer::Optimizer, a::MOI.PrimalStatus)
end

function MOI.get(optimizer::Optimizer, a::MOI.DualStatus)
if a.N > MOI.get(optimizer, MOI.ResultCount())
if a.result_index > MOI.get(optimizer, MOI.ResultCount())
return MOI.NO_SOLUTION
end
osqpstatus = optimizer.results.info.status
if osqpstatus == :Unsolved
return MOI.NO_SOLUTION
elseif osqpstatus == :Dual_infeasible
return MOI.INFEASIBILITY_CERTIFICATE
# FIXME is it `NO_SOLUTION` (e.g. `NaN`s) or `INFEASIBLE_POINT` (e.g. current dual solution that we know is infeasible with the dual certificate)
return MOI.NO_SOLUTION
elseif osqpstatus == :Primal_infeasible
return MOI.INFEASIBILITY_CERTIFICATE
elseif osqpstatus == :Primal_infeasible_inaccurate
Expand Down Expand Up @@ -629,7 +630,7 @@ function MOI.set(optimizer::Optimizer, attr::MOI.ConstraintFunction, ci::CI{Affi
row = constraint_rows(optimizer, ci)
optimizer.modcache.A[row, :] = 0
for term in f.terms
col = term.variable_index.value
col = term.variable.value
coeff = term.coefficient
optimizer.modcache.A[row, col] += coeff
end
Expand All @@ -648,7 +649,7 @@ function MOI.set(optimizer::Optimizer, attr::MOI.ConstraintFunction, ci::CI{Vect
end
for term in f.terms
row = rows[term.output_index]
col = term.scalar_term.variable_index.value
col = term.scalar_term.variable.value
coeff = term.scalar_term.coefficient
optimizer.modcache.A[row, col] += coeff
end
Expand Down Expand Up @@ -707,12 +708,20 @@ end
# Objective modification
function MOI.modify(optimizer::Optimizer, attr::MOI.ObjectiveFunction, change::MOI.ScalarConstantChange)
MOI.is_empty(optimizer) && throw(MOI.ModifyObjectiveNotAllowed(change))
optimizer.objconstant = change.new_constant
constant = change.new_constant
if optimizer.sense == MOI.MAX_SENSE
constant = -constant
end
optimizer.objconstant = constant
end

function MOI.modify(optimizer::Optimizer, attr::MOI.ObjectiveFunction, change::MOI.ScalarCoefficientChange)
MOI.is_empty(optimizer) && throw(MOI.ModifyObjectiveNotAllowed(change))
optimizer.modcache.q[change.variable.value] = change.new_coefficient
coef = change.new_coefficient
if optimizer.sense == MOI.MAX_SENSE
coef = -coef
end
optimizer.modcache.q[change.variable.value] = coef
end

# There is currently no ScalarQuadraticCoefficientChange.
Expand Down
Loading