Libjulia / c-api : how to unbox SparseMatrixCSC

A new question about the libjulia c-api. My project is fully functional and I’m able to interact with the c-api in my VS2017 / C++ solution.

I work on a C++ wrapper of the ACME package that (intensively) use sparse matrix, but I can’t see how to “unbox” the SparseMatrixCSC with the c-api.

The package contains two major circuit functions that create : one “incidence” matrix (sparse), and two “topology” matrices (sparse). The first one return a SparseMatrixCSC of integers. The second return a tuple of two SparseMatrixCSC of Integer. The “topomat” function call the “incidence” function that is used to produce the two topologicals matrices.

My C/C++ method call the “topomat” function to get a tuple :

jl_value_t* topo = jl_eval_string((ACME_ID + ".topomat(" + id + ")").c_str());

I get a jl_value_t* pointer that is a tuple - alright. As I know, the tuple contains two matrices :

size_t num_mat = jl_nfields(topo);

Return 2. OK.

jl_value_t* mat1 = jl_get_nth_field(topo, 0);
jl_value_t* mat2 = jl_get_nth_field(topo, 1);

Return 2 valid pointers where jl_typeof_str return => SparseMatrixCSC

But now, I’m confused. How to use thèses pointers?

A call to :

jl_is_mutable(mat1) 

return true.

If I call :

jl_value_t* eltype = (jl_value_t*)jl_array_eltype(mat1);

Return a value pointer that is now typed : bitstype

If I call :

jl_value_t *ty = jl_typeof(val1);
int sz = jl_datatype_size(ty);

I get a sz integer of => 20

But I really don’t know how to work with a 2D array (matrix) with my pointers. The current Embedded documentation talk about the C->libjulia 1D array.

If someone can help me with a little example of code, I would be delighted.

I haven’t tested this, but you could get e.g. the colptr like this:

jl_value_t* mat1 = jl_get_nth_field(topo, 0);
jl_array_t* colptr = (jl_array_t*)jl_get_nth_field(mat1, 0);
int_t* colptr_raw = (int_t*)jl_array_data(colptr);
size_t colptr_n = jl_array_len(colptr);

colptr_raw can then be used as a C array with length colptr_n. Same for the other spare matrix fields, except for the element types.

Thank you for you answer, barche, but this process doesn’t work.

jl_array_t* colptr = (jl_array_t*)jl_get_nth_field(mat1, 0);

return a bad structure :

jl_array_t* colptr (0x0402b0f8)
{
    data=0x00000009 
    length=67241042 
    flags={how=1 ndims=130 pooled=0 ...}
    elsize=0
    offset=67241170
    nrows=521
    maxsize=67240850
    ncols=67240850
}

The raw data pointer is a bad address : 0x00000009

The first matrix (mat1) is a sparse matrix with full size:

julia> full(tv)
9×16 Array{Int32,2}:
 1  1  0   0  0   0  0  0  0   0  0  0   0  0  0  0
 1  0  1   0  0   0  0  0  0   0  0  0   0  0  0  0
 0  0  0  -1  1   0  0  0  0   0  0  0   0  0  0  0
 0  0  0  -1  0   1  1  0  0   0  0  0   0  0  0  0
 1  0  0  -1  0   1  0  1  0   0  0  0   0  0  0  0
 0  0  0  -1  0   1  0  0  1   0  1  0   0  0  0  0
 1  0  0  -1  0   1  0  0  0   1  0  1   0  0  0  0
 0  0  0   1  0  -1  0  0  0  -1  0  0  -1  1  1  0
 0  0  0   0  0   0  0  0  0   0  0  0   0  1  0  1

The second matrix (mat2) :

julia> full(ti)
7×16 Array{Int32,2}:
 1  -1  -1  0  0  0   0  -1  0  0   0  -1  0  0   0   0
 0   0   0  1  1  0   1   1  0  0   1   1  0  0  -1   0
 0   0   0  0  0  1  -1  -1  0  0  -1  -1  0  0   1   0
 0   0   0  0  0  0   0   0  1  0  -1   0  0  0   0   0
 0   0   0  0  0  0   0   0  0  1   0  -1  0  0   1   0
 0   0   0  0  0  0   0   0  0  0   0   0  1  0   1   0
 0   0   0  0  0  0   0   0  0  0   0   0  0  1  -1  -1

Matrices come from the “birdie” circuit example of the ACME package.

Oops, sorry, the colptr is the third field, not the first, I was confused by the printing output. Dumping the type yields:

SparseMatrixCSC{Tv,Ti<:Integer} <: AbstractSparseArray{Tv,Ti<:Integer,2}
  m::Int64
  n::Int64
  colptr::Array{Ti,1}
  rowval::Array{Ti,1}
  nzval::Array{Tv,1}

So it should be

jl_array_t* colptr = (jl_array_t*)jl_get_nth_field(mat1, 2);

So simple :slight_smile:

So, for someone who want to get the SparseMatrixCSC fields, the answer is :

int m = jl_unbox_int32(jl_get_nth_field(mat1, 0));
int n = jl_unbox_int32(jl_get_nth_field(mat1, 1));
jl_array_t* colptr = (jl_array_t*)jl_get_nth_field(mat1, 2);
jl_array_t* rowval = (jl_array_t*)jl_get_nth_field(mat1, 3);
jl_array_t* nzval  = (jl_array_t*)jl_get_nth_field(mat1, 4);

In my case, m = 9, n = 16, colptr, rowval & nzval are valid jl_array_t pointers.

Thank’s to barche !

This code will segfault. Be sure to root the variables.

Also, you don’t need to use any API, you can directly declare it as a C struct

struct jl_sparse_matrix_csc_t {
    int64_t m;
    int64_t n;
    jl_array_t *colptr;
    jl_array_t *rowval;
    jl_array_t *nzval;
};

And cast the pointer to this. You can do the same with the tuple too.

Thank you yuyichao. You’re right, but not “segfault” here, the code compile and run under VS2017 even if I use jl_unbox_int32 with int64 for input.

But you’re right, the better way is to use the jl_sparse_matrix_csc_t !

Thank you very much for your answer too !!!

Well, it won’t segfault every time. But it will once in a few thousand times when the GC decided to give you a bad day.