diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 0000000..0f66259 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,22 @@ +name: CompatHelper + +on: + schedule: + - cron: '00 00 * * *' + workflow_dispatch: + +jobs: + CompatHelper: + runs-on: ${{ matrix.os }} + strategy: + matrix: + julia-version: [1.2.0] + julia-arch: [x86] + os: [ubuntu-latest] + steps: + - name: Pkg.add("CompatHelper") + run: julia -e 'using Pkg; Pkg.add("CompatHelper")' + - name: CompatHelper.main() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 0000000..f49313b --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,15 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e40b8e2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI +on: + push: + branches: [master] + pull_request: + types: [opened, synchronize, reopened] +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - version: '1.0' + os: ubuntu-latest + arch: x64 + - version: '1' + os: ubuntu-latest + arch: x64 + - version: '1' + os: ubuntu-latest + arch: x86 +# - version: 'nightly' +# os: ubuntu-latest +# arch: x64 + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v1 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + with: + depwarn: error + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v1 + with: + file: lcov.info diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 69de33b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: julia -codecov: true -julia: - - nightly - - 1.0 - - 1 -notifications: - email: false diff --git a/Project.toml b/Project.toml index daa4680..50c056f 100644 --- a/Project.toml +++ b/Project.toml @@ -11,7 +11,7 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] -MathOptInterface = "0.9" +MathOptInterface = "0.10.5" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 8099a20..9a476fb 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -48,47 +48,6 @@ function MOI.get(optimizer::Optimizer, ::MOI.ConstraintDual, return optimizer.y end -function MOI.is_empty(optimizer::Optimizer) - return optimizer.sense === nothing && optimizer.objective_function === nothing && - optimizer.set === nothing -end - -function MOI.empty!(optimizer::Optimizer) - optimizer.sense = nothing - optimizer.objective_function = nothing - optimizer.set = nothing - optimizer.X = nothing - optimizer.x = nothing - optimizer.y = nothing - optimizer.results = nothing -end - -MOI.Utilities.supports_default_copy_to(::Optimizer, names::Bool) = !names - -function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; kwargs...) - return MOI.Utilities.automatic_copy_to(dest, src; kwargs...) -end - -function MOI.get(optimizer::Optimizer, ::MOI.ListOfModelAttributesSet) - attributes = MOI.AbstractModelAttribute[] - if optimizer.sense !== nothing - push!(optimizer, optimizer.sense) - end - if optimizer.objective_function !== nothing - F = typeof(optimizer.objective_function) - push!(optimizer, MOI.ObjectiveFunction{F}()) - end - return attributes -end - -function MOI.get(::Optimizer, ::MOI.ListOfVariableAttributesSet) - return MOI.AbstractVariableAttribute[] -end - -function MOI.get(::Optimizer, ::MOI.ListOfConstraintAttributesSet) - return MOI.AbstractConstraintAttribute[] -end - function MOI.supports( optimizer::Optimizer{T}, ::Union{MOI.ObjectiveSense, @@ -108,35 +67,6 @@ end function MOI.is_valid(optimizer::Optimizer, ci::MOI.ConstraintIndex{MOI.VectorOfVariables, Elliptope}) return optimizer.set !== nothing && isone(ci.value) end -function MOI.get(optimizer::Optimizer, ::MOI.ConstraintFunction, - ci::MOI.ConstraintIndex{MOI.VectorOfVariables, Elliptope}) - MOI.throw_if_not_valid(optimizer, ci) - return MOI.VectorOfVariables(MOI.get(optimizer, MOI.ListOfVariableIndices())) -end -function MOI.get(optimizer::Optimizer, ::MOI.NumberOfVariables) - return optimizer.set === nothing ? 0 : MOI.dimension(optimizer.set) -end -function MOI.get(optimizer::Optimizer, ::MOI.ListOfVariableIndices) - return MOI.VariableIndex.(1:MOI.get(optimizer, MOI.NumberOfVariables())) -end -MOI.get(optimizer::Optimizer, ::MOI.VariableName, ::MOI.VariableIndex) = "" -function MOI.get(optimizer::Optimizer, ::MOI.ListOfConstraints) - constraint_types = Tuple{DataType, DataType}[] - if optimizer.set !== nothing - push!(constraint_types, (MOI.VectorOfVariables, Elliptope)) - end - return constraint_types -end -function MOI.get(optimizer::Optimizer, ::MOI.NumberOfConstraints{MOI.VectorOfVariables, Elliptope}) - return optimizer.set === nothing ? 0 : 1 -end -function MOI.get(optimizer::Optimizer, ::MOI.ListOfConstraintIndices{MOI.VectorOfVariables, Elliptope}) - indices = MOI.ConstraintIndex{MOI.VectorOfVariables, Elliptope}[] - if optimizer.set !== nothing - push!(indices, MOI.ConstraintIndex{MOI.VectorOfVariables, Elliptope}(1)) - end - return indices -end function MOI.set(optimizer::Optimizer, ::MOI.ObjectiveSense, sense::MOI.OptimizationSense) if sense == MOI.FEASIBILITY_SENSE @@ -155,6 +85,7 @@ MOI.get(optimizer::Optimizer{T}, ::MOI.ObjectiveFunctionType) where {T} = MOI.Sc function MOI.get(optimizer::Optimizer{T}, ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}) where T return optimizer.objective_function end + function MOI.optimize!(optimizer::Optimizer{T}) where T !iszero(optimizer.objective_function.constant) && error("Nonzero objective constant not supported") mapping = [(i, j) for j in 2:optimizer.set.side_dimension for i in 1:(j - 1)] @@ -162,7 +93,7 @@ function MOI.optimize!(optimizer::Optimizer{T}) where T J = Int[] V = T[] for term in optimizer.objective_function.terms - i, j = mapping[term.variable_index.value] + i, j = mapping[term.variable.value] push!(I, i) push!(I, j) push!(J, j) @@ -181,3 +112,86 @@ function MOI.optimize!(optimizer::Optimizer{T}) where T optimizer.x = [optimizer.X[i, j] for j in 2:optimizer.set.side_dimension for i in 1:(j - 1)] return end + +# Needed to copy to the optimizer +function MOI.is_empty(optimizer::Optimizer) + return optimizer.sense === nothing && optimizer.objective_function === nothing && + optimizer.set === nothing +end + +function MOI.empty!(optimizer::Optimizer) + optimizer.sense = nothing + optimizer.objective_function = nothing + optimizer.set = nothing + optimizer.X = nothing + optimizer.x = nothing + optimizer.y = nothing + optimizer.results = nothing +end + +MOI.supports_incremental_interface(::Optimizer) = true + +function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) + return MOI.Utilities.default_copy_to(dest, src) +end + +# Needed to copy from the optimizer +function MOI.get(optimizer::Optimizer, ::MOI.ListOfOptimizerAttributesSet) + return collect(keys(optimizer.options)) +end + +function MOI.get(optimizer::Optimizer, ::MOI.ListOfModelAttributesSet) + attributes = MOI.AbstractModelAttribute[] + if optimizer.sense !== nothing + push!(attributes, MOI.ObjectiveSense()) + end + if optimizer.objective_function !== nothing + F = typeof(optimizer.objective_function) + push!(attributes, MOI.ObjectiveFunction{F}()) + end + return attributes +end + +function MOI.get(::Optimizer, ::MOI.ListOfVariableAttributesSet) + return MOI.AbstractVariableAttribute[] +end + +function MOI.get(::Optimizer, ::MOI.ListOfConstraintAttributesSet) + return MOI.AbstractConstraintAttribute[] +end + +function MOI.get(optimizer::Optimizer, ::MOI.ListOfConstraintTypesPresent) + constraint_types = Tuple{DataType, DataType}[] + if optimizer.set !== nothing + push!(constraint_types, (MOI.VectorOfVariables, Elliptope)) + end + return constraint_types +end +function MOI.get(optimizer::Optimizer, ::MOI.NumberOfConstraints{MOI.VectorOfVariables, Elliptope}) + return optimizer.set === nothing ? 0 : 1 +end +function MOI.get(optimizer::Optimizer, ::MOI.ListOfConstraintIndices{MOI.VectorOfVariables, Elliptope}) + indices = MOI.ConstraintIndex{MOI.VectorOfVariables, Elliptope}[] + if optimizer.set !== nothing + push!(indices, MOI.ConstraintIndex{MOI.VectorOfVariables, Elliptope}(1)) + end + return indices +end +function MOI.get(optimizer::Optimizer, ::MOI.ConstraintFunction, + ci::MOI.ConstraintIndex{MOI.VectorOfVariables, Elliptope}) + MOI.throw_if_not_valid(optimizer, ci) + return MOI.VectorOfVariables(MOI.get(optimizer, MOI.ListOfVariableIndices())) +end +function MOI.get(optimizer::Optimizer, ::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{MOI.VectorOfVariables, Elliptope}) + MOI.throw_if_not_valid(optimizer, ci) + return optimizer.set +end + +function MOI.get(optimizer::Optimizer, ::MOI.NumberOfVariables) + return optimizer.set === nothing ? 0 : MOI.dimension(optimizer.set) +end +function MOI.get(optimizer::Optimizer, ::MOI.ListOfVariableIndices) + return MOI.VariableIndex.(1:MOI.get(optimizer, MOI.NumberOfVariables())) +end +MOI.get(optimizer::Optimizer, ::MOI.VariableName, ::MOI.VariableIndex) = "" diff --git a/src/interface.jl b/src/interface.jl index c958d90..671a875 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -31,7 +31,7 @@ const SUPPORTED_GETTABLE_ATTRIBUTES = Union{ MOI.TerminationStatus, MOI.PrimalStatus, MOI.DualStatus, - MOI.SolveTime, + MOI.SolveTimeSec, NumberOfIterations, NumberOfCholeskyFactorizations } diff --git a/src/mc_psd.jl b/src/mc_psd.jl index 5669abd..01178fa 100644 --- a/src/mc_psd.jl +++ b/src/mc_psd.jl @@ -108,7 +108,7 @@ function mc_psd(L::Symmetric{T}; end # end of main loop results = Dict{MOI.AbstractModelAttribute, Any}( - MOI.SolveTime() => (current - start) / 1e9, + MOI.SolveTimeSec() => (current - start) / 1e9, NumberOfIterations() => iter, NumberOfCholeskyFactorizations() => cholcnt, ) diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 7f938cd..2ab010a 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -7,23 +7,16 @@ const MOIU = MOI.Utilities const MOIB = MOI.Bridges import MCPSD -const OPTIMIZER_CONSTRUCTOR = MOI.OptimizerWithAttributes(MCPSD.Optimizer, MOI.Silent() => true) -const OPTIMIZER = MOI.instantiate(OPTIMIZER_CONSTRUCTOR) @testset "SolverName" begin - @test MOI.get(OPTIMIZER, MOI.SolverName()) == "MCPSD" + @test MOI.get(MCPSD.Optimizer(), MOI.SolverName()) == "MCPSD" end -@testset "supports_default_copy_to" begin - @test MOIU.supports_default_copy_to(OPTIMIZER, false) - @test !MOIU.supports_default_copy_to(OPTIMIZER, true) +@testset "supports_incremental_interface" begin + @test MOI.supports_incremental_interface(MCPSD.Optimizer()) end -const BRIDGED = MOI.instantiate(OPTIMIZER_CONSTRUCTOR, with_bridge_type = Float64) -const CONFIG = MOIT.TestConfig(atol=1e-6, rtol=1e-6) - function moi_test(optimizer, L::Matrix{T}, expected_X, expected_y, expected_obj, tol) where T - MOI.empty!(optimizer) @test MOI.is_empty(optimizer) @test MOI.get(optimizer, MOI.SolverName()) == "MCPSD" MOI.supports(optimizer, MCPSD.Digits()) @@ -36,16 +29,15 @@ function moi_test(optimizer, L::Matrix{T}, expected_X, expected_y, expected_obj, MOI.set(optimizer, MOI.Silent(), true) @test MOI.get(optimizer, MOI.Silent()) @test MOI.is_empty(optimizer) - @test isempty(MOI.get(optimizer, MOI.ListOfConstraints())) + @test isempty(MOI.get(optimizer, MOI.ListOfConstraintTypesPresent())) F = MOI.VectorOfVariables S = MCPSD.Elliptope @test MOI.get(optimizer, MOI.NumberOfConstraints{F, S}()) == 0 @test isempty(MOI.get(optimizer, MOI.ListOfConstraintIndices{F, S}())) - @test MOI.supports_add_constrained_variables(optimizer, MCPSD.Elliptope) x, cx = MOI.add_constrained_variables(optimizer, MCPSD.Elliptope(size(L, 1))) @test !MOI.is_empty(optimizer) @test cx == MOI.ConstraintIndex{F, S}(1) - @test MOI.get(optimizer, MOI.ListOfConstraints()) == [(F, S)] + @test MOI.get(optimizer, MOI.ListOfConstraintTypesPresent()) == [(F, S)] @test MOI.get(optimizer, MOI.NumberOfConstraints{F, S}()) == 1 @test MOI.get(optimizer, MOI.ListOfConstraintIndices{F, S}()) == [cx] @test all(x -> MOI.is_valid(optimizer, x), x) @@ -71,15 +63,15 @@ function moi_test(optimizer, L::Matrix{T}, expected_X, expected_y, expected_obj, @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ expected_x atol=tol rtol=tol @test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ expected_y atol=tol rtol=tol @test MOI.get(optimizer, MCPSD.NumberOfIterations()) == 19 - @test MOI.get(optimizer, MOI.SolveTime()) isa Float64 - @show MOI.get(optimizer, MOI.SolveTime()) + @test MOI.get(optimizer, MOI.SolveTimeSec()) isa Float64 + @show MOI.get(optimizer, MOI.SolveTimeSec()) end @testset "[MOI_wrapper] Wikipedia example with $T" for T in [Float64, BigFloat] optimizer = MCPSD.Optimizer{T}() moi_test(optimizer, wikipedia_example(T)...) + cached = MOIU.CachingOptimizer(MCPSD.Optimizer{T}(), MOIU.AUTOMATIC) MOI.empty!(optimizer) - cached = MOIU.CachingOptimizer(MCPSD.Optimizer{T}(), optimizer) - moi_test(optimizer, wikipedia_example(T)...) + MOIU.reset_optimizer(cached, optimizer) + moi_test(cached, wikipedia_example(T)...) end - diff --git a/test/runtests.jl b/test/runtests.jl index 3376330..fd54e5e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -22,8 +22,8 @@ function direct_test(L::Matrix{T}, expected_X, expected_y, expected_obj, tol) wh @test y isa Vector{T} @test y ≈ expected_y atol=tol rtol=tol @test results[MCPSD.NumberOfIterations()] == 19 - @test results[MOI.SolveTime()] isa Float64 - @show results[MOI.SolveTime()] + @test results[MOI.SolveTimeSec()] isa Float64 + @show results[MOI.SolveTimeSec()] end end