diff --git a/Manifest.toml b/Manifest.toml index a5f837c..04b7c1d 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -1,8 +1,8 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.11.5" +julia_version = "1.12.2" manifest_format = "2.0" -project_hash = "50848fc542d76b4c16d74beb234d94121ecda552" +project_hash = "2ccee5e212b4700701dc3d1848104837eff8853c" [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" @@ -59,9 +59,9 @@ version = "0.19.0" [[deps.Clang_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "TOML", "Zlib_jll", "libLLVM_jll"] -git-tree-sha1 = "ebfc8e89823ec2c85ed9fedabe52db149da4c5ec" +git-tree-sha1 = "f85df021a5fd31ac59ea7126232b2875a848544f" uuid = "0ee61d77-7f21-5576-8119-9fcc46b10100" -version = "16.0.6+5" +version = "18.1.7+4" [[deps.CodecBzip2]] deps = ["Bzip2_jll", "TranscodingStreams"] @@ -94,13 +94,7 @@ weakdeps = ["Dates", "LinearAlgebra"] [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.1.1+0" - -[[deps.DataStructures]] -deps = ["OrderedCollections"] -git-tree-sha1 = "e357641bb3e0638d353c4b29ea0e40ea644066a6" -uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.19.3" +version = "1.3.0+1" [[deps.Dates]] deps = ["Printf"] @@ -127,7 +121,7 @@ version = "0.9.5" [[deps.Downloads]] deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" -version = "1.6.0" +version = "1.7.0" [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" @@ -185,6 +179,11 @@ version = "1.14.3" [deps.JSON3.weakdeps] ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.12.0" + [[deps.LazyArtifacts]] deps = ["Artifacts", "Pkg"] uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" @@ -196,24 +195,24 @@ uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" version = "0.6.4" [[deps.LibCURL_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"] uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "8.6.0+0" +version = "8.15.0+0" [[deps.LibGit2]] -deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" version = "1.11.0" [[deps.LibGit2_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll"] uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" -version = "1.7.2+0" +version = "1.9.0+0" [[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +deps = ["Artifacts", "Libdl", "OpenSSL_jll"] uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" -version = "1.11.0+1" +version = "1.11.3+1" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" @@ -222,7 +221,7 @@ version = "1.11.0" [[deps.LinearAlgebra]] deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -version = "1.11.0" +version = "1.12.0" [[deps.LogExpFunctions]] deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] @@ -250,20 +249,15 @@ uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" version = "0.5.16" [[deps.Markdown]] -deps = ["Base64"] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" version = "1.11.0" [[deps.MathOptInterface]] -deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "DataStructures", "ForwardDiff", "JSON3", "LinearAlgebra", "MutableArithmetics", "NaNMath", "OrderedCollections", "PrecompileTools", "Printf", "SparseArrays", "SpecialFunctions", "Test"] -git-tree-sha1 = "a2cbab4256690aee457d136752c404e001f27768" +deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "ForwardDiff", "JSON3", "LinearAlgebra", "MutableArithmetics", "NaNMath", "OrderedCollections", "PrecompileTools", "Printf", "SparseArrays", "SpecialFunctions", "Test"] +git-tree-sha1 = "181c2611c7aa6a362fdf937b1e2af55e6691181f" uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" -version = "1.46.0" - -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.6+0" +version = "1.48.0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" @@ -271,7 +265,7 @@ version = "1.11.0" [[deps.MozillaCACerts_jll]] uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2023.12.12" +version = "2025.5.20" [[deps.MutableArithmetics]] deps = ["LinearAlgebra", "SparseArrays", "Test"] @@ -287,17 +281,22 @@ version = "1.1.3" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" -version = "1.2.0" +version = "1.3.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.27+1" +version = "0.3.29+0" [[deps.OpenLibm_jll]] deps = ["Artifacts", "Libdl"] uuid = "05823500-19ac-5b8b-9628-191a04bc5112" -version = "0.8.5+0" +version = "0.8.7+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.5.4+0" [[deps.OpenSpecFun_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] @@ -319,7 +318,7 @@ version = "2.8.3" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.11.0" +version = "1.12.0" [deps.Pkg.extensions] REPLExt = "REPL" @@ -329,9 +328,9 @@ version = "1.11.0" [[deps.PrecompileTools]] deps = ["Preferences"] -git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +git-tree-sha1 = "07a921781cab75691315adc645096ed5e370cb77" uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -version = "1.2.1" +version = "1.3.3" [[deps.Preferences]] deps = ["TOML"] @@ -345,6 +344,7 @@ uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" version = "1.11.0" [[deps.Profile]] +deps = ["StyledStrings"] uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" version = "1.11.0" @@ -364,7 +364,7 @@ version = "1.11.0" [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -version = "1.11.0" +version = "1.12.0" [[deps.SpecialFunctions]] deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] @@ -413,10 +413,14 @@ version = "2.6.0" Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "7.7.0+0" +version = "7.8.3+2" [[deps.TOML]] deps = ["Dates"] @@ -450,7 +454,13 @@ version = "1.11.0" [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.13+1" +version = "1.3.1+2" + +[[deps.cuPDLPx]] +deps = ["CUDA_Runtime_jll", "CUDA_SDK_jll", "Clang", "MathOptInterface", "Test", "cuPDLPx_jll"] +path = "." +uuid = "bcd6524d-1420-4b17-a582-359cb8a71a63" +version = "0.1.4" [[deps.cuPDLPx_jll]] deps = ["Artifacts", "CUDA_Runtime_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "TOML", "Zlib_jll"] @@ -461,19 +471,19 @@ version = "0.1.4+0" [[deps.libLLVM_jll]] deps = ["Artifacts", "Libdl"] uuid = "8f36deef-c2a5-5394-99ed-8e07531fb29a" -version = "16.0.6+5" +version = "18.1.7+5" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.11.0+0" +version = "5.15.0+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.59.0+0" +version = "1.64.0+1" [[deps.p7zip_jll]] -deps = ["Artifacts", "Libdl"] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+2" +version = "17.7.0+0" diff --git a/README.md b/README.md index 1102b7c..d53cde1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # cuPDLPx.jl Julia interface for cuPDLPx. + +## Use with JuMP + +To use cuPDLPx with JuMP, use `cuPDLPx.Optimizer`: + +```julia +using JuMP, cuPDLPx +model = Model(cuPDLPx.Optimizer) +``` diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl new file mode 100644 index 0000000..677724a --- /dev/null +++ b/src/MOI_wrapper.jl @@ -0,0 +1,314 @@ +import MathOptInterface as MOI + +# Inspired from `Clp.jl/src/MOI_wrapper/MOI_wrapper.jl` +MOI.Utilities.@product_of_sets( + _LPProductOfSets, + MOI.EqualTo{T}, + MOI.GreaterThan{T}, + MOI.LessThan{T}, + MOI.Interval{T}, +) + +const OptimizerCache = MOI.Utilities.GenericModel{ + Cdouble, + MOI.Utilities.ObjectiveContainer{Cdouble}, + MOI.Utilities.VariablesContainer{Cdouble}, + MOI.Utilities.MatrixOfConstraints{ + Cdouble, + MOI.Utilities.MutableSparseMatrixCSC{ + Cdouble, + Cint, + MOI.Utilities.ZeroBasedIndexing, + }, + MOI.Utilities.Hyperrectangle{Cdouble}, + _LPProductOfSets{Cdouble}, + }, +} + +Base.show(io::IO, ::Type{OptimizerCache}) = print(io, "cuPDLPx.OptimizerCache") + +const BOUND_SETS = Union{ + MOI.GreaterThan{Float64}, + MOI.LessThan{Float64}, + MOI.EqualTo{Float64}, + MOI.Interval{Float64}, +} + +""" + Optimizer() + +Create a new cuPDLP optimizer. +""" +mutable struct Optimizer <: MOI.AbstractOptimizer + result::Union{Nothing,LibcuPDLPx.cupdlpx_result_t} + max_sense::Bool + + function Optimizer() + return new( + nothing, + false, + ) + end +end + +function MOI.default_cache(::Optimizer, ::Type) + return OptimizerCache() +end + +# ==================== +# empty functions +# ==================== + +function MOI.is_empty(optimizer::Optimizer) + return isnothing(optimizer.result) +end + +function MOI.empty!(optimizer::Optimizer) + optimizer.result = nothing + return +end + +MOI.get(::Optimizer, ::MOI.SolverName) = "cuPDLPx" + +# MOI.RawOptimizerAttribute + +function MOI.supports(::Optimizer, param::MOI.RawOptimizerAttribute) + error("TODO") + return hasfield(PdhgParameters, Symbol(param.name)) +end + +function MOI.set(optimizer::Optimizer, param::MOI.RawOptimizerAttribute, value) + error("TODO") + if !MOI.supports(optimizer, param) + throw(MOI.UnsupportedAttribute(param)) + end + setfield!(optimizer.parameters, Symbol(param.name), value) + return +end + +function MOI.get(optimizer::Optimizer, param::MOI.RawOptimizerAttribute) + error("TODO") + if !MOI.supports(optimizer, param) + throw(MOI.UnsupportedAttribute(param)) + end + return getfield(optimizer.parameters, Symbol(param.name)) +end + +# MOI.Silent + +MOI.supports(::Optimizer, ::MOI.Silent) = true + +function MOI.set(optimizer::Optimizer, ::MOI.Silent, value::Bool) + error("TODO") + optimizer.silent = value + return +end + +MOI.get(optimizer::Optimizer, ::MOI.Silent) = optimizer.silent + +# ======================================== +# Supported constraints and objectives +# ======================================== + +function MOI.supports_constraint( + ::Optimizer, + ::Type{MOI.VariableIndex}, + ::Type{<:BOUND_SETS}, +) + return true +end + +function MOI.supports_constraint( + ::Optimizer, + ::Type{MOI.ScalarAffineFunction{Float64}}, + ::Type{<:BOUND_SETS}, +) + return true +end + + +MOI.supports(::Optimizer, ::MOI.ObjectiveSense) = true + +function MOI.supports( + ::Optimizer, + ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, +) + return true +end + +# =============================== +# Optimize and post-optimize +# =============================== + +function _flip_sense(optimizer::Optimizer, obj) + return optimizer.max_sense ? -obj : obj +end + +function sparse_matrix(A::MOI.Utilities.MutableSparseMatrixCSC{Cdouble,Cint,MOI.Utilities.ZeroBasedIndexing}) + A_csc = LibcuPDLPx.MatrixCSC( + length(A.rowval), + pointer(A.colptr), + pointer(A.rowval), + pointer(A.nzval) + ) + + # 1. Allocate zeroed struct on Julia side + A_desc_ref = Ref{LibcuPDLPx.matrix_desc_t}() + A_desc_ref[] = LibcuPDLPx.matrix_desc_t(ntuple(_ -> UInt8(0), 56)) # Clear memory + A_desc_ptr = Base.unsafe_convert(Ptr{LibcuPDLPx.matrix_desc_t}, A_desc_ref) + + # 2. Set Scalar Fields + A_desc_ptr.m = Cint(A.m) + A_desc_ptr.n = Cint(A.n) + A_desc_ptr.fmt = LibcuPDLPx.matrix_csc + A_desc_ptr.zero_tolerance = 0.0 + + # 3. Set The Union Data + A_desc_ptr.data.csc = A_csc + + return A_desc_ptr +end + +function MOI.optimize!(dest::Optimizer, src::OptimizerCache) + MOI.empty!(dest) + dest.max_sense = MOI.get(src, MOI.ObjectiveSense()) == MOI.MAX_SENSE + obj = MOI.get(src, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) + c = zeros(Cdouble, src.constraints.coefficients.n) + for term in obj.terms + c[term.variable.value] += _flip_sense(dest, term.coefficient) + end + obj_const = [_flip_sense(dest, MOI.constant(obj))] + prob = LibcuPDLPx.create_lp_problem( + pointer(c), + sparse_matrix(src.constraints.coefficients), + pointer(src.constraints.constants.lower), + pointer(src.constraints.constants.upper), + pointer(src.variables.lower), + pointer(src.variables.upper), + pointer(obj_const) + ) + @assert prob != C_NULL + + params_ref = Ref{Lib.pdhg_parameters_t}() + Lib.set_default_parameters(Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref)) + params_ptr = Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref) + + result_ptr = Lib.solve_lp_problem(prob, params_ptr) + @assert result_ptr != C_NULL + dest.result = unsafe_load(result_ptr) + return MOI.Utilities.identity_index_map(src), false +end + +function MOI.optimize!(dest::Optimizer, src::MOI.ModelLike) + cache = OptimizerCache() + index_map = MOI.copy_to(cache, src) + MOI.optimize!(dest, cache) + return index_map, false +end + +function MOI.get(optimizer::Optimizer, ::MOI.SolveTimeSec) + return optimizer.result.cumulative_time_sec +end + +function MOI.get(optimizer::Optimizer, ::MOI.RawStatusString) + if isnothing(optimizer.result) + return "Optimize not called" + else + error("TODO") + end +end + +const _TERMINATION_STATUS_MAP = Dict( + LibcuPDLPx.TERMINATION_REASON_UNSPECIFIED => MOI.OPTIMIZE_NOT_CALLED, + LibcuPDLPx.TERMINATION_REASON_OPTIMAL => MOI.OPTIMAL, + LibcuPDLPx.TERMINATION_REASON_PRIMAL_INFEASIBLE => MOI.INFEASIBLE, + LibcuPDLPx.TERMINATION_REASON_DUAL_INFEASIBLE => MOI.DUAL_INFEASIBLE, + LibcuPDLPx.TERMINATION_REASON_TIME_LIMIT => MOI.TIME_LIMIT, + LibcuPDLPx.TERMINATION_REASON_ITERATION_LIMIT => MOI.ITERATION_LIMIT, + LibcuPDLPx.TERMINATION_REASON_FEAS_POLISH_SUCCESS => MOI.OTHER_ERROR, # TODO +) + +# Implements getter for result value and statuses +function MOI.get(optimizer::Optimizer, ::MOI.TerminationStatus) + return isnothing(optimizer.result) ? MOI.OPTIMIZE_NOT_CALLED : + _TERMINATION_STATUS_MAP[optimizer.result.termination_reason] +end + +function MOI.get(optimizer::Optimizer, attr::MOI.ObjectiveValue) + MOI.check_result_index_bounds(optimizer, attr) + return _flip_sense(optimizer, optimizer.result.iteration_stats[end].convergence_information[].primal_objective) +end + +function MOI.get(optimizer::Optimizer, attr::MOI.DualObjectiveValue) + MOI.check_result_index_bounds(optimizer, attr) + return _flip_sense(optimizer, optimizer.result.iteration_stats[end].convergence_information[].dual_objective) +end + +const _PRIMAL_STATUS_MAP = Dict( + LibcuPDLPx.TERMINATION_REASON_UNSPECIFIED => MOI.NO_SOLUTION, + LibcuPDLPx.TERMINATION_REASON_OPTIMAL => MOI.FEASIBLE_POINT, + LibcuPDLPx.TERMINATION_REASON_PRIMAL_INFEASIBLE => MOI.NO_SOLUTION, + LibcuPDLPx.TERMINATION_REASON_DUAL_INFEASIBLE => MOI.INFEASIBILITY_CERTIFICATE, + LibcuPDLPx.TERMINATION_REASON_TIME_LIMIT => MOI.UNKNOWN_RESULT_STATUS, + LibcuPDLPx.TERMINATION_REASON_ITERATION_LIMIT => MOI.UNKNOWN_RESULT_STATUS, + LibcuPDLPx.TERMINATION_REASON_FEAS_POLISH_SUCCESS => MOI.UNKNOWN_RESULT_STATUS, +) + +function MOI.get(optimizer::Optimizer, attr::MOI.PrimalStatus) + if attr.result_index > MOI.get(optimizer, MOI.ResultCount()) + return MOI.NO_SOLUTION + end + return _PRIMAL_STATUS_MAP[optimizer.result.termination_reason] +end + +function MOI.get( + optimizer::Optimizer, + attr::MOI.VariablePrimal, + vi::MOI.VariableIndex, +) + MOI.check_result_index_bounds(optimizer, attr) + return optimizer.result.primal_solution[vi.value] +end + +const _DUAL_STATUS_MAP = Dict( + LibcuPDLPx.TERMINATION_REASON_UNSPECIFIED => MOI.NO_SOLUTION, + LibcuPDLPx.TERMINATION_REASON_OPTIMAL => MOI.FEASIBLE_POINT, + LibcuPDLPx.TERMINATION_REASON_PRIMAL_INFEASIBLE => MOI.INFEASIBILITY_CERTIFICATE, + LibcuPDLPx.TERMINATION_REASON_DUAL_INFEASIBLE => MOI.NO_SOLUTION, + LibcuPDLPx.TERMINATION_REASON_TIME_LIMIT => MOI.UNKNOWN_RESULT_STATUS, + LibcuPDLPx.TERMINATION_REASON_ITERATION_LIMIT => MOI.UNKNOWN_RESULT_STATUS, + LibcuPDLPx.TERMINATION_REASON_FEAS_POLISH_SUCCESS => MOI.UNKNOWN_RESULT_STATUS, +) + +function MOI.get(optimizer::Optimizer, attr::MOI.DualStatus) + if attr.result_index > MOI.get(optimizer, MOI.ResultCount()) + return MOI.NO_SOLUTION + end + return _DUAL_STATUS_MAP[optimizer.result.termination_reason] +end + +function MOI.get( + optimizer::Optimizer, + attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.EqualTo{Float64}}, +) + MOI.check_result_index_bounds(optimizer, attr) + return optimizer.result.dual_solution[ci.value] +end + +function MOI.get( + optimizer::Optimizer, + attr::MOI.ConstraintDual, + ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},MOI.GreaterThan{Float64}}, +) + MOI.check_result_index_bounds(optimizer, attr) + return optimizer.result.dual_solution[optimizer.num_equalities + ci.value] +end + +function MOI.get(optimizer::Optimizer, ::MOI.ResultCount) + if isnothing(optimizer.result) + return 0 + else + return 1 + end +end diff --git a/src/cuPDLPx.jl b/src/cuPDLPx.jl index cdad5a1..7154a79 100644 --- a/src/cuPDLPx.jl +++ b/src/cuPDLPx.jl @@ -2,5 +2,6 @@ module cuPDLPx # using Base.Enums include("LibcuPDLPx.jl") +include("MOI_wrapper.jl") -end # module cuPDLPx \ No newline at end of file +end # module cuPDLPx diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl new file mode 100644 index 0000000..456903c --- /dev/null +++ b/test/MOI_wrapper.jl @@ -0,0 +1,89 @@ +module TestMOI + +using Test +import MathOptInterface as MOI +import cuPDLPx + +function test_runtests() + optimizer = cuPDLPx.Optimizer() + MOI.set(optimizer, MOI.Silent(), true) # comment this to enable output + model = MOI.Bridges.full_bridge_optimizer( + MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + optimizer, + ), + Float64, + ) + config = MOI.Test.Config( + rtol = 1e-1, + atol = 1e-1, + exclude = Any[ + MOI.ConstraintBasisStatus, + MOI.VariableBasisStatus, + MOI.ConstraintName, + MOI.VariableName, + MOI.ObjectiveBound, + MOI.SolverVersion, + ], + ) + MOI.Test.runtests( + model, + config, + exclude = [ + # No constraint so cuPDLP fails with the CUDA error + # `Grid dimensions should be non-null` + r"^test_variable_solve_with_lowerbound$", + r"^test_variable_solve_with_upperbound$", + r"^test_solve_result_index$", + r"^test_solve_TerminationStatus_DUAL_INFEASIBLE$", + r"^test_modification_transform_singlevariable_lessthan$", + r"^test_modification_const_scalar_objective$", + r"^test_DualObjectiveValue_Max_VariableIndex_LessThan$", + r"^test_DualObjectiveValue_Min_VariableIndex_GreaterThan$", + r"^test_attribute_RawStatusString$", + r"^test_attribute_SolveTimeSec$", + r"^test_solve_optimize_twice$", + r"^test_solve_VariableIndex_ConstraintDual_MAX_SENSE$", + r"^test_solve_VariableIndex_ConstraintDual_MIN_SENSE$", + r"^test_modification_set_singlevariable_lessthan$", + r"^test_objective_ObjectiveFunction_VariableIndex$", + r"^test_objective_ObjectiveFunction_blank$", + r"^test_objective_ObjectiveFunction_constant$", + r"^test_objective_ObjectiveFunction_duplicate_terms$", + r"^test_objective_FEASIBILITY_SENSE_clears_objective$", + r"^test_modification_coef_scalar_objective$", + r"^test_linear_variable_open_intervals$", + r"^test_modification_delete_variable_with_single_variable_obj$", + r"^test_modification_delete_variables_in_a_batch$", + # Not all constraints have finite bounds on at least one side. + r"^test_linear_open_intervals$", + # Error from MOI fallback : Fallback getter for variable constraint dual does not support other variable-wise constraints on the variable. + r"^test_linear_integration_delete_variables$", + # Expression: isapprox(target, obj, config) + # Evaluated: isapprox(-41.980944701810785, -37.700944701810776, ...) + r"^test_infeasible_affine_MAX_SENSE_offset$", + # Expression: isapprox(-target, obj, config) + # Evaluated: isapprox(14.020439047717783, 17.100439047717785, ...) + r"^test_infeasible_affine_MIN_SENSE$", + # Expression: isapprox(-target, obj, config) + # Evaluated: isapprox(14.020439047717783, 18.300439047717788, ...) + r"^test_infeasible_affine_MIN_SENSE_offset$", + ] + ) + return +end + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +end # module + +TestMOI.runtests() diff --git a/test/runtests.jl b/test/runtests.jl index 8d01c18..ab78c07 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -340,4 +340,6 @@ const Lib = cuPDLPx.LibcuPDLPx println(" > Direct API Solve test passed.") end -end \ No newline at end of file +end + +include("MOI_wrapper.jl")