From 03c3c08e487535deb23e8d4207f94992873b4761 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 29 Jun 2022 10:57:30 +1200 Subject: [PATCH 1/3] Remove acronyms in the MOI wrapper --- src/MOI_wrapper.jl | 333 ++++++++++++++++++++++++++++---------------- src/modcaches.jl | 96 +++++++------ test/MOI_wrapper.jl | 11 +- 3 files changed, 272 insertions(+), 168 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index acc45e9..c98b397 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -3,43 +3,32 @@ module MathOptInterfaceOSQP include("modcaches.jl") using .ModificationCaches -using SparseArrays -using MathOptInterface -using MathOptInterface.Utilities -using LinearAlgebra: rmul! +import LinearAlgebra: rmul! +import MathOptInterface +import SparseArrays +import OSQP export Optimizer, OSQPSettings, OSQPModel const MOI = MathOptInterface -const MOIU = MathOptInterface.Utilities -const CI = MOI.ConstraintIndex -const VI = MOI.VariableIndex const SparseTriplets = Tuple{Vector{Int},Vector{Int},Vector{<:Any}} -const Affine = MOI.ScalarAffineFunction{Float64} -const Quadratic = MOI.ScalarQuadraticFunction{Float64} -const VectorAffine = MOI.VectorAffineFunction{Float64} - -const Interval = MOI.Interval{Float64} -const LessThan = MOI.LessThan{Float64} -const GreaterThan = MOI.GreaterThan{Float64} -const EqualTo = MOI.EqualTo{Float64} -const IntervalConvertible = Union{Interval,LessThan,GreaterThan,EqualTo} +const IntervalConvertible = Union{ + MOI.Interval{Float64}, + MOI.LessThan{Float64}, + MOI.GreaterThan{Float64}, + MOI.EqualTo{Float64}, +} -const Zeros = MOI.Zeros -const Nonnegatives = MOI.Nonnegatives -const Nonpositives = MOI.Nonpositives -const SupportedVectorSets = Union{Zeros,Nonnegatives,Nonpositives} +const SupportedVectorSets = Union{MOI.Zeros,MOI.Nonnegatives,MOI.Nonpositives} -import OSQP - -lower(::Zeros, i::Int) = 0.0 -lower(::Nonnegatives, i::Int) = 0.0 -lower(::Nonpositives, i::Int) = -Inf -upper(::Zeros, i::Int) = 0.0 -upper(::Nonnegatives, i::Int) = Inf -upper(::Nonpositives, i::Int) = 0.0 +lower(::MOI.Zeros, i::Int) = 0.0 +lower(::MOI.Nonnegatives, i::Int) = 0.0 +lower(::MOI.Nonpositives, i::Int) = -Inf +upper(::MOI.Zeros, i::Int) = 0.0 +upper(::MOI.Nonnegatives, i::Int) = Inf +upper(::MOI.Nonpositives, i::Int) = 0.0 # TODO: just use ∈ on 0.7 (allocates on 0.6): function _contains(haystack, needle) @@ -98,6 +87,7 @@ end MOI.get(::Optimizer, ::MOI.SolverName) = "OSQP" MOI.supports(::Optimizer, ::MOI.Silent) = true + function MOI.set(optimizer::Optimizer, ::MOI.Silent, value::Bool) optimizer.silent = value if !MOI.is_empty(optimizer) @@ -110,16 +100,19 @@ function MOI.set(optimizer::Optimizer, ::MOI.Silent, value::Bool) ) end end + return end + MOI.get(optimizer::Optimizer, ::MOI.Silent) = optimizer.silent MOI.supports(::Optimizer, ::MOI.TimeLimitSec) = true + function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, limit::Real) MOI.set(model, OSQPSettings.TimeLimit(), limit) return end -function MOI.set(model::Optimizer, attr::MOI.TimeLimitSec, ::Nothing) +function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, ::Nothing) delete!(model.settings, :time_limit) if !MOI.is_empty(model) OSQP.update_settings!(model.inner, time_limit = 0.0) @@ -143,14 +136,15 @@ function MOI.empty!(optimizer::Optimizer) optimizer.modcache = ProblemModificationCache{Float64}() optimizer.warmstartcache = WarmStartCache{Float64}() empty!(optimizer.rowranges) - return optimizer + optimizer + return end MOI.is_empty(optimizer::Optimizer) = optimizer.inner.isempty function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) MOI.empty!(dest) - idxmap = MOIU.IndexMap(dest, src) + idxmap = MOI.Utilities.IndexMap(dest, src) assign_constraint_row_ranges!(dest.rowranges, idxmap, src) dest.sense, P, q, dest.objconstant = processobjective(src, idxmap) A, l, u, dest.constrconstant = @@ -168,13 +162,14 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) end """ -Set up index map from `src` variables and constraints to `dest` variables and constraints. +Set up index map from `src` variables and constraints to `dest` variables and +constraints. """ -function MOIU.IndexMap(dest::Optimizer, src::MOI.ModelLike) - idxmap = MOIU.IndexMap() +function MOI.Utilities.IndexMap(dest::Optimizer, src::MOI.ModelLike) + idxmap = MOI.Utilities.IndexMap() vis_src = MOI.get(src, MOI.ListOfVariableIndices()) for i in eachindex(vis_src) - idxmap[vis_src[i]] = VI(i) + idxmap[vis_src[i]] = MOI.VariableIndex(i) end i = 0 for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent()) @@ -183,7 +178,7 @@ function MOIU.IndexMap(dest::Optimizer, src::MOI.ModelLike) cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F,S}()) for ci in cis_src i += 1 - idxmap[ci] = CI{F,S}(i) + idxmap[ci] = MOI.ConstraintIndex{F,S}(i) end end return idxmap @@ -191,7 +186,7 @@ end function assign_constraint_row_ranges!( rowranges::Dict{Int,UnitRange{Int}}, - idxmap::MOIU.IndexMap, + idxmap::MOI.Utilities.IndexMap, src::MOI.ModelLike, ) startrow = 1 @@ -205,28 +200,32 @@ function assign_constraint_row_ranges!( startrow = endrow + 1 end end + return end function constraint_rows( rowranges::Dict{Int,UnitRange{Int}}, - ci::CI{<:Any,<:MOI.AbstractScalarSet}, + ci::MOI.ConstraintIndex{<:Any,<:MOI.AbstractScalarSet}, ) rowrange = rowranges[ci.value] length(rowrange) == 1 || error() return first(rowrange) end + function constraint_rows( rowranges::Dict{Int,UnitRange{Int}}, - ci::CI{<:Any,<:MOI.AbstractVectorSet}, + ci::MOI.ConstraintIndex{<:Any,<:MOI.AbstractVectorSet}, ) return rowranges[ci.value] end -function constraint_rows(optimizer::Optimizer, ci::CI) + +function constraint_rows(optimizer::Optimizer, ci::MOI.ConstraintIndex) return constraint_rows(optimizer.rowranges, ci) end """ -Return objective sense, as well as matrix `P`, vector `q`, and scalar `c` such that objective function is `1/2 x' P x + q' x + c`. +Return objective sense, as well as matrix `P`, vector `q`, and scalar `c` such +that objective function is `1/2 x' P x + q' x + c`. """ function processobjective(src::MOI.ModelLike, idxmap) sense = MOI.get(src, MOI.ObjectiveSense()) @@ -239,7 +238,7 @@ function processobjective(src::MOI.ModelLike, idxmap) src, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), ) - P = spzeros(n, n) + P = SparseArrays.spzeros(n, n) processlinearterms!(q, faffine.terms, idxmap) c = faffine.constant elseif function_type == MOI.ScalarQuadraticFunction{Float64} @@ -257,7 +256,7 @@ function processobjective(src::MOI.ModelLike, idxmap) ] V = [term.coefficient for term in fquadratic.quadratic_terms] upper_triangularize!(I, J, V) - P = sparse(I, J, V, n, n) + P = SparseArrays.sparse(I, J, V, n, n) processlinearterms!(q, fquadratic.affine_terms, idxmap) c = fquadratic.constant else @@ -269,7 +268,7 @@ function processobjective(src::MOI.ModelLike, idxmap) end sense == MOI.MAX_SENSE && (rmul!(P, -1); rmul!(q, -1); c = -c) else - P = spzeros(n, n) + P = SparseArrays.spzeros(n, n) q = zeros(n) c = 0.0 end @@ -292,14 +291,16 @@ function processlinearterms!( coeff = term.coefficient q[idxmapfun(var).value] += coeff end + return end function processlinearterms!( q, terms::Vector{<:MOI.ScalarAffineTerm}, - idxmap::MOIU.IndexMap, + idxmap::MOI.Utilities.IndexMap, ) - return processlinearterms!(q, terms, var -> idxmap[var]) + processlinearterms!(q, terms, var -> idxmap[var]) + return end function upper_triangularize!(I::Vector{Int}, J::Vector{Int}, V::Vector) @@ -310,6 +311,7 @@ function upper_triangularize!(I::Vector{Int}, J::Vector{Int}, V::Vector) I[i], J[i] = J[i], I[i] end end + return end function processconstraints( @@ -340,8 +342,8 @@ function processconstraints( l .-= constant u .-= constant n = MOI.get(src, MOI.NumberOfVariables()) - A = sparse(I, J, V, m, n) - return (A, l, u, constant) + A = SparseArrays.sparse(I, J, V, m, n) + return A, l, u, constant end function processconstraints!( @@ -363,22 +365,27 @@ function processconstraints!( processlinearpart!(triplets, f, rows, idxmap) processconstraintset!(bounds, rows, s) end - return nothing + return end -function processconstant!(c::Vector{Float64}, row::Int, f::Affine) +function processconstant!( + c::Vector{Float64}, + row::Int, + f::MOI.ScalarAffineFunction{Float64}, +) c[row] = MOI.constant(f, Float64) - return nothing + return end function processconstant!( c::Vector{Float64}, rows::UnitRange{Int}, - f::VectorAffine, + f::MOI.VectorAffineFunction{Float64}, ) for (i, row) in enumerate(rows) c[row] = f.constants[i] end + return end function processlinearpart!( @@ -396,6 +403,7 @@ function processlinearpart!( push!(J, col) push!(V, coeff) end + return end function processlinearpart!( @@ -414,6 +422,7 @@ function processlinearpart!( push!(J, col) push!(V, coeff) end + return end function processconstraintset!( @@ -421,18 +430,19 @@ function processconstraintset!( row::Int, s::IntervalConvertible, ) - return processconstraintset!(bounds, row, MOI.Interval(s)) + processconstraintset!(bounds, row, MOI.Interval(s)) + return end function processconstraintset!( bounds::Tuple{<:Vector,<:Vector}, row::Int, - interval::Interval, + interval::MOI.Interval{Float64}, ) l, u = bounds l[row] = interval.lower u[row] = interval.upper - return nothing + return end function processconstraintset!( @@ -445,6 +455,7 @@ function processconstraintset!( l[row] = lower(s, i) u[row] = upper(s, i) end + return end function processprimalstart!(x, src::MOI.ModelLike, idxmap) @@ -458,11 +469,12 @@ function processprimalstart!(x, src::MOI.ModelLike, idxmap) vis_src = MOI.get(src, MOI.ListOfVariableIndices()) for vi in vis_src value = MOI.get(src, MOI.VariablePrimalStart(), vi) - if value != nothing + if value !== nothing x[idxmap[vi].value] = value end end end + return end function processdualstart!( @@ -483,7 +495,7 @@ function processdualstart!( for ci in cis_src rows = constraint_rows(rowranges, idxmap[ci]) dual = MOI.get(src, MOI.ConstraintDualStart(), ci) - if dual != nothing + if dual !== nothing for (i, row) in enumerate(rows) y[row] = -dual[i] # opposite dual convention end @@ -491,16 +503,22 @@ function processdualstart!( end end end + return end ## Standard optimizer attributes: + MOI.get(optimizer::Optimizer, ::MOI.ObjectiveSense) = optimizer.sense -function MOI.get(optimizer::Optimizer, a::MOI.NumberOfVariables) + +function MOI.get(optimizer::Optimizer, ::MOI.NumberOfVariables) return OSQP.dimensions(optimizer.inner)[1] end -function MOI.get(optimizer::Optimizer, a::MOI.ListOfVariableIndices) - return [VI(i) for i in 1:MOI.get(optimizer, MOI.NumberOfVariables())] # TODO: support for UnitRange would be nice +function MOI.get(optimizer::Optimizer, ::MOI.ListOfVariableIndices) + return [ + MOI.VariableIndex(i) for + i in 1:MOI.get(optimizer, MOI.NumberOfVariables()) + ] # TODO: support for UnitRange would be nice end ## Solver-specific optimizer attributes: @@ -538,10 +556,13 @@ end # module using .OSQPSettings _symbol(param::MOI.RawOptimizerAttribute) = Symbol(param.name) + _symbol(a::OSQPAttribute) = Symbol(a) + function OSQPSettings.isupdatable(param::MOI.RawOptimizerAttribute) return _contains(OSQP.UPDATABLE_SETTINGS, _symbol(param)) end + function MOI.set( optimizer::Optimizer, a::Union{OSQPAttribute,MOI.RawOptimizerAttribute}, @@ -569,14 +590,17 @@ function MOI.optimize!(optimizer::Optimizer) processupdates!(optimizer.inner, optimizer.warmstartcache) OSQP.solve!(optimizer.inner, optimizer.results) optimizer.hasresults = true - # Copy previous solution into warm start cache without setting the dirty bit: + # Copy previous solution into warm start cache without setting the dirty + # bit: copyto!(optimizer.warmstartcache.x.data, optimizer.results.x) copyto!(optimizer.warmstartcache.y.data, optimizer.results.y) - return nothing + return end ## Optimizer attributes: + MOI.get(optimizer::Optimizer, ::MOI.RawSolver) = optimizer.inner + MOI.get(optimizer::Optimizer, ::MOI.ResultCount) = optimizer.hasresults ? 1 : 0 function MOI.supports( @@ -585,7 +609,14 @@ function MOI.supports( ) return true end -MOI.supports(::Optimizer, ::MOI.ObjectiveFunction{Quadratic}) = true + +function MOI.supports( + ::Optimizer, + ::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}, +) + return true +end + MOI.supports(::Optimizer, ::MOI.ObjectiveSense) = true function MOI.set( @@ -593,17 +624,19 @@ function MOI.set( a::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, obj::MOI.ScalarAffineFunction{Float64}, ) - MOI.is_empty(optimizer) && throw(MOI.SetAttributeNotAllowed(a)) + if MOI.is_empty(optimizer) + throw(MOI.SetAttributeNotAllowed(a)) + end optimizer.modcache.P[:] = 0 processlinearterms!(optimizer.modcache.q, obj.terms) optimizer.objconstant = MOI.constant(obj) - return nothing + return end function MOI.set( optimizer::Optimizer, - a::MOI.ObjectiveFunction{Quadratic}, - obj::Quadratic, + a::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}, + obj::MOI.ScalarQuadraticFunction{Float64}, ) MOI.is_empty(optimizer) && throw(MOI.SetAttributeNotAllowed(a)) cache = optimizer.modcache @@ -617,7 +650,9 @@ function MOI.set( throw( MOI.SetAttributeNotAllowed( a, - "This nonzero entry was not in the sparsity pattern of the objective function provided at `MOI.copy_to` and OSQP does not support changing the sparsity pattern.", + "This nonzero entry was not in the sparsity pattern of " * + "the objective function provided at `MOI.copy_to` and " * + "OSQP does not support changing the sparsity pattern.", ), ) end @@ -625,7 +660,7 @@ function MOI.set( end processlinearterms!(optimizer.modcache.q, obj.affine_terms) optimizer.objconstant = MOI.constant(obj) - return nothing + return end function MOI.get(optimizer::Optimizer, a::MOI.ObjectiveValue) @@ -634,14 +669,15 @@ function MOI.get(optimizer::Optimizer, a::MOI.ObjectiveValue) return ifelse(optimizer.sense == MOI.MAX_SENSE, -rawobj, rawobj) end -error_not_solved() = error("Problem is unsolved.") function check_has_results(optimizer::Optimizer) if !hasresults(optimizer) - error_not_solved() + error("Problem is unsolved.") end + return end -# Since these aren't explicitly returned by OSQP, I feel like it would be better to have a fallback method compute these: +# 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, ::MOI.SolveTimeSec) check_has_results(optimizer) return optimizer.results.info.run_time @@ -684,7 +720,9 @@ function MOI.get(optimizer::Optimizer, a::MOI.PrimalStatus) if osqpstatus == :Unsolved return MOI.NO_SOLUTION elseif osqpstatus == :Primal_infeasible - # 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) + # 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 @@ -692,7 +730,9 @@ function MOI.get(optimizer::Optimizer, a::MOI.PrimalStatus) 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?) + else + # :Interrupted, :Max_iter_reached, :Solved_inaccurate, :Non_convex + # (TODO: good idea? use OSQP.SOLUTION_PRESENT?) return MOI.NO_SOLUTION end end @@ -705,7 +745,9 @@ function MOI.get(optimizer::Optimizer, a::MOI.DualStatus) if osqpstatus == :Unsolved return MOI.NO_SOLUTION elseif osqpstatus == :Dual_infeasible - # 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) + # 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 @@ -713,18 +755,26 @@ function MOI.get(optimizer::Optimizer, a::MOI.DualStatus) return MOI.NEARLY_INFEASIBILITY_CERTIFICATE elseif osqpstatus == :Solved return MOI.FEASIBLE_POINT - else # :Interrupted, :Max_iter_reached, :Solved_inaccurate, :Non_convex (TODO: good idea? use OSQP.SOLUTION_PRESENT?) + else + # :Interrupted, :Max_iter_reached, :Solved_inaccurate, :Non_convex + # (TODO: good idea? use OSQP.SOLUTION_PRESENT?) return MOI.NO_SOLUTION end end ## Variables: -function MOI.is_valid(optimizer::Optimizer, vi::VI) + +function MOI.is_valid(optimizer::Optimizer, vi::MOI.VariableIndex) return vi.value ∈ 1:MOI.get(optimizer, MOI.NumberOfVariables()) end ## Variable attributes: -function MOI.get(optimizer::Optimizer, a::MOI.VariablePrimal, vi::VI) + +function MOI.get( + optimizer::Optimizer, + a::MOI.VariablePrimal, + vi::MOI.VariableIndex, +) MOI.check_result_index_bounds(optimizer, a) x = ifelse( _contains(OSQP.SOLUTION_PRESENT, optimizer.results.info.status), @@ -737,23 +787,28 @@ end function MOI.set( optimizer::Optimizer, a::MOI.VariablePrimalStart, - vi::VI, + vi::MOI.VariableIndex, value, ) - MOI.is_empty(optimizer) && throw(MOI.SetAttributeNotAllowed(a)) + if MOI.is_empty(optimizer) + throw(MOI.SetAttributeNotAllowed(a)) + end return optimizer.warmstartcache.x[vi.value] = value end ## Constraints: -function MOI.is_valid(optimizer::Optimizer, ci::CI) - MOI.is_empty(optimizer) && return false + +function MOI.is_valid(optimizer::Optimizer, ci::MOI.ConstraintIndex) + if MOI.is_empty(optimizer) + return false + end return ci.value ∈ keys(optimizer.rowranges) end function MOI.set( optimizer::Optimizer, a::MOI.ConstraintDualStart, - ci::CI, + ci::MOI.ConstraintIndex, value, ) MOI.is_empty(optimizer) && throw(MOI.SetAttributeNotAllowed(a)) @@ -761,17 +816,23 @@ function MOI.set( for (i, row) in enumerate(rows) optimizer.warmstartcache.y[row] = -value[i] # opposite dual convention end - return nothing + return end # function modification: + function MOI.set( optimizer::Optimizer, - attr::MOI.ConstraintFunction, - ci::CI{Affine,<:IntervalConvertible}, - f::Affine, + ::MOI.ConstraintFunction, + ci::MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + <:IntervalConvertible, + }, + f::MOI.ScalarAffineFunction{Float64}, ) - MOI.is_valid(optimizer, ci) || throw(MOI.InvalidIndex(ci)) + if !MOI.is_valid(optimizer, ci) + throw(MOI.InvalidIndex(ci)) + end row = constraint_rows(optimizer, ci) optimizer.modcache.A[row, :] = 0 for term in f.terms @@ -783,16 +844,21 @@ function MOI.set( optimizer.constrconstant[row] = f.constant optimizer.modcache.l[row] += Δconstant optimizer.modcache.u[row] += Δconstant - return nothing + return end function MOI.set( optimizer::Optimizer, - attr::MOI.ConstraintFunction, - ci::CI{VectorAffine,<:SupportedVectorSets}, - f::VectorAffine, + ::MOI.ConstraintFunction, + ci::MOI.ConstraintIndex{ + MOI.VectorAffineFunction{Float64}, + <:SupportedVectorSets, + }, + f::MOI.VectorAffineFunction{Float64}, ) - MOI.is_valid(optimizer, ci) || throw(MOI.InvalidIndex(ci)) + if !MOI.is_valid(optimizer, ci) + throw(MOI.InvalidIndex(ci)) + end rows = constraint_rows(optimizer, ci) for row in rows optimizer.modcache.A[row, :] = 0 @@ -809,71 +875,87 @@ function MOI.set( optimizer.modcache.l[row] += Δconstant optimizer.modcache.u[row] += Δconstant end + return end # set modification: + function MOI.set( optimizer::Optimizer, - attr::MOI.ConstraintSet, - ci::CI{Affine,S}, + ::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},S}, s::S, ) where {S<:IntervalConvertible} - MOI.is_valid(optimizer, ci) || throw(MOI.InvalidIndex(ci)) - interval = S <: Interval ? s : MOI.Interval(s) + if !MOI.is_valid(optimizer, ci) + throw(MOI.InvalidIndex(ci)) + end + interval = S <: MOI.Interval ? s : MOI.Interval(s) row = constraint_rows(optimizer, ci) constant = optimizer.constrconstant[row] optimizer.modcache.l[row] = interval.lower - constant optimizer.modcache.u[row] = interval.upper - constant - return nothing + return end function MOI.set( optimizer::Optimizer, - attr::MOI.ConstraintSet, - ci::CI{VectorAffine,S}, + ::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{MOI.VectorAffineFunction{Float64},S}, s::S, ) where {S<:SupportedVectorSets} - MOI.is_valid(optimizer, ci) || throw(MOI.InvalidIndex(ci)) + if !MOI.is_valid(optimizer, ci) + throw(MOI.InvalidIndex(ci)) + end rows = constraint_rows(optimizer, ci) for (i, row) in enumerate(rows) constant = optimizer.constrconstant[row] optimizer.modcache.l[row] = lower(s, i) - constant optimizer.modcache.u[row] = upper(s, i) - constant end - return nothing + return end # partial function modification: + function MOI.modify( optimizer::Optimizer, - ci::CI{Affine,<:IntervalConvertible}, + ci::MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + <:IntervalConvertible, + }, change::MOI.ScalarCoefficientChange, ) MOI.is_valid(optimizer, ci) || throw(MOI.InvalidIndex(ci)) row = constraint_rows(optimizer, ci) optimizer.modcache.A[row, change.variable.value] = change.new_coefficient - return nothing + return end # TODO: MultirowChange? function MOI.supports_constraint( - optimizer::Optimizer, - ::Type{Affine}, + ::Optimizer, + ::Type{MOI.ScalarAffineFunction{Float64}}, ::Type{<:IntervalConvertible}, ) return true end + function MOI.supports_constraint( - optimizer::Optimizer, - ::Type{VectorAffine}, + ::Optimizer, + ::Type{MOI.VectorAffineFunction{Float64}}, ::Type{<:SupportedVectorSets}, ) return true end ## Constraint attributes: -function MOI.get(optimizer::Optimizer, a::MOI.ConstraintDual, ci::CI) + +function MOI.get( + optimizer::Optimizer, + a::MOI.ConstraintDual, + ci::MOI.ConstraintIndex, +) MOI.check_result_index_bounds(optimizer, a) y = ifelse( _contains(OSQP.SOLUTION_PRESENT, optimizer.results.info.status), @@ -885,12 +967,15 @@ function MOI.get(optimizer::Optimizer, a::MOI.ConstraintDual, ci::CI) end # Objective modification + function MOI.modify( optimizer::Optimizer, - attr::MOI.ObjectiveFunction, + ::MOI.ObjectiveFunction, change::MOI.ScalarConstantChange, ) - MOI.is_empty(optimizer) && throw(MOI.ModifyObjectiveNotAllowed(change)) + if MOI.is_empty(optimizer) + throw(MOI.ModifyObjectiveNotAllowed(change)) + end constant = change.new_constant if optimizer.sense == MOI.MAX_SENSE constant = -constant @@ -900,10 +985,12 @@ end function MOI.modify( optimizer::Optimizer, - attr::MOI.ObjectiveFunction, + ::MOI.ObjectiveFunction, change::MOI.ScalarCoefficientChange, ) - MOI.is_empty(optimizer) && throw(MOI.ModifyObjectiveNotAllowed(change)) + if MOI.is_empty(optimizer) + throw(MOI.ModifyObjectiveNotAllowed(change)) + end coef = change.new_coefficient if optimizer.sense == MOI.MAX_SENSE coef = -coef @@ -913,16 +1000,16 @@ end # There is currently no ScalarQuadraticCoefficientChange. -MOIU.@model( - OSQPModel, # modelname - (), # scalarsets - (MOI.Interval, MOI.LessThan, MOI.GreaterThan, MOI.EqualTo), # typedscalarsets - (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives), # vectorsets - (), # typedvectorsets - (), # scalarfunctions - (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction), # typedscalarfunctions - (), # vectorfunctions - (MOI.VectorAffineFunction,) # typedvectorfunctions +MOI.Utilities.@model( + OSQPModel, + (), + (MOI.Interval, MOI.LessThan, MOI.GreaterThan, MOI.EqualTo), + (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives), + (), + (), + (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction), + (), + (MOI.VectorAffineFunction,) ) end # module diff --git a/src/modcaches.jl b/src/modcaches.jl index a2716fa..5a27860 100644 --- a/src/modcaches.jl +++ b/src/modcaches.jl @@ -1,7 +1,8 @@ module ModificationCaches -using LinearAlgebra -using SparseArrays +import LinearAlgebra +import OSQP +import SparseArrays export VectorModificationCache, MatrixModificationCache, @@ -9,8 +10,6 @@ export VectorModificationCache, WarmStartCache, processupdates! -import OSQP - mutable struct VectorModificationCache{T} data::Vector{T} dirty::Bool @@ -19,11 +18,17 @@ mutable struct VectorModificationCache{T} end end function Base.setindex!(cache::VectorModificationCache, x, i::Integer) - return (cache.dirty = true; cache.data[i] = x) + cache.dirty = true + cache.data[i] = x + return x end + function Base.setindex!(cache::VectorModificationCache, x, ::Colon) - return (cache.dirty = true; cache.data .= x) + cache.dirty = true + cache.data .= x + return x end + Base.getindex(cache::VectorModificationCache, i::Integer) = cache.data[i] function processupdates!( @@ -35,31 +40,38 @@ function processupdates!( updatefun(model, cache.data) cache.dirty = false end + return end struct MatrixModificationCache{T} cartesian_indices::Vector{CartesianIndex{2}} - cartesian_indices_set::Set{CartesianIndex{2}} # to speed up checking whether indices are out of bounds in setindex! + # To speed up checking whether indices are out of bounds in setindex! + cartesian_indices_set::Set{CartesianIndex{2}} cartesian_indices_per_row::Dict{Int,Vector{CartesianIndex{2}}} modifications::Dict{CartesianIndex{2},T} vals::Vector{T} inds::Vector{Int} - function MatrixModificationCache(S::SparseMatrixCSC{T}) where {T} - cartesian_indices = Vector{CartesianIndex{2}}(undef, nnz(S)) + function MatrixModificationCache( + S::SparseArrays.SparseMatrixCSC{T}, + ) where {T} + cartesian_indices = + Vector{CartesianIndex{2}}(undef, SparseArrays.nnz(S)) cartesian_indices_per_row = Dict{Int,Vector{CartesianIndex{2}}}() - sizehint!(cartesian_indices_per_row, nnz(S)) - @inbounds for col in 1:S.n, k in S.colptr[col]:(S.colptr[col+1]-1) # from sparse findn - row = S.rowval[k] - I = CartesianIndex(row, col) - cartesian_indices[k] = I - push!( - get!(() -> CartesianIndex{2}[], cartesian_indices_per_row, row), - I, - ) + sizehint!(cartesian_indices_per_row, SparseArrays.nnz(S)) + @inbounds for col in 1:S.n + for k in S.colptr[col]:(S.colptr[col+1]-1) # from sparse findn + row = S.rowval[k] + I = CartesianIndex(row, col) + cartesian_indices[k] = I + if !haskey(cartesian_indices_per_row, row) + cartesian_indices_per_row[row] = CartesianIndex{2}[] + end + push!(cartesian_indices_per_row[row], I) + end end modifications = Dict{CartesianIndex{2},Int}() - sizehint!(modifications, nnz(S)) + sizehint!(modifications, SparseArrays.nnz(S)) return new{T}( cartesian_indices, Set(cartesian_indices), @@ -78,20 +90,22 @@ function Base.setindex!( col::Integer, ) I = CartesianIndex(row, col) - @boundscheck I ∈ cache.cartesian_indices_set || throw( - ArgumentError("Changing the sparsity pattern is not allowed."), - ) - return cache.modifications[I] = x + @boundscheck if !(I ∈ cache.cartesian_indices_set) + throw(ArgumentError("Changing the sparsity pattern is not allowed.")) + end + cache.modifications[I] = x + return x end function Base.setindex!(cache::MatrixModificationCache, x::Real, ::Colon) # used to zero out the entire matrix - @boundscheck x == 0 || throw( - ArgumentError("Changing the sparsity pattern is not allowed."), - ) + @boundscheck if x != 0 + throw(ArgumentError("Changing the sparsity pattern is not allowed.")) + end for I in cache.cartesian_indices cache.modifications[I] = 0 end + return x end function Base.setindex!( @@ -101,12 +115,13 @@ function Base.setindex!( ::Colon, ) # used to zero out a row - @boundscheck x == 0 || throw( - ArgumentError("Changing the sparsity pattern is not allowed."), - ) + @boundscheck if x != 0 + throw(ArgumentError("Changing the sparsity pattern is not allowed.")) + end for I in cache.cartesian_indices_per_row[row] cache.modifications[I] = 0 end + return x end function Base.getindex( @@ -122,8 +137,7 @@ function processupdates!( cache::MatrixModificationCache, updatefun::Function, ) - dirty = !isempty(cache.modifications) - if dirty + if !isempty(cache.modifications) nmods = length(cache.modifications) resize!(cache.vals, nmods) resize!(cache.inds, nmods) @@ -139,6 +153,7 @@ function processupdates!( updatefun(model, cache.vals, cache.inds) empty!(cache.modifications) end + return end # More OSQP-specific from here on: @@ -151,31 +166,32 @@ struct ProblemModificationCache{T} ProblemModificationCache{T}() where {T} = new{T}() function ProblemModificationCache( - P::SparseMatrixCSC, + P::SparseArrays.SparseMatrixCSC, q::Vector{T}, - A::SparseMatrixCSC, + A::SparseArrays.SparseMatrixCSC, l::Vector{T}, u::Vector{T}, ) where {T} MC = MatrixModificationCache VC = VectorModificationCache - return new{T}(MC(triu(P)), VC(q), MC(A), VC(l), VC(u)) + return new{T}(MC(LinearAlgebra.triu(P)), VC(q), MC(A), VC(l), VC(u)) end end function processupdates!(model::OSQP.Model, cache::ProblemModificationCache) if cache.l.dirty && cache.u.dirty - # Special case because setting just l or u may cause an 'upper bound must be greater than or equal to lower bound' error + # Special case because setting just l or u may cause an 'upper bound + # must be greater than or equal to lower bound' error OSQP.update_bounds!(model, cache.l.data, cache.u.data) cache.l.dirty = false cache.u.dirty = false end - processupdates!(model, cache.P, OSQP.update_P!) processupdates!(model, cache.q, OSQP.update_q!) processupdates!(model, cache.A, OSQP.update_A!) processupdates!(model, cache.l, OSQP.update_l!) - return processupdates!(model, cache.u, OSQP.update_u!) + processupdates!(model, cache.u, OSQP.update_u!) + return end struct WarmStartCache{T} @@ -193,13 +209,15 @@ end function processupdates!(model::OSQP.Model, cache::WarmStartCache) if cache.x.dirty && cache.y.dirty - # Special case because setting warm start for x only zeroes the stored warm start for y and vice versa. + # Special case because setting warm start for x only zeroes the stored + # warm start for y and vice versa. OSQP.warm_start_x_y!(model, cache.x.data, cache.y.data) cache.x.dirty = false cache.y.dirty = false end processupdates!(model, cache.x, OSQP.warm_start_x!) - return processupdates!(model, cache.y, OSQP.warm_start_y!) + processupdates!(model, cache.y, OSQP.warm_start_y!) + return end end diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 0969d27..9cbf7a5 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -7,7 +7,6 @@ using SparseArrays using MathOptInterface const MOI = MathOptInterface -const MOIU = MOI.Utilities using OSQP using OSQP.MathOptInterfaceOSQP @@ -49,8 +48,8 @@ function defaultoptimizer() end function bridged_optimizer() optimizer = defaultoptimizer() - cached = MOIU.CachingOptimizer( - MOIU.UniversalFallback(OSQPModel{Float64}()), + cached = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(OSQPModel{Float64}()), optimizer, ) return MOI.Bridges.full_bridge_optimizer(cached, Float64) @@ -58,7 +57,7 @@ end # FIXME: type piracy. Needs https://github.com/jump-dev/MathOptInterface.jl/issues/1310 function MOI.get( - optimizer::MOIU.CachingOptimizer, + optimizer::MOI.Utilities.CachingOptimizer, attr::MOI.ConstraintPrimal, ci::MOI.ConstraintIndex, ) @@ -219,7 +218,7 @@ function _test_optimizer_modification( modfun::Base.Callable, model::MOI.ModelLike, optimizer::T, - idxmap::MOIU.IndexMap, + idxmap::MOI.Utilities.IndexMap, cleanoptimizer::T, config::MOI.Test.Config, ) where {T<:MOI.AbstractOptimizer} @@ -633,7 +632,7 @@ function test_no_CachingOptimizer_Warm_starting() l = [1.0; 0; 0] u = [1.0; 0.7; 0.7] - model = MOIU.UniversalFallback(OSQPModel{Float64}()) + model = MOI.Utilities.UniversalFallback(OSQPModel{Float64}()) optimizer = defaultoptimizer() x = MOI.add_variables(model, 2) From b9a82d25a7f77b5a33c94e6c859218312455d122 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 30 Jun 2022 09:52:31 +1200 Subject: [PATCH 2/3] More updates --- src/MOI_wrapper.jl | 62 ++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index c98b397..335d765 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -3,7 +3,7 @@ module MathOptInterfaceOSQP include("modcaches.jl") using .ModificationCaches -import LinearAlgebra: rmul! +import LinearAlgebra import MathOptInterface import SparseArrays import OSQP @@ -33,7 +33,9 @@ upper(::MOI.Nonpositives, i::Int) = 0.0 # TODO: just use ∈ on 0.7 (allocates on 0.6): function _contains(haystack, needle) for x in haystack - x == needle && return true + if x == needle + return true + end end return false end @@ -266,7 +268,11 @@ function processobjective(src::MOI.ModelLike, idxmap) ), ) end - sense == MOI.MAX_SENSE && (rmul!(P, -1); rmul!(q, -1); c = -c) + if sense == MOI.MAX_SENSE + LinearAlgebra.rmul!(P, -1) + LinearAlgebra.rmul!(q, -1) + c = -c + end else P = SparseArrays.spzeros(n, n) q = zeros(n) @@ -515,10 +521,7 @@ function MOI.get(optimizer::Optimizer, ::MOI.NumberOfVariables) end function MOI.get(optimizer::Optimizer, ::MOI.ListOfVariableIndices) - return [ - MOI.VariableIndex(i) for - i in 1:MOI.get(optimizer, MOI.NumberOfVariables()) - ] # TODO: support for UnitRange would be nice + return map(MOI.VariableIndex, 1:MOI.get(optimizer, MOI.NumberOfVariables())) end ## Solver-specific optimizer attributes: @@ -575,6 +578,7 @@ function MOI.set( if !MOI.is_empty(optimizer) OSQP.update_settings!(optimizer.inner; setting => value) end + return end function MOI.get( @@ -688,7 +692,9 @@ function MOI.get(optimizer::Optimizer, ::MOI.RawStatusString) end function MOI.get(optimizer::Optimizer, ::MOI.TerminationStatus) - hasresults(optimizer) || return MOI.OPTIMIZE_NOT_CALLED + if !hasresults(optimizer) + return MOI.OPTIMIZE_NOT_CALLED + end osqpstatus = optimizer.results.info.status if osqpstatus == :Unsolved return MOI.OPTIMIZE_NOT_CALLED @@ -776,6 +782,7 @@ function MOI.get( vi::MOI.VariableIndex, ) MOI.check_result_index_bounds(optimizer, a) + MOI.throw_if_not_valid(optimizer, vi) x = ifelse( _contains(OSQP.SOLUTION_PRESENT, optimizer.results.info.status), optimizer.results.x, @@ -786,14 +793,13 @@ end function MOI.set( optimizer::Optimizer, - a::MOI.VariablePrimalStart, + ::MOI.VariablePrimalStart, vi::MOI.VariableIndex, value, ) - if MOI.is_empty(optimizer) - throw(MOI.SetAttributeNotAllowed(a)) - end - return optimizer.warmstartcache.x[vi.value] = value + MOI.throw_if_not_valid(optimizer, vi) + optimizer.warmstartcache.x[vi.value] = value + return end ## Constraints: @@ -807,11 +813,11 @@ end function MOI.set( optimizer::Optimizer, - a::MOI.ConstraintDualStart, + ::MOI.ConstraintDualStart, ci::MOI.ConstraintIndex, value, ) - MOI.is_empty(optimizer) && throw(MOI.SetAttributeNotAllowed(a)) + MOI.throw_if_not_valid(optimizer, ci) rows = constraint_rows(optimizer, ci) for (i, row) in enumerate(rows) optimizer.warmstartcache.y[row] = -value[i] # opposite dual convention @@ -830,9 +836,7 @@ function MOI.set( }, f::MOI.ScalarAffineFunction{Float64}, ) - if !MOI.is_valid(optimizer, ci) - throw(MOI.InvalidIndex(ci)) - end + MOI.throw_if_not_valid(optimizer, ci) row = constraint_rows(optimizer, ci) optimizer.modcache.A[row, :] = 0 for term in f.terms @@ -886,9 +890,7 @@ function MOI.set( ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},S}, s::S, ) where {S<:IntervalConvertible} - if !MOI.is_valid(optimizer, ci) - throw(MOI.InvalidIndex(ci)) - end + MOI.throw_if_not_valid(optimizer, ci) interval = S <: MOI.Interval ? s : MOI.Interval(s) row = constraint_rows(optimizer, ci) constant = optimizer.constrconstant[row] @@ -903,9 +905,7 @@ function MOI.set( ci::MOI.ConstraintIndex{MOI.VectorAffineFunction{Float64},S}, s::S, ) where {S<:SupportedVectorSets} - if !MOI.is_valid(optimizer, ci) - throw(MOI.InvalidIndex(ci)) - end + MOI.throw_if_not_valid(optimizer, ci) rows = constraint_rows(optimizer, ci) for (i, row) in enumerate(rows) constant = optimizer.constrconstant[row] @@ -925,7 +925,7 @@ function MOI.modify( }, change::MOI.ScalarCoefficientChange, ) - MOI.is_valid(optimizer, ci) || throw(MOI.InvalidIndex(ci)) + MOI.throw_if_not_valid(optimizer, ci) row = constraint_rows(optimizer, ci) optimizer.modcache.A[row, change.variable.value] = change.new_coefficient return @@ -973,14 +973,12 @@ function MOI.modify( ::MOI.ObjectiveFunction, change::MOI.ScalarConstantChange, ) - if MOI.is_empty(optimizer) - throw(MOI.ModifyObjectiveNotAllowed(change)) - end constant = change.new_constant if optimizer.sense == MOI.MAX_SENSE constant = -constant end - return optimizer.objconstant = constant + optimizer.objconstant = constant + return end function MOI.modify( @@ -988,14 +986,12 @@ function MOI.modify( ::MOI.ObjectiveFunction, change::MOI.ScalarCoefficientChange, ) - if MOI.is_empty(optimizer) - throw(MOI.ModifyObjectiveNotAllowed(change)) - end coef = change.new_coefficient if optimizer.sense == MOI.MAX_SENSE coef = -coef end - return optimizer.modcache.q[change.variable.value] = coef + optimizer.modcache.q[change.variable.value] = coef + return end # There is currently no ScalarQuadraticCoefficientChange. From 704d57896b256e0313b4772f20f6e9e6f252cca4 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 30 Jun 2022 10:15:42 +1200 Subject: [PATCH 3/3] Tidy test/MOI_wrapper --- test/MOI_wrapper.jl | 60 +++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 9cbf7a5..10ace01 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -1,17 +1,15 @@ module TestOSQP -using Test -using LinearAlgebra -using Random -using SparseArrays - -using MathOptInterface -const MOI = MathOptInterface - using OSQP using OSQP.MathOptInterfaceOSQP +using Test -const Affine = MOI.ScalarAffineFunction{Float64} +import LinearAlgebra +import MathOptInterface +import Random +import SparseArrays + +const MOI = MathOptInterface function runtests() for name in names(@__MODULE__; all = true) @@ -46,6 +44,7 @@ function defaultoptimizer() MOI.set(optimizer, OSQPSettings.AdaptiveRhoInterval(), 25) # required for deterministic behavior return optimizer end + function bridged_optimizer() optimizer = defaultoptimizer() cached = MOI.Utilities.CachingOptimizer( @@ -103,15 +102,15 @@ function test_runtests() end function test_ProblemModificationCache() - rng = MersenneTwister(1234) + rng = Random.MersenneTwister(1234) n = 15 m = 10 q = randn(rng, n) - P = (X = sprandn(rng, n, n, 0.1); X' * X) - P += eps() * I # needed for a test later on + P = (X = SparseArrays.sprandn(rng, n, n, 0.1); X' * X) + P += eps() * LinearAlgebra.I # needed for a test later on l = -rand(rng, m) u = rand(rng, m) - A = sprandn(rng, m, n, 0.6) + A = SparseArrays.sprandn(rng, m, n, 0.6) modcache = MathOptInterfaceOSQP.ProblemModificationCache(P, q, A, l, u) model = OSQP.Model() @@ -154,8 +153,8 @@ function test_ProblemModificationCache() @test qmod_update_results.x ≈ qmod_setup_results.x atol = 1e-7 # Modify A, ensure that updating results in the same solution as calling setup! with the modified A and q - (rows, cols, _) = findnz(A) - Amodindex = rand(rng, 1:nnz(A)) + (rows, cols, _) = SparseArrays.findnz(A) + Amodindex = rand(rng, 1:SparseArrays.nnz(A)) row = rows[Amodindex] col = cols[Amodindex] val = randn(rng) @@ -190,7 +189,7 @@ function test_ProblemModificationCache() MathOptInterfaceOSQP.processupdates!(model, modcache) Pmod_update_results = OSQP.solve!(model) model4 = OSQP.Model() - Pmod = sparse(1.0I, n, n) + Pmod = SparseArrays.sparse(1.0 * LinearAlgebra.I, n, n) OSQP.setup!( model4; P = Pmod, @@ -399,7 +398,7 @@ function test_no_CachingOptimizer_problem_modification_after_copy_to() defaultoptimizer(), config, ) do m - attr = MOI.ObjectiveFunction{Affine}() + attr = MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}() return MOI.modify(m, attr, MOI.ScalarConstantChange(objconstant)) end objval_after = MOI.get(optimizer, MOI.ObjectiveValue()) @@ -413,7 +412,7 @@ function test_no_CachingOptimizer_problem_modification_after_copy_to() defaultoptimizer(), config, ) do m - attr = MOI.ObjectiveFunction{Affine}() + attr = MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}() return MOI.modify( m, attr, @@ -535,8 +534,8 @@ function no_CachingOptimizer_Vector_problem_modification_after_copy_to() P11 = 11.0 q = [3.0, 4.0] u = [0.0, 0.0, -15, 100, 80] - A = sparse(Float64[-1 0; 0 -1; -1 -3; 2 5; 3 4]) - I, J, coeffs = findnz(A) + A = SparseArrays.sparse(Float64[-1 0; 0 -1; -1 -3; 2 5; 3 4]) + I, J, coeffs = SparseArrays.findnz(A) objf = MOI.ScalarQuadraticFunction( [term(2 * P11, x[1], x[1]), term(0.0, x[1], x[2])], term.(q, x), @@ -596,7 +595,7 @@ function no_CachingOptimizer_Vector_problem_modification_after_copy_to() # make random modifications to constraints randvectorconfig = MOI.Test.Config(atol = Inf, rtol = 1e-4) - rng = MersenneTwister(1234) + rng = Random.MersenneTwister(1234) for i in 1:100 newcoeffs = copy(coeffs) modindex = rand(rng, 1:length(newcoeffs)) @@ -710,12 +709,15 @@ function test_vector_equality_constraint() b = rand(rng, n) C = rand(rng, m, n) d = rand(rng, m) - C⁺ = pinv(C) - Q = I - C⁺ * C - expected = Q * (pinv(A * Q) * (b - A * C⁺ * d)) + C⁺ * d # note: can be quite badly conditioned + C⁺ = LinearAlgebra.pinv(C) + Q = LinearAlgebra.I - C⁺ * C + expected = + Q * (LinearAlgebra.pinv(A * Q) * (b - A * C⁺ * d)) + C⁺ * d # note: can be quite badly conditioned @test C * expected ≈ d atol = 1e-12 - P = Symmetric(sparse(triu(A' * A))) + P = LinearAlgebra.Symmetric( + SparseArrays.sparse(LinearAlgebra.triu(A' * A)), + ) q = -2 * A' * b r = b' * b @@ -723,7 +725,7 @@ function test_vector_equality_constraint() end make_objective = function (P, q, r, x) - I, J, coeffs = findnz(P.data) + I, J, coeffs = SparseArrays.findnz(P.data) return MOI.ScalarQuadraticFunction( term.( 2 * coeffs, @@ -736,7 +738,7 @@ function test_vector_equality_constraint() end make_constraint_fun = function (C, d, x) - I, J, coeffs = findnz(sparse(C)) + I, J, coeffs = SparseArrays.findnz(SparseArrays.sparse(C)) return cf = MOI.VectorAffineFunction( MOI.VectorAffineTerm.( Int64.(I), @@ -755,12 +757,12 @@ function test_vector_equality_constraint() getindex.(Ref(idxmap), x), ) ≈ expected atol = 1e-4 @test MOI.get(optimizer, MOI.ObjectiveValue()) ≈ - norm(A * expected - b)^2 atol = 1e-4 + LinearAlgebra.norm(A * expected - b)^2 atol = 1e-4 end n = 8 m = 2 - rng = MersenneTwister(1234) + rng = Random.MersenneTwister(1234) A, b, C, d, P, q, r, expected = generate_problem_data(rng, n, m) model = OSQPModel{Float64}()