Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bindings/matlab/Contents.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
% binsparse_from_ssmc - convert SSMC A+Zeros to a Binsparse matrix struct
% binsparse_minimize_types - minimize value/index types in a Binsparse struct
% binsparse_write_string_dataset - write an HDF5 UTF-8 string dataset
% binsparse_write_ssmc_coo - write MATLAB CSC directly as row-sorted COO
% binsparse_read_string_dataset - read an HDF5 UTF-8 string dataset
%
% MATLAB helpers:
Expand All @@ -24,9 +25,11 @@
% test_binsparse_write - write and round-trip tests for binsparse_write
% test_binsparse_from_ssmc - basic test for binsparse_from_ssmc
% test_binsparse_minimize_roundtrip - SSMC conversion + type minimization test
% test_binsparse_matrix_views - non-owning matrix view ownership tests
% test_binsparse_struct - tests for the Binsparse struct helpers
% test_binsparse_to_ssmc_problem - tests for Binsparse Problem conversion
% test_binsparse_write_ssmc_problem - end-to-end SSMC writer test
% test_binsparse_write_ssmc_coo - direct row-sorted COO writer tests
% test_binsparse_roundtrip_dir - round-trip every .h5 file in a directory

% SPDX-FileCopyrightText: 2024 Binsparse Developers
Expand Down
3 changes: 3 additions & 0 deletions bindings/matlab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ end
| `binsparse_write.c` | MEX function for writing Binsparse matrix files |
| `binsparse_from_ssmc.c` | MEX function converting SuiteSparse A+Zeros to a Binsparse struct |
| `binsparse_minimize_types.c` | MEX function minimizing value/index types in a Binsparse struct |
| `binsparse_write_ssmc_coo.c` | Direct memory-efficient MATLAB CSC to row-sorted COO writer |
| `binsparse_write_string_dataset.c` | MEX function writing HDF5 UTF-8 string datasets |
| `matlab_bsp_helpers.h` | Shared MATLAB/Binsparse conversion helpers for the MEX sources |
| `binsparse_write_ssmc_problem.m` | Write a full SSMC Problem struct to one Binsparse file |
Expand All @@ -199,9 +200,11 @@ end
| `test_binsparse_write.m` | Test script for write functionality |
| `test_binsparse_from_ssmc.m` | Test script for SSMC conversion |
| `test_binsparse_minimize_roundtrip.m` | Test script for type minimization |
| `test_binsparse_matrix_views.m` | Test non-owning matrix view ownership and cleanup |
| `test_binsparse_struct.m` | Test script for the struct helpers |
| `test_binsparse_to_ssmc_problem.m` | Test script for Problem conversion |
| `test_binsparse_write_ssmc_problem.m` | End-to-end test for the SSMC writer |
| `test_binsparse_write_ssmc_coo.m` | Test direct row-sorted COO conversion and writing |
| `test_binsparse_roundtrip_dir.m` | Round-trip every .h5 file in a directory |
| `Contents.m` | Directory listing for MATLAB's `help` |
| `README.md` | This documentation file |
Expand Down
4 changes: 3 additions & 1 deletion bindings/matlab/binsparse_build_matlab_bindings.m
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ function compile_mex_functions(paths, verbose)
% List of MEX functions to compile
mex_files = {'binsparse_read.c', 'binsparse_write.c', ...
'binsparse_from_ssmc.c', 'binsparse_minimize_types.c', ...
'binsparse_write_ssmc_coo.c', ...
'binsparse_write_string_dataset.c', ...
'binsparse_read_string_dataset.c'};

Expand Down Expand Up @@ -140,7 +141,8 @@ function compile_mex_functions(paths, verbose)
rpath = [' LDFLAGS=''$LDFLAGS -fPIC ' rpath ' '' '] ;
end

include_flags = sprintf('-I%s', paths.include_dir);
include_flags = sprintf('-I%s -I%s', paths.include_dir, ...
fullfile(lib_dir, 'include'));
if ~isempty(paths.hdf5_include_dir)
include_flags = sprintf('%s -I%s', include_flags, ...
paths.hdf5_include_dir);
Expand Down
6 changes: 4 additions & 2 deletions bindings/matlab/binsparse_build_octave_bindings.m
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ function compile_octave_functions(paths, verbose)
% List of MEX functions to compile
mex_files = {'binsparse_read.c', 'binsparse_write.c', ...
'binsparse_from_ssmc.c', 'binsparse_minimize_types.c', ...
'binsparse_write_ssmc_coo.c', ...
'binsparse_write_string_dataset.c', ...
'binsparse_read_string_dataset.c'};

Expand All @@ -136,12 +137,13 @@ function compile_octave_functions(paths, verbose)
fprintf(' Compiling %s... ', mex_file);

% Prepare mkoctfile command with library linking
include_flag = sprintf('-I%s', paths.include_dir);
lib_dir = fullfile(paths.binsparse_root, 'build');
include_flag = sprintf('-I%s -I%s', paths.include_dir, ...
fullfile(lib_dir, 'include'));
if ~isempty(paths.hdf5_include_dir)
include_flag = sprintf('%s -I%s', include_flag, ...
paths.hdf5_include_dir);
end
lib_dir = fullfile(paths.binsparse_root, 'build');
lib_path = fullfile(lib_dir, 'libbinsparse.a');
cjson_lib_dir = fullfile(lib_dir, '_deps', 'cjson-build');

Expand Down
121 changes: 96 additions & 25 deletions bindings/matlab/binsparse_from_ssmc.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,46 @@ static bsp_type_t sparse_value_type(const matlab_csc_t* matrix) {
return matrix->is_complex ? BSP_COMPLEX_FLOAT64 : BSP_FLOAT64;
}

static bsp_type_t index_type_for_extent(size_t extent) {
return bsp_pick_integer_type(extent == 0 ? 0 : extent - 1);
}

static bool sparse_values_are_iso(const matlab_csc_t* matrix, double* real,
double* imag) {
if (matrix->nnz == 0) {
return false;
}

*real = matrix->values[0];
*imag = matrix->imag_values ? matrix->imag_values[0] : 0.0;
for (size_t i = 1; i < matrix->nnz; i++) {
double current_imag = matrix->imag_values ? matrix->imag_values[i] : 0.0;
if (matrix->values[i] != *real || current_imag != *imag) {
return false;
}
}
return true;
}

static bool merged_values_are_iso(const matlab_csc_t* a,
const matlab_csc_t* z, double* real,
double* imag) {
if (a->nnz + z->nnz == 0) {
return false;
}

if (a->nnz == 0) {
*real = 0.0;
*imag = 0.0;
return true;
}

if (!sparse_values_are_iso(a, real, imag)) {
return false;
}
return z->nnz == 0 || (*real == 0.0 && *imag == 0.0);
}

static void write_sparse_value(const matlab_csc_t* matrix, mwIndex src,
bsp_matrix_t* out, uint64_t dst) {
if (matrix->is_complex) {
Expand All @@ -86,6 +126,16 @@ static void write_explicit_zero(bsp_matrix_t* out, uint64_t dst) {
}
}

static void write_iso_value(bsp_matrix_t* out, double real, double imag) {
if (out->values.type == BSP_COMPLEX_FLOAT64) {
double _Complex* out_values = (double _Complex*) out->values.data;
out_values[0] = real + imag * I;
} else {
double* out_values = (double*) out->values.data;
out_values[0] = real;
}
}

static void build_csc_merged(const matlab_csc_t* a, const matlab_csc_t* z,
bsp_matrix_t* out) {
bsp_error_t error;
Expand All @@ -96,56 +146,68 @@ static void build_csc_merged(const matlab_csc_t* a, const matlab_csc_t* z,
out->nnz = a->nnz + z->nnz;
out->format = BSP_CSC;
out->structure = BSP_GENERAL;
out->is_iso = false;
double iso_real = 0.0;
double iso_imag = 0.0;
out->is_iso = merged_values_are_iso(a, z, &iso_real, &iso_imag);

error = construct_array_with_allocator(
&out->values, out->nnz, sparse_value_type(a), bsp_matlab_allocator);
&out->values, out->is_iso ? 1 : out->nnz, sparse_value_type(a),
bsp_matlab_allocator);
if (error != BSP_SUCCESS) {
mexErrMsgIdAndTxt("BinSparse:MemoryError",
"Failed to allocate values array");
}

error = construct_array_with_allocator(&out->indices_1, out->nnz, BSP_UINT64,
bsp_matlab_allocator);
bsp_type_t row_index_type = index_type_for_extent(out->nrows);
error = construct_array_with_allocator(&out->indices_1, out->nnz,
row_index_type, bsp_matlab_allocator);
if (error != BSP_SUCCESS) {
mexErrMsgIdAndTxt("BinSparse:MemoryError",
"Failed to allocate indices array");
}

bsp_type_t pointer_type = bsp_pick_integer_type(out->nnz);
error = construct_array_with_allocator(&out->pointers_to_1, out->ncols + 1,
BSP_UINT64, bsp_matlab_allocator);
pointer_type, bsp_matlab_allocator);
if (error != BSP_SUCCESS) {
mexErrMsgIdAndTxt("BinSparse:MemoryError",
"Failed to allocate pointers array");
}

uint64_t* out_colptr = (uint64_t*) out->pointers_to_1.data;
uint64_t* out_rowind = (uint64_t*) out->indices_1.data;
if (out->is_iso) {
write_iso_value(out, iso_real, iso_imag);
}

out_colptr[0] = 0;
uint64_t running = 0;
bsp_array_write(out->pointers_to_1, 0, running);
for (mwIndex j = 0; j < a->ncols; j++) {
mwIndex a_count = a->colptr[j + 1] - a->colptr[j];
mwIndex z_count = z->colptr[j + 1] - z->colptr[j];
out_colptr[j + 1] = out_colptr[j] + a_count + z_count;
running += a_count + z_count;
bsp_array_write(out->pointers_to_1, j + 1, running);
}

for (mwIndex j = 0; j < a->ncols; j++) {
mwIndex a_ptr = a->colptr[j];
mwIndex a_end = a->colptr[j + 1];
mwIndex z_ptr = z->colptr[j];
mwIndex z_end = z->colptr[j + 1];
uint64_t out_ptr = out_colptr[j];
uint64_t out_ptr = (uint64_t) a->colptr[j] + (uint64_t) z->colptr[j];

while (a_ptr < a_end || z_ptr < z_end) {
if (z_ptr >= z_end ||
(a_ptr < a_end && a->rowind[a_ptr] < z->rowind[z_ptr])) {
write_sparse_value(a, a_ptr, out, out_ptr);
out_rowind[out_ptr] = (uint64_t) a->rowind[a_ptr];
if (!out->is_iso) {
write_sparse_value(a, a_ptr, out, out_ptr);
}
bsp_array_write(out->indices_1, out_ptr, a->rowind[a_ptr]);
a_ptr++;
} else if (a_ptr >= a_end ||
(z_ptr < z_end && z->rowind[z_ptr] < a->rowind[a_ptr])) {
write_explicit_zero(out, out_ptr);
out_rowind[out_ptr] = (uint64_t) z->rowind[z_ptr];
if (!out->is_iso) {
write_explicit_zero(out, out_ptr);
}
bsp_array_write(out->indices_1, out_ptr, z->rowind[z_ptr]);
z_ptr++;
} else {
mexErrMsgIdAndTxt("BinSparse:DuplicateIndex",
Expand All @@ -154,7 +216,8 @@ static void build_csc_merged(const matlab_csc_t* a, const matlab_csc_t* z,
out_ptr++;
}

if (out_ptr != out_colptr[j + 1]) {
if (out_ptr != (uint64_t) a->colptr[j + 1] +
(uint64_t) z->colptr[j + 1]) {
mexErrMsgIdAndTxt("BinSparse:InternalError",
"Merged column counts do not match");
}
Expand Down Expand Up @@ -204,39 +267,47 @@ static void build_csc_from_a(const matlab_csc_t* a, bsp_matrix_t* out) {
out->nnz = a->nnz;
out->format = BSP_CSC;
out->structure = BSP_GENERAL;
out->is_iso = false;
double iso_real = 0.0;
double iso_imag = 0.0;
out->is_iso = sparse_values_are_iso(a, &iso_real, &iso_imag);

error = construct_array_with_allocator(
&out->values, out->nnz, sparse_value_type(a), bsp_matlab_allocator);
&out->values, out->is_iso ? 1 : out->nnz, sparse_value_type(a),
bsp_matlab_allocator);
if (error != BSP_SUCCESS) {
mexErrMsgIdAndTxt("BinSparse:MemoryError",
"Failed to allocate values array");
}

error = construct_array_with_allocator(&out->indices_1, out->nnz, BSP_UINT64,
bsp_matlab_allocator);
bsp_type_t row_index_type = index_type_for_extent(out->nrows);
error = construct_array_with_allocator(&out->indices_1, out->nnz,
row_index_type, bsp_matlab_allocator);
if (error != BSP_SUCCESS) {
mexErrMsgIdAndTxt("BinSparse:MemoryError",
"Failed to allocate indices array");
}

bsp_type_t pointer_type = bsp_pick_integer_type(out->nnz);
error = construct_array_with_allocator(&out->pointers_to_1, out->ncols + 1,
BSP_UINT64, bsp_matlab_allocator);
pointer_type, bsp_matlab_allocator);
if (error != BSP_SUCCESS) {
mexErrMsgIdAndTxt("BinSparse:MemoryError",
"Failed to allocate pointers array");
}

uint64_t* out_colptr = (uint64_t*) out->pointers_to_1.data;
uint64_t* out_rowind = (uint64_t*) out->indices_1.data;
if (out->is_iso) {
write_iso_value(out, iso_real, iso_imag);
}

for (size_t i = 0; i < out->nnz; i++) {
write_sparse_value(a, i, out, (uint64_t) i);
out_rowind[i] = (uint64_t) a->rowind[i];
if (!out->is_iso) {
write_sparse_value(a, i, out, (uint64_t) i);
}
bsp_array_write(out->indices_1, i, a->rowind[i]);
}

for (size_t i = 0; i < out->ncols + 1; i++) {
out_colptr[i] = (uint64_t) a->colptr[i];
bsp_array_write(out->pointers_to_1, i, a->colptr[i]);
}
}

Expand Down
3 changes: 1 addition & 2 deletions bindings/matlab/binsparse_minimize_types.c
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,7 @@ void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) {
}

bsp_matrix_t matrix;
bsp_error_t error = matlab_struct_to_bsp_matrix_allocator(
prhs[0], &matrix, bsp_matlab_allocator);
bsp_error_t error = matlab_struct_to_bsp_matrix_view(prhs[0], &matrix);
if (error != BSP_SUCCESS) {
mexErrMsgIdAndTxt("BinSparse:ConversionError",
"Failed to convert MATLAB struct to matrix: %s",
Expand Down
3 changes: 1 addition & 2 deletions bindings/matlab/binsparse_write.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) {
}

// Convert MATLAB struct to bsp_matrix_t
error = matlab_struct_to_bsp_matrix_allocator(prhs[1], &matrix,
bsp_matlab_allocator);
error = matlab_struct_to_bsp_matrix_view(prhs[1], &matrix);
if (error != BSP_SUCCESS) {
mxFree(filename);
mexErrMsgIdAndTxt("BinSparse:ConversionError",
Expand Down
Loading
Loading