Skip to content
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Manifest.toml
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ os:
- linux
- osx
julia:
- 0.6
- 0.7
- 1.0
- 1.1
notifications:
email: false
before_script:
- julia -e 'using Pkg; Pkg.add(PackageSpec(name="MathOptInterface", rev="master"))'
Copy link
Member

Choose a reason for hiding this comment

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

Add a TODO to remove this before merging?

Copy link
Member Author

Choose a reason for hiding this comment

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

When MOI is released, I remove this to lines and push to trigger the new build. I'll leave this comment unresolved as a reminder to be sure

after_success:
- julia -e 'cd(Pkg.dir("ECOS")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())'
- julia -e 'cd(Pkg.dir("ECOS")); Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())'
- julia -e 'import Pkg; Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder()); Codecov.submit(process_folder())'
25 changes: 25 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name = "ECOS"
uuid = "e2685f51-7e38-5353-a97d-a921fd2c8199"
repo = "https://github.com/JuliaOpt/ECOS.jl.git"
version = "0.9.4"

[deps]
BinaryProvider = "b99e7846-7c00-51b0-8f62-c81ae34c0232"
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
MathProgBase = "fdba3010-5040-5b88-9595-932c9decdf73"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[compat]
BinaryProvider = "≥ 0.3.0"
MathOptInterface = "0.9"
MathProgBase = "~0.5.0, ~0.6, ~0.7"
julia = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
5 changes: 0 additions & 5 deletions REQUIRE

This file was deleted.

3 changes: 1 addition & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
environment:
matrix:
- julia_version: 0.6
- julia_version: 0.7
- julia_version: 1.0
- julia_version: 1.1

platform:
- x86 # 32-bit
Expand Down
6 changes: 2 additions & 4 deletions src/ECOS.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
# Contains the wrapper itself
#############################################################################

__precompile__()
module ECOS

using Compat
using Compat.SparseArrays
using Compat.LinearAlgebra
using SparseArrays
using LinearAlgebra

# Try to load the binary dependency
if isfile(joinpath(dirname(@__FILE__),"..","deps","deps.jl"))
Expand Down
107 changes: 86 additions & 21 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ struct Solution
dual_eq::Vector{Float64}
dual_ineq::Vector{Float64}
slack::Vector{Float64}
objval::Float64
objbnd::Float64
objective_value::Float64
dual_objective_value::Float64
objective_constant::Float64
solve_time::Float64
end
const OPTIMIZE_NOT_CALLED = -1
Solution() = Solution(OPTIMIZE_NOT_CALLED, Float64[], Float64[], Float64[],
Float64[], NaN, NaN)
Float64[], NaN, NaN, NaN, NaN)

# Used to build the data with allocate-load during `copy_to`.
# When `optimize!` is called, a the data is used to build `ECOSMatrix`
Expand All @@ -32,7 +34,7 @@ mutable struct ModelData
JG::Vector{Int} # List of equality cols
VG::Vector{Float64} # List of equality coefficients
h::Vector{Float64} # List of equality coefficients
objconstant::Float64 # The objective is min c'x + objconstant
objective_constant::Float64 # The objective is min c'x + objective_constant
c::Vector{Float64}
end

Expand All @@ -58,14 +60,32 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
maxsense::Bool
data::Union{Nothing, ModelData} # only non-Nothing between MOI.copy_to and MOI.optimize!
sol::Solution
options
silent::Bool
options::Dict{Symbol, Any}
function Optimizer(; kwargs...)
new(ConeData(), false, nothing, Solution(), kwargs)
optimizer = new(ConeData(), false, nothing, Solution(), false, Dict{Symbol, Any}())
for (key, value) in kwargs
MOI.set(optimizer, MOI.RawParameter(key), value)
end
return optimizer
end
end

MOI.get(::Optimizer, ::MOI.SolverName) = "ECOS"

function MOI.set(optimizer::Optimizer, param::MOI.RawParameter, value)
optimizer.options[param.name] = value
end
function MOI.get(optimizer::Optimizer, param::MOI.RawParameter)
return optimizer.options[param.name]
Copy link
Member

Choose a reason for hiding this comment

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

TODO: This gives a poor error message if the name of the parameter is invalid.

Copy link
Member Author

Choose a reason for hiding this comment

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

We are currently lacking a convention for that. Even UniversalFallback do not have a nice error:
https://github.com/JuliaOpt/MathOptInterface.jl/blob/52d166f91614d5d522bf055cac232c7c5ba3ea96/src/Utilities/universalfallback.jl#L104-L105
Should it be a AttributeNotSetError ? Or just nothing ?

end

MOI.supports(::Optimizer, ::MOI.Silent) = true
function MOI.set(optimizer::Optimizer, ::MOI.Silent, value::Bool)
optimizer.silent = value
end
MOI.get(optimizer::Optimizer, ::MOI.Silent) = optimizer.silent

function MOI.is_empty(instance::Optimizer)
!instance.maxsense && instance.data === nothing
end
Expand Down Expand Up @@ -97,7 +117,7 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; kws...)
return MOIU.automatic_copy_to(dest, src; kws...)
end

using Compat.SparseArrays
using SparseArrays

# Computes cone dimensions
constroffset(cone::ConeData, ci::CI{<:MOI.AbstractFunction, MOI.Zeros}) = ci.value
Expand Down Expand Up @@ -214,7 +234,7 @@ function MOIU.load(instance::Optimizer, ::MOI.ObjectiveFunction,
f::MOI.ScalarAffineFunction)
c0 = Vector(sparsevec(variable_index_value.(f.terms), coefficient.(f.terms),
instance.data.n))
instance.data.objconstant = f.constant
instance.data.objective_constant = f.constant
instance.data.c = instance.maxsense ? -c0 : c0
return nothing
end
Expand All @@ -231,28 +251,61 @@ function MOI.optimize!(instance::Optimizer)
b = instance.data.b
G = ECOS.ECOSMatrix(sparse(instance.data.IG, instance.data.JG, instance.data.VG, m, n))
h = instance.data.h
objconstant = instance.data.objconstant
objective_constant = instance.data.objective_constant
c = instance.data.c
instance.data = nothing # Allows GC to free instance.data before A is loaded to ECOS
options = instance.options
if instance.silent
options = copy(options)
options[:verbose] = false
end
ecos_prob_ptr = ECOS.setup(n, m, cone.f, cone.l, length(cone.qa), cone.qa,
cone.ep, G, A, c, h, b; instance.options...)
cone.ep, G, A, c, h, b; options...)
start_time = time()
ret_val = ECOS.solve(ecos_prob_ptr)
solve_time = time() - start_time
ecos_prob = unsafe_wrap(Array, ecos_prob_ptr, 1)[1]
primal = unsafe_wrap(Array, ecos_prob.x, n)[:]
dual_eq = unsafe_wrap(Array, ecos_prob.y, cone.f)[:]
dual_ineq = unsafe_wrap(Array, ecos_prob.z, m)[:]
slack = unsafe_wrap(Array, ecos_prob.s, m)[:]
ECOS.cleanup(ecos_prob_ptr, 0)
objval = (instance.maxsense ? -1 : 1) * dot(c, primal)
if ret_val != ECOS.ECOS_DINF
objval += objconstant
end
objbnd = -(dot(b, dual_eq) + dot(h, dual_ineq))
if ret_val != ECOS.ECOS_PINF
objbnd += objconstant
objective_value = (instance.maxsense ? -1 : 1) * dot(c, primal)
dual_objective_value = (instance.maxsense ? 1 : -1) * (dot(b, dual_eq) + dot(h, dual_ineq))
instance.sol = Solution(ret_val, primal, dual_eq, dual_ineq, slack, objective_value,
dual_objective_value, objective_constant, solve_time)
end

MOI.get(optimizer::Optimizer, ::MOI.SolveTime) = optimizer.sol.solve_time
function MOI.get(optimizer::Optimizer, ::MOI.RawStatusString)
# Strings from https://github.com/ifa-ethz/ecos/blob/master/include/ecos.h
flag = optimizer.sol.ret_val
if flag == OPTIMIZE_NOT_CALLED
return "Optimize not called"
elseif flag == ECOS_OPTIMAL
return "Problem solved to optimality"
elseif flag == ECOS_OPTIMAL + ECOS_INACC_OFFSET
return "Problem solved to inaccurate optimality"
elseif flag == ECOS_PINF
return "Found certificate of primal infeasibility"
elseif flag == ECOS_PINF + ECOS_INACC_OFFSET
return "Found inaccurate certificate of primal infeasibility"
elseif flag == ECOS_DINF
return "Found certificate of dual infeasibility"
elseif flag == ECOS_DINF + ECOS_INACC_OFFSET
return "Found inaccurate certificate of dual infeasibility"
elseif flag == ECOS_MAXIT
return "Maximum number of iterations reached"
elseif flag == ECOS_NUMERICS
return "Search direction unreliable"
elseif flag == ECOS_OUTCONE
return "s or z got outside the cone, numerics?"
elseif flag == ECOS_SIGINT
return "solver interrupted by a signal/ctrl-c"
else
@assert flag == ECOS_FATAL
return "Unknown problem in solver"
end
instance.sol = Solution(ret_val, primal, dual_eq, dual_ineq, slack, objval,
objbnd)
end

# Implements getter for result value and statuses
Expand All @@ -279,8 +332,20 @@ function MOI.get(instance::Optimizer, ::MOI.TerminationStatus)
end
end

MOI.get(instance::Optimizer, ::MOI.ObjectiveValue) = instance.sol.objval
MOI.get(instance::Optimizer, ::MOI.ObjectiveBound) = instance.sol.objbnd
function MOI.get(optimizer::Optimizer, ::MOI.ObjectiveValue)
value = optimizer.sol.objective_value
if !MOIU.is_ray(MOI.get(optimizer, MOI.PrimalStatus()))
value += optimizer.sol.objective_constant
end
return value
end
function MOI.get(optimizer::Optimizer, ::MOI.DualObjectiveValue)
value = optimizer.sol.dual_objective_value
if !MOIU.is_ray(MOI.get(optimizer, MOI.DualStatus()))
value += optimizer.sol.objective_constant
end
return value
end

function MOI.get(instance::Optimizer, ::MOI.PrimalStatus)
flag = instance.sol.ret_val
Expand Down
7 changes: 3 additions & 4 deletions test/MOI_wrapper.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Compat
using Compat.Test
using Test

using MathOptInterface
const MOI = MathOptInterface
Expand All @@ -8,7 +7,8 @@ const MOIU = MOI.Utilities
const MOIB = MOI.Bridges

import ECOS
const optimizer = ECOS.Optimizer(verbose=false)
const optimizer = ECOS.Optimizer()
MOI.set(optimizer, MOI.Silent(), true)

@testset "SolverName" begin
@test MOI.get(optimizer, MOI.SolverName()) == "ECOS"
Expand All @@ -30,7 +30,6 @@ MOIU.@model(ECOSModelData,
const cache = MOIU.UniversalFallback(ECOSModelData{Float64}())
const cached = MOIU.CachingOptimizer(cache, optimizer)

# Essential bridges that are needed for all tests
const bridged = MOIB.full_bridge_optimizer(cached, Float64)

# SOC2 requires 1e-4
Expand Down
4 changes: 2 additions & 2 deletions test/MPB_linear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
# Test the MathProgBase.jl interface for the ECOS.jl solver wrapper
#############################################################################

using Compat.Test
using Compat.LinearAlgebra
using Test
using LinearAlgebra
using MathProgBase
using ECOS

Expand Down
6 changes: 3 additions & 3 deletions test/direct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
# Test the direct interface for the ECOS.jl solver wrapper
#############################################################################

using Compat.Test
using Compat.LinearAlgebra
using Compat.SparseArrays
using Test
using LinearAlgebra
using SparseArrays

# The values below are copied from data.h in ECOS source code
import ECOS
Expand Down
2 changes: 1 addition & 1 deletion test/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#############################################################################

import ECOS
using Compat.Test
using Test

println(ECOS.ver())

Expand Down
2 changes: 1 addition & 1 deletion test/stress.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Generates a random cone problem to test the MPB interface perf
using Compat.SparseArrays
using SparseArrays

rows = 10000
cols = 10000
Expand Down