From 8bb2aeca0bee66b4a435f9729f952b8c10966a8a Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Mon, 15 Dec 2025 02:25:21 -0500 Subject: [PATCH 01/16] MOI test run all (some failed) --- src/LibcuPDLPx.jl | 10 ++- src/MOI_wrapper.jl | 214 +++++++++++++++++++++++++-------------------- test/runtests.jl | 5 +- 3 files changed, 126 insertions(+), 103 deletions(-) diff --git a/src/LibcuPDLPx.jl b/src/LibcuPDLPx.jl index 30adc03..e989014 100644 --- a/src/LibcuPDLPx.jl +++ b/src/LibcuPDLPx.jl @@ -1,6 +1,8 @@ module LibcuPDLPx using cuPDLPx_jll +# TODO: I have to add this in my local environment. This might be a issue in JLL. +const libcupdlpx = "/home/zdpeng/.julia/artifacts/4d407e51174c3bfe2f138e6e1db2531d0bc6240d/lib/libcupdlpx.so" export cuPDLPx_jll @enum termination_reason_t::UInt32 begin @@ -51,15 +53,15 @@ end struct pdhg_parameters_t l_inf_ruiz_iterations::Cint - has_pock_chambolle_alpha::Bool + has_pock_chambolle_alpha::Cint pock_chambolle_alpha::Cdouble - bound_objective_rescaling::Bool - verbose::Bool + bound_objective_rescaling::Cint + verbose::Cint termination_evaluation_frequency::Cint termination_criteria::termination_criteria_t restart_params::restart_parameters_t reflection_coefficient::Cdouble - feasibility_polishing::Bool + feasibility_polishing::Cint end struct cupdlpx_result_t diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 677724a..e8faa71 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1,6 +1,7 @@ import MathOptInterface as MOI -# Inspired from `Clp.jl/src/MOI_wrapper/MOI_wrapper.jl` +const Lib = cuPDLPx.LibcuPDLPx + MOI.Utilities.@product_of_sets( _LPProductOfSets, MOI.EqualTo{T}, @@ -15,11 +16,7 @@ const OptimizerCache = MOI.Utilities.GenericModel{ MOI.Utilities.VariablesContainer{Cdouble}, MOI.Utilities.MatrixOfConstraints{ Cdouble, - MOI.Utilities.MutableSparseMatrixCSC{ - Cdouble, - Cint, - MOI.Utilities.ZeroBasedIndexing, - }, + MOI.Utilities.MutableSparseMatrixCSC{Cdouble,Cint,MOI.Utilities.ZeroBasedIndexing}, MOI.Utilities.Hyperrectangle{Cdouble}, _LPProductOfSets{Cdouble}, }, @@ -37,17 +34,22 @@ const BOUND_SETS = Union{ """ Optimizer() -Create a new cuPDLP optimizer. +Create a new cuPDLPx optimizer. """ mutable struct Optimizer <: MOI.AbstractOptimizer - result::Union{Nothing,LibcuPDLPx.cupdlpx_result_t} + result::Union{Nothing,Lib.cupdlpx_result_t} + parameters::Lib.pdhg_parameters_t + num_equalities::Int max_sense::Bool + silent::Bool function Optimizer() - return new( - nothing, - false, + params_ref = Ref{Lib.pdhg_parameters_t}() + Lib.set_default_parameters( + Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref), ) + + return new(nothing, params_ref[], 0, false, false) end end @@ -56,58 +58,78 @@ function MOI.default_cache(::Optimizer, ::Type) end # ==================== -# empty functions +# Helper: Immutable Update # ==================== - -function MOI.is_empty(optimizer::Optimizer) - return isnothing(optimizer.result) +function _update_immutable(obj::T, field::Symbol, value) where {T} + args = map(fieldnames(T)) do f + f == field ? value : getfield(obj, f) + end + return T(args...) end -function MOI.empty!(optimizer::Optimizer) - optimizer.result = nothing - return -end +# ==================== +# Parameters +# ==================== MOI.get(::Optimizer, ::MOI.SolverName) = "cuPDLPx" -# MOI.RawOptimizerAttribute - function MOI.supports(::Optimizer, param::MOI.RawOptimizerAttribute) - error("TODO") - return hasfield(PdhgParameters, Symbol(param.name)) + return hasfield(Lib.pdhg_parameters_t, 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) + optimizer.parameters = + _update_immutable(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.TimeLimitSec) = true +function MOI.set(optimizer::Optimizer, ::MOI.TimeLimitSec, value::Real) + current_criteria = optimizer.parameters.termination_criteria + new_criteria = _update_immutable(current_criteria, :time_sec_limit, Float64(value)) + optimizer.parameters = + _update_immutable(optimizer.parameters, :termination_criteria, new_criteria) + return +end + +function MOI.get(optimizer::Optimizer, ::MOI.TimeLimitSec) + return optimizer.parameters.termination_criteria.time_sec_limit +end MOI.supports(::Optimizer, ::MOI.Silent) = true - function MOI.set(optimizer::Optimizer, ::MOI.Silent, value::Bool) - error("TODO") optimizer.silent = value + new_verbose = value ? 0 : 1 + optimizer.parameters = _update_immutable(optimizer.parameters, :verbose, new_verbose) return end - MOI.get(optimizer::Optimizer, ::MOI.Silent) = optimizer.silent +# ==================== +# Empty & Status +# ==================== + +function MOI.is_empty(optimizer::Optimizer) + return isnothing(optimizer.result) +end + +function MOI.empty!(optimizer::Optimizer) + optimizer.result = nothing + return +end + # ======================================== -# Supported constraints and objectives +# Constraints & Objectives # ======================================== function MOI.supports_constraint( @@ -126,7 +148,6 @@ function MOI.supports_constraint( return true end - MOI.supports(::Optimizer, ::MOI.ObjectiveSense) = true function MOI.supports( @@ -137,65 +158,83 @@ function MOI.supports( end # =============================== -# Optimize and post-optimize +# 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( +function create_matrix_desc_ref( + A::MOI.Utilities.MutableSparseMatrixCSC{Cdouble,Cint,MOI.Utilities.ZeroBasedIndexing}, +) + A_csc = Lib.MatrixCSC( length(A.rowval), pointer(A.colptr), pointer(A.rowval), - pointer(A.nzval) + 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) + desc_ref = Ref{Lib.matrix_desc_t}() + + desc_val = Lib.matrix_desc_t(ntuple(_ -> UInt8(0), 56)) + desc_ref[] = desc_val - # 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 + desc_ptr = Base.unsafe_convert(Ptr{Lib.matrix_desc_t}, desc_ref) - # 3. Set The Union Data - A_desc_ptr.data.csc = A_csc + desc_ptr.m = Cint(A.m) + desc_ptr.n = Cint(A.n) + desc_ptr.fmt = Lib.matrix_csc + desc_ptr.zero_tolerance = 1e-12 + desc_ptr.data.csc = A_csc - return A_desc_ptr + return desc_ref end function MOI.optimize!(dest::Optimizer, src::OptimizerCache) MOI.empty!(dest) + if src.constraints.coefficients.n == 0 + dest.result = nothing + return MOI.Utilities.identity_index_map(src), false + end + 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( + + dest.num_equalities = MOI.get( + src, + MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64},MOI.EqualTo{Float64}}(), + ) + + matrix_desc_ref = create_matrix_desc_ref(src.constraints.coefficients) + + matrix_desc_ptr = Base.unsafe_convert(Ptr{Lib.matrix_desc_t}, matrix_desc_ref) + + prob = Lib.create_lp_problem( pointer(c), - sparse_matrix(src.constraints.coefficients), + matrix_desc_ptr, pointer(src.constraints.constants.lower), pointer(src.constraints.constants.upper), pointer(src.variables.lower), pointer(src.variables.upper), - pointer(obj_const) + 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_ref = Ref(dest.parameters) 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 @@ -206,29 +245,28 @@ function MOI.optimize!(dest::Optimizer, src::MOI.ModelLike) return index_map, false end +# ==================== +# Result Getters +# ==================== + 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 + return isnothing(optimizer.result) ? "Optimize not called" : "Solver finished" 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 + Lib.TERMINATION_REASON_UNSPECIFIED => MOI.OPTIMIZE_NOT_CALLED, + Lib.TERMINATION_REASON_OPTIMAL => MOI.OPTIMAL, + Lib.TERMINATION_REASON_PRIMAL_INFEASIBLE => MOI.INFEASIBLE, + Lib.TERMINATION_REASON_DUAL_INFEASIBLE => MOI.DUAL_INFEASIBLE, + Lib.TERMINATION_REASON_TIME_LIMIT => MOI.TIME_LIMIT, + Lib.TERMINATION_REASON_ITERATION_LIMIT => MOI.ITERATION_LIMIT, + Lib.TERMINATION_REASON_FEAS_POLISH_SUCCESS => MOI.OTHER_ERROR, ) -# 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] @@ -236,22 +274,22 @@ 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) + return _flip_sense(optimizer, optimizer.result.primal_objective_value) 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) + return _flip_sense(optimizer, optimizer.result.dual_objective_value) 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, + Lib.TERMINATION_REASON_UNSPECIFIED => MOI.NO_SOLUTION, + Lib.TERMINATION_REASON_OPTIMAL => MOI.FEASIBLE_POINT, + Lib.TERMINATION_REASON_PRIMAL_INFEASIBLE => MOI.NO_SOLUTION, + Lib.TERMINATION_REASON_DUAL_INFEASIBLE => MOI.INFEASIBILITY_CERTIFICATE, + Lib.TERMINATION_REASON_TIME_LIMIT => MOI.FEASIBLE_POINT, + Lib.TERMINATION_REASON_ITERATION_LIMIT => MOI.FEASIBLE_POINT, + Lib.TERMINATION_REASON_FEAS_POLISH_SUCCESS => MOI.FEASIBLE_POINT, ) function MOI.get(optimizer::Optimizer, attr::MOI.PrimalStatus) @@ -261,30 +299,16 @@ function MOI.get(optimizer::Optimizer, attr::MOI.PrimalStatus) return _PRIMAL_STATUS_MAP[optimizer.result.termination_reason] end -function MOI.get( - optimizer::Optimizer, - attr::MOI.VariablePrimal, - vi::MOI.VariableIndex, -) +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] + return _PRIMAL_STATUS_MAP[optimizer.result.termination_reason] end function MOI.get( @@ -302,13 +326,9 @@ function MOI.get( 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] + 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 + return isnothing(optimizer.result) ? 0 : 1 end diff --git a/test/runtests.jl b/test/runtests.jl index ab78c07..cefc274 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -73,7 +73,8 @@ const Lib = cuPDLPx.LibcuPDLPx # If alignment/padding is wrong, these will likely be garbage values. # verbose is usually a boolean (0 or 1) - @test (params.verbose == true) || (params.verbose == false) + # @test (params.verbose == true) || (params.verbose == false) + @test params.verbose isa Integer # Time limit should be positive (usually infinity or a large number) @test params.termination_criteria.time_sec_limit > 0 @@ -291,7 +292,7 @@ const Lib = cuPDLPx.LibcuPDLPx A_desc_ptr.m = Cint(m_cons) A_desc_ptr.n = Cint(n_vars) A_desc_ptr.fmt = Lib.matrix_csr - A_desc_ptr.zero_tolerance = 0.0 + A_desc_ptr.zero_tolerance = 1e-12 # 3. Set The Union Data A_desc_ptr.data.csr = A_csr From e223437bddb0abcda53d122e433a79bdb1aebc6f Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Sat, 20 Dec 2025 12:49:07 -0500 Subject: [PATCH 02/16] correct _PRIMAL_STATUS_MAP to _DUAL_STATUS_MAP --- src/MOI_wrapper.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index e8faa71..5fe28a5 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -308,7 +308,7 @@ function MOI.get(optimizer::Optimizer, attr::MOI.DualStatus) if attr.result_index > MOI.get(optimizer, MOI.ResultCount()) return MOI.NO_SOLUTION end - return _PRIMAL_STATUS_MAP[optimizer.result.termination_reason] + return _DUAL_STATUS_MAP[optimizer.result.termination_reason] end function MOI.get( From 0b2a41114c1c0b9c03870f21c3c06ac00bf1ae9c Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Sat, 20 Dec 2025 12:50:30 -0500 Subject: [PATCH 03/16] update cuPDLPx_jll to 0.1.5, which supports CUDA 13.0 --- Manifest.toml | 20 ++++++++++---------- Project.toml | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Manifest.toml b/Manifest.toml index 04b7c1d..901feca 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.12.2" manifest_format = "2.0" -project_hash = "2ccee5e212b4700701dc3d1848104837eff8853c" +project_hash = "f7e9da03273385049341b25c4033f9502b2f4291" [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" @@ -35,9 +35,9 @@ version = "0.5.0" [[deps.CUDA_Driver_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "2023be0b10c56d259ea84a94dbfc021aa452f2c6" +git-tree-sha1 = "63b4911c80ade9de10ec4b766e99cb1a628f465f" uuid = "4ee394cb-3365-5eb0-8335-949819d2adfc" -version = "13.0.2+0" +version = "13.1.0+0" [[deps.CUDA_Runtime_jll]] deps = ["Artifacts", "CUDA_Driver_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] @@ -47,9 +47,9 @@ version = "0.19.2+0" [[deps.CUDA_SDK_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8c546633bdb21b7b806276c0677a5656b4f675bc" +git-tree-sha1 = "f2f5388f3141f39a0d2a9145ceaa280fb055ec60" uuid = "6cbf2f2e-7e60-5632-ac76-dca2274e0be0" -version = "13.0.2+1" +version = "13.1.0+0" [[deps.Clang]] deps = ["CEnum", "Clang_jll", "Downloads", "Pkg", "TOML"] @@ -334,9 +334,9 @@ version = "1.3.3" [[deps.Preferences]] deps = ["TOML"] -git-tree-sha1 = "0f27480397253da18fe2c12a4ba4eb9eb208bf3d" +git-tree-sha1 = "522f093a29b31a93e34eaea17ba055d850edea28" uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.5.0" +version = "1.5.1" [[deps.Printf]] deps = ["Unicode"] @@ -460,13 +460,13 @@ version = "1.3.1+2" deps = ["CUDA_Runtime_jll", "CUDA_SDK_jll", "Clang", "MathOptInterface", "Test", "cuPDLPx_jll"] path = "." uuid = "bcd6524d-1420-4b17-a582-359cb8a71a63" -version = "0.1.4" +version = "0.1.5" [[deps.cuPDLPx_jll]] deps = ["Artifacts", "CUDA_Runtime_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "TOML", "Zlib_jll"] -git-tree-sha1 = "ba09f738d421a949c9058167743c33275cf58400" +git-tree-sha1 = "de696a114f309c8832934c6234b923eb54f31784" uuid = "bca5daad-f4d3-5101-ae12-8b63679c982c" -version = "0.1.4+0" +version = "0.1.5+0" [[deps.libLLVM_jll]] deps = ["Artifacts", "Libdl"] diff --git a/Project.toml b/Project.toml index b886991..f5aba18 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "cuPDLPx" uuid = "bcd6524d-1420-4b17-a582-359cb8a71a63" -version = "0.1.4" +version = "0.1.5" [deps] CUDA_Runtime_jll = "76a88914-d11a-5bdc-97e0-2f5a05c973a2" @@ -16,4 +16,4 @@ CUDA_SDK_jll = "13.0.2" Clang = "0.19.0" MathOptInterface = "1.46.0" Test = "1.11.0" -cuPDLPx_jll = "0.1.4" +cuPDLPx_jll = "0.1.5" From 0bf9f3fd1b912afd06f9ef5ff56a725eacaceaee Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Sat, 20 Dec 2025 13:33:32 -0500 Subject: [PATCH 04/16] fix const libcupdlpx issue --- src/LibcuPDLPx.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LibcuPDLPx.jl b/src/LibcuPDLPx.jl index e989014..f309e39 100644 --- a/src/LibcuPDLPx.jl +++ b/src/LibcuPDLPx.jl @@ -1,8 +1,7 @@ module LibcuPDLPx using cuPDLPx_jll -# TODO: I have to add this in my local environment. This might be a issue in JLL. -const libcupdlpx = "/home/zdpeng/.julia/artifacts/4d407e51174c3bfe2f138e6e1db2531d0bc6240d/lib/libcupdlpx.so" +const libcupdlpx = cuPDLPx_jll.libcupdlpx export cuPDLPx_jll @enum termination_reason_t::UInt32 begin From 8bf7d949823fb8d9962327adc9261ddb79019320 Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Sat, 20 Dec 2025 16:14:01 -0500 Subject: [PATCH 05/16] add _DUAL_STATUS_MAP back --- src/MOI_wrapper.jl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 5fe28a5..64bac67 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -287,9 +287,19 @@ const _PRIMAL_STATUS_MAP = Dict( Lib.TERMINATION_REASON_OPTIMAL => MOI.FEASIBLE_POINT, Lib.TERMINATION_REASON_PRIMAL_INFEASIBLE => MOI.NO_SOLUTION, Lib.TERMINATION_REASON_DUAL_INFEASIBLE => MOI.INFEASIBILITY_CERTIFICATE, - Lib.TERMINATION_REASON_TIME_LIMIT => MOI.FEASIBLE_POINT, - Lib.TERMINATION_REASON_ITERATION_LIMIT => MOI.FEASIBLE_POINT, - Lib.TERMINATION_REASON_FEAS_POLISH_SUCCESS => MOI.FEASIBLE_POINT, + Lib.TERMINATION_REASON_TIME_LIMIT => MOI.UNKNOWN_RESULT_STATUS, + Lib.TERMINATION_REASON_ITERATION_LIMIT => MOI.UNKNOWN_RESULT_STATUS, + Lib.TERMINATION_REASON_FEAS_POLISH_SUCCESS => MOI.UNKNOWN_RESULT_STATUS, +) + +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.PrimalStatus) From 1c5a95d985161cc9b0afff6bae04ab8a94fcc323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 21 Dec 2025 11:56:24 +0100 Subject: [PATCH 06/16] Fixes --- src/MOI_wrapper.jl | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 64bac67..62b8554 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -39,7 +39,7 @@ Create a new cuPDLPx optimizer. mutable struct Optimizer <: MOI.AbstractOptimizer result::Union{Nothing,Lib.cupdlpx_result_t} parameters::Lib.pdhg_parameters_t - num_equalities::Int + sets::Union{Nothing,_LPProductOfSets{Cdouble}} max_sense::Bool silent::Bool @@ -49,7 +49,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref), ) - return new(nothing, params_ref[], 0, false, false) + return new(nothing, params_ref[], nothing, false, false) end end @@ -125,6 +125,7 @@ end function MOI.empty!(optimizer::Optimizer) optimizer.result = nothing + optimizer.sets = nothing return end @@ -207,10 +208,7 @@ function MOI.optimize!(dest::Optimizer, src::OptimizerCache) end obj_const = [_flip_sense(dest, MOI.constant(obj))] - dest.num_equalities = MOI.get( - src, - MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64},MOI.EqualTo{Float64}}(), - ) + dest.sets = src.constraints.sets matrix_desc_ref = create_matrix_desc_ref(src.constraints.coefficients) @@ -311,7 +309,7 @@ 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] + return unsafe_load(optimizer.result.primal_solution, vi.value) end function MOI.get(optimizer::Optimizer, attr::MOI.DualStatus) @@ -324,19 +322,11 @@ 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}}, + ci::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64}}, ) MOI.check_result_index_bounds(optimizer, attr) - return optimizer.result.dual_solution[optimizer.num_equalities+ci.value] + row = only(MOI.Utilities.rows(optimizer.sets, ci)) + return unsafe_load(optimizer.result.dual_solution, row) end function MOI.get(optimizer::Optimizer, ::MOI.ResultCount) From f95b37071a7135c8c8885ada75bc2f31772f3aee Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Mon, 22 Dec 2025 22:40:11 -0500 Subject: [PATCH 07/16] update runtests.jl --- test/runtests.jl | 136 ++++++++--------------------------------------- 1 file changed, 22 insertions(+), 114 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index cefc274..e94514e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,105 +7,55 @@ const Lib = cuPDLPx.LibcuPDLPx # ========================================== # 1. Basic Enum Mapping Tests - # Verify that Julia Enum values match C header definitions # ========================================== @testset "Enum Mapping" begin - # Check termination_reason_t @test Int(Lib.TERMINATION_REASON_OPTIMAL) == 1 @test Int(Lib.TERMINATION_REASON_TIME_LIMIT) == 4 - # Check matrix_format_t @test Int(Lib.matrix_dense) == 0 @test Int(Lib.matrix_csr) == 1 @test Int(Lib.matrix_csc) == 2 end # ========================================== - # 2. Union Memory Layout Tests (MatrixData) - # Verify if the generated code handles memory addresses correctly. - # The 'x + 0' logic should map the specific struct pointers to the same address as the raw data. + # 2. Union Memory Layout Tests # ========================================== @testset "Union Memory Layout (MatrixData)" begin - # MatrixData is defined as a 32-byte blob @test sizeof(Lib.MatrixData) == 32 - - # Simulate creating a zero-initialized Union data_blob = Lib.MatrixData(ntuple(_ -> UInt8(0), 32)) data_ref = Ref(data_blob) data_ptr = Base.unsafe_convert(Ptr{Lib.MatrixData}, data_ref) - - # Test the .csr / .dense property accessors - # Logic: The converted pointer address must match the original pointer address - # because in a Union, all members start at offset 0. - ptr_csr = data_ptr.csr - ptr_dense = data_ptr.dense - - @test ptr_csr isa Ptr{Lib.MatrixCSR} - @test ptr_dense isa Ptr{Lib.MatrixDense} - - # Critical Check: Verify memory addresses are identical - @test Int(data_ptr) == Int(ptr_csr) - @test Int(data_ptr) == Int(ptr_dense) - println(" > MatrixData Union logic verified (Address offsets are correct).") + @test data_ptr.csr isa Ptr{Lib.MatrixCSR} + @test Int(data_ptr) == Int(data_ptr.csr) + println(" > MatrixData Union logic verified.") end # ========================================== - # 3. Real C Function Call Test (Core Verification) + # 3. C Function Call Test # ========================================== @testset "C Function Call: set_default_parameters" begin - # 1. Allocate memory on the Julia side - # Create an uninitialized pdhg_parameters_t struct on the heap params_ref = Ref{Lib.pdhg_parameters_t}() - - # Get the raw pointer params_ptr = Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref) - - # 2. Call the C function - # This function writes default values into the memory pointed to by params_ptr. Lib.set_default_parameters(params_ptr) - - # 3. Read and verify results - # Retrieve the modified struct from the Ref params = params_ref[] - - # Verify sanity of the loaded values. - # If alignment/padding is wrong, these will likely be garbage values. - # verbose is usually a boolean (0 or 1) - # @test (params.verbose == true) || (params.verbose == false) @test params.verbose isa Integer - - # Time limit should be positive (usually infinity or a large number) @test params.termination_criteria.time_sec_limit > 0 - - # Check nested struct alignment - # eps_optimal_relative should be a small positive number (e.g., 1e-6) - @test params.termination_criteria.eps_optimal_relative < 1.0 - @test params.termination_criteria.eps_optimal_relative >= 0.0 - - # Check restart parameters - @test params.restart_params.k_p isa Float64 - - println(" > Successfully called set_default_parameters from C library.") - println(" > Defaults loaded check: Time Limit = $(params.termination_criteria.time_sec_limit)") + println(" > Successfully called set_default_parameters.") end # ========================================== - # 4. Struct Size Sanity Check - # Ensure Julia's matrix_desc_t matches the C definition padding + # 4. Struct Size Check # ========================================== @testset "Struct Size Sanity Check" begin - # matrix_desc_t is defined as NTuple{56, UInt8} in the generated code @test sizeof(Lib.matrix_desc_t) == 56 end # ========================================== - # 6. Integration Test: Read MPS from file + # 6. Integration Test: MPS File # ========================================== @testset "MPS File Solve (AFIRO)" begin - # 1. Create a temporary MPS file - # AFIRO is a standard small Netlib LP test case (27 rows, 32 cols) mps_content = """ NAME AFIRO ROWS @@ -195,20 +145,13 @@ const Lib = cuPDLPx.LibcuPDLPx write(mps_path, mps_content) println(" > Created temp MPS file at: $mps_path") - # 2. Read MPS using the C library - # Note: read_mps_file returns a Ptr{lp_problem_t} prob = Lib.read_mps_file(mps_path) - @test prob != C_NULL - # Verify problem dimensions (AFIRO should be small) prob_data = unsafe_load(prob) println(" > Loaded Problem: $(prob_data.num_variables) vars, $(prob_data.num_constraints) cons") - # AFIRO stats: 32 variables, 27 constraints (values depend on presolve/parsing, but checking >0 is good) @test prob_data.num_variables > 0 - @test prob_data.num_constraints > 0 - # 3. Solve 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) @@ -220,14 +163,12 @@ const Lib = cuPDLPx.LibcuPDLPx println(" > MPS Solve Status: $(result.termination_reason)") println(" > MPS Primal Obj: $(result.primal_objective_value)") - # AFIRO optimal objective is approx -4.6475314286e+02 target_obj = -464.75 @test isapprox(result.primal_objective_value, target_obj, rtol=1e-2) - # 4. Cleanup Lib.cupdlpx_result_free(result_ptr) Lib.lp_problem_free(prob) - rm(mps_path) # delete temp file + rm(mps_path) end # ========================================== @@ -236,44 +177,28 @@ const Lib = cuPDLPx.LibcuPDLPx @testset "Direct API Solve" begin println("\n > Starting Direct API Solve Test...") - # ------------------------------------------------------- - # Problem Data Definition - # maximize - # x + y + 2 z - # subject to - # x + 2 y + 3 z <= 4 - # x + y >= 1 - # 0 <= x, y, z <= 1 - # ------------------------------------------------------- - n_vars = 3 - m_cons = 2 - - # 1. Objective Vector (Minimize direction) + # 1. Objective Vector c = Cdouble[-1.0, -1.0, -2.0] - obj_const = Cdouble[0.0] # Must be a pointer + obj_const = Cdouble[0.0] - # 2. Variable Bounds (Relax Binary -> [0, 1]) + # 2. Variable Bounds var_lb = Cdouble[0.0, 0.0, 0.0] var_ub = Cdouble[1.0, 1.0, 1.0] - # 3. Constraint Matrix Data (CSR Format) - # Row 0: 1.0, 2.0, 3.0 (indices 0, 1, 2) - # Row 1: -1.0, -1.0 (indices 0, 1) + # 3. Constraint Matrix Data csr_vals = Cdouble[1.0, 2.0, 3.0, -1.0, -1.0] csr_col_ind = Cint[0, 1, 2, 0, 1] csr_row_ptr = Cint[0, 3, 5] nnz = Cint(5) # 4. Constraint Bounds - # Row 0: ... <= 4.0 -> (-Inf, 4.0] - # Row 1: ... <= -1.0 -> (-Inf, -1.0] con_lb = Cdouble[-Inf, -Inf] con_ub = Cdouble[4.0, -1.0] - # ------------------------------------------------------- - # C Struct Construction - # ------------------------------------------------------- - + # 5. Refs + A_desc_ref = Ref{Lib.matrix_desc_t}() + params_ref = Ref{Lib.pdhg_parameters_t}() + # A. Construct MatrixCSR A_csr = Lib.MatrixCSR( nnz, @@ -283,24 +208,15 @@ const Lib = cuPDLPx.LibcuPDLPx ) # B. Construct matrix_desc_t - # 1. Allocate zeroed struct on Julia side - A_desc_ref = Ref{Lib.matrix_desc_t}() - A_desc_ref[] = Lib.matrix_desc_t(ntuple(_ -> UInt8(0), 56)) # Clear memory + A_desc_ref[] = Lib.matrix_desc_t(ntuple(_ -> UInt8(0), 56)) A_desc_ptr = Base.unsafe_convert(Ptr{Lib.matrix_desc_t}, A_desc_ref) - # 2. Set Scalar Fields - A_desc_ptr.m = Cint(m_cons) - A_desc_ptr.n = Cint(n_vars) + A_desc_ptr.m = Cint(2) # m_cons = 2 + A_desc_ptr.n = Cint(3) # n_vars = 3 + A_desc_ptr.fmt = Lib.matrix_csr A_desc_ptr.zero_tolerance = 1e-12 - - # 3. Set The Union Data A_desc_ptr.data.csr = A_csr - - # ------------------------------------------------------- - # Create and Solve - # ------------------------------------------------------- - # Create LP Problem prob = Lib.create_lp_problem( pointer(c), @@ -314,9 +230,8 @@ const Lib = cuPDLPx.LibcuPDLPx @test prob != C_NULL # Setup Parameters - 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) - Lib.set_default_parameters(params_ptr) # Solve result_ptr = Lib.solve_lp_problem(prob, params_ptr) @@ -324,18 +239,11 @@ const Lib = cuPDLPx.LibcuPDLPx result = unsafe_load(result_ptr) - # ------------------------------------------------------- - # Validation - # ------------------------------------------------------- println(" > Direct Solve Status: $(result.termination_reason)") println(" > Direct Solve Obj: $(result.primal_objective_value)") - - # Optimal happens at x=1, y=0, z=1 => Obj = 3. - # Since we minimized negative: Target = -3.0 @test result.termination_reason == Lib.TERMINATION_REASON_OPTIMAL @test isapprox(result.primal_objective_value, -3.0, atol=1e-4) - # Cleanup Lib.cupdlpx_result_free(result_ptr) Lib.lp_problem_free(prob) println(" > Direct API Solve test passed.") From 48985b027dd8a627fbc957e74190db59e28bf52c Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Tue, 23 Dec 2025 10:06:06 -0500 Subject: [PATCH 08/16] update cuPDLPx_jll to v0.2.1 --- Manifest.toml | 8 ++++---- Project.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Manifest.toml b/Manifest.toml index 901feca..2a0ac20 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.12.2" manifest_format = "2.0" -project_hash = "f7e9da03273385049341b25c4033f9502b2f4291" +project_hash = "86c2f8e60043f83a38f3cd9a15f4fa8595acb6dc" [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" @@ -460,13 +460,13 @@ version = "1.3.1+2" deps = ["CUDA_Runtime_jll", "CUDA_SDK_jll", "Clang", "MathOptInterface", "Test", "cuPDLPx_jll"] path = "." uuid = "bcd6524d-1420-4b17-a582-359cb8a71a63" -version = "0.1.5" +version = "0.2.1" [[deps.cuPDLPx_jll]] deps = ["Artifacts", "CUDA_Runtime_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "TOML", "Zlib_jll"] -git-tree-sha1 = "de696a114f309c8832934c6234b923eb54f31784" +git-tree-sha1 = "932e4c4ff9577954f256d8cb39b8273306863aa5" uuid = "bca5daad-f4d3-5101-ae12-8b63679c982c" -version = "0.1.5+0" +version = "0.2.1+0" [[deps.libLLVM_jll]] deps = ["Artifacts", "Libdl"] diff --git a/Project.toml b/Project.toml index f5aba18..50bb6b4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "cuPDLPx" uuid = "bcd6524d-1420-4b17-a582-359cb8a71a63" -version = "0.1.5" +version = "0.2.1" [deps] CUDA_Runtime_jll = "76a88914-d11a-5bdc-97e0-2f5a05c973a2" @@ -16,4 +16,4 @@ CUDA_SDK_jll = "13.0.2" Clang = "0.19.0" MathOptInterface = "1.46.0" Test = "1.11.0" -cuPDLPx_jll = "0.1.5" +cuPDLPx_jll = "0.2.1" From ab1eb4a28f4e714d971ddfd16ad87acf1f3d9233 Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Tue, 23 Dec 2025 12:36:00 -0500 Subject: [PATCH 09/16] update LibcuPDLPx.jl to work with cuPDLPx_jll v0.2.1 --- src/LibcuPDLPx.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/LibcuPDLPx.jl b/src/LibcuPDLPx.jl index f309e39..0f101f9 100644 --- a/src/LibcuPDLPx.jl +++ b/src/LibcuPDLPx.jl @@ -61,16 +61,28 @@ struct pdhg_parameters_t restart_params::restart_parameters_t reflection_coefficient::Cdouble feasibility_polishing::Cint + presolve::Cint end struct cupdlpx_result_t num_variables::Cint num_constraints::Cint + num_nonzeros::Cint + + num_reduced_variables::Cint + num_reduced_constraints::Cint + num_reduced_nonzeros::Cint + primal_solution::Ptr{Cdouble} dual_solution::Ptr{Cdouble} + reduced_cost::Ptr{Cdouble} + total_count::Cint rescaling_time_sec::Cdouble cumulative_time_sec::Cdouble + presolve_time::Cdouble + presolve_status::Cint + absolute_primal_residual::Cdouble relative_primal_residual::Cdouble absolute_dual_residual::Cdouble From 51dd1843215aa1f96a94f321b95e5d2e4781111a Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Thu, 25 Dec 2025 13:07:16 -0500 Subject: [PATCH 10/16] update cuPDLPx_jll to v0.2.2 --- Manifest.toml | 8 ++++---- Project.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Manifest.toml b/Manifest.toml index 2a0ac20..535c841 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.12.2" manifest_format = "2.0" -project_hash = "86c2f8e60043f83a38f3cd9a15f4fa8595acb6dc" +project_hash = "54309def7c908068951b3458be89fae48b84f475" [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" @@ -460,13 +460,13 @@ version = "1.3.1+2" deps = ["CUDA_Runtime_jll", "CUDA_SDK_jll", "Clang", "MathOptInterface", "Test", "cuPDLPx_jll"] path = "." uuid = "bcd6524d-1420-4b17-a582-359cb8a71a63" -version = "0.2.1" +version = "0.2.2" [[deps.cuPDLPx_jll]] deps = ["Artifacts", "CUDA_Runtime_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "TOML", "Zlib_jll"] -git-tree-sha1 = "932e4c4ff9577954f256d8cb39b8273306863aa5" +git-tree-sha1 = "84f425232e24b1d8a961c5fc3b3d30e2d9b834ad" uuid = "bca5daad-f4d3-5101-ae12-8b63679c982c" -version = "0.2.1+0" +version = "0.2.2+0" [[deps.libLLVM_jll]] deps = ["Artifacts", "Libdl"] diff --git a/Project.toml b/Project.toml index 50bb6b4..0b687ae 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "cuPDLPx" uuid = "bcd6524d-1420-4b17-a582-359cb8a71a63" -version = "0.2.1" +version = "0.2.2" [deps] CUDA_Runtime_jll = "76a88914-d11a-5bdc-97e0-2f5a05c973a2" @@ -16,4 +16,4 @@ CUDA_SDK_jll = "13.0.2" Clang = "0.19.0" MathOptInterface = "1.46.0" Test = "1.11.0" -cuPDLPx_jll = "0.2.1" +cuPDLPx_jll = "0.2.2" From 0545a328ae9457fedde436106d118bf97193d4cf Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Mon, 29 Dec 2025 18:27:19 -0500 Subject: [PATCH 11/16] add missing params: sv_max_iter and sv_tol --- src/LibcuPDLPx.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/LibcuPDLPx.jl b/src/LibcuPDLPx.jl index 0f101f9..d481825 100644 --- a/src/LibcuPDLPx.jl +++ b/src/LibcuPDLPx.jl @@ -57,6 +57,8 @@ struct pdhg_parameters_t bound_objective_rescaling::Cint verbose::Cint termination_evaluation_frequency::Cint + sv_max_iter::Cint + sv_tol::Cdouble termination_criteria::termination_criteria_t restart_params::restart_parameters_t reflection_coefficient::Cdouble From 3ed0e315886adb07d9ea666a39ff73bebb735661 Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Mon, 29 Dec 2025 19:36:00 -0500 Subject: [PATCH 12/16] add native_result_ptr and native_problem_ptr --- src/MOI_wrapper.jl | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 62b8554..e977478 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -38,6 +38,8 @@ Create a new cuPDLPx optimizer. """ mutable struct Optimizer <: MOI.AbstractOptimizer result::Union{Nothing,Lib.cupdlpx_result_t} + native_result_ptr::Ptr{Lib.cupdlpx_result_t} + native_problem_ptr::Ptr{Lib.lp_problem_t} parameters::Lib.pdhg_parameters_t sets::Union{Nothing,_LPProductOfSets{Cdouble}} max_sense::Bool @@ -49,7 +51,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref), ) - return new(nothing, params_ref[], nothing, false, false) + return new(nothing, C_NULL, C_NULL, params_ref[], nothing, false, false) end end @@ -124,6 +126,14 @@ function MOI.is_empty(optimizer::Optimizer) end function MOI.empty!(optimizer::Optimizer) + if optimizer.native_result_ptr != C_NULL + Lib.cupdlpx_result_free(optimizer.native_result_ptr) + optimizer.native_result_ptr = C_NULL + end + if optimizer.native_problem_ptr != C_NULL + Lib.lp_problem_free(optimizer.native_problem_ptr) + optimizer.native_problem_ptr = C_NULL + end optimizer.result = nothing optimizer.sets = nothing return @@ -169,11 +179,14 @@ end function create_matrix_desc_ref( A::MOI.Utilities.MutableSparseMatrixCSC{Cdouble,Cint,MOI.Utilities.ZeroBasedIndexing}, ) + colptr_ptr = isempty(A.colptr) ? C_NULL : pointer(A.colptr) + rowval_ptr = isempty(A.rowval) ? C_NULL : pointer(A.rowval) + nzval_ptr = isempty(A.nzval) ? C_NULL : pointer(A.nzval) A_csc = Lib.MatrixCSC( length(A.rowval), - pointer(A.colptr), - pointer(A.rowval), - pointer(A.nzval), + colptr_ptr, + rowval_ptr, + nzval_ptr, ) desc_ref = Ref{Lib.matrix_desc_t}() @@ -211,9 +224,11 @@ function MOI.optimize!(dest::Optimizer, src::OptimizerCache) dest.sets = src.constraints.sets matrix_desc_ref = create_matrix_desc_ref(src.constraints.coefficients) - matrix_desc_ptr = Base.unsafe_convert(Ptr{Lib.matrix_desc_t}, matrix_desc_ref) + params_ref = Ref(dest.parameters) + params_ptr = Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref) + prob = Lib.create_lp_problem( pointer(c), matrix_desc_ptr, @@ -225,13 +240,12 @@ function MOI.optimize!(dest::Optimizer, src::OptimizerCache) ) @assert prob != C_NULL - params_ref = Ref(dest.parameters) - 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) + dest.native_result_ptr = result_ptr + Lib.lp_problem_free(prob) return MOI.Utilities.identity_index_map(src), false end From 9d1192b7d47c2298388a223e80ae26b2efc3b285 Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Mon, 29 Dec 2025 19:55:18 -0500 Subject: [PATCH 13/16] update MOI silent --- src/MOI_wrapper.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index e977478..ddd6a73 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -111,8 +111,6 @@ end MOI.supports(::Optimizer, ::MOI.Silent) = true function MOI.set(optimizer::Optimizer, ::MOI.Silent, value::Bool) optimizer.silent = value - new_verbose = value ? 0 : 1 - optimizer.parameters = _update_immutable(optimizer.parameters, :verbose, new_verbose) return end MOI.get(optimizer::Optimizer, ::MOI.Silent) = optimizer.silent @@ -226,6 +224,14 @@ function MOI.optimize!(dest::Optimizer, src::OptimizerCache) matrix_desc_ref = create_matrix_desc_ref(src.constraints.coefficients) matrix_desc_ptr = Base.unsafe_convert(Ptr{Lib.matrix_desc_t}, matrix_desc_ref) + solve_params = dest.parameters + # TODO: not working + # if dest.silent + # solve_params = _update_immutable(solve_params, :verbose, Cint(0)) + # end + params_ref = Ref{Lib.pdhg_parameters_t}(solve_params) + params_ptr = Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref) + params_ref = Ref(dest.parameters) params_ptr = Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref) From 30da5c99e322e8926cf56ee179950b45b0db6b8a Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Mon, 29 Dec 2025 20:03:38 -0500 Subject: [PATCH 14/16] update --- src/MOI_wrapper.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index ddd6a73..b0edc5d 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -224,13 +224,13 @@ function MOI.optimize!(dest::Optimizer, src::OptimizerCache) matrix_desc_ref = create_matrix_desc_ref(src.constraints.coefficients) matrix_desc_ptr = Base.unsafe_convert(Ptr{Lib.matrix_desc_t}, matrix_desc_ref) - solve_params = dest.parameters # TODO: not working + # solve_params = dest.parameters # if dest.silent # solve_params = _update_immutable(solve_params, :verbose, Cint(0)) # end - params_ref = Ref{Lib.pdhg_parameters_t}(solve_params) - params_ptr = Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref) + # params_ref = Ref{Lib.pdhg_parameters_t}(solve_params) + # params_ptr = Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref) params_ref = Ref(dest.parameters) params_ptr = Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref) From 3ec1536554a05a698e716b334d767c1c76baa693 Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Thu, 8 Jan 2026 01:14:38 -0500 Subject: [PATCH 15/16] add GC.@preserve for params_ref --- src/MOI_wrapper.jl | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index b0edc5d..a13592f 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -235,23 +235,25 @@ function MOI.optimize!(dest::Optimizer, src::OptimizerCache) params_ref = Ref(dest.parameters) params_ptr = Base.unsafe_convert(Ptr{Lib.pdhg_parameters_t}, params_ref) - prob = Lib.create_lp_problem( - pointer(c), - matrix_desc_ptr, - 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 + GC.@preserve params_ref begin + prob = Lib.create_lp_problem( + pointer(c), + matrix_desc_ptr, + 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 - result_ptr = Lib.solve_lp_problem(prob, params_ptr) - @assert result_ptr != C_NULL + result_ptr = Lib.solve_lp_problem(prob, params_ptr) + @assert result_ptr != C_NULL - dest.result = unsafe_load(result_ptr) - dest.native_result_ptr = result_ptr - Lib.lp_problem_free(prob) + dest.result = unsafe_load(result_ptr) + dest.native_result_ptr = result_ptr + Lib.lp_problem_free(prob) + end return MOI.Utilities.identity_index_map(src), false end From 2e754a55ced6ca3eca36f4db328693b1d5ab15d0 Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Thu, 8 Jan 2026 10:50:49 -0500 Subject: [PATCH 16/16] add optimizer.max_sense = false --- src/MOI_wrapper.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index a13592f..fa128e8 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -134,6 +134,7 @@ function MOI.empty!(optimizer::Optimizer) end optimizer.result = nothing optimizer.sets = nothing + optimizer.max_sense = false return end @@ -205,12 +206,12 @@ end function MOI.optimize!(dest::Optimizer, src::OptimizerCache) MOI.empty!(dest) + dest.max_sense = MOI.get(src, MOI.ObjectiveSense()) == MOI.MAX_SENSE if src.constraints.coefficients.n == 0 dest.result = nothing return MOI.Utilities.identity_index_map(src), false end - 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)