Skip to content

Commit

Permalink
use function wrappers for type stability and remove unnecessary code
Browse files Browse the repository at this point in the history
  • Loading branch information
rdeits committed Dec 19, 2017
1 parent 5602b77 commit 51a9e2b
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 103 deletions.
144 changes: 41 additions & 103 deletions src/PATHSolver.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ else
error("PATHSolver not properly installed. Please run Pkg.build(\"PATHSolver\")")
end




export solveMCP, options

# Global function pointers for the user-supplied function and jacobian evaluators.
const user_f = Ref(FunctionWrapper{Vector{Cdouble}, Tuple{Vector{Cdouble}}}(identity))
# The annotated SparseMatrixCSC return type will automatically convert the
# jacobian into the correct sparse form for PATH
const user_j = Ref(FunctionWrapper{SparseMatrixCSC{Cdouble, Cint}, Tuple{Vector{Cdouble}}}(identity))

count_nonzeros(M::AbstractSparseMatrix) = nnz(M)
count_nonzeros(M::AbstractMatrix) = count(x -> !iszero(x), M) # fallback for dense matrices

function solveMCP(f_eval::Function, lb::Vector, ub::Vector, var_name=C_NULL, con_name=C_NULL)
j_eval = x -> ForwardDiff.jacobian(f_eval, x)
return solveMCP(f_eval, j_eval, lb, ub, var_name, con_name)
Expand All @@ -33,8 +36,7 @@ function solveMCP(f_eval::Function, j_eval::Function, lb::Vector, ub::Vector, va
f = zeros(n)

J0 = j_eval(z)
s_col, s_len, s_row, s_data = sparse_matrix(J0)
nnz = length(s_data)
nnz = count_nonzeros(J0)

t = ccall( (:path_main, "libpath47julia"), Cint,
(Cint, Cint,
Expand Down Expand Up @@ -85,113 +87,49 @@ end
# static int (*f_eval)(int n, double *z, double *f);
# static int (*j_eval)(int n, int nnz, double *z, int *col_start, int *col_len,
# int *row, double *data);


function f_user_wrap(n::Cint, z::Ptr{Cdouble}, f::Ptr{Cdouble})
F = user_f[](unsafe_wrap(Array{Cdouble}, z, Int(n), false))
unsafe_store_vector!(f, F)
return Cint(0)
end

function j_user_wrap(n::Cint, nnz::Cint, z::Ptr{Cdouble},
col_start::Ptr{Cint}, col_len::Ptr{Cint}, row::Ptr{Cint}, data::Ptr{Cdouble})

J = user_j[](unsafe_wrap(Array{Cdouble}, z, Int(n), false))

s_col, s_len, s_row, s_data = sparse_matrix(J)

unsafe_store_vector!(col_start, s_col)
unsafe_store_vector!(col_len, s_len)
unsafe_store_vector!(row, s_row)
unsafe_store_vector!(data, s_data)

function f_user_wrap(n::Cint, z_ptr::Ptr{Cdouble}, f_ptr::Ptr{Cdouble})
z = unsafe_wrap(Array{Cdouble}, z_ptr, Int(n), false)
f = unsafe_wrap(Array{Cdouble}, f_ptr, Int(n), false)
f .= user_f[](z)
return Cint(0)
end
###############################################################################



function j_user_wrap(n::Cint, expected_nnz::Cint, z_ptr::Ptr{Cdouble},
col_start_ptr::Ptr{Cint}, col_len_ptr::Ptr{Cint},
row_ptr::Ptr{Cint}, data_ptr::Ptr{Cdouble})

###############################################################################
# Converting the Jacobian matrix to the sparse matrix format of the PATH Solver
###############################################################################
function sparse_matrix(A::AbstractSparseArray)
m, n = size(A)
@assert m==n

col_start = Array{Int}(n)
col_len = Array{Int}(n)
row = Array{Int}(0)
data = Array{Float64}(0)
for j in 1:n
if j==1
col_start[j] = 1
else
col_start[j] = col_start[j-1] + col_len[j-1]
end

col_len[j] = 0
for i in 1:n
if A[i,j] != 0.0
col_len[j] += 1
push!(row, i)
push!(data, A[i,j])
end
end
z = unsafe_wrap(Array{Cdouble}, z_ptr, Int(n), false)
J::SparseMatrixCSC{Cdouble, Cint} = user_j[](z)
if nnz(J) > expected_nnz
error("Evaluated jacobian has more nonzero entries than were initially provided in solveMCP()")
end

return col_start, col_len, row, data
end

function sparse_matrix(A::Matrix)
return sparse_matrix(sparse(A))
# m, n = size(A)
# @assert m==n
#
# col_start = Array{Int}(n)
# col_len = Array{Int}(n)
# row = Array{Int}(0)
# data = Array{Float64}(0)
# for j in 1:n
# if j==1
# col_start[j] = 1
# else
# col_start[j] = col_start[j-1] + col_len[j-1]
# end
#
# col_len[j] = 0
# for i in 1:n
# if A[i,j] != 0.0
# col_len[j] += 1
# push!(row, i)
# push!(data, A[i,j])
# end
# end
# end
#
# return col_start, col_len, row, data
end
###############################################################################






function unsafe_store_vector!(x_ptr::Ptr{Cint}, x_val::Vector)
for i in 1:length(x_val)
unsafe_store!(x_ptr, x_val[i], i)
# Transfer data from the computed jacobian into the sparse format that PATH
# expects. Fortunately, PATH uses a compressed-sparse-column storage which
# is compatible with Julia's default SparseMatrixCSC format.

# col_start in PATH corresponds to J.colptr[1:end-1]
col_start = unsafe_wrap(Array{Cint}, col_start_ptr, Int(n), false)
# col_len in PATH corresponds to diff(J.colptr)
col_len = unsafe_wrap(Array{Cint}, col_len_ptr, Int(n), false)
# row in PATH corresponds to rowvals(J)
row = unsafe_wrap(Array{Cint}, row_ptr, Int(expected_nnz), false)
# data in PATH corresponds to nonzeros(J)
data = unsafe_wrap(Array{Cdouble}, data_ptr, Int(expected_nnz), false)

@inbounds for i in 1:n
col_start[i] = J.colptr[i]
col_len[i] = J.colptr[i + 1] - J.colptr[i]
end
return
end

function unsafe_store_vector!(x_ptr::Ptr{Cdouble}, x_val::Vector)
for i in 1:length(x_val)
unsafe_store!(x_ptr, x_val[i], i)
rv = rowvals(J)
nz = nonzeros(J)
num_nonzeros = nnz(J)
@inbounds for i in 1:num_nonzeros
row[i] = rv[i]
data[i] = nz[i]
end
return
return Cint(0)
end



end # Module
12 changes: 12 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ using PATHSolver
using ForwardDiff
using Base.Test

include("sparse_matrix.jl")

M = [0 0 -1 -1 ;
0 0 1 -2 ;
1 -1 2 -2 ;
Expand Down Expand Up @@ -95,6 +97,16 @@ status, z, f = solveMCP(elemfunc, jacfunc, lb, ub, var_name, con_name)
@test status == :Solved

function test_in_local_scope()
# Verify that we can solve MCPs in local scope. Surprisingly, this is
# is relevant because it affects the way closures are generated. To be
# specific, you can do the following in global scope:
#
# julia> y = [1]
# julia> cfunction(x -> x + y[1], Int, (Int,))
#
# but running the same code inside a function will fail with:
# ERROR: closures are not yet c-callable

M = [0 0 -1 -1 ;
0 0 1 -2 ;
1 -1 2 -2 ;
Expand Down
60 changes: 60 additions & 0 deletions test/sparse_matrix.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using PATHSolver
using Base.Test


# Verify that pulling data directly out of the SparseMatrixCSC form gives
# exactly what the previous hand-coded algorithm gave.
@testset "sparse matrix" begin
function sparse_matrix_reference(A::AbstractSparseMatrix)
m, n = size(A)
@assert m==n

col_start = Array{Int}(n)
col_len = Array{Int}(n)
row = Array{Int}(0)
data = Array{Float64}(0)
for j in 1:n
if j==1
col_start[j] = 1
else
col_start[j] = col_start[j-1] + col_len[j-1]
end

col_len[j] = 0
for i in 1:n
if A[i,j] != 0.0
col_len[j] += 1
push!(row, i)
push!(data, A[i,j])
end
end
end
return col_start, col_len, row, data
end

sparse_matrix_reference(A::Matrix) = sparse_matrix_reference(sparse(A))

function sparse_matrix_csc(A::SparseMatrixCSC)
m, n = size(A)
@assert m==n
col_start = A.colptr[1:end-1]
col_len = diff(A.colptr)
row = rowvals(A)
data = nonzeros(A)
return col_start, col_len, row, data
end

sparse_matrix_csc(A::Matrix) = sparse_matrix_csc(convert(SparseMatrixCSC, A))

srand(42)
for i in 1:100
n = rand(5:20)
M = sprandn(n, n, 0.1)
@test sparse_matrix_csc(M) == sparse_matrix_reference(M)

Mf = full(M)
@test sparse_matrix_csc(Mf) == sparse_matrix_reference(Mf)
@test sparse_matrix_csc(Mf) == sparse_matrix_csc(M)
end
end

0 comments on commit 51a9e2b

Please sign in to comment.