From 761c7e38da4c7645ab82685d14344748e1f81df0 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Mon, 18 Aug 2025 13:03:15 -0700 Subject: [PATCH 01/10] Replace `assert` with `bsp_error_t` for `bsp_array_t`. --- examples/simple_read.c | 2 +- examples/simple_write.c | 10 ++- include/binsparse/array.h | 81 ++++++++++--------- include/binsparse/binsparse.h | 1 + include/binsparse/convert_matrix.h | 57 ++++++++++--- include/binsparse/error.h | 62 ++++++++++++++ include/binsparse/generate.h | 24 +++++- include/binsparse/hdf5_wrapper.h | 16 +++- include/binsparse/matrix.h | 8 +- .../matrix_market/matrix_market_read.h | 61 +++++++++++--- include/binsparse/minimize_values.h | 30 ++++--- include/binsparse/tensor.h | 6 +- src/read_matrix.c | 10 ++- 13 files changed, 284 insertions(+), 84 deletions(-) create mode 100644 include/binsparse/error.h diff --git a/examples/simple_read.c b/examples/simple_read.c index a4ba9bb..3af4921 100644 --- a/examples/simple_read.c +++ b/examples/simple_read.c @@ -19,7 +19,7 @@ int main(int argc, char** argv) { printf("%lu: %d\n", i, values[i]); } - bsp_destroy_array_t(array); + bsp_destroy_array_t(&array); H5Fclose(f); return 0; diff --git a/examples/simple_write.c b/examples/simple_write.c index deb47d8..cdb5024 100644 --- a/examples/simple_write.c +++ b/examples/simple_write.c @@ -11,7 +11,13 @@ int main(int argc, char** argv) { hid_t f = H5Fcreate(file_name, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); - bsp_array_t array = bsp_construct_array_t(1000, BSP_INT32); + bsp_array_t array; + bsp_error_t error = bsp_construct_array_t(&array, 1000, BSP_INT32); + if (error != BSP_SUCCESS) { + printf("Error: Failed to allocate array\n"); + H5Fclose(f); + return 1; + } int* values = (int*) array.data; @@ -22,7 +28,7 @@ int main(int argc, char** argv) { bsp_write_array(f, "test", array, 0); H5Fclose(f); - bsp_destroy_array_t(array); + bsp_destroy_array_t(&array); return 0; } diff --git a/include/binsparse/array.h b/include/binsparse/array.h index 8ac69af..60b392e 100644 --- a/include/binsparse/array.h +++ b/include/binsparse/array.h @@ -6,9 +6,9 @@ #pragma once -#include #include #include +#include #include #include #include @@ -28,63 +28,70 @@ static inline bsp_array_t bsp_construct_default_array_t() { return array; } -static inline bsp_array_t bsp_construct_array_t(size_t size, bsp_type_t type) { +static inline bsp_error_t bsp_construct_array_t(bsp_array_t* array, size_t size, + bsp_type_t type) { size_t byte_size = size * bsp_type_size(type); - bsp_array_t array; - array.allocator = bsp_default_allocator; - array.data = array.allocator.malloc(byte_size); - assert(array.data != NULL); - array.size = size; - array.type = type; + array->allocator = bsp_default_allocator; + array->data = array->allocator.malloc(byte_size); - return array; + if (array->data == NULL) { + return BSP_ERROR_MEMORY; + } + + array->size = size; + array->type = type; + + return BSP_SUCCESS; } -static inline bsp_array_t bsp_copy_construct_array_t(bsp_array_t other) { - bsp_array_t array = bsp_construct_array_t(other.size, other.type); - memcpy(array.data, other.data, other.size * bsp_type_size(other.type)); +static inline bsp_error_t bsp_copy_construct_array_t(bsp_array_t* array, + bsp_array_t other) { + bsp_error_t error = bsp_construct_array_t(array, other.size, other.type); + if (error != BSP_SUCCESS) { + return error; + } + + memcpy(array->data, other.data, other.size * bsp_type_size(other.type)); - return array; + return BSP_SUCCESS; } -static inline bsp_array_t bsp_complex_array_to_fp(bsp_array_t other) { - assert(other.type == BSP_COMPLEX_FLOAT32 || - other.type == BSP_COMPLEX_FLOAT64); +static inline bsp_error_t bsp_complex_array_to_fp(bsp_array_t* array) { + if (array->type != BSP_COMPLEX_FLOAT32 && + array->type != BSP_COMPLEX_FLOAT64) { + return BSP_ERROR_TYPE; + } - bsp_array_t array; - array.data = other.data; - array.size = other.size * 2; - array.allocator = other.allocator; + array->size = array->size * 2; - if (other.type == BSP_COMPLEX_FLOAT32) { - array.type = BSP_FLOAT32; + if (array->type == BSP_COMPLEX_FLOAT32) { + array->type = BSP_FLOAT32; } else { - array.type = BSP_FLOAT64; + array->type = BSP_FLOAT64; } - return array; + return BSP_SUCCESS; } -static inline bsp_array_t bsp_fp_array_to_complex(bsp_array_t other) { - assert(other.type == BSP_FLOAT32 || other.type == BSP_FLOAT64); - - bsp_array_t array; - array.data = other.data; - array.size = other.size / 2; - array.allocator = other.allocator; +static inline bsp_error_t bsp_fp_array_to_complex(bsp_array_t* array) { + if (array->type != BSP_FLOAT32 && array->type != BSP_FLOAT64) { + return BSP_ERROR_TYPE; + } - if (other.type == BSP_FLOAT32) { - array.type = BSP_COMPLEX_FLOAT32; + if (array->type == BSP_FLOAT32) { + array->type = BSP_COMPLEX_FLOAT32; } else { - array.type = BSP_COMPLEX_FLOAT64; + array->type = BSP_COMPLEX_FLOAT64; } - return array; + array->size = array->size / 2; + + return BSP_SUCCESS; } -static inline void bsp_destroy_array_t(bsp_array_t array) { - array.allocator.free(array.data); +static inline void bsp_destroy_array_t(bsp_array_t* array) { + array->allocator.free(array->data); } static inline bool bsp_array_equal(bsp_array_t x, bsp_array_t y) { diff --git a/include/binsparse/binsparse.h b/include/binsparse/binsparse.h index bb6eecd..b77735a 100644 --- a/include/binsparse/binsparse.h +++ b/include/binsparse/binsparse.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/include/binsparse/convert_matrix.h b/include/binsparse/convert_matrix.h index 2f6c332..a8b8038 100644 --- a/include/binsparse/convert_matrix.h +++ b/include/binsparse/convert_matrix.h @@ -37,17 +37,30 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, bsp_type_t index_type = bsp_pick_integer_type(max_dim); - result.values = bsp_copy_construct_array_t(matrix.values); + bsp_error_t error = + bsp_copy_construct_array_t(&result.values, matrix.values); + if (error != BSP_SUCCESS) { + return bsp_construct_default_matrix_t(); + } // There is a corner case with tall and skinny matrices where we need a // higher width for rowind. In order to keep rowind/colind the same type, // we might upcast. if (index_type == matrix.indices_1.type) { - result.indices_1 = bsp_copy_construct_array_t(matrix.indices_1); + error = bsp_copy_construct_array_t(&result.indices_1, matrix.indices_1); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&result.values); + return bsp_construct_default_matrix_t(); + } } else { - result.indices_1 = - bsp_construct_array_t(matrix.indices_1.size, index_type); + error = bsp_construct_array_t(&result.indices_1, matrix.indices_1.size, + index_type); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&result.values); + return bsp_construct_default_matrix_t(); + } + for (size_t i = 0; i < matrix.indices_1.size; i++) { size_t index; bsp_array_read(matrix.indices_1, i, index); @@ -55,7 +68,12 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, } } - result.indices_0 = bsp_construct_array_t(matrix.nnz, index_type); + error = bsp_construct_array_t(&result.indices_0, matrix.nnz, index_type); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&result.values); + bsp_destroy_array_t(&result.indices_1); + return bsp_construct_default_matrix_t(); + } for (size_t i = 0; i < matrix.nrows; i++) { size_t row_begin, row_end; @@ -109,12 +127,26 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, // indices can be copied exactly. Values' type will not change, but // column indices might, thus the extra branch. - result.values = bsp_copy_construct_array_t(matrix.values); + bsp_error_t error = + bsp_copy_construct_array_t(&result.values, matrix.values); + if (error != BSP_SUCCESS) { + return bsp_construct_default_matrix_t(); + } if (index_type == matrix.indices_1.type) { - result.indices_1 = bsp_copy_construct_array_t(matrix.indices_1); + error = + bsp_copy_construct_array_t(&result.indices_1, matrix.indices_1); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&result.values); + return bsp_construct_default_matrix_t(); + } } else { - result.indices_1 = bsp_construct_array_t(matrix.nnz, index_type); + error = + bsp_construct_array_t(&result.indices_1, matrix.nnz, index_type); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&result.values); + return bsp_construct_default_matrix_t(); + } for (size_t i = 0; i < matrix.nnz; i++) { size_t index; @@ -123,8 +155,13 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, } } - result.pointers_to_1 = - bsp_construct_array_t(matrix.nrows + 1, index_type); + error = bsp_construct_array_t(&result.pointers_to_1, matrix.nrows + 1, + index_type); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&result.values); + bsp_destroy_array_t(&result.indices_1); + return bsp_construct_default_matrix_t(); + } bsp_array_t rowptr = result.pointers_to_1; diff --git a/include/binsparse/error.h b/include/binsparse/error.h new file mode 100644 index 0000000..2988c16 --- /dev/null +++ b/include/binsparse/error.h @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +typedef enum bsp_error_t { + BSP_SUCCESS = 0, + + // Memory-related failures (malloc, array allocation, etc.) + BSP_ERROR_MEMORY = 1, + + // File I/O failures (file not found, read/write errors, HDF5 operations) + BSP_ERROR_IO = 2, + + // JSON parsing and metadata errors + BSP_ERROR_FORMAT = 3, + + // Invalid input parameters, dimensions, types, etc. + BSP_ERROR_INVALID_INPUT = 4, + + // Unsupported operations or not-yet-implemented features + BSP_ERROR_UNSUPPORTED = 5, + + // Type-related errors (invalid types, type mismatches) + BSP_ERROR_TYPE = 6, + + // Generic internal errors (should-never-happen cases) + BSP_ERROR_INTERNAL = 7 + +} bsp_error_t; + +/** + * Get a human-readable error message for a bsp_error_t code. + * + * @param error The error code + * @return A constant string describing the error + */ +static inline const char* bsp_get_error_string(bsp_error_t error) { + switch (error) { + case BSP_SUCCESS: + return "Success"; + case BSP_ERROR_MEMORY: + return "Memory allocation or management error"; + case BSP_ERROR_IO: + return "File I/O or HDF5 operation error"; + case BSP_ERROR_FORMAT: + return "JSON parsing or metadata format error"; + case BSP_ERROR_INVALID_INPUT: + return "Invalid input parameters or data"; + case BSP_ERROR_UNSUPPORTED: + return "Unsupported operation or feature"; + case BSP_ERROR_TYPE: + return "Data type error or type mismatch"; + case BSP_ERROR_INTERNAL: + return "Internal library error"; + default: + return "Unknown error"; + } +} diff --git a/include/binsparse/generate.h b/include/binsparse/generate.h index 93205a1..3b3043d 100644 --- a/include/binsparse/generate.h +++ b/include/binsparse/generate.h @@ -74,9 +74,27 @@ static inline bsp_matrix_t bsp_generate_coo(size_t m, size_t n, size_t nnz, matrix.nrows = m; matrix.ncols = n; matrix.nnz = nnz; - matrix.values = bsp_construct_array_t(nnz, value_type); - matrix.indices_0 = bsp_construct_array_t(nnz, index_type); - matrix.indices_1 = bsp_construct_array_t(nnz, index_type); + + bsp_error_t error; + error = bsp_construct_array_t(&matrix.values, nnz, value_type); + if (error != BSP_SUCCESS) { + // Return empty matrix on error + return matrix; + } + + error = bsp_construct_array_t(&matrix.indices_0, nnz, index_type); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&matrix.values); + return matrix; + } + + error = bsp_construct_array_t(&matrix.indices_1, nnz, index_type); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&matrix.values); + bsp_destroy_array_t(&matrix.indices_0); + return matrix; + } + matrix.format = BSP_COO; bsp_array_fill_random(matrix.values, 100); diff --git a/include/binsparse/hdf5_wrapper.h b/include/binsparse/hdf5_wrapper.h index 5aded28..c8f07ea 100644 --- a/include/binsparse/hdf5_wrapper.h +++ b/include/binsparse/hdf5_wrapper.h @@ -24,7 +24,10 @@ static inline int bsp_write_array(hid_t f, const char* label, bsp_array_t array, int compression_level) { if (array.type == BSP_COMPLEX_FLOAT32 || array.type == BSP_COMPLEX_FLOAT64) { - array = bsp_complex_array_to_fp(array); + bsp_error_t error = bsp_complex_array_to_fp(&array); + if (error != BSP_SUCCESS) { + return -3; // Type conversion error + } } hsize_t hsize[1]; @@ -198,12 +201,21 @@ static inline bsp_array_t bsp_read_array(hid_t f, const char* label) { bsp_type_t type = bsp_get_bsp_type(hdf5_type); - bsp_array_t array = bsp_construct_array_t(dims[0], type); + bsp_array_t array = bsp_construct_default_array_t(); + bsp_error_t error = bsp_construct_array_t(&array, dims[0], type); + if (error != BSP_SUCCESS) { + H5Dclose(dset); + H5Sclose(fspace); + return bsp_construct_default_array_t(); + } herr_t status = H5Dread(dset, bsp_get_hdf5_native_type(type), H5S_ALL, H5S_ALL, H5P_DEFAULT, array.data); if (status < 0) { + bsp_destroy_array_t(&array); + H5Dclose(dset); + H5Sclose(fspace); return bsp_construct_default_array_t(); } diff --git a/include/binsparse/matrix.h b/include/binsparse/matrix.h index db858c7..c4563f1 100644 --- a/include/binsparse/matrix.h +++ b/include/binsparse/matrix.h @@ -39,10 +39,10 @@ static inline bsp_matrix_t bsp_construct_default_matrix_t() { } static inline void bsp_destroy_matrix_t(bsp_matrix_t matrix) { - bsp_destroy_array_t(matrix.values); - bsp_destroy_array_t(matrix.indices_0); - bsp_destroy_array_t(matrix.indices_1); - bsp_destroy_array_t(matrix.pointers_to_1); + bsp_destroy_array_t(&matrix.values); + bsp_destroy_array_t(&matrix.indices_0); + bsp_destroy_array_t(&matrix.indices_1); + bsp_destroy_array_t(&matrix.pointers_to_1); } static inline size_t bsp_matrix_nbytes(bsp_matrix_t mat) { diff --git a/include/binsparse/matrix_market/matrix_market_read.h b/include/binsparse/matrix_market/matrix_market_read.h index c823a40..383acad 100644 --- a/include/binsparse/matrix_market/matrix_market_read.h +++ b/include/binsparse/matrix_market/matrix_market_read.h @@ -38,7 +38,11 @@ static inline bsp_matrix_t bsp_mmread_explicit_array(const char* file_path, matrix.ncols = metadata.ncols; matrix.nnz = matrix.nrows * matrix.ncols; - matrix.values = bsp_construct_array_t(matrix.nnz, value_type); + bsp_error_t error = + bsp_construct_array_t(&matrix.values, matrix.nnz, value_type); + if (error != BSP_SUCCESS) { + return matrix; // Return empty matrix on error + } matrix.format = BSP_DMAT; @@ -135,14 +139,33 @@ bsp_mmread_explicit_coordinate(const char* file_path, bsp_type_t value_type, matrix.ncols = metadata.ncols; matrix.nnz = metadata.nnz; - matrix.indices_0 = bsp_construct_array_t(matrix.nnz, index_type); - matrix.indices_1 = bsp_construct_array_t(matrix.nnz, index_type); + bsp_error_t error = + bsp_construct_array_t(&matrix.indices_0, matrix.nnz, index_type); + if (error != BSP_SUCCESS) { + return matrix; + } + + error = bsp_construct_array_t(&matrix.indices_1, matrix.nnz, index_type); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&matrix.indices_0); + return matrix; + } if (mm_type == BSP_MM_PATTERN) { - matrix.values = bsp_construct_array_t(1, value_type); + error = bsp_construct_array_t(&matrix.values, 1, value_type); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&matrix.indices_0); + bsp_destroy_array_t(&matrix.indices_1); + return matrix; + } bsp_array_write(matrix.values, 0, true); } else { - matrix.values = bsp_construct_array_t(matrix.nnz, value_type); + error = bsp_construct_array_t(&matrix.values, matrix.nnz, value_type); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&matrix.indices_0); + bsp_destroy_array_t(&matrix.indices_1); + return matrix; + } } matrix.format = BSP_COO; @@ -235,13 +258,29 @@ bsp_mmread_explicit_coordinate(const char* file_path, bsp_type_t value_type, qsort(indices, matrix.nnz, sizeof(size_t), bsp_coo_comparison_row_sort_operator_impl_); - bsp_array_t rowind = bsp_copy_construct_array_t(matrix.indices_0); - bsp_array_t colind = bsp_copy_construct_array_t(matrix.indices_1); + bsp_array_t rowind; + bsp_array_t colind; + + error = bsp_copy_construct_array_t(&rowind, matrix.indices_0); + if (error != BSP_SUCCESS) { + return matrix; + } + + error = bsp_copy_construct_array_t(&colind, matrix.indices_1); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&rowind); + return matrix; + } bsp_array_t values; if (!matrix.is_iso) { - values = bsp_copy_construct_array_t(matrix.values); + error = bsp_copy_construct_array_t(&values, matrix.values); + if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&rowind); + bsp_destroy_array_t(&colind); + return matrix; + } } for (size_t i = 0; i < matrix.nnz; i++) { @@ -252,13 +291,13 @@ bsp_mmread_explicit_coordinate(const char* file_path, bsp_type_t value_type, } } - bsp_destroy_array_t(matrix.indices_0); - bsp_destroy_array_t(matrix.indices_1); + bsp_destroy_array_t(&matrix.indices_0); + bsp_destroy_array_t(&matrix.indices_1); matrix.indices_0 = rowind; matrix.indices_1 = colind; if (!matrix.is_iso) { - bsp_destroy_array_t(matrix.values); + bsp_destroy_array_t(&matrix.values); matrix.values = values; } diff --git a/include/binsparse/minimize_values.h b/include/binsparse/minimize_values.h index 4969000..7ef18f8 100644 --- a/include/binsparse/minimize_values.h +++ b/include/binsparse/minimize_values.h @@ -24,8 +24,12 @@ static inline bsp_matrix_t bsp_matrix_minimize_values(bsp_matrix_t matrix) { } if (float32_representable) { - bsp_array_t new_values = - bsp_construct_array_t(matrix.values.size, BSP_FLOAT32); + bsp_array_t new_values; + bsp_error_t error = + bsp_construct_array_t(&new_values, matrix.values.size, BSP_FLOAT32); + if (error != BSP_SUCCESS) { + return matrix; // Return original matrix on error + } float* n_values = (float*) new_values.data; @@ -33,7 +37,7 @@ static inline bsp_matrix_t bsp_matrix_minimize_values(bsp_matrix_t matrix) { n_values[i] = values[i]; } - bsp_destroy_array_t(matrix.values); + bsp_destroy_array_t(&matrix.values); matrix.values = new_values; } } else if (matrix.values.type == BSP_INT64) { @@ -78,8 +82,12 @@ static inline bsp_matrix_t bsp_matrix_minimize_values(bsp_matrix_t matrix) { value_type = BSP_INT64; } } - bsp_array_t new_values = - bsp_construct_array_t(matrix.values.size, value_type); + bsp_array_t new_values; + bsp_error_t error = + bsp_construct_array_t(&new_values, matrix.values.size, value_type); + if (error != BSP_SUCCESS) { + return matrix; // Return original matrix on error + } for (size_t i = 0; i < matrix.values.size; i++) { int64_t value; @@ -87,7 +95,7 @@ static inline bsp_matrix_t bsp_matrix_minimize_values(bsp_matrix_t matrix) { bsp_array_write(new_values, i, value); } - bsp_destroy_array_t(matrix.values); + bsp_destroy_array_t(&matrix.values); matrix.values = new_values; } else if (matrix.values.type == BSP_COMPLEX_FLOAT64) { bool float32_representable = true; @@ -101,8 +109,12 @@ static inline bsp_matrix_t bsp_matrix_minimize_values(bsp_matrix_t matrix) { } if (float32_representable) { - bsp_array_t new_values = - bsp_construct_array_t(matrix.values.size, BSP_COMPLEX_FLOAT32); + bsp_array_t new_values; + bsp_error_t error = bsp_construct_array_t(&new_values, matrix.values.size, + BSP_COMPLEX_FLOAT32); + if (error != BSP_SUCCESS) { + return matrix; // Return original matrix on error + } float _Complex* n_values = (float _Complex*) new_values.data; @@ -110,7 +122,7 @@ static inline bsp_matrix_t bsp_matrix_minimize_values(bsp_matrix_t matrix) { n_values[i] = values[i]; } - bsp_destroy_array_t(matrix.values); + bsp_destroy_array_t(&matrix.values); matrix.values = new_values; } } diff --git a/include/binsparse/tensor.h b/include/binsparse/tensor.h index 4d63512..31d6b89 100644 --- a/include/binsparse/tensor.h +++ b/include/binsparse/tensor.h @@ -74,7 +74,7 @@ static void bsp_destroy_level_t(bsp_level_t* level) { switch (level->kind) { case BSP_TENSOR_ELEMENT: { bsp_element_t* element = (bsp_element_t*) level->data; - bsp_destroy_array_t(element->values); + bsp_destroy_array_t(&element->values); free(element); break; } @@ -88,10 +88,10 @@ static void bsp_destroy_level_t(bsp_level_t* level) { bsp_sparse_t* sparse = (bsp_sparse_t*) level->data; if (sparse->pointers_to != NULL) - bsp_destroy_array_t(*sparse->pointers_to); + bsp_destroy_array_t(sparse->pointers_to); if (sparse->indices != NULL) { for (int i = 0; i < sparse->rank; i++) { - bsp_destroy_array_t(sparse->indices[i]); + bsp_destroy_array_t(&sparse->indices[i]); } } bsp_destroy_level_t(sparse->child); diff --git a/src/read_matrix.c b/src/read_matrix.c index 464210c..e3f233e 100644 --- a/src/read_matrix.c +++ b/src/read_matrix.c @@ -83,7 +83,10 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { } if (strlen(type_string) >= 8 && strncmp(type_string, "complex[", 8) == 0) { - matrix.values = bsp_fp_array_to_complex(matrix.values); + bsp_error_t error = bsp_fp_array_to_complex(&matrix.values); + if (error != BSP_SUCCESS) { + // Handle error - for now just continue with original array + } } } @@ -188,7 +191,10 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { } if (strlen(type_string) >= 8 && strncmp(type_string, "complex[", 8) == 0) { - matrix.values = bsp_fp_array_to_complex(matrix.values); + bsp_error_t error = bsp_fp_array_to_complex(&matrix.values); + if (error != BSP_SUCCESS) { + // Handle error - for now just continue with original array + } } } From d1801b96a21def08733631815137996f2c5b57a6 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Mon, 18 Aug 2025 20:51:10 +0000 Subject: [PATCH 02/10] Add error handling to `bsp_matrix_t` functions. --- examples/benchmark_read.c | 4 +- examples/benchmark_read_parallel.c | 4 +- examples/bsp2mtx.c | 2 +- examples/check_equivalence.c | 4 +- examples/check_equivalence_parallel.c | 4 +- examples/mtx2bsp.c | 4 +- include/binsparse/array.h | 11 +++-- include/binsparse/convert_matrix.h | 40 ++++++++++++++----- include/binsparse/generate.h | 6 +-- include/binsparse/hdf5_wrapper.h | 37 ++++++++++++----- include/binsparse/matrix.h | 29 +++++++------- .../matrix_market/matrix_market_read.h | 6 ++- src/read_matrix.c | 6 ++- 13 files changed, 98 insertions(+), 59 deletions(-) diff --git a/examples/benchmark_read.c b/examples/benchmark_read.c index c1ef342..9d72d89 100644 --- a/examples/benchmark_read.c +++ b/examples/benchmark_read.c @@ -80,7 +80,7 @@ int main(int argc, char** argv) { // If running warm cache experiments, read once to warm cache. if (!cold_cache) { bsp_matrix_t mat = bsp_read_matrix(file_name, NULL); - bsp_destroy_matrix_t(mat); + bsp_destroy_matrix_t(&mat); } for (size_t i = 0; i < num_trials; i++) { @@ -92,7 +92,7 @@ int main(int argc, char** argv) { double end = gettime(); durations[i] = end - begin; nbytes = bsp_matrix_nbytes(mat); - bsp_destroy_matrix_t(mat); + bsp_destroy_matrix_t(&mat); double gbytes = ((double) nbytes) / 1024 / 1024 / 1024; double gbytes_s = gbytes / durations[i]; diff --git a/examples/benchmark_read_parallel.c b/examples/benchmark_read_parallel.c index b485938..c83ece9 100644 --- a/examples/benchmark_read_parallel.c +++ b/examples/benchmark_read_parallel.c @@ -83,7 +83,7 @@ int main(int argc, char** argv) { // If running warm cache experiments, read once to warm cache. if (!cold_cache) { bsp_matrix_t mat = bsp_read_matrix_parallel(file_name, NULL, num_threads); - bsp_destroy_matrix_t(mat); + bsp_destroy_matrix_t(&mat); } for (size_t i = 0; i < num_trials; i++) { @@ -97,7 +97,7 @@ int main(int argc, char** argv) { durations[i] = end - begin; nbytes = bsp_matrix_nbytes(mat); - bsp_destroy_matrix_t(mat); + bsp_destroy_matrix_t(&mat); double gbytes = ((double) nbytes) / 1024 / 1024 / 1024; double gbytes_s = gbytes / durations[i]; diff --git a/examples/bsp2mtx.c b/examples/bsp2mtx.c index 6e3f483..ac2cbb4 100644 --- a/examples/bsp2mtx.c +++ b/examples/bsp2mtx.c @@ -29,7 +29,7 @@ int main(int argc, char** argv) { bsp_mmwrite(output_fname, matrix); printf(" === Done writing. ===\n"); - bsp_destroy_matrix_t(matrix); + bsp_destroy_matrix_t(&matrix); return 0; } diff --git a/examples/check_equivalence.c b/examples/check_equivalence.c index 0acea42..c15558d 100644 --- a/examples/check_equivalence.c +++ b/examples/check_equivalence.c @@ -128,13 +128,13 @@ int main(int argc, char** argv) { if (matrix1.format != matrix2.format) { if (matrix1.format != BSP_COOR) { bsp_matrix_t intermediate = bsp_convert_matrix(matrix1, BSP_COOR); - bsp_destroy_matrix_t(matrix1); + bsp_destroy_matrix_t(&matrix1); matrix1 = intermediate; } if (matrix2.format != BSP_COOR) { bsp_matrix_t intermediate = bsp_convert_matrix(matrix2, BSP_COOR); - bsp_destroy_matrix_t(matrix2); + bsp_destroy_matrix_t(&matrix2); matrix2 = intermediate; } } diff --git a/examples/check_equivalence_parallel.c b/examples/check_equivalence_parallel.c index b773cf8..399c4c4 100644 --- a/examples/check_equivalence_parallel.c +++ b/examples/check_equivalence_parallel.c @@ -136,13 +136,13 @@ int main(int argc, char** argv) { if (matrix1.format != matrix2.format) { if (matrix1.format != BSP_COOR) { bsp_matrix_t intermediate = bsp_convert_matrix(matrix1, BSP_COOR); - bsp_destroy_matrix_t(matrix1); + bsp_destroy_matrix_t(&matrix1); matrix1 = intermediate; } if (matrix2.format != BSP_COOR) { bsp_matrix_t intermediate = bsp_convert_matrix(matrix2, BSP_COOR); - bsp_destroy_matrix_t(matrix2); + bsp_destroy_matrix_t(&matrix2); matrix2 = intermediate; } } diff --git a/examples/mtx2bsp.c b/examples/mtx2bsp.c index 07db200..621a17d 100644 --- a/examples/mtx2bsp.c +++ b/examples/mtx2bsp.c @@ -151,7 +151,7 @@ int main(int argc, char** argv) { if (format != BSP_COOR) { begin = gettime(); bsp_matrix_t converted_matrix = bsp_convert_matrix(matrix, format); - bsp_destroy_matrix_t(matrix); + bsp_destroy_matrix_t(&matrix); matrix = converted_matrix; end = gettime(); duration = end - begin; @@ -170,7 +170,7 @@ int main(int argc, char** argv) { printf("%lf seconds writing Binsparse file...\n", duration); printf(" === Done writing. ===\n"); - bsp_destroy_matrix_t(matrix); + bsp_destroy_matrix_t(&matrix); return 0; } diff --git a/include/binsparse/array.h b/include/binsparse/array.h index 60b392e..8c1be72 100644 --- a/include/binsparse/array.h +++ b/include/binsparse/array.h @@ -20,12 +20,11 @@ typedef struct bsp_array_t { bsp_allocator_t allocator; } bsp_array_t; -static inline bsp_array_t bsp_construct_default_array_t() { - bsp_array_t array; - array.data = NULL; - array.size = 0; - array.allocator = bsp_default_allocator; - return array; +static inline bsp_error_t bsp_construct_default_array_t(bsp_array_t* array) { + array->data = NULL; + array->size = 0; + array->allocator = bsp_default_allocator; + return BSP_SUCCESS; } static inline bsp_error_t bsp_construct_array_t(bsp_array_t* array, size_t size, diff --git a/include/binsparse/convert_matrix.h b/include/binsparse/convert_matrix.h index a8b8038..93c96ef 100644 --- a/include/binsparse/convert_matrix.h +++ b/include/binsparse/convert_matrix.h @@ -20,7 +20,8 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, // *Convert to COO* from another format. if (matrix.format == BSP_CSR) { // Convert CSR -> COOR - bsp_matrix_t result = bsp_construct_default_matrix_t(); + bsp_matrix_t result; + bsp_construct_default_matrix_t(&result); result.format = BSP_COOR; @@ -40,7 +41,9 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, bsp_error_t error = bsp_copy_construct_array_t(&result.values, matrix.values); if (error != BSP_SUCCESS) { - return bsp_construct_default_matrix_t(); + bsp_matrix_t empty_result; + bsp_construct_default_matrix_t(&empty_result); + return empty_result; } // There is a corner case with tall and skinny matrices where we need a @@ -51,14 +54,18 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, error = bsp_copy_construct_array_t(&result.indices_1, matrix.indices_1); if (error != BSP_SUCCESS) { bsp_destroy_array_t(&result.values); - return bsp_construct_default_matrix_t(); + bsp_matrix_t empty_result; + bsp_construct_default_matrix_t(&empty_result); + return empty_result; } } else { error = bsp_construct_array_t(&result.indices_1, matrix.indices_1.size, index_type); if (error != BSP_SUCCESS) { bsp_destroy_array_t(&result.values); - return bsp_construct_default_matrix_t(); + bsp_matrix_t empty_result; + bsp_construct_default_matrix_t(&empty_result); + return empty_result; } for (size_t i = 0; i < matrix.indices_1.size; i++) { @@ -72,7 +79,9 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, if (error != BSP_SUCCESS) { bsp_destroy_array_t(&result.values); bsp_destroy_array_t(&result.indices_1); - return bsp_construct_default_matrix_t(); + bsp_matrix_t empty_result; + bsp_construct_default_matrix_t(&empty_result); + return empty_result; } for (size_t i = 0; i < matrix.nrows; i++) { @@ -95,13 +104,14 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, if (matrix.format != BSP_COOR) { bsp_matrix_t intermediate = bsp_convert_matrix(matrix, BSP_COOR); bsp_matrix_t result = bsp_convert_matrix(intermediate, format); - bsp_destroy_matrix_t(intermediate); + bsp_destroy_matrix_t(&intermediate); return result; } else { if (format == BSP_CSR) { // Convert COOR -> CSR - bsp_matrix_t result = bsp_construct_default_matrix_t(); + bsp_matrix_t result; + bsp_construct_default_matrix_t(&result); result.format = BSP_CSR; @@ -130,7 +140,9 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, bsp_error_t error = bsp_copy_construct_array_t(&result.values, matrix.values); if (error != BSP_SUCCESS) { - return bsp_construct_default_matrix_t(); + bsp_matrix_t empty_result; + bsp_construct_default_matrix_t(&empty_result); + return empty_result; } if (index_type == matrix.indices_1.type) { @@ -138,14 +150,18 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, bsp_copy_construct_array_t(&result.indices_1, matrix.indices_1); if (error != BSP_SUCCESS) { bsp_destroy_array_t(&result.values); - return bsp_construct_default_matrix_t(); + bsp_matrix_t empty_result; + bsp_construct_default_matrix_t(&empty_result); + return empty_result; } } else { error = bsp_construct_array_t(&result.indices_1, matrix.nnz, index_type); if (error != BSP_SUCCESS) { bsp_destroy_array_t(&result.values); - return bsp_construct_default_matrix_t(); + bsp_matrix_t empty_result; + bsp_construct_default_matrix_t(&empty_result); + return empty_result; } for (size_t i = 0; i < matrix.nnz; i++) { @@ -160,7 +176,9 @@ static inline bsp_matrix_t bsp_convert_matrix(bsp_matrix_t matrix, if (error != BSP_SUCCESS) { bsp_destroy_array_t(&result.values); bsp_destroy_array_t(&result.indices_1); - return bsp_construct_default_matrix_t(); + bsp_matrix_t empty_result; + bsp_construct_default_matrix_t(&empty_result); + return empty_result; } bsp_array_t rowptr = result.pointers_to_1; diff --git a/include/binsparse/generate.h b/include/binsparse/generate.h index 3b3043d..b625669 100644 --- a/include/binsparse/generate.h +++ b/include/binsparse/generate.h @@ -70,13 +70,13 @@ static inline void bsp_array_fill_random(bsp_array_t array, size_t bound) { static inline bsp_matrix_t bsp_generate_coo(size_t m, size_t n, size_t nnz, bsp_type_t value_type, bsp_type_t index_type) { - bsp_matrix_t matrix = bsp_construct_default_matrix_t(); + bsp_matrix_t matrix; + bsp_construct_default_matrix_t(&matrix); matrix.nrows = m; matrix.ncols = n; matrix.nnz = nnz; - bsp_error_t error; - error = bsp_construct_array_t(&matrix.values, nnz, value_type); + bsp_error_t error = bsp_construct_array_t(&matrix.values, nnz, value_type); if (error != BSP_SUCCESS) { // Return empty matrix on error return matrix; diff --git a/include/binsparse/hdf5_wrapper.h b/include/binsparse/hdf5_wrapper.h index c8f07ea..eb2bda9 100644 --- a/include/binsparse/hdf5_wrapper.h +++ b/include/binsparse/hdf5_wrapper.h @@ -84,13 +84,17 @@ static inline bsp_array_t bsp_read_array_parallel(hid_t f, const char* label, hid_t dset = H5Dopen2(f, label, H5P_DEFAULT); if (dset == H5I_INVALID_HID) { - return bsp_construct_default_array_t(); + bsp_array_t empty_array; + bsp_construct_default_array_t(&empty_array); + return empty_array; } hid_t fspace = H5Dget_space(dset); if (fspace == H5I_INVALID_HID) { - return bsp_construct_default_array_t(); + bsp_array_t empty_array; + bsp_construct_default_array_t(&empty_array); + return empty_array; } hsize_t dims[3]; @@ -98,7 +102,9 @@ static inline bsp_array_t bsp_read_array_parallel(hid_t f, const char* label, int r = H5Sget_simple_extent_dims(fspace, dims, NULL); if (r < 0) { - return bsp_construct_default_array_t(); + bsp_array_t empty_array; + bsp_construct_default_array_t(&empty_array); + return empty_array; } hid_t hdf5_type = H5Dget_type(dset); @@ -180,13 +186,17 @@ static inline bsp_array_t bsp_read_array(hid_t f, const char* label) { hid_t dset = H5Dopen2(f, label, H5P_DEFAULT); if (dset == H5I_INVALID_HID) { - return bsp_construct_default_array_t(); + bsp_array_t empty_array; + bsp_construct_default_array_t(&empty_array); + return empty_array; } hid_t fspace = H5Dget_space(dset); if (fspace == H5I_INVALID_HID) { - return bsp_construct_default_array_t(); + bsp_array_t empty_array; + bsp_construct_default_array_t(&empty_array); + return empty_array; } hsize_t dims[3]; @@ -194,19 +204,26 @@ static inline bsp_array_t bsp_read_array(hid_t f, const char* label) { int r = H5Sget_simple_extent_dims(fspace, dims, NULL); if (r < 0) { - return bsp_construct_default_array_t(); + bsp_array_t empty_array; + bsp_construct_default_array_t(&empty_array); + return empty_array; } hid_t hdf5_type = H5Dget_type(dset); bsp_type_t type = bsp_get_bsp_type(hdf5_type); - bsp_array_t array = bsp_construct_default_array_t(); + bsp_array_t array; + bsp_construct_default_array_t(&array); + bsp_error_t error = bsp_construct_array_t(&array, dims[0], type); if (error != BSP_SUCCESS) { + bsp_destroy_array_t(&array); H5Dclose(dset); H5Sclose(fspace); - return bsp_construct_default_array_t(); + bsp_array_t empty_array; + bsp_construct_default_array_t(&empty_array); + return empty_array; } herr_t status = H5Dread(dset, bsp_get_hdf5_native_type(type), H5S_ALL, @@ -216,7 +233,9 @@ static inline bsp_array_t bsp_read_array(hid_t f, const char* label) { bsp_destroy_array_t(&array); H5Dclose(dset); H5Sclose(fspace); - return bsp_construct_default_array_t(); + bsp_array_t empty_array; + bsp_construct_default_array_t(&empty_array); + return empty_array; } H5Dclose(dset); diff --git a/include/binsparse/matrix.h b/include/binsparse/matrix.h index c4563f1..04ad858 100644 --- a/include/binsparse/matrix.h +++ b/include/binsparse/matrix.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -26,23 +27,21 @@ typedef struct bsp_matrix_t { bsp_structure_t structure; } bsp_matrix_t; -static inline bsp_matrix_t bsp_construct_default_matrix_t() { - bsp_matrix_t mat; - mat.values = bsp_construct_default_array_t(); - mat.indices_0 = bsp_construct_default_array_t(); - mat.indices_1 = bsp_construct_default_array_t(); - mat.pointers_to_1 = bsp_construct_default_array_t(); - mat.nrows = mat.ncols = mat.nnz = 0; - mat.is_iso = false; - mat.structure = BSP_GENERAL; - return mat; +static inline void bsp_construct_default_matrix_t(bsp_matrix_t* matrix) { + bsp_construct_default_array_t(&matrix->values); + bsp_construct_default_array_t(&matrix->indices_0); + bsp_construct_default_array_t(&matrix->indices_1); + bsp_construct_default_array_t(&matrix->pointers_to_1); + matrix->nrows = matrix->ncols = matrix->nnz = 0; + matrix->is_iso = false; + matrix->structure = BSP_GENERAL; } -static inline void bsp_destroy_matrix_t(bsp_matrix_t matrix) { - bsp_destroy_array_t(&matrix.values); - bsp_destroy_array_t(&matrix.indices_0); - bsp_destroy_array_t(&matrix.indices_1); - bsp_destroy_array_t(&matrix.pointers_to_1); +static inline void bsp_destroy_matrix_t(bsp_matrix_t* matrix) { + bsp_destroy_array_t(&matrix->values); + bsp_destroy_array_t(&matrix->indices_0); + bsp_destroy_array_t(&matrix->indices_1); + bsp_destroy_array_t(&matrix->pointers_to_1); } static inline size_t bsp_matrix_nbytes(bsp_matrix_t mat) { diff --git a/include/binsparse/matrix_market/matrix_market_read.h b/include/binsparse/matrix_market/matrix_market_read.h index 383acad..4db0947 100644 --- a/include/binsparse/matrix_market/matrix_market_read.h +++ b/include/binsparse/matrix_market/matrix_market_read.h @@ -32,7 +32,8 @@ static inline bsp_matrix_t bsp_mmread_explicit_array(const char* file_path, assert(false); } - bsp_matrix_t matrix = bsp_construct_default_matrix_t(); + bsp_matrix_t matrix; + bsp_construct_default_matrix_t(&matrix); matrix.nrows = metadata.nrows; matrix.ncols = metadata.ncols; @@ -129,7 +130,8 @@ bsp_mmread_explicit_coordinate(const char* file_path, bsp_type_t value_type, assert(false); } - bsp_matrix_t matrix = bsp_construct_default_matrix_t(); + bsp_matrix_t matrix; + bsp_construct_default_matrix_t(&matrix); if (mm_type == BSP_MM_PATTERN) { matrix.is_iso = true; diff --git a/src/read_matrix.c b/src/read_matrix.c index e3f233e..18e4d6a 100644 --- a/src/read_matrix.c +++ b/src/read_matrix.c @@ -12,7 +12,8 @@ #if __STDC_VERSION__ >= 201112L bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { - bsp_matrix_t matrix = bsp_construct_default_matrix_t(); + bsp_matrix_t matrix; + bsp_construct_default_matrix_t(&matrix); char* json_string = bsp_read_attribute(f, (char*) "binsparse"); @@ -120,7 +121,8 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { #endif bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { - bsp_matrix_t matrix = bsp_construct_default_matrix_t(); + bsp_matrix_t matrix; + bsp_construct_default_matrix_t(&matrix); char* json_string = bsp_read_attribute(f, (char*) "binsparse"); From 8405a288e37c10d03c9e100f3a0404cbafd1b479 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Mon, 18 Aug 2025 14:30:22 -0700 Subject: [PATCH 03/10] Update `bsp_matrix_t` to have proper error handling, update parsing functions. --- examples/bsp-ls.c | 8 +- examples/simple_read.c | 8 +- examples/simple_write.c | 7 +- include/binsparse/hdf5_wrapper.h | 142 +++++++++++++++++-------------- include/binsparse/write_matrix.h | 9 +- src/read_matrix.c | 79 ++++++++++++++--- src/read_tensor.c | 27 +++++- src/write_matrix.c | 66 ++++++++------ src/write_tensor.c | 50 ++++++----- 9 files changed, 258 insertions(+), 138 deletions(-) diff --git a/examples/bsp-ls.c b/examples/bsp-ls.c index 8ecdd94..9207224 100644 --- a/examples/bsp-ls.c +++ b/examples/bsp-ls.c @@ -18,7 +18,13 @@ void print_group_info(hid_t g, const char* name) { H5E_END_TRY; if (bsp_json != H5I_INVALID_HID) { - char* json_string = bsp_read_attribute(g, "binsparse"); + char* json_string; + bsp_error_t error = bsp_read_attribute(&json_string, g, "binsparse"); + if (error != BSP_SUCCESS) { + printf("Error reading binsparse attribute: %s\n", + bsp_get_error_string(error)); + return; + } cJSON* j = cJSON_Parse(json_string); diff --git a/examples/simple_read.c b/examples/simple_read.c index 3af4921..0edfa08 100644 --- a/examples/simple_read.c +++ b/examples/simple_read.c @@ -11,7 +11,13 @@ int main(int argc, char** argv) { hid_t f = H5Fopen(file_name, H5F_ACC_RDWR, H5P_DEFAULT); - bsp_array_t array = bsp_read_array(f, "test"); + bsp_array_t array; + bsp_error_t error = bsp_read_array(&array, f, "test"); + if (error != BSP_SUCCESS) { + printf("Error reading array: %s\n", bsp_get_error_string(error)); + H5Fclose(f); + return 1; + } int* values = (int*) array.data; diff --git a/examples/simple_write.c b/examples/simple_write.c index cdb5024..261f228 100644 --- a/examples/simple_write.c +++ b/examples/simple_write.c @@ -12,12 +12,7 @@ int main(int argc, char** argv) { hid_t f = H5Fcreate(file_name, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); bsp_array_t array; - bsp_error_t error = bsp_construct_array_t(&array, 1000, BSP_INT32); - if (error != BSP_SUCCESS) { - printf("Error: Failed to allocate array\n"); - H5Fclose(f); - return 1; - } + bsp_construct_array_t(&array, 1000, BSP_INT32); int* values = (int*) array.data; diff --git a/include/binsparse/hdf5_wrapper.h b/include/binsparse/hdf5_wrapper.h index eb2bda9..c82a695 100644 --- a/include/binsparse/hdf5_wrapper.h +++ b/include/binsparse/hdf5_wrapper.h @@ -21,12 +21,13 @@ // Write an array to a dataset / file // Returns 0 on success, nonzero on error. -static inline int bsp_write_array(hid_t f, const char* label, bsp_array_t array, - int compression_level) { +static inline bsp_error_t bsp_write_array(hid_t f, const char* label, + bsp_array_t array, + int compression_level) { if (array.type == BSP_COMPLEX_FLOAT32 || array.type == BSP_COMPLEX_FLOAT64) { bsp_error_t error = bsp_complex_array_to_fp(&array); if (error != BSP_SUCCESS) { - return -3; // Type conversion error + return BSP_ERROR_TYPE; } } @@ -59,7 +60,10 @@ static inline int bsp_write_array(hid_t f, const char* label, bsp_array_t array, H5Dcreate2(f, label, hdf5_standard_type, fspace, lcpl, dcpl, H5P_DEFAULT); if (dset == H5I_INVALID_HID) { - return -1; + H5Sclose(fspace); + H5Pclose(lcpl); + H5Pclose(dcpl); + return BSP_ERROR_IO; } hid_t hdf5_native_type = bsp_get_hdf5_native_type(array.type); @@ -67,34 +71,41 @@ static inline int bsp_write_array(hid_t f, const char* label, bsp_array_t array, hid_t r = H5Dwrite(dset, hdf5_native_type, H5S_ALL, fspace, H5P_DEFAULT, array.data); - if (r == H5I_INVALID_HID) { - return -2; + if (r < 0) { + H5Dclose(dset); + H5Sclose(fspace); + H5Pclose(lcpl); + H5Pclose(dcpl); + return BSP_ERROR_IO; } H5Sclose(fspace); + H5Dclose(dset); H5Pclose(lcpl); H5Pclose(dcpl); - return 0; + return BSP_SUCCESS; } #if __STDC_VERSION__ >= 201112L -static inline bsp_array_t bsp_read_array_parallel(hid_t f, const char* label, +static inline bsp_error_t bsp_read_array_parallel(bsp_array_t* array, hid_t f, + const char* label, int num_threads) { hid_t dset = H5Dopen2(f, label, H5P_DEFAULT); if (dset == H5I_INVALID_HID) { - bsp_array_t empty_array; - bsp_construct_default_array_t(&empty_array); - return empty_array; + H5Dclose(dset); + bsp_construct_default_array_t(array); + return BSP_ERROR_IO; } hid_t fspace = H5Dget_space(dset); if (fspace == H5I_INVALID_HID) { - bsp_array_t empty_array; - bsp_construct_default_array_t(&empty_array); - return empty_array; + H5Sclose(fspace); + H5Dclose(dset); + bsp_construct_default_array_t(array); + return BSP_ERROR_IO; } hsize_t dims[3]; @@ -102,9 +113,10 @@ static inline bsp_array_t bsp_read_array_parallel(hid_t f, const char* label, int r = H5Sget_simple_extent_dims(fspace, dims, NULL); if (r < 0) { - bsp_array_t empty_array; - bsp_construct_default_array_t(&empty_array); - return empty_array; + H5Sclose(fspace); + H5Dclose(dset); + bsp_construct_default_array_t(array); + return BSP_ERROR_IO; } hid_t hdf5_type = H5Dget_type(dset); @@ -113,10 +125,9 @@ static inline bsp_array_t bsp_read_array_parallel(hid_t f, const char* label, // Array will be written into a POSIX shared memory. bsp_shm_t array_shm = bsp_shm_new(dims[0] * bsp_type_size(type)); - bsp_array_t array; - array.type = type; - array.size = dims[0]; - array.allocator = bsp_shm_allocator; + array->type = type; + array->size = dims[0]; + array->allocator = bsp_shm_allocator; bsp_shm_t active_children_shm = bsp_shm_new(sizeof(_Atomic int)); @@ -140,17 +151,17 @@ static inline bsp_array_t bsp_read_array_parallel(hid_t f, const char* label, } } - array.data = bsp_shm_attach(array_shm); + array->data = bsp_shm_attach(array_shm); if (thread_num == 0) { bsp_shm_delete(array_shm); } - hsize_t chunk_size = (array.size + num_threads - 1) / num_threads; - hsize_t start = (chunk_size * thread_num < array.size) + hsize_t chunk_size = (array->size + num_threads - 1) / num_threads; + hsize_t start = (chunk_size * thread_num < array->size) ? chunk_size * thread_num - : array.size; + : array->size; hsize_t count = - (start + chunk_size <= array.size) ? chunk_size : array.size - start; + (start + chunk_size <= array->size) ? chunk_size : array->size - start; if (count > 0) { H5Sselect_hyperslab(fspace, H5S_SELECT_SET, &start, NULL, &count, NULL); @@ -158,17 +169,17 @@ static inline bsp_array_t bsp_read_array_parallel(hid_t f, const char* label, hid_t memspace_id = H5Screate_simple(1, &count, NULL); H5Dread(dset, bsp_get_hdf5_native_type(type), memspace_id, fspace, - H5P_DEFAULT, ((char*) array.data) + start * bsp_type_size(type)); + H5P_DEFAULT, ((char*) array->data) + start * bsp_type_size(type)); H5Sclose(memspace_id); } - H5Dclose(dset); H5Sclose(fspace); + H5Dclose(dset); if (thread_num > 0) { atomic_fetch_add_explicit(active_children, -1, memory_order_relaxed); bsp_shm_detach(active_children); - bsp_shm_detach(array.data); + bsp_shm_detach(array->data); exit(0); } @@ -178,25 +189,26 @@ static inline bsp_array_t bsp_read_array_parallel(hid_t f, const char* label, } bsp_shm_detach(active_children); - return array; + return BSP_SUCCESS; } #endif -static inline bsp_array_t bsp_read_array(hid_t f, const char* label) { +static inline bsp_error_t bsp_read_array(bsp_array_t* array, hid_t f, + const char* label) { hid_t dset = H5Dopen2(f, label, H5P_DEFAULT); if (dset == H5I_INVALID_HID) { - bsp_array_t empty_array; - bsp_construct_default_array_t(&empty_array); - return empty_array; + bsp_construct_default_array_t(array); + return BSP_ERROR_IO; } hid_t fspace = H5Dget_space(dset); if (fspace == H5I_INVALID_HID) { - bsp_array_t empty_array; - bsp_construct_default_array_t(&empty_array); - return empty_array; + H5Sclose(fspace); + H5Dclose(dset); + bsp_construct_default_array_t(array); + return BSP_ERROR_IO; } hsize_t dims[3]; @@ -204,47 +216,42 @@ static inline bsp_array_t bsp_read_array(hid_t f, const char* label) { int r = H5Sget_simple_extent_dims(fspace, dims, NULL); if (r < 0) { - bsp_array_t empty_array; - bsp_construct_default_array_t(&empty_array); - return empty_array; + H5Dclose(dset); + H5Sclose(fspace); + bsp_construct_default_array_t(array); + return BSP_ERROR_IO; } hid_t hdf5_type = H5Dget_type(dset); bsp_type_t type = bsp_get_bsp_type(hdf5_type); - bsp_array_t array; - bsp_construct_default_array_t(&array); - - bsp_error_t error = bsp_construct_array_t(&array, dims[0], type); + bsp_error_t error = bsp_construct_array_t(array, dims[0], type); if (error != BSP_SUCCESS) { - bsp_destroy_array_t(&array); H5Dclose(dset); H5Sclose(fspace); - bsp_array_t empty_array; - bsp_construct_default_array_t(&empty_array); - return empty_array; + bsp_construct_default_array_t(array); + return BSP_ERROR_MEMORY; } herr_t status = H5Dread(dset, bsp_get_hdf5_native_type(type), H5S_ALL, - H5S_ALL, H5P_DEFAULT, array.data); + H5S_ALL, H5P_DEFAULT, array->data); if (status < 0) { - bsp_destroy_array_t(&array); - H5Dclose(dset); + bsp_destroy_array_t(array); H5Sclose(fspace); - bsp_array_t empty_array; - bsp_construct_default_array_t(&empty_array); - return empty_array; + H5Dclose(dset); + bsp_construct_default_array_t(array); + return BSP_ERROR_IO; } - H5Dclose(dset); H5Sclose(fspace); - return array; + H5Dclose(dset); + return BSP_SUCCESS; } -static inline void bsp_write_attribute(hid_t f, const char* label, - const char* string) { +static inline bsp_error_t bsp_write_attribute(hid_t f, const char* label, + const char* string) { hid_t strtype = H5Tcopy(H5T_C_S1); H5Tset_size(strtype, strlen(string)); H5Tset_cset(strtype, H5T_CSET_UTF8); @@ -258,23 +265,28 @@ static inline void bsp_write_attribute(hid_t f, const char* label, H5Tclose(strtype); H5Aclose(attribute); H5Sclose(dataspace); + + return BSP_SUCCESS; } -static inline char* bsp_read_attribute(hid_t f, const char* label) { +static inline bsp_error_t bsp_read_attribute(char** string, hid_t f, + const char* label) { hid_t attribute = H5Aopen(f, label, H5P_DEFAULT); - hid_t strtype = H5Aget_type(attribute); - hid_t type_class = H5Tget_class(strtype); - assert(type_class == H5T_STRING); + if (attribute == H5I_INVALID_HID) { + return BSP_ERROR_FORMAT; + } + + hid_t strtype = H5Aget_type(attribute); size_t size = H5Tget_size(strtype); - char* string = (char*) malloc(size + 1); + *string = (char*) malloc(size + 1); - H5Aread(attribute, strtype, string); + H5Aread(attribute, strtype, *string); H5Aclose(attribute); H5Tclose(strtype); - return string; + return BSP_SUCCESS; } diff --git a/include/binsparse/write_matrix.h b/include/binsparse/write_matrix.h index 53b89f5..2b067d2 100644 --- a/include/binsparse/write_matrix.h +++ b/include/binsparse/write_matrix.h @@ -18,12 +18,13 @@ extern "C" { #ifdef BSP_USE_HDF5 #include -int bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, cJSON* user_json, - int compression_level); +bsp_error_t bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, + cJSON* user_json, int compression_level); #endif -int bsp_write_matrix(const char* fname, bsp_matrix_t matrix, const char* group, - cJSON* user_json, int compression_level); +bsp_error_t bsp_write_matrix(const char* fname, bsp_matrix_t matrix, + const char* group, cJSON* user_json, + int compression_level); #ifdef __cplusplus } diff --git a/src/read_matrix.c b/src/read_matrix.c index 18e4d6a..bc9a75a 100644 --- a/src/read_matrix.c +++ b/src/read_matrix.c @@ -15,7 +15,11 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { bsp_matrix_t matrix; bsp_construct_default_matrix_t(&matrix); - char* json_string = bsp_read_attribute(f, (char*) "binsparse"); + char* json_string; + bsp_error_t error = bsp_read_attribute(&json_string, f, (char*) "binsparse"); + if (error != BSP_SUCCESS) { + return matrix; + } cJSON* j = cJSON_Parse(json_string); @@ -73,7 +77,12 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { assert(data_types_ != NULL); if (cJSON_HasObjectItem(data_types_, "values")) { - matrix.values = bsp_read_array_parallel(f, (char*) "values", num_threads); + error = bsp_read_array_parallel(&matrix.values, f, (char*) "values", + num_threads); + if (error != BSP_SUCCESS) { + free(json_string); + return matrix; + } cJSON* value_type = cJSON_GetObjectItemCaseSensitive(data_types_, "values"); char* type_string = cJSON_GetStringValue(value_type); @@ -92,18 +101,36 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { } if (cJSON_HasObjectItem(data_types_, "indices_0")) { - matrix.indices_0 = - bsp_read_array_parallel(f, (char*) "indices_0", num_threads); + error = bsp_read_array_parallel(&matrix.indices_0, f, (char*) "indices_0", + num_threads); + if (error != BSP_SUCCESS) { + free(json_string); + bsp_destroy_array_t(&matrix.values); + return matrix; + } } if (cJSON_HasObjectItem(data_types_, "indices_1")) { - matrix.indices_1 = - bsp_read_array_parallel(f, (char*) "indices_1", num_threads); + error = bsp_read_array_parallel(&matrix.indices_1, f, (char*) "indices_1", + num_threads); + if (error != BSP_SUCCESS) { + free(json_string); + bsp_destroy_array_t(&matrix.values); + bsp_destroy_array_t(&matrix.indices_0); + return matrix; + } } if (cJSON_HasObjectItem(data_types_, "pointers_to_1")) { - matrix.pointers_to_1 = - bsp_read_array_parallel(f, (char*) "pointers_to_1", num_threads); + error = bsp_read_array_parallel(&matrix.pointers_to_1, f, + (char*) "pointers_to_1", num_threads); + if (error != BSP_SUCCESS) { + free(json_string); + bsp_destroy_array_t(&matrix.values); + bsp_destroy_array_t(&matrix.indices_0); + bsp_destroy_array_t(&matrix.indices_1); + return matrix; + } } if (cJSON_HasObjectItem(binsparse, "structure")) { @@ -124,7 +151,11 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { bsp_matrix_t matrix; bsp_construct_default_matrix_t(&matrix); - char* json_string = bsp_read_attribute(f, (char*) "binsparse"); + char* json_string; + bsp_error_t error = bsp_read_attribute(&json_string, f, (char*) "binsparse"); + if (error != BSP_SUCCESS) { + return matrix; + } cJSON* j = cJSON_Parse(json_string); @@ -182,7 +213,11 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { assert(data_types_ != NULL); if (cJSON_HasObjectItem(data_types_, "values")) { - matrix.values = bsp_read_array(f, (char*) "values"); + error = bsp_read_array(&matrix.values, f, (char*) "values"); + if (error != BSP_SUCCESS) { + free(json_string); + return matrix; + } cJSON* value_type = cJSON_GetObjectItemCaseSensitive(data_types_, "values"); char* type_string = cJSON_GetStringValue(value_type); @@ -201,15 +236,33 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { } if (cJSON_HasObjectItem(data_types_, "indices_0")) { - matrix.indices_0 = bsp_read_array(f, (char*) "indices_0"); + error = bsp_read_array(&matrix.indices_0, f, (char*) "indices_0"); + if (error != BSP_SUCCESS) { + free(json_string); + bsp_destroy_array_t(&matrix.values); + return matrix; + } } if (cJSON_HasObjectItem(data_types_, "indices_1")) { - matrix.indices_1 = bsp_read_array(f, (char*) "indices_1"); + error = bsp_read_array(&matrix.indices_1, f, (char*) "indices_1"); + if (error != BSP_SUCCESS) { + free(json_string); + bsp_destroy_array_t(&matrix.values); + bsp_destroy_array_t(&matrix.indices_0); + return matrix; + } } if (cJSON_HasObjectItem(data_types_, "pointers_to_1")) { - matrix.pointers_to_1 = bsp_read_array(f, (char*) "pointers_to_1"); + error = bsp_read_array(&matrix.pointers_to_1, f, (char*) "pointers_to_1"); + if (error != BSP_SUCCESS) { + free(json_string); + bsp_destroy_array_t(&matrix.values); + bsp_destroy_array_t(&matrix.indices_0); + bsp_destroy_array_t(&matrix.indices_1); + return matrix; + } } if (cJSON_HasObjectItem(binsparse, "structure")) { diff --git a/src/read_tensor.c b/src/read_tensor.c index 01453ac..cce6004 100644 --- a/src/read_tensor.c +++ b/src/read_tensor.c @@ -32,7 +32,11 @@ char* key_with_index(const char* key, size_t index) { bsp_tensor_t bsp_read_tensor_from_group(hid_t f) { bsp_tensor_t tensor = bsp_construct_default_tensor_t(); - char* json_string = bsp_read_attribute(f, (char*) "binsparse"); + char* json_string; + bsp_error_t error = bsp_read_attribute(&json_string, f, (char*) "binsparse"); + if (error != BSP_SUCCESS) { + return tensor; + } cJSON* j = cJSON_Parse(json_string); @@ -103,7 +107,12 @@ bsp_tensor_t bsp_read_tensor_from_group(hid_t f) { // base case: working with an element. if (strcmp(type, "element") == 0) { - bsp_array_t values = bsp_read_array(f, (char*) "values"); + bsp_array_t values; + bsp_error_t error = bsp_read_array(&values, f, (char*) "values"); + if (error != BSP_SUCCESS) { + free(json_string); + return tensor; + } cur_level->kind = BSP_TENSOR_ELEMENT; bsp_element_t* data = malloc(sizeof(bsp_element_t)); data->values = values; @@ -136,7 +145,12 @@ bsp_tensor_t bsp_read_tensor_from_group(hid_t f) { if (depth != 0) { char* pointers_key = key_with_index("pointers_to_", depth); data->pointers_to = malloc(sizeof(bsp_array_t)); - *data->pointers_to = bsp_read_array(f, pointers_key); + error = bsp_read_array(data->pointers_to, f, pointers_key); + if (error != BSP_SUCCESS) { + free(pointers_key); + free(json_string); + return tensor; + } free(pointers_key); } else { data->pointers_to = NULL; @@ -146,7 +160,12 @@ bsp_tensor_t bsp_read_tensor_from_group(hid_t f) { data->indices = malloc(rank * sizeof(bsp_array_t)); for (int idx = 0; idx < rank; idx++) { char* indices_key = key_with_index("indices_", depth + idx); - data->indices[idx] = bsp_read_array(f, indices_key); + error = bsp_read_array(&data->indices[idx], f, indices_key); + if (error != BSP_SUCCESS) { + free(indices_key); + free(json_string); + return tensor; + } free(indices_key); } diff --git a/src/write_matrix.c b/src/write_matrix.c index 7c012b0..0b202bb 100644 --- a/src/write_matrix.c +++ b/src/write_matrix.c @@ -86,51 +86,61 @@ char* bsp_generate_json(bsp_matrix_t matrix, cJSON* user_json) { return string; } -int bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, cJSON* user_json, - int compression_level) { - int result = +bsp_error_t bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, + cJSON* user_json, int compression_level) { + bsp_error_t error = bsp_write_array(f, (char*) "values", matrix.values, compression_level); - - if (result != 0) - return result; + if (error != BSP_SUCCESS) { + return error; + } if (matrix.indices_0.size > 0) { - result = bsp_write_array(f, (char*) "indices_0", matrix.indices_0, - compression_level); - if (result != 0) { - return result; + error = bsp_write_array(f, (char*) "indices_0", matrix.indices_0, + compression_level); + if (error != BSP_SUCCESS) { + return error; } } if (matrix.indices_1.size > 0) { - result = bsp_write_array(f, (char*) "indices_1", matrix.indices_1, - compression_level); - if (result != 0) { - return result; + error = bsp_write_array(f, (char*) "indices_1", matrix.indices_1, + compression_level); + if (error != BSP_SUCCESS) { + return error; } } if (matrix.pointers_to_1.size > 0) { - result = bsp_write_array(f, (char*) "pointers_to_1", matrix.pointers_to_1, - compression_level); - if (result != 0) { - return result; + error = bsp_write_array(f, (char*) "pointers_to_1", matrix.pointers_to_1, + compression_level); + if (error != BSP_SUCCESS) { + return error; } } char* json_string = bsp_generate_json(matrix, user_json); - bsp_write_attribute(f, (char*) "binsparse", json_string); + error = bsp_write_attribute(f, (char*) "binsparse", json_string); + if (error != BSP_SUCCESS) { + free(json_string); + return error; + } free(json_string); - return 0; + return BSP_SUCCESS; } -int bsp_write_matrix(const char* fname, bsp_matrix_t matrix, const char* group, - cJSON* user_json, int compression_level) { +bsp_error_t bsp_write_matrix(const char* fname, bsp_matrix_t matrix, + const char* group, cJSON* user_json, + int compression_level) { if (group == NULL) { hid_t f = H5Fcreate(fname, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); - bsp_write_matrix_to_group(f, matrix, user_json, compression_level); + bsp_error_t error = + bsp_write_matrix_to_group(f, matrix, user_json, compression_level); + if (error != BSP_SUCCESS) { + H5Fclose(f); + return error; + } H5Fclose(f); } else { hid_t f; @@ -140,9 +150,15 @@ int bsp_write_matrix(const char* fname, bsp_matrix_t matrix, const char* group, f = H5Fcreate(fname, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); } hid_t g = H5Gcreate1(f, group, H5P_DEFAULT); - bsp_write_matrix_to_group(g, matrix, user_json, compression_level); + bsp_error_t error = + bsp_write_matrix_to_group(g, matrix, user_json, compression_level); + if (error != BSP_SUCCESS) { + H5Gclose(g); + H5Fclose(f); + return error; + } H5Gclose(g); H5Fclose(f); } - return 0; + return BSP_SUCCESS; } diff --git a/src/write_tensor.c b/src/write_tensor.c index 11a84ef..f212aeb 100644 --- a/src/write_tensor.c +++ b/src/write_tensor.c @@ -53,8 +53,8 @@ static cJSON* init_tensor_json(bsp_tensor_t tensor, cJSON* user_json) { return j; } -int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json, - int compression_level) { +bsp_error_t bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, + cJSON* user_json, int compression_level) { // bsp_matrix_t matrix; cJSON* j = init_tensor_json(tensor, user_json); // tensor: @@ -85,10 +85,11 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json, } // attempt to write an array. - int result = bsp_write_array(f, (char*) "values", values, compression_level); - if (result != 0) { + bsp_error_t error = + bsp_write_array(f, (char*) "values", values, compression_level); + if (error != BSP_SUCCESS) { cJSON_Delete(j); - return result; + return error; } int rank = 0; @@ -107,11 +108,11 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json, cJSON_AddStringToObject(data_types, key_with_index("pointers_to_", rank), bsp_get_type_string(sparse->pointers_to->type)); - result = bsp_write_array(f, key_with_index("pointers_to_", rank), - *sparse->pointers_to, compression_level); - if (result != 0) { + error = bsp_write_array(f, key_with_index("pointers_to_", rank), + *sparse->pointers_to, compression_level); + if (error != BSP_SUCCESS) { cJSON_Delete(j); - return result; + return error; } } @@ -119,11 +120,11 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json, cJSON_AddStringToObject(data_types, key_with_index("indices_", rank + i), bsp_get_type_string(sparse->indices[i].type)); - result = bsp_write_array(f, key_with_index("indices_", rank + i), - sparse->indices[i], compression_level); - if (result != 0) { + error = bsp_write_array(f, key_with_index("indices_", rank + i), + sparse->indices[i], compression_level); + if (error != BSP_SUCCESS) { cJSON_Delete(j); - return result; + return error; } } @@ -153,17 +154,28 @@ int bsp_write_tensor_to_group(hid_t f, bsp_tensor_t tensor, cJSON* user_json, } char* json_string = cJSON_Print(j); - bsp_write_attribute(f, (char*) "binsparse", json_string); + error = bsp_write_attribute(f, (char*) "binsparse", json_string); + if (error != BSP_SUCCESS) { + free(json_string); + cJSON_Delete(j); + return error; + } free(json_string); - return 0; + return BSP_SUCCESS; } -int bsp_write_tensor(const char* fname, bsp_tensor_t tensor, const char* group, - cJSON* user_json, int compression_level) { +bsp_error_t bsp_write_tensor(const char* fname, bsp_tensor_t tensor, + const char* group, cJSON* user_json, + int compression_level) { if (group == NULL) { hid_t f = H5Fcreate(fname, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); - bsp_write_tensor_to_group(f, tensor, user_json, compression_level); + bsp_error_t error = + bsp_write_tensor_to_group(f, tensor, user_json, compression_level); + if (error != BSP_SUCCESS) { + H5Fclose(f); + return error; + } H5Fclose(f); } else { hid_t f; @@ -177,5 +189,5 @@ int bsp_write_tensor(const char* fname, bsp_tensor_t tensor, const char* group, H5Gclose(g); H5Fclose(f); } - return 0; + return BSP_SUCCESS; } From 13a54719c3f52cf9bee8527fd9394cea0d63627e Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Mon, 18 Aug 2025 21:02:46 -0700 Subject: [PATCH 04/10] Add option to use a specific allocator. --- include/binsparse/array.h | 23 +++++++++++++++----- include/binsparse/hdf5_wrapper.h | 35 +++++++++++++++++++++---------- include/binsparse/matrix.h | 14 +++++++++---- include/binsparse/read_matrix.h | 5 +++++ src/read_matrix.c | 36 +++++++++++++++++++++++--------- 5 files changed, 83 insertions(+), 30 deletions(-) diff --git a/include/binsparse/array.h b/include/binsparse/array.h index 8c1be72..b3b7db2 100644 --- a/include/binsparse/array.h +++ b/include/binsparse/array.h @@ -20,18 +20,25 @@ typedef struct bsp_array_t { bsp_allocator_t allocator; } bsp_array_t; -static inline bsp_error_t bsp_construct_default_array_t(bsp_array_t* array) { +static inline bsp_error_t +bsp_construct_default_array_t_allocator(bsp_array_t* array, + bsp_allocator_t allocator) { array->data = NULL; array->size = 0; - array->allocator = bsp_default_allocator; + array->allocator = allocator; return BSP_SUCCESS; } -static inline bsp_error_t bsp_construct_array_t(bsp_array_t* array, size_t size, - bsp_type_t type) { +static inline bsp_error_t bsp_construct_default_array_t(bsp_array_t* array) { + return bsp_construct_default_array_t_allocator(array, bsp_default_allocator); +} + +static inline bsp_error_t +bsp_construct_array_t_allocator(bsp_array_t* array, size_t size, + bsp_type_t type, bsp_allocator_t allocator) { size_t byte_size = size * bsp_type_size(type); - array->allocator = bsp_default_allocator; + array->allocator = allocator; array->data = array->allocator.malloc(byte_size); if (array->data == NULL) { @@ -44,6 +51,12 @@ static inline bsp_error_t bsp_construct_array_t(bsp_array_t* array, size_t size, return BSP_SUCCESS; } +static inline bsp_error_t bsp_construct_array_t(bsp_array_t* array, size_t size, + bsp_type_t type) { + return bsp_construct_array_t_allocator(array, size, type, + bsp_default_allocator); +} + static inline bsp_error_t bsp_copy_construct_array_t(bsp_array_t* array, bsp_array_t other) { bsp_error_t error = bsp_construct_array_t(array, other.size, other.type); diff --git a/include/binsparse/hdf5_wrapper.h b/include/binsparse/hdf5_wrapper.h index c82a695..3c871dd 100644 --- a/include/binsparse/hdf5_wrapper.h +++ b/include/binsparse/hdf5_wrapper.h @@ -193,12 +193,13 @@ static inline bsp_error_t bsp_read_array_parallel(bsp_array_t* array, hid_t f, } #endif -static inline bsp_error_t bsp_read_array(bsp_array_t* array, hid_t f, - const char* label) { +static inline bsp_error_t bsp_read_array_allocator(bsp_array_t* array, hid_t f, + const char* label, + bsp_allocator_t allocator) { hid_t dset = H5Dopen2(f, label, H5P_DEFAULT); if (dset == H5I_INVALID_HID) { - bsp_construct_default_array_t(array); + bsp_construct_default_array_t_allocator(array, allocator); return BSP_ERROR_IO; } @@ -207,7 +208,7 @@ static inline bsp_error_t bsp_read_array(bsp_array_t* array, hid_t f, if (fspace == H5I_INVALID_HID) { H5Sclose(fspace); H5Dclose(dset); - bsp_construct_default_array_t(array); + bsp_construct_default_array_t_allocator(array, allocator); return BSP_ERROR_IO; } @@ -218,7 +219,7 @@ static inline bsp_error_t bsp_read_array(bsp_array_t* array, hid_t f, if (r < 0) { H5Dclose(dset); H5Sclose(fspace); - bsp_construct_default_array_t(array); + bsp_construct_default_array_t_allocator(array, allocator); return BSP_ERROR_IO; } @@ -226,11 +227,12 @@ static inline bsp_error_t bsp_read_array(bsp_array_t* array, hid_t f, bsp_type_t type = bsp_get_bsp_type(hdf5_type); - bsp_error_t error = bsp_construct_array_t(array, dims[0], type); + bsp_error_t error = + bsp_construct_array_t_allocator(array, dims[0], type, allocator); if (error != BSP_SUCCESS) { H5Dclose(dset); H5Sclose(fspace); - bsp_construct_default_array_t(array); + bsp_construct_default_array_t_allocator(array, allocator); return BSP_ERROR_MEMORY; } @@ -241,7 +243,7 @@ static inline bsp_error_t bsp_read_array(bsp_array_t* array, hid_t f, bsp_destroy_array_t(array); H5Sclose(fspace); H5Dclose(dset); - bsp_construct_default_array_t(array); + bsp_construct_default_array_t_allocator(array, allocator); return BSP_ERROR_IO; } @@ -250,6 +252,11 @@ static inline bsp_error_t bsp_read_array(bsp_array_t* array, hid_t f, return BSP_SUCCESS; } +static inline bsp_error_t bsp_read_array(bsp_array_t* array, hid_t f, + const char* label) { + return bsp_read_array_allocator(array, f, label, bsp_default_allocator); +} + static inline bsp_error_t bsp_write_attribute(hid_t f, const char* label, const char* string) { hid_t strtype = H5Tcopy(H5T_C_S1); @@ -269,8 +276,9 @@ static inline bsp_error_t bsp_write_attribute(hid_t f, const char* label, return BSP_SUCCESS; } -static inline bsp_error_t bsp_read_attribute(char** string, hid_t f, - const char* label) { +static inline bsp_error_t +bsp_read_attribute_allocator(char** string, hid_t f, const char* label, + bsp_allocator_t allocator) { hid_t attribute = H5Aopen(f, label, H5P_DEFAULT); if (attribute == H5I_INVALID_HID) { @@ -281,7 +289,7 @@ static inline bsp_error_t bsp_read_attribute(char** string, hid_t f, size_t size = H5Tget_size(strtype); - *string = (char*) malloc(size + 1); + *string = (char*) allocator.malloc(size + 1); H5Aread(attribute, strtype, *string); @@ -290,3 +298,8 @@ static inline bsp_error_t bsp_read_attribute(char** string, hid_t f, return BSP_SUCCESS; } + +static inline bsp_error_t bsp_read_attribute(char** string, hid_t f, + const char* label) { + return bsp_read_attribute_allocator(string, f, label, bsp_default_allocator); +} diff --git a/include/binsparse/matrix.h b/include/binsparse/matrix.h index 04ad858..bedb3ed 100644 --- a/include/binsparse/matrix.h +++ b/include/binsparse/matrix.h @@ -27,16 +27,22 @@ typedef struct bsp_matrix_t { bsp_structure_t structure; } bsp_matrix_t; -static inline void bsp_construct_default_matrix_t(bsp_matrix_t* matrix) { +static inline void +bsp_construct_default_matrix_t_allocator(bsp_matrix_t* matrix, + bsp_allocator_t allocator) { bsp_construct_default_array_t(&matrix->values); - bsp_construct_default_array_t(&matrix->indices_0); - bsp_construct_default_array_t(&matrix->indices_1); - bsp_construct_default_array_t(&matrix->pointers_to_1); + bsp_construct_default_array_t_allocator(&matrix->indices_0, allocator); + bsp_construct_default_array_t_allocator(&matrix->indices_1, allocator); + bsp_construct_default_array_t_allocator(&matrix->pointers_to_1, allocator); matrix->nrows = matrix->ncols = matrix->nnz = 0; matrix->is_iso = false; matrix->structure = BSP_GENERAL; } +static inline void bsp_construct_default_matrix_t(bsp_matrix_t* matrix) { + bsp_construct_default_matrix_t_allocator(matrix, bsp_default_allocator); +} + static inline void bsp_destroy_matrix_t(bsp_matrix_t* matrix) { bsp_destroy_array_t(&matrix->values); bsp_destroy_array_t(&matrix->indices_0); diff --git a/include/binsparse/read_matrix.h b/include/binsparse/read_matrix.h index 948ffba..699d9a8 100644 --- a/include/binsparse/read_matrix.h +++ b/include/binsparse/read_matrix.h @@ -11,6 +11,7 @@ extern "C" { #endif #ifdef BSP_USE_HDF5 +#include #include #if __STDC_VERSION__ >= 201112L @@ -18,6 +19,8 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads); #endif bsp_matrix_t bsp_read_matrix_from_group(hid_t f); +bsp_matrix_t bsp_read_matrix_from_group_allocator(hid_t f, + bsp_allocator_t allocator); #endif #if __STDC_VERSION__ >= 201112L @@ -26,6 +29,8 @@ bsp_matrix_t bsp_read_matrix_parallel(const char* file_name, const char* group, #endif bsp_matrix_t bsp_read_matrix(const char* file_name, const char* group); +bsp_matrix_t bsp_read_matrix_allocator(const char* file_name, const char* group, + bsp_allocator_t allocator); #ifdef __cplusplus } diff --git a/src/read_matrix.c b/src/read_matrix.c index bc9a75a..9f9ced9 100644 --- a/src/read_matrix.c +++ b/src/read_matrix.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -147,12 +148,14 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { } #endif -bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { +bsp_matrix_t bsp_read_matrix_from_group_allocator(hid_t f, + bsp_allocator_t allocator) { bsp_matrix_t matrix; - bsp_construct_default_matrix_t(&matrix); + bsp_construct_default_matrix_t_allocator(&matrix, allocator); char* json_string; - bsp_error_t error = bsp_read_attribute(&json_string, f, (char*) "binsparse"); + bsp_error_t error = bsp_read_attribute_allocator( + &json_string, f, (char*) "binsparse", allocator); if (error != BSP_SUCCESS) { return matrix; } @@ -213,7 +216,8 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { assert(data_types_ != NULL); if (cJSON_HasObjectItem(data_types_, "values")) { - error = bsp_read_array(&matrix.values, f, (char*) "values"); + error = bsp_read_array_allocator(&matrix.values, f, (char*) "values", + allocator); if (error != BSP_SUCCESS) { free(json_string); return matrix; @@ -236,7 +240,8 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { } if (cJSON_HasObjectItem(data_types_, "indices_0")) { - error = bsp_read_array(&matrix.indices_0, f, (char*) "indices_0"); + error = bsp_read_array_allocator(&matrix.indices_0, f, (char*) "indices_0", + allocator); if (error != BSP_SUCCESS) { free(json_string); bsp_destroy_array_t(&matrix.values); @@ -245,7 +250,8 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { } if (cJSON_HasObjectItem(data_types_, "indices_1")) { - error = bsp_read_array(&matrix.indices_1, f, (char*) "indices_1"); + error = bsp_read_array_allocator(&matrix.indices_1, f, (char*) "indices_1", + allocator); if (error != BSP_SUCCESS) { free(json_string); bsp_destroy_array_t(&matrix.values); @@ -255,7 +261,8 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { } if (cJSON_HasObjectItem(data_types_, "pointers_to_1")) { - error = bsp_read_array(&matrix.pointers_to_1, f, (char*) "pointers_to_1"); + error = bsp_read_array_allocator(&matrix.pointers_to_1, f, + (char*) "pointers_to_1", allocator); if (error != BSP_SUCCESS) { free(json_string); bsp_destroy_array_t(&matrix.values); @@ -278,6 +285,10 @@ bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { return matrix; } +bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { + return bsp_read_matrix_from_group_allocator(f, bsp_default_allocator); +} + static inline size_t bsp_final_dot(const char* str) { size_t dot_idx = 0; for (size_t i = 0; str[i] != '\0'; i++) { @@ -315,13 +326,14 @@ bsp_matrix_t bsp_read_matrix_parallel(const char* file_name, const char* group, } #endif -bsp_matrix_t bsp_read_matrix(const char* file_name, const char* group) { +bsp_matrix_t bsp_read_matrix_allocator(const char* file_name, const char* group, + bsp_allocator_t allocator) { if (group == NULL) { size_t idx = bsp_final_dot(file_name); if (strcmp(file_name + idx, ".hdf5") == 0 || strcmp(file_name + idx, ".h5") == 0) { hid_t f = H5Fopen(file_name, H5F_ACC_RDONLY, H5P_DEFAULT); - bsp_matrix_t matrix = bsp_read_matrix_from_group(f); + bsp_matrix_t matrix = bsp_read_matrix_from_group_allocator(f, allocator); H5Fclose(f); return matrix; } else if (strcmp(file_name + idx, ".mtx") == 0) { @@ -332,9 +344,13 @@ bsp_matrix_t bsp_read_matrix(const char* file_name, const char* group) { } else { hid_t f = H5Fopen(file_name, H5F_ACC_RDONLY, H5P_DEFAULT); hid_t g = H5Gopen1(f, group); - bsp_matrix_t matrix = bsp_read_matrix_from_group(g); + bsp_matrix_t matrix = bsp_read_matrix_from_group_allocator(g, allocator); H5Gclose(g); H5Fclose(f); return matrix; } } + +bsp_matrix_t bsp_read_matrix(const char* file_name, const char* group) { + return bsp_read_matrix_allocator(file_name, group, bsp_default_allocator); +} From b37c626c86ef0c969e2d8ecf8fcc1c902084aa20 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Mon, 18 Aug 2025 21:33:33 -0700 Subject: [PATCH 05/10] Add error handling for Binsparse matrix IO functions. --- README.md | 4 +- examples/benchmark_read.c | 6 +- examples/benchmark_read_parallel.c | 6 +- examples/benchmark_write.c | 6 +- examples/bsp2mtx.c | 5 +- examples/check_equivalence.c | 5 +- examples/check_equivalence_parallel.c | 7 +- examples/mtx2bsp.c | 4 +- examples/simple_matrix_read.c | 3 +- examples/simple_matrix_write.c | 2 +- examples/simple_read.c | 7 +- include/binsparse/error.h | 21 ++++ include/binsparse/read_matrix.h | 22 ++-- src/read_matrix.c | 167 ++++++++++++++------------ 14 files changed, 155 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index a806025..29a5a82 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ This library provides a C interface for reading and writing binsparse matrices. #include int main(int argc, char** argv) { - bsp_matrix_t mat = bsp_read_matrix("chesapeake.bsp.hdf5"); + bsp_matrix_t mat; + bsp_read_matrix(&mat, "chesapeake.bsp.hdf5", NULL); if (mat.format == BSP_COO) { float* values = mat.values.data; @@ -33,6 +34,7 @@ int main(int argc, char** argv) { bsp_get_matrix_format_string(mat.format)); } + bsp_destroy_matrix_t(&mat); return 0; } ``` diff --git a/examples/benchmark_read.c b/examples/benchmark_read.c index 9d72d89..6162fd5 100644 --- a/examples/benchmark_read.c +++ b/examples/benchmark_read.c @@ -79,7 +79,8 @@ int main(int argc, char** argv) { // If running warm cache experiments, read once to warm cache. if (!cold_cache) { - bsp_matrix_t mat = bsp_read_matrix(file_name, NULL); + bsp_matrix_t mat; + BSP_CHECK(bsp_read_matrix(&mat, file_name, NULL)); bsp_destroy_matrix_t(&mat); } @@ -88,7 +89,8 @@ int main(int argc, char** argv) { flush_cache(); } double begin = gettime(); - bsp_matrix_t mat = bsp_read_matrix(file_name, NULL); + bsp_matrix_t mat; + BSP_CHECK(bsp_read_matrix(&mat, file_name, NULL)); double end = gettime(); durations[i] = end - begin; nbytes = bsp_matrix_nbytes(mat); diff --git a/examples/benchmark_read_parallel.c b/examples/benchmark_read_parallel.c index c83ece9..33af3fa 100644 --- a/examples/benchmark_read_parallel.c +++ b/examples/benchmark_read_parallel.c @@ -82,7 +82,8 @@ int main(int argc, char** argv) { // If running warm cache experiments, read once to warm cache. if (!cold_cache) { - bsp_matrix_t mat = bsp_read_matrix_parallel(file_name, NULL, num_threads); + bsp_matrix_t mat; + BSP_CHECK(bsp_read_matrix_parallel(&mat, file_name, NULL, num_threads)); bsp_destroy_matrix_t(&mat); } @@ -92,7 +93,8 @@ int main(int argc, char** argv) { } fflush(stdout); double begin = gettime(); - bsp_matrix_t mat = bsp_read_matrix_parallel(file_name, NULL, num_threads); + bsp_matrix_t mat; + BSP_CHECK(bsp_read_matrix_parallel(&mat, file_name, NULL, num_threads)); double end = gettime(); durations[i] = end - begin; nbytes = bsp_matrix_nbytes(mat); diff --git a/examples/benchmark_write.c b/examples/benchmark_write.c index 8af5881..7b459cc 100644 --- a/examples/benchmark_write.c +++ b/examples/benchmark_write.c @@ -97,7 +97,8 @@ int main(int argc, char** argv) { double durations[num_trials]; - bsp_matrix_t mat = bsp_read_matrix(file_name, NULL); + bsp_matrix_t mat; + BSP_CHECK(bsp_read_matrix(&mat, file_name, NULL)); size_t nbytes = bsp_matrix_nbytes(mat); char output_filename[2048]; @@ -124,7 +125,8 @@ int main(int argc, char** argv) { printf("Writing to file %s\n", output_filename); double begin = gettime(); - bsp_write_matrix(output_filename, mat, NULL, NULL, compression_level); + BSP_CHECK( + bsp_write_matrix(output_filename, mat, NULL, NULL, compression_level)); if (flush_each_write) { flush_writes(); diff --git a/examples/bsp2mtx.c b/examples/bsp2mtx.c index ac2cbb4..8b40c32 100644 --- a/examples/bsp2mtx.c +++ b/examples/bsp2mtx.c @@ -19,8 +19,9 @@ int main(int argc, char** argv) { char* output_fname = argv[2]; printf(" === Reading file... ===\n"); - bsp_matrix_t matrix = bsp_read_matrix(input_fname, NULL); - printf(" === Done writing. ===\n"); + bsp_matrix_t matrix; + BSP_CHECK(bsp_read_matrix(&matrix, input_fname, NULL)); + printf(" === Done reading. ===\n"); if (matrix.format != BSP_COO) { matrix = bsp_convert_matrix(matrix, BSP_COO); } diff --git a/examples/check_equivalence.c b/examples/check_equivalence.c index c15558d..2fba86b 100644 --- a/examples/check_equivalence.c +++ b/examples/check_equivalence.c @@ -110,8 +110,9 @@ int main(int argc, char** argv) { printf("Matrix 2: %s and %s\n", info2.fname, (info2.dataset == NULL) ? "root" : info2.dataset); - bsp_matrix_t matrix1 = bsp_read_matrix(info1.fname, info1.dataset); - bsp_matrix_t matrix2 = bsp_read_matrix(info2.fname, info2.dataset); + bsp_matrix_t matrix1, matrix2; + BSP_CHECK(bsp_read_matrix(&matrix1, info1.fname, info1.dataset)); + BSP_CHECK(bsp_read_matrix(&matrix2, info2.fname, info2.dataset)); bool perform_suitesparse_declamping = true; if (perform_suitesparse_declamping && diff --git a/examples/check_equivalence_parallel.c b/examples/check_equivalence_parallel.c index 399c4c4..ecdcaf0 100644 --- a/examples/check_equivalence_parallel.c +++ b/examples/check_equivalence_parallel.c @@ -117,9 +117,10 @@ int main(int argc, char** argv) { fflush(stdout); - bsp_matrix_t matrix1 = bsp_read_matrix(info1.fname, info1.dataset); - bsp_matrix_t matrix2 = - bsp_read_matrix_parallel(info2.fname, info2.dataset, num_threads); + bsp_matrix_t matrix1, matrix2; + BSP_CHECK(bsp_read_matrix(&matrix1, info1.fname, info1.dataset)); + BSP_CHECK(bsp_read_matrix_parallel(&matrix2, info2.fname, info2.dataset, + num_threads)); bool perform_suitesparse_declamping = true; if (perform_suitesparse_declamping && diff --git a/examples/mtx2bsp.c b/examples/mtx2bsp.c index 621a17d..6e1c7d1 100644 --- a/examples/mtx2bsp.c +++ b/examples/mtx2bsp.c @@ -163,8 +163,8 @@ int main(int argc, char** argv) { printf(" === Writing to %s... ===\n", output_fname); begin = gettime(); - bsp_write_matrix(output_fname, matrix, group_name, user_json, - compression_level); + BSP_CHECK(bsp_write_matrix(output_fname, matrix, group_name, user_json, + compression_level)); end = gettime(); duration = end - begin; printf("%lf seconds writing Binsparse file...\n", duration); diff --git a/examples/simple_matrix_read.c b/examples/simple_matrix_read.c index acbdabc..8ca1411 100644 --- a/examples/simple_matrix_read.c +++ b/examples/simple_matrix_read.c @@ -9,7 +9,8 @@ int main(int argc, char** argv) { const char* file_name = "test.hdf5"; - bsp_matrix_t mat = bsp_read_matrix(file_name, NULL); + bsp_matrix_t mat; + BSP_CHECK(bsp_read_matrix(&mat, file_name, NULL)); if (mat.format == BSP_COO) { float* values = (float*) mat.values.data; diff --git a/examples/simple_matrix_write.c b/examples/simple_matrix_write.c index 5ee6f3f..852c7e1 100644 --- a/examples/simple_matrix_write.c +++ b/examples/simple_matrix_write.c @@ -21,7 +21,7 @@ int main(int argc, char** argv) { printf("%d, %d: %f\n", rowind[i], colind[i], values[i]); } - bsp_write_matrix("test.hdf5", mat, NULL, NULL, 9); + BSP_CHECK(bsp_write_matrix("test.hdf5", mat, NULL, NULL, 9)); return 0; } diff --git a/examples/simple_read.c b/examples/simple_read.c index 0edfa08..23722a8 100644 --- a/examples/simple_read.c +++ b/examples/simple_read.c @@ -12,12 +12,7 @@ int main(int argc, char** argv) { hid_t f = H5Fopen(file_name, H5F_ACC_RDWR, H5P_DEFAULT); bsp_array_t array; - bsp_error_t error = bsp_read_array(&array, f, "test"); - if (error != BSP_SUCCESS) { - printf("Error reading array: %s\n", bsp_get_error_string(error)); - H5Fclose(f); - return 1; - } + BSP_CHECK(bsp_read_array(&array, f, "test")); int* values = (int*) array.data; diff --git a/include/binsparse/error.h b/include/binsparse/error.h index 2988c16..73b3f92 100644 --- a/include/binsparse/error.h +++ b/include/binsparse/error.h @@ -6,6 +6,9 @@ #pragma once +#include +#include + typedef enum bsp_error_t { BSP_SUCCESS = 0, @@ -60,3 +63,21 @@ static inline const char* bsp_get_error_string(bsp_error_t error) { return "Unknown error"; } } + +/** + * BSP_CHECK macro - checks if a bsp_error_t is BSP_SUCCESS. + * If not, prints file, line number, and error message, then aborts. + * + * Usage: BSP_CHECK(bsp_read_matrix(&matrix, "file.hdf5", NULL)); + * + * @param call The function call that returns a bsp_error_t + */ +#define BSP_CHECK(call) \ + do { \ + bsp_error_t _bsp_error = (call); \ + if (_bsp_error != BSP_SUCCESS) { \ + fprintf(stderr, "BSP Error at %s:%d: %s\n", __FILE__, __LINE__, \ + bsp_get_error_string(_bsp_error)); \ + abort(); \ + } \ + } while (0) diff --git a/include/binsparse/read_matrix.h b/include/binsparse/read_matrix.h index 699d9a8..875e2d3 100644 --- a/include/binsparse/read_matrix.h +++ b/include/binsparse/read_matrix.h @@ -15,22 +15,26 @@ extern "C" { #include #if __STDC_VERSION__ >= 201112L -bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads); +bsp_error_t bsp_read_matrix_from_group_parallel(bsp_matrix_t* matrix, hid_t f, + int num_threads); #endif -bsp_matrix_t bsp_read_matrix_from_group(hid_t f); -bsp_matrix_t bsp_read_matrix_from_group_allocator(hid_t f, - bsp_allocator_t allocator); +bsp_error_t bsp_read_matrix_from_group(bsp_matrix_t* matrix, hid_t f); +bsp_error_t bsp_read_matrix_from_group_allocator(bsp_matrix_t* matrix, hid_t f, + bsp_allocator_t allocator); #endif #if __STDC_VERSION__ >= 201112L -bsp_matrix_t bsp_read_matrix_parallel(const char* file_name, const char* group, - int num_threads); +bsp_error_t bsp_read_matrix_parallel(bsp_matrix_t* matrix, + const char* file_name, const char* group, + int num_threads); #endif -bsp_matrix_t bsp_read_matrix(const char* file_name, const char* group); -bsp_matrix_t bsp_read_matrix_allocator(const char* file_name, const char* group, - bsp_allocator_t allocator); +bsp_error_t bsp_read_matrix(bsp_matrix_t* matrix, const char* file_name, + const char* group); +bsp_error_t bsp_read_matrix_allocator(bsp_matrix_t* matrix, + const char* file_name, const char* group, + bsp_allocator_t allocator); #ifdef __cplusplus } diff --git a/src/read_matrix.c b/src/read_matrix.c index 9f9ced9..32d8438 100644 --- a/src/read_matrix.c +++ b/src/read_matrix.c @@ -12,14 +12,14 @@ #include #if __STDC_VERSION__ >= 201112L -bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { - bsp_matrix_t matrix; - bsp_construct_default_matrix_t(&matrix); +bsp_error_t bsp_read_matrix_from_group_parallel(bsp_matrix_t* matrix, hid_t f, + int num_threads) { + bsp_construct_default_matrix_t(matrix); char* json_string; bsp_error_t error = bsp_read_attribute(&json_string, f, (char*) "binsparse"); if (error != BSP_SUCCESS) { - return matrix; + return error; } cJSON* j = cJSON_Parse(json_string); @@ -46,7 +46,7 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { assert(format != 0); - matrix.format = format; + matrix->format = format; cJSON* nnz_ = cJSON_GetObjectItemCaseSensitive(binsparse, "number_of_stored_values"); @@ -68,69 +68,70 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { size_t ncols = cJSON_GetNumberValue(ncols_); - matrix.nrows = nrows; - matrix.ncols = ncols; - matrix.nnz = nnz; - matrix.format = format; + matrix->nrows = nrows; + matrix->ncols = ncols; + matrix->nnz = nnz; + matrix->format = format; cJSON* data_types_ = cJSON_GetObjectItemCaseSensitive(binsparse, "data_types"); assert(data_types_ != NULL); if (cJSON_HasObjectItem(data_types_, "values")) { - error = bsp_read_array_parallel(&matrix.values, f, (char*) "values", + error = bsp_read_array_parallel(&matrix->values, f, (char*) "values", num_threads); if (error != BSP_SUCCESS) { free(json_string); - return matrix; + return error; } cJSON* value_type = cJSON_GetObjectItemCaseSensitive(data_types_, "values"); char* type_string = cJSON_GetStringValue(value_type); if (strlen(type_string) >= 4 && strncmp(type_string, "iso[", 4) == 0) { - matrix.is_iso = true; + matrix->is_iso = true; type_string += 4; } if (strlen(type_string) >= 8 && strncmp(type_string, "complex[", 8) == 0) { - bsp_error_t error = bsp_fp_array_to_complex(&matrix.values); + bsp_error_t error = bsp_fp_array_to_complex(&matrix->values); if (error != BSP_SUCCESS) { - // Handle error - for now just continue with original array + // TODO: handle error + return error; } } } if (cJSON_HasObjectItem(data_types_, "indices_0")) { - error = bsp_read_array_parallel(&matrix.indices_0, f, (char*) "indices_0", + error = bsp_read_array_parallel(&matrix->indices_0, f, (char*) "indices_0", num_threads); if (error != BSP_SUCCESS) { free(json_string); - bsp_destroy_array_t(&matrix.values); - return matrix; + bsp_destroy_array_t(&matrix->values); + return error; } } if (cJSON_HasObjectItem(data_types_, "indices_1")) { - error = bsp_read_array_parallel(&matrix.indices_1, f, (char*) "indices_1", + error = bsp_read_array_parallel(&matrix->indices_1, f, (char*) "indices_1", num_threads); if (error != BSP_SUCCESS) { free(json_string); - bsp_destroy_array_t(&matrix.values); - bsp_destroy_array_t(&matrix.indices_0); - return matrix; + bsp_destroy_array_t(&matrix->values); + bsp_destroy_array_t(&matrix->indices_0); + return error; } } if (cJSON_HasObjectItem(data_types_, "pointers_to_1")) { - error = bsp_read_array_parallel(&matrix.pointers_to_1, f, + error = bsp_read_array_parallel(&matrix->pointers_to_1, f, (char*) "pointers_to_1", num_threads); if (error != BSP_SUCCESS) { free(json_string); - bsp_destroy_array_t(&matrix.values); - bsp_destroy_array_t(&matrix.indices_0); - bsp_destroy_array_t(&matrix.indices_1); - return matrix; + bsp_destroy_array_t(&matrix->values); + bsp_destroy_array_t(&matrix->indices_0); + bsp_destroy_array_t(&matrix->indices_1); + return error; } } @@ -138,26 +139,25 @@ bsp_matrix_t bsp_read_matrix_from_group_parallel(hid_t f, int num_threads) { cJSON* structure_ = cJSON_GetObjectItemCaseSensitive(binsparse, "structure"); char* structure = cJSON_GetStringValue(structure_); - matrix.structure = bsp_get_structure(structure); + matrix->structure = bsp_get_structure(structure); } cJSON_Delete(j); free(json_string); - return matrix; + return BSP_SUCCESS; } #endif -bsp_matrix_t bsp_read_matrix_from_group_allocator(hid_t f, - bsp_allocator_t allocator) { - bsp_matrix_t matrix; - bsp_construct_default_matrix_t_allocator(&matrix, allocator); +bsp_error_t bsp_read_matrix_from_group_allocator(bsp_matrix_t* matrix, hid_t f, + bsp_allocator_t allocator) { + bsp_construct_default_matrix_t_allocator(matrix, allocator); char* json_string; bsp_error_t error = bsp_read_attribute_allocator( &json_string, f, (char*) "binsparse", allocator); if (error != BSP_SUCCESS) { - return matrix; + return error; } cJSON* j = cJSON_Parse(json_string); @@ -184,7 +184,7 @@ bsp_matrix_t bsp_read_matrix_from_group_allocator(hid_t f, assert(format != 0); - matrix.format = format; + matrix->format = format; cJSON* nnz_ = cJSON_GetObjectItemCaseSensitive(binsparse, "number_of_stored_values"); @@ -206,69 +206,70 @@ bsp_matrix_t bsp_read_matrix_from_group_allocator(hid_t f, size_t ncols = cJSON_GetNumberValue(ncols_); - matrix.nrows = nrows; - matrix.ncols = ncols; - matrix.nnz = nnz; - matrix.format = format; + matrix->nrows = nrows; + matrix->ncols = ncols; + matrix->nnz = nnz; + matrix->format = format; cJSON* data_types_ = cJSON_GetObjectItemCaseSensitive(binsparse, "data_types"); assert(data_types_ != NULL); if (cJSON_HasObjectItem(data_types_, "values")) { - error = bsp_read_array_allocator(&matrix.values, f, (char*) "values", + error = bsp_read_array_allocator(&matrix->values, f, (char*) "values", allocator); if (error != BSP_SUCCESS) { free(json_string); - return matrix; + return error; } cJSON* value_type = cJSON_GetObjectItemCaseSensitive(data_types_, "values"); char* type_string = cJSON_GetStringValue(value_type); if (strlen(type_string) >= 4 && strncmp(type_string, "iso[", 4) == 0) { - matrix.is_iso = true; + matrix->is_iso = true; type_string += 4; } if (strlen(type_string) >= 8 && strncmp(type_string, "complex[", 8) == 0) { - bsp_error_t error = bsp_fp_array_to_complex(&matrix.values); + bsp_error_t error = bsp_fp_array_to_complex(&matrix->values); if (error != BSP_SUCCESS) { - // Handle error - for now just continue with original array + // TODO: handle error + return error; } } } if (cJSON_HasObjectItem(data_types_, "indices_0")) { - error = bsp_read_array_allocator(&matrix.indices_0, f, (char*) "indices_0", + error = bsp_read_array_allocator(&matrix->indices_0, f, (char*) "indices_0", allocator); if (error != BSP_SUCCESS) { free(json_string); - bsp_destroy_array_t(&matrix.values); - return matrix; + bsp_destroy_array_t(&matrix->values); + return error; } } if (cJSON_HasObjectItem(data_types_, "indices_1")) { - error = bsp_read_array_allocator(&matrix.indices_1, f, (char*) "indices_1", + error = bsp_read_array_allocator(&matrix->indices_1, f, (char*) "indices_1", allocator); if (error != BSP_SUCCESS) { free(json_string); - bsp_destroy_array_t(&matrix.values); - bsp_destroy_array_t(&matrix.indices_0); - return matrix; + bsp_destroy_array_t(&matrix->values); + bsp_destroy_array_t(&matrix->indices_0); + return error; } } if (cJSON_HasObjectItem(data_types_, "pointers_to_1")) { - error = bsp_read_array_allocator(&matrix.pointers_to_1, f, + error = bsp_read_array_allocator(&matrix->pointers_to_1, f, (char*) "pointers_to_1", allocator); if (error != BSP_SUCCESS) { free(json_string); - bsp_destroy_array_t(&matrix.values); - bsp_destroy_array_t(&matrix.indices_0); - bsp_destroy_array_t(&matrix.indices_1); - return matrix; + bsp_destroy_array_t(&matrix->values); + bsp_destroy_array_t(&matrix->indices_0); + bsp_destroy_array_t(&matrix->indices_1); + return error; } } @@ -276,17 +277,17 @@ bsp_matrix_t bsp_read_matrix_from_group_allocator(hid_t f, cJSON* structure_ = cJSON_GetObjectItemCaseSensitive(binsparse, "structure"); char* structure = cJSON_GetStringValue(structure_); - matrix.structure = bsp_get_structure(structure); + matrix->structure = bsp_get_structure(structure); } cJSON_Delete(j); free(json_string); - return matrix; + return BSP_SUCCESS; } -bsp_matrix_t bsp_read_matrix_from_group(hid_t f) { - return bsp_read_matrix_from_group_allocator(f, bsp_default_allocator); +bsp_error_t bsp_read_matrix_from_group(bsp_matrix_t* matrix, hid_t f) { + return bsp_read_matrix_from_group_allocator(matrix, f, bsp_default_allocator); } static inline size_t bsp_final_dot(const char* str) { @@ -300,57 +301,69 @@ static inline size_t bsp_final_dot(const char* str) { } #if __STDC_VERSION__ >= 201112L -bsp_matrix_t bsp_read_matrix_parallel(const char* file_name, const char* group, - int num_threads) { +bsp_error_t bsp_read_matrix_parallel(bsp_matrix_t* matrix, + const char* file_name, const char* group, + int num_threads) { if (group == NULL) { size_t idx = bsp_final_dot(file_name); if (strcmp(file_name + idx, ".hdf5") == 0 || strcmp(file_name + idx, ".h5") == 0) { hid_t f = H5Fopen(file_name, H5F_ACC_RDONLY, H5P_DEFAULT); - bsp_matrix_t matrix = bsp_read_matrix_from_group_parallel(f, num_threads); + bsp_error_t error = + bsp_read_matrix_from_group_parallel(matrix, f, num_threads); H5Fclose(f); - return matrix; + return error; } else if (strcmp(file_name + idx, ".mtx") == 0) { - return bsp_mmread(file_name); + // TODO: implement error-handling for Matrix Market. + *matrix = bsp_mmread(file_name); + return BSP_SUCCESS; } else { - assert(false); + return BSP_ERROR_IO; } } else { hid_t f = H5Fopen(file_name, H5F_ACC_RDONLY, H5P_DEFAULT); hid_t g = H5Gopen1(f, group); - bsp_matrix_t matrix = bsp_read_matrix_from_group_parallel(g, num_threads); + bsp_error_t error = + bsp_read_matrix_from_group_parallel(matrix, g, num_threads); H5Gclose(g); H5Fclose(f); - return matrix; + return error; } } #endif -bsp_matrix_t bsp_read_matrix_allocator(const char* file_name, const char* group, - bsp_allocator_t allocator) { +bsp_error_t bsp_read_matrix_allocator(bsp_matrix_t* matrix, + const char* file_name, const char* group, + bsp_allocator_t allocator) { if (group == NULL) { size_t idx = bsp_final_dot(file_name); if (strcmp(file_name + idx, ".hdf5") == 0 || strcmp(file_name + idx, ".h5") == 0) { hid_t f = H5Fopen(file_name, H5F_ACC_RDONLY, H5P_DEFAULT); - bsp_matrix_t matrix = bsp_read_matrix_from_group_allocator(f, allocator); + bsp_error_t error = + bsp_read_matrix_from_group_allocator(matrix, f, allocator); H5Fclose(f); - return matrix; + return error; } else if (strcmp(file_name + idx, ".mtx") == 0) { - return bsp_mmread(file_name); + // TODO: implement error-handling for Matrix Market. + *matrix = bsp_mmread(file_name); + return BSP_SUCCESS; } else { - assert(false); + return BSP_ERROR_IO; } } else { hid_t f = H5Fopen(file_name, H5F_ACC_RDONLY, H5P_DEFAULT); hid_t g = H5Gopen1(f, group); - bsp_matrix_t matrix = bsp_read_matrix_from_group_allocator(g, allocator); + bsp_error_t error = + bsp_read_matrix_from_group_allocator(matrix, g, allocator); H5Gclose(g); H5Fclose(f); - return matrix; + return error; } } -bsp_matrix_t bsp_read_matrix(const char* file_name, const char* group) { - return bsp_read_matrix_allocator(file_name, group, bsp_default_allocator); +bsp_error_t bsp_read_matrix(bsp_matrix_t* matrix, const char* file_name, + const char* group) { + return bsp_read_matrix_allocator(matrix, file_name, group, + bsp_default_allocator); } From 355caa3e8c886070d865139f4034b0901c19fb40 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Tue, 19 Aug 2025 11:03:57 -0700 Subject: [PATCH 06/10] Add sanitizers build option, fix a few memory leaks. --- CMakeLists.txt | 8 ++++++++ examples/check_equivalence.c | 5 +++++ examples/mtx2bsp.c | 3 +++ include/binsparse/detail/parse_dataset.h | 5 +++++ include/binsparse/matrix_market/matrix_market_inspector.h | 4 ++++ include/binsparse/matrix_market/matrix_market_read.h | 8 ++++++++ src/write_matrix.c | 3 ++- 7 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ea7531..76cf717 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,8 @@ set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 20) set(CMAKE_C_FLAGS "-O3 -march=native") +option(ENABLE_SANITIZERS "Enable Clang sanitizers" OFF) + include(GNUInstallDirs) add_library(binsparse STATIC) @@ -84,6 +86,12 @@ install(FILES DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/binsparse) if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + if (ENABLE_SANITIZERS) + set(SANITIZER_FLAGS "-fsanitize=address,undefined") + target_compile_options(binsparse INTERFACE ${SANITIZER_FLAGS} -g -O1 -fno-omit-frame-pointer) + target_link_options(binsparse INTERFACE ${SANITIZER_FLAGS}) + endif() + add_subdirectory(examples) add_subdirectory(test) endif() diff --git a/examples/check_equivalence.c b/examples/check_equivalence.c index 2fba86b..641ec8c 100644 --- a/examples/check_equivalence.c +++ b/examples/check_equivalence.c @@ -203,6 +203,11 @@ int main(int argc, char** argv) { return 9; } + bsp_destroy_matrix_t(&matrix1); + bsp_destroy_matrix_t(&matrix2); + bsp_destroy_fdataset_info_t(&info1); + bsp_destroy_fdataset_info_t(&info2); + printf("The files are equivalent.\n"); printf("OK!\n"); diff --git a/examples/mtx2bsp.c b/examples/mtx2bsp.c index 6e1c7d1..1d88613 100644 --- a/examples/mtx2bsp.c +++ b/examples/mtx2bsp.c @@ -171,6 +171,9 @@ int main(int argc, char** argv) { printf(" === Done writing. ===\n"); bsp_destroy_matrix_t(&matrix); + bsp_destroy_mm_metadata(&m); + bsp_destroy_fdataset_info_t(&info2); + cJSON_Delete(user_json); return 0; } diff --git a/include/binsparse/detail/parse_dataset.h b/include/binsparse/detail/parse_dataset.h index 4facff7..489be88 100644 --- a/include/binsparse/detail/parse_dataset.h +++ b/include/binsparse/detail/parse_dataset.h @@ -13,6 +13,11 @@ typedef struct { char* dataset; } bsp_fdataset_info_t; +static inline void bsp_destroy_fdataset_info_t(bsp_fdataset_info_t* info) { + free(info->fname); + free(info->dataset); +} + static inline bsp_fdataset_info_t bsp_parse_fdataset_string(char* str) { size_t len = strlen(str); diff --git a/include/binsparse/matrix_market/matrix_market_inspector.h b/include/binsparse/matrix_market/matrix_market_inspector.h index 8811a89..0e35fff 100644 --- a/include/binsparse/matrix_market/matrix_market_inspector.h +++ b/include/binsparse/matrix_market/matrix_market_inspector.h @@ -32,6 +32,10 @@ typedef struct bsp_mm_metadata { char* comments; } bsp_mm_metadata; +static inline void bsp_destroy_mm_metadata(bsp_mm_metadata* metadata) { + free(metadata->comments); +} + static inline bsp_mm_metadata bsp_mmread_metadata(const char* file_path) { FILE* f = fopen(file_path, "r"); diff --git a/include/binsparse/matrix_market/matrix_market_read.h b/include/binsparse/matrix_market/matrix_market_read.h index 4db0947..2c64ef8 100644 --- a/include/binsparse/matrix_market/matrix_market_read.h +++ b/include/binsparse/matrix_market/matrix_market_read.h @@ -109,6 +109,8 @@ static inline bsp_matrix_t bsp_mmread_explicit_array(const char* file_path, fclose(f); + bsp_destroy_mm_metadata(&metadata); + return matrix; } @@ -306,6 +308,8 @@ bsp_mmread_explicit_coordinate(const char* file_path, bsp_type_t value_type, free(indices); fclose(f); + bsp_destroy_mm_metadata(&metadata); + return matrix; } @@ -315,8 +319,10 @@ static inline bsp_matrix_t bsp_mmread_explicit(const char* file_path, bsp_mm_metadata metadata = bsp_mmread_metadata(file_path); if (strcmp(metadata.format, "array") == 0) { + bsp_destroy_mm_metadata(&metadata); return bsp_mmread_explicit_array(file_path, value_type, index_type); } else if (strcmp(metadata.format, "coordinate") == 0) { + bsp_destroy_mm_metadata(&metadata); return bsp_mmread_explicit_coordinate(file_path, value_type, index_type); } else { assert(false); @@ -345,5 +351,7 @@ static inline bsp_matrix_t bsp_mmread(const char* file_path) { bsp_type_t index_type = bsp_pick_integer_type(max_dim); + bsp_destroy_mm_metadata(&metadata); + return bsp_mmread_explicit(file_path, value_type, index_type); } diff --git a/src/write_matrix.c b/src/write_matrix.c index 0b202bb..322318c 100644 --- a/src/write_matrix.c +++ b/src/write_matrix.c @@ -22,7 +22,8 @@ char* bsp_generate_json(bsp_matrix_t matrix, cJSON* user_json) { cJSON* item; cJSON_ArrayForEach(item, user_json) { - cJSON_AddItemToObject(j, item->string, item); + cJSON* item_copy = cJSON_Duplicate(item, 1); // 1 = deep copy + cJSON_AddItemToObject(j, item->string, item_copy); } cJSON_AddStringToObject(binsparse, "version", BINSPARSE_VERSION); From 61f288f3b69f896a752ac3355d5e3c148b401e18 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Tue, 19 Aug 2025 19:33:05 -0700 Subject: [PATCH 07/10] Separate functions that depend on `cJSON` or `hdf5` into separate headers. `binsparse_all.h` includes everything. --- examples/bsp-ls.c | 2 +- examples/mtx2bsp.c | 6 ++-- include/binsparse/binsparse_all.h | 11 +++++++ include/binsparse/binsparse_cJSON.h | 34 +++++++++++++++++++++ include/binsparse/binsparse_hdf5.h | 43 ++++++++++++++++++++++++++ include/binsparse/read_matrix.h | 17 ++--------- include/binsparse/write_matrix.h | 10 +----- src/read_matrix.c | 7 +++-- src/write_matrix.c | 47 +++++++++++++++++++++++------ 9 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 include/binsparse/binsparse_all.h create mode 100644 include/binsparse/binsparse_cJSON.h create mode 100644 include/binsparse/binsparse_hdf5.h diff --git a/examples/bsp-ls.c b/examples/bsp-ls.c index 9207224..7fb0870 100644 --- a/examples/bsp-ls.c +++ b/examples/bsp-ls.c @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ -#include +#include herr_t visit_group(hid_t loc_id, const char* name, const H5L_info_t* linfo, void* opdata); diff --git a/examples/mtx2bsp.c b/examples/mtx2bsp.c index 1d88613..9b35c3f 100644 --- a/examples/mtx2bsp.c +++ b/examples/mtx2bsp.c @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ -#include +#include #include #include @@ -163,8 +163,8 @@ int main(int argc, char** argv) { printf(" === Writing to %s... ===\n", output_fname); begin = gettime(); - BSP_CHECK(bsp_write_matrix(output_fname, matrix, group_name, user_json, - compression_level)); + BSP_CHECK(bsp_write_matrix_cjson(output_fname, matrix, group_name, user_json, + compression_level)); end = gettime(); duration = end - begin; printf("%lf seconds writing Binsparse file...\n", duration); diff --git a/include/binsparse/binsparse_all.h b/include/binsparse/binsparse_all.h new file mode 100644 index 0000000..34f3463 --- /dev/null +++ b/include/binsparse/binsparse_all.h @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include +#include diff --git a/include/binsparse/binsparse_cJSON.h b/include/binsparse/binsparse_cJSON.h new file mode 100644 index 0000000..c74b9c4 --- /dev/null +++ b/include/binsparse/binsparse_cJSON.h @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include + +#include + +#ifndef BSP_BINSPARSE_CJSON_H +#define BSP_BINSPARSE_CJSON_H +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +bsp_error_t bsp_write_matrix_cjson(const char* fname, bsp_matrix_t matrix, + const char* group, cJSON* user_json, + int compression_level); + +#ifdef BSP_BINSPARSE_HDF5_H +bsp_error_t bsp_write_matrix_to_group_cjson(hid_t f, bsp_matrix_t matrix, + cJSON* user_json, + int compression_level); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/include/binsparse/binsparse_hdf5.h b/include/binsparse/binsparse_hdf5.h new file mode 100644 index 0000000..c97bea1 --- /dev/null +++ b/include/binsparse/binsparse_hdf5.h @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include + +#include + +#ifndef BSP_BINSPARSE_HDF5_H +#define BSP_BINSPARSE_HDF5_H +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if __STDC_VERSION__ >= 201112L +bsp_error_t bsp_read_matrix_from_group_parallel(bsp_matrix_t* matrix, hid_t f, + int num_threads); +#endif + +bsp_error_t bsp_read_matrix_from_group(bsp_matrix_t* matrix, hid_t f); +bsp_error_t bsp_read_matrix_from_group_allocator(bsp_matrix_t* matrix, hid_t f, + bsp_allocator_t allocator); + +bsp_error_t bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, + const char* user_json, + int compression_level); + +#ifdef BSP_BINSPARSE_CJSON_H +bsp_error_t bsp_write_matrix_to_group_cjson(hid_t f, bsp_matrix_t matrix, + cJSON* user_json, + int compression_level); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/include/binsparse/read_matrix.h b/include/binsparse/read_matrix.h index 875e2d3..523f34c 100644 --- a/include/binsparse/read_matrix.h +++ b/include/binsparse/read_matrix.h @@ -6,22 +6,11 @@ #pragma once -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef BSP_USE_HDF5 #include -#include +#include -#if __STDC_VERSION__ >= 201112L -bsp_error_t bsp_read_matrix_from_group_parallel(bsp_matrix_t* matrix, hid_t f, - int num_threads); -#endif - -bsp_error_t bsp_read_matrix_from_group(bsp_matrix_t* matrix, hid_t f); -bsp_error_t bsp_read_matrix_from_group_allocator(bsp_matrix_t* matrix, hid_t f, - bsp_allocator_t allocator); +#ifdef __cplusplus +extern "C" { #endif #if __STDC_VERSION__ >= 201112L diff --git a/include/binsparse/write_matrix.h b/include/binsparse/write_matrix.h index 2b067d2..e535569 100644 --- a/include/binsparse/write_matrix.h +++ b/include/binsparse/write_matrix.h @@ -13,17 +13,9 @@ extern "C" { // TODO: make cJSON optional. #include -#include - -#ifdef BSP_USE_HDF5 -#include - -bsp_error_t bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, - cJSON* user_json, int compression_level); -#endif bsp_error_t bsp_write_matrix(const char* fname, bsp_matrix_t matrix, - const char* group, cJSON* user_json, + const char* group, const char* user_json, int compression_level); #ifdef __cplusplus diff --git a/src/read_matrix.c b/src/read_matrix.c index 32d8438..e06a82b 100644 --- a/src/read_matrix.c +++ b/src/read_matrix.c @@ -5,6 +5,8 @@ */ #include +#include +#include #include #include #include @@ -24,8 +26,9 @@ bsp_error_t bsp_read_matrix_from_group_parallel(bsp_matrix_t* matrix, hid_t f, cJSON* j = cJSON_Parse(json_string); - assert(j != NULL); - assert(cJSON_IsObject(j)); + if (j == NULL || !cJSON_IsObject(j)) { + return BSP_ERROR_FORMAT; + } cJSON* binsparse = cJSON_GetObjectItemCaseSensitive(j, "binsparse"); assert(cJSON_IsObject(binsparse)); diff --git a/src/write_matrix.c b/src/write_matrix.c index 322318c..2345ae3 100644 --- a/src/write_matrix.c +++ b/src/write_matrix.c @@ -5,7 +5,7 @@ */ #include -#include +#include #include #include #include @@ -87,8 +87,9 @@ char* bsp_generate_json(bsp_matrix_t matrix, cJSON* user_json) { return string; } -bsp_error_t bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, - cJSON* user_json, int compression_level) { +bsp_error_t bsp_write_matrix_to_group_cjson(hid_t f, bsp_matrix_t matrix, + cJSON* user_json, + int compression_level) { bsp_error_t error = bsp_write_array(f, (char*) "values", matrix.values, compression_level); if (error != BSP_SUCCESS) { @@ -131,13 +132,26 @@ bsp_error_t bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, return BSP_SUCCESS; } -bsp_error_t bsp_write_matrix(const char* fname, bsp_matrix_t matrix, - const char* group, cJSON* user_json, - int compression_level) { +bsp_error_t bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, + const char* user_json, + int compression_level) { + cJSON* user_json_cjson = cJSON_Parse(user_json); + if (user_json_cjson == NULL) { + return BSP_ERROR_FORMAT; + } + bsp_error_t error = bsp_write_matrix_to_group_cjson( + f, matrix, user_json_cjson, compression_level); + cJSON_Delete(user_json_cjson); + return error; +} + +bsp_error_t bsp_write_matrix_cjson(const char* fname, bsp_matrix_t matrix, + const char* group, cJSON* user_json, + int compression_level) { if (group == NULL) { hid_t f = H5Fcreate(fname, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); - bsp_error_t error = - bsp_write_matrix_to_group(f, matrix, user_json, compression_level); + bsp_error_t error = bsp_write_matrix_to_group_cjson(f, matrix, user_json, + compression_level); if (error != BSP_SUCCESS) { H5Fclose(f); return error; @@ -151,8 +165,8 @@ bsp_error_t bsp_write_matrix(const char* fname, bsp_matrix_t matrix, f = H5Fcreate(fname, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); } hid_t g = H5Gcreate1(f, group, H5P_DEFAULT); - bsp_error_t error = - bsp_write_matrix_to_group(g, matrix, user_json, compression_level); + bsp_error_t error = bsp_write_matrix_to_group_cjson(g, matrix, user_json, + compression_level); if (error != BSP_SUCCESS) { H5Gclose(g); H5Fclose(f); @@ -163,3 +177,16 @@ bsp_error_t bsp_write_matrix(const char* fname, bsp_matrix_t matrix, } return BSP_SUCCESS; } + +bsp_error_t bsp_write_matrix(const char* fname, bsp_matrix_t matrix, + const char* group, const char* user_json, + int compression_level) { + cJSON* user_json_cjson = cJSON_Parse(user_json); + if (user_json_cjson == NULL) { + return BSP_ERROR_FORMAT; + } + bsp_error_t error = bsp_write_matrix_cjson( + fname, matrix, group, user_json_cjson, compression_level); + cJSON_Delete(user_json_cjson); + return error; +} From 4c406faf625ba20aac9102289ba26b09c8a774f7 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Tue, 19 Aug 2025 20:10:33 -0700 Subject: [PATCH 08/10] Move hdf5 types into another file. --- include/binsparse/binsparse.h | 1 - include/binsparse/detail/hdf5_types.h | 208 ++++++++++++++++++++++++++ include/binsparse/hdf5_wrapper.h | 1 + include/binsparse/types.h | 199 ------------------------ 4 files changed, 209 insertions(+), 200 deletions(-) create mode 100644 include/binsparse/detail/hdf5_types.h diff --git a/include/binsparse/binsparse.h b/include/binsparse/binsparse.h index b77735a..189aa7c 100644 --- a/include/binsparse/binsparse.h +++ b/include/binsparse/binsparse.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/include/binsparse/detail/hdf5_types.h b/include/binsparse/detail/hdf5_types.h new file mode 100644 index 0000000..02f0315 --- /dev/null +++ b/include/binsparse/detail/hdf5_types.h @@ -0,0 +1,208 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include + +static inline hid_t bsp_get_hdf5_standard_type(bsp_type_t type) { + if (type == BSP_UINT8) { + return H5T_STD_U8LE; + } else if (type == BSP_UINT16) { + return H5T_STD_U16LE; + } else if (type == BSP_UINT32) { + return H5T_STD_U32LE; + } else if (type == BSP_UINT64) { + return H5T_STD_U64LE; + } else if (type == BSP_INT8) { + return H5T_STD_I8LE; + } else if (type == BSP_INT16) { + return H5T_STD_I16LE; + } else if (type == BSP_INT32) { + return H5T_STD_I32LE; + } else if (type == BSP_INT64) { + return H5T_STD_I64LE; + } else if (type == BSP_FLOAT32) { + return H5T_IEEE_F32LE; + } else if (type == BSP_FLOAT64) { + return H5T_IEEE_F64LE; + } else if (type == BSP_BINT8) { + return H5T_STD_I8LE; + } else { + return H5I_INVALID_HID; + } +} + +static inline bsp_type_t bsp_get_bsp_type(hid_t type) { + H5T_class_t cl = H5Tget_class(type); + H5T_order_t order = H5Tget_order(type); + H5T_sign_t sign = H5Tget_sign(type); + size_t size = H5Tget_size(type); + + if (cl == H5T_INTEGER) { + if (sign == H5T_SGN_NONE) { + if (size == 1) { + return BSP_UINT8; + } else if (size == 2) { + return BSP_UINT16; + } else if (size == 4) { + return BSP_UINT32; + } else if (size == 8) { + return BSP_UINT64; + } else { + return BSP_INVALID_TYPE; + } + } else /* if (sign == H5T_SGN_2) */ { + if (size == 1) { + return BSP_INT8; + } else if (size == 2) { + return BSP_INT16; + } else if (size == 4) { + return BSP_INT32; + } else if (size == 8) { + return BSP_INT64; + } else { + return BSP_INVALID_TYPE; + } + } + } else if (cl == H5T_FLOAT) { + if (size == 4) { + return BSP_FLOAT32; + } else if (size == 8) { + return BSP_FLOAT64; + } else { + return BSP_INVALID_TYPE; + } + } else { + return BSP_INVALID_TYPE; + } +} + +// NOTE: This code is a bit silly, but it seems to be the only +// way to generically determine the HDF5 native types for +// stdint's fixed width integer types. +static inline hid_t bsp_get_hdf5_native_type(bsp_type_t type) { + if (type == BSP_INT8 || type == BSP_BINT8) { + if (sizeof(int8_t) == sizeof(char)) { + return H5T_NATIVE_CHAR; + } else if (sizeof(int8_t) == sizeof(short)) { + return H5T_NATIVE_SHORT; + } else if (sizeof(int8_t) == sizeof(int)) { + return H5T_NATIVE_INT; + } else if (sizeof(int8_t) == sizeof(long)) { + return H5T_NATIVE_LONG; + } else if (sizeof(int8_t) == sizeof(long long)) { + return H5T_NATIVE_LLONG; + } else { + assert(false); + } + } else if (type == BSP_INT16) { + if (sizeof(int16_t) == sizeof(char)) { + return H5T_NATIVE_CHAR; + } else if (sizeof(int16_t) == sizeof(short)) { + return H5T_NATIVE_SHORT; + } else if (sizeof(int16_t) == sizeof(int)) { + return H5T_NATIVE_INT; + } else if (sizeof(int32_t) == sizeof(long)) { + return H5T_NATIVE_LONG; + } else if (sizeof(int64_t) == sizeof(long long)) { + return H5T_NATIVE_LLONG; + } else { + assert(false); + } + } else if (type == BSP_INT32) { + if (sizeof(int32_t) == sizeof(char)) { + return H5T_NATIVE_CHAR; + } else if (sizeof(int32_t) == sizeof(short)) { + return H5T_NATIVE_SHORT; + } else if (sizeof(int32_t) == sizeof(int)) { + return H5T_NATIVE_INT; + } else if (sizeof(int32_t) == sizeof(long)) { + return H5T_NATIVE_LONG; + } else if (sizeof(int32_t) == sizeof(long long)) { + return H5T_NATIVE_LLONG; + } else { + assert(false); + } + } else if (type == BSP_INT64) { + if (sizeof(int64_t) == sizeof(char)) { + return H5T_NATIVE_CHAR; + } else if (sizeof(int64_t) == sizeof(short)) { + return H5T_NATIVE_SHORT; + } else if (sizeof(int64_t) == sizeof(int)) { + return H5T_NATIVE_INT; + } else if (sizeof(int64_t) == sizeof(long)) { + return H5T_NATIVE_LONG; + } else if (sizeof(int64_t) == sizeof(long long)) { + return H5T_NATIVE_LLONG; + } else { + assert(false); + } + } else if (type == BSP_UINT8) { + if (sizeof(uint8_t) == sizeof(unsigned char)) { + return H5T_NATIVE_UCHAR; + } else if (sizeof(uint8_t) == sizeof(unsigned short)) { + return H5T_NATIVE_USHORT; + } else if (sizeof(uint8_t) == sizeof(unsigned int)) { + return H5T_NATIVE_UINT; + } else if (sizeof(uint8_t) == sizeof(unsigned long)) { + return H5T_NATIVE_ULONG; + } else if (sizeof(uint8_t) == sizeof(unsigned long long)) { + return H5T_NATIVE_ULLONG; + } else { + assert(false); + } + } else if (type == BSP_UINT16) { + if (sizeof(uint16_t) == sizeof(unsigned char)) { + return H5T_NATIVE_UCHAR; + } else if (sizeof(uint16_t) == sizeof(unsigned short)) { + return H5T_NATIVE_USHORT; + } else if (sizeof(uint16_t) == sizeof(unsigned int)) { + return H5T_NATIVE_UINT; + } else if (sizeof(uint16_t) == sizeof(unsigned long)) { + return H5T_NATIVE_ULONG; + } else if (sizeof(uint16_t) == sizeof(unsigned long long)) { + return H5T_NATIVE_ULLONG; + } else { + assert(false); + } + } else if (type == BSP_UINT32) { + if (sizeof(uint32_t) == sizeof(unsigned char)) { + return H5T_NATIVE_UCHAR; + } else if (sizeof(uint32_t) == sizeof(unsigned short)) { + return H5T_NATIVE_USHORT; + } else if (sizeof(uint32_t) == sizeof(unsigned int)) { + return H5T_NATIVE_UINT; + } else if (sizeof(uint32_t) == sizeof(unsigned long)) { + return H5T_NATIVE_ULONG; + } else if (sizeof(uint32_t) == sizeof(unsigned long long)) { + return H5T_NATIVE_ULLONG; + } else { + assert(false); + } + } else if (type == BSP_UINT64) { + if (sizeof(uint64_t) == sizeof(unsigned char)) { + return H5T_NATIVE_UCHAR; + } else if (sizeof(uint64_t) == sizeof(unsigned short)) { + return H5T_NATIVE_USHORT; + } else if (sizeof(uint64_t) == sizeof(unsigned int)) { + return H5T_NATIVE_UINT; + } else if (sizeof(uint64_t) == sizeof(unsigned long)) { + return H5T_NATIVE_ULONG; + } else if (sizeof(uint64_t) == sizeof(unsigned long long)) { + return H5T_NATIVE_ULLONG; + } else { + assert(false); + } + } else if (type == BSP_FLOAT32) { + return H5T_NATIVE_FLOAT; + } else if (type == BSP_FLOAT64) { + return H5T_NATIVE_DOUBLE; + } else { + return H5I_INVALID_HID; + } +} diff --git a/include/binsparse/hdf5_wrapper.h b/include/binsparse/hdf5_wrapper.h index 3c871dd..16ff7a7 100644 --- a/include/binsparse/hdf5_wrapper.h +++ b/include/binsparse/hdf5_wrapper.h @@ -13,6 +13,7 @@ #include +#include #include #if __STDC_VERSION__ >= 201112L diff --git a/include/binsparse/types.h b/include/binsparse/types.h index e3c9e53..e38c7e8 100644 --- a/include/binsparse/types.h +++ b/include/binsparse/types.h @@ -7,7 +7,6 @@ #pragma once #include -#include typedef enum bsp_type_t { BSP_UINT8 = 0, @@ -90,204 +89,6 @@ static inline size_t bsp_type_size(bsp_type_t type) { } } -static inline hid_t bsp_get_hdf5_standard_type(bsp_type_t type) { - if (type == BSP_UINT8) { - return H5T_STD_U8LE; - } else if (type == BSP_UINT16) { - return H5T_STD_U16LE; - } else if (type == BSP_UINT32) { - return H5T_STD_U32LE; - } else if (type == BSP_UINT64) { - return H5T_STD_U64LE; - } else if (type == BSP_INT8) { - return H5T_STD_I8LE; - } else if (type == BSP_INT16) { - return H5T_STD_I16LE; - } else if (type == BSP_INT32) { - return H5T_STD_I32LE; - } else if (type == BSP_INT64) { - return H5T_STD_I64LE; - } else if (type == BSP_FLOAT32) { - return H5T_IEEE_F32LE; - } else if (type == BSP_FLOAT64) { - return H5T_IEEE_F64LE; - } else if (type == BSP_BINT8) { - return H5T_STD_I8LE; - } else { - return H5I_INVALID_HID; - } -} - -static inline bsp_type_t bsp_get_bsp_type(hid_t type) { - H5T_class_t cl = H5Tget_class(type); - H5T_order_t order = H5Tget_order(type); - H5T_sign_t sign = H5Tget_sign(type); - size_t size = H5Tget_size(type); - - if (cl == H5T_INTEGER) { - if (sign == H5T_SGN_NONE) { - if (size == 1) { - return BSP_UINT8; - } else if (size == 2) { - return BSP_UINT16; - } else if (size == 4) { - return BSP_UINT32; - } else if (size == 8) { - return BSP_UINT64; - } else { - return BSP_INVALID_TYPE; - } - } else /* if (sign == H5T_SGN_2) */ { - if (size == 1) { - return BSP_INT8; - } else if (size == 2) { - return BSP_INT16; - } else if (size == 4) { - return BSP_INT32; - } else if (size == 8) { - return BSP_INT64; - } else { - return BSP_INVALID_TYPE; - } - } - } else if (cl == H5T_FLOAT) { - if (size == 4) { - return BSP_FLOAT32; - } else if (size == 8) { - return BSP_FLOAT64; - } else { - return BSP_INVALID_TYPE; - } - } else { - return BSP_INVALID_TYPE; - } -} - -// NOTE: This code is a bit silly, but it seems to be the only -// way to generically determine the HDF5 native types for -// stdint's fixed width integer types. -static inline hid_t bsp_get_hdf5_native_type(bsp_type_t type) { - if (type == BSP_INT8 || type == BSP_BINT8) { - if (sizeof(int8_t) == sizeof(char)) { - return H5T_NATIVE_CHAR; - } else if (sizeof(int8_t) == sizeof(short)) { - return H5T_NATIVE_SHORT; - } else if (sizeof(int8_t) == sizeof(int)) { - return H5T_NATIVE_INT; - } else if (sizeof(int8_t) == sizeof(long)) { - return H5T_NATIVE_LONG; - } else if (sizeof(int8_t) == sizeof(long long)) { - return H5T_NATIVE_LLONG; - } else { - assert(false); - } - } else if (type == BSP_INT16) { - if (sizeof(int16_t) == sizeof(char)) { - return H5T_NATIVE_CHAR; - } else if (sizeof(int16_t) == sizeof(short)) { - return H5T_NATIVE_SHORT; - } else if (sizeof(int16_t) == sizeof(int)) { - return H5T_NATIVE_INT; - } else if (sizeof(int32_t) == sizeof(long)) { - return H5T_NATIVE_LONG; - } else if (sizeof(int64_t) == sizeof(long long)) { - return H5T_NATIVE_LLONG; - } else { - assert(false); - } - } else if (type == BSP_INT32) { - if (sizeof(int32_t) == sizeof(char)) { - return H5T_NATIVE_CHAR; - } else if (sizeof(int32_t) == sizeof(short)) { - return H5T_NATIVE_SHORT; - } else if (sizeof(int32_t) == sizeof(int)) { - return H5T_NATIVE_INT; - } else if (sizeof(int32_t) == sizeof(long)) { - return H5T_NATIVE_LONG; - } else if (sizeof(int32_t) == sizeof(long long)) { - return H5T_NATIVE_LLONG; - } else { - assert(false); - } - } else if (type == BSP_INT64) { - if (sizeof(int64_t) == sizeof(char)) { - return H5T_NATIVE_CHAR; - } else if (sizeof(int64_t) == sizeof(short)) { - return H5T_NATIVE_SHORT; - } else if (sizeof(int64_t) == sizeof(int)) { - return H5T_NATIVE_INT; - } else if (sizeof(int64_t) == sizeof(long)) { - return H5T_NATIVE_LONG; - } else if (sizeof(int64_t) == sizeof(long long)) { - return H5T_NATIVE_LLONG; - } else { - assert(false); - } - } else if (type == BSP_UINT8) { - if (sizeof(uint8_t) == sizeof(unsigned char)) { - return H5T_NATIVE_UCHAR; - } else if (sizeof(uint8_t) == sizeof(unsigned short)) { - return H5T_NATIVE_USHORT; - } else if (sizeof(uint8_t) == sizeof(unsigned int)) { - return H5T_NATIVE_UINT; - } else if (sizeof(uint8_t) == sizeof(unsigned long)) { - return H5T_NATIVE_ULONG; - } else if (sizeof(uint8_t) == sizeof(unsigned long long)) { - return H5T_NATIVE_ULLONG; - } else { - assert(false); - } - } else if (type == BSP_UINT16) { - if (sizeof(uint16_t) == sizeof(unsigned char)) { - return H5T_NATIVE_UCHAR; - } else if (sizeof(uint16_t) == sizeof(unsigned short)) { - return H5T_NATIVE_USHORT; - } else if (sizeof(uint16_t) == sizeof(unsigned int)) { - return H5T_NATIVE_UINT; - } else if (sizeof(uint16_t) == sizeof(unsigned long)) { - return H5T_NATIVE_ULONG; - } else if (sizeof(uint16_t) == sizeof(unsigned long long)) { - return H5T_NATIVE_ULLONG; - } else { - assert(false); - } - } else if (type == BSP_UINT32) { - if (sizeof(uint32_t) == sizeof(unsigned char)) { - return H5T_NATIVE_UCHAR; - } else if (sizeof(uint32_t) == sizeof(unsigned short)) { - return H5T_NATIVE_USHORT; - } else if (sizeof(uint32_t) == sizeof(unsigned int)) { - return H5T_NATIVE_UINT; - } else if (sizeof(uint32_t) == sizeof(unsigned long)) { - return H5T_NATIVE_ULONG; - } else if (sizeof(uint32_t) == sizeof(unsigned long long)) { - return H5T_NATIVE_ULLONG; - } else { - assert(false); - } - } else if (type == BSP_UINT64) { - if (sizeof(uint64_t) == sizeof(unsigned char)) { - return H5T_NATIVE_UCHAR; - } else if (sizeof(uint64_t) == sizeof(unsigned short)) { - return H5T_NATIVE_USHORT; - } else if (sizeof(uint64_t) == sizeof(unsigned int)) { - return H5T_NATIVE_UINT; - } else if (sizeof(uint64_t) == sizeof(unsigned long)) { - return H5T_NATIVE_ULONG; - } else if (sizeof(uint64_t) == sizeof(unsigned long long)) { - return H5T_NATIVE_ULLONG; - } else { - assert(false); - } - } else if (type == BSP_FLOAT32) { - return H5T_NATIVE_FLOAT; - } else if (type == BSP_FLOAT64) { - return H5T_NATIVE_DOUBLE; - } else { - return H5I_INVALID_HID; - } -} - // Given the maximum value `max_value` that must be stored, // pick an unsigned integer type for indices. static inline bsp_type_t bsp_pick_integer_type(size_t max_value) { From 23ac92d3b1acea6998fb90b693b6e46e307876c0 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Tue, 19 Aug 2025 20:33:10 -0700 Subject: [PATCH 09/10] Fix HDF5 inclusion. --- examples/benchmark_read.c | 1 + examples/benchmark_read_parallel.c | 1 + examples/benchmark_write.c | 1 + examples/simple_read.c | 2 +- examples/simple_write.c | 2 +- include/binsparse/binsparse_hdf5.h | 2 ++ include/binsparse/types.h | 2 ++ src/write_matrix.c | 1 + src/write_tensor.c | 8 +++++--- 9 files changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/benchmark_read.c b/examples/benchmark_read.c index 6162fd5..0a1894a 100644 --- a/examples/benchmark_read.c +++ b/examples/benchmark_read.c @@ -7,6 +7,7 @@ #include #include #include +#include double gettime() { struct timespec time; diff --git a/examples/benchmark_read_parallel.c b/examples/benchmark_read_parallel.c index 33af3fa..24a5597 100644 --- a/examples/benchmark_read_parallel.c +++ b/examples/benchmark_read_parallel.c @@ -7,6 +7,7 @@ #include #include #include +#include double gettime() { struct timespec time; diff --git a/examples/benchmark_write.c b/examples/benchmark_write.c index 7b459cc..d2e5a60 100644 --- a/examples/benchmark_write.c +++ b/examples/benchmark_write.c @@ -7,6 +7,7 @@ #include #include #include +#include double gettime() { struct timespec time; diff --git a/examples/simple_read.c b/examples/simple_read.c index 23722a8..18d2eac 100644 --- a/examples/simple_read.c +++ b/examples/simple_read.c @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ -#include +#include int main(int argc, char** argv) { char* file_name = (char*) "test.hdf5"; diff --git a/examples/simple_write.c b/examples/simple_write.c index 261f228..0a51b49 100644 --- a/examples/simple_write.c +++ b/examples/simple_write.c @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ -#include +#include int main(int argc, char** argv) { const char* file_name = "test.hdf5"; diff --git a/include/binsparse/binsparse_hdf5.h b/include/binsparse/binsparse_hdf5.h index c97bea1..2aef4a5 100644 --- a/include/binsparse/binsparse_hdf5.h +++ b/include/binsparse/binsparse_hdf5.h @@ -9,6 +9,8 @@ #include #include +#include + #include #ifndef BSP_BINSPARSE_HDF5_H diff --git a/include/binsparse/types.h b/include/binsparse/types.h index e38c7e8..b487444 100644 --- a/include/binsparse/types.h +++ b/include/binsparse/types.h @@ -7,6 +7,8 @@ #pragma once #include +#include +#include typedef enum bsp_type_t { BSP_UINT8 = 0, diff --git a/src/write_matrix.c b/src/write_matrix.c index 2345ae3..d87244a 100644 --- a/src/write_matrix.c +++ b/src/write_matrix.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include diff --git a/src/write_tensor.c b/src/write_tensor.c index f212aeb..1b71f17 100644 --- a/src/write_tensor.c +++ b/src/write_tensor.c @@ -4,13 +4,15 @@ * SPDX-License-Identifier: BSD-3-Clause */ +#include +#include +#include + #include -#include #include -#include -#include #include +#include static cJSON* init_tensor_json(bsp_tensor_t tensor, cJSON* user_json) { cJSON* j = cJSON_CreateObject(); From c82561a5f301da09fba34f0dcb1e68116f080e9a Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Wed, 15 Jul 2026 12:46:11 -0700 Subject: [PATCH 10/10] Implement Matlab bindings (#24) * Some Matlab files * Prevent `clang-format` from messing with Matlab files. * Update Matlab bindings, fine-tune `.gitignore`. * Add support for all types to Matlab. * Implement `binsparse_write` bindings. * Update Matlab bindings to use zero-copy transfer. --- .gitignore | 10 +- .pre-commit-config.yaml | 1 + CMakeLists.txt | 11 +- bindings/matlab/README.md | 281 ++++++++++++++ bindings/matlab/binsparse_read.c | 242 ++++++++++++ bindings/matlab/binsparse_write.c | 357 ++++++++++++++++++ bindings/matlab/bsp_matrix_create.m | 71 ++++ bindings/matlab/bsp_matrix_info.m | 51 +++ bindings/matlab/build_matlab_bindings.m | 149 ++++++++ bindings/matlab/build_octave_bindings.m | 185 +++++++++ bindings/matlab/compile_binsparse_read.m | 53 +++ .../matlab/compile_binsparse_read_octave.m | 76 ++++ bindings/matlab/compile_binsparse_write.m | 53 +++ .../matlab/compile_binsparse_write_octave.m | 76 ++++ bindings/matlab/compile_octave.sh | 181 +++++++++ bindings/matlab/test_binsparse_read.m | 71 ++++ bindings/matlab/test_binsparse_write.m | 122 ++++++ bindings/matlab/test_bsp_matrix_struct.m | 76 ++++ src/write_matrix.c | 9 +- 19 files changed, 2067 insertions(+), 8 deletions(-) create mode 100644 bindings/matlab/README.md create mode 100644 bindings/matlab/binsparse_read.c create mode 100644 bindings/matlab/binsparse_write.c create mode 100644 bindings/matlab/bsp_matrix_create.m create mode 100644 bindings/matlab/bsp_matrix_info.m create mode 100644 bindings/matlab/build_matlab_bindings.m create mode 100644 bindings/matlab/build_octave_bindings.m create mode 100644 bindings/matlab/compile_binsparse_read.m create mode 100644 bindings/matlab/compile_binsparse_read_octave.m create mode 100644 bindings/matlab/compile_binsparse_write.m create mode 100644 bindings/matlab/compile_binsparse_write_octave.m create mode 100755 bindings/matlab/compile_octave.sh create mode 100644 bindings/matlab/test_binsparse_read.m create mode 100644 bindings/matlab/test_binsparse_write.m create mode 100644 bindings/matlab/test_bsp_matrix_struct.m diff --git a/.gitignore b/.gitignore index 8dddad7..77ff7ad 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,15 @@ scripts venv -build* +build/ +build-*/ compile_flags.txt ._* tensor_test_files + +# MATLAB/Octave MEX files +*.mex +*.mexa64 +*.mexw32 +*.mexw64 +*.mexmaci64 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b750526..6cb3e62 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,6 +8,7 @@ repos: rev: v16.0.6 hooks: - id: clang-format + exclude: '\.m$' - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 76cf717..5fd39e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,12 @@ find_package(HDF5 REQUIRED COMPONENTS C) target_link_libraries(binsparse PUBLIC ${HDF5_C_LIBRARIES}) include(FetchContent) + +# Force cJSON to build as static library for MEX compatibility +set(BUILD_SHARED_LIBS_BACKUP ${BUILD_SHARED_LIBS}) +set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Build cJSON as static library") +set(ENABLE_CJSON_TEST OFF CACHE INTERNAL "Disable cJSON tests") + FetchContent_Declare( cJSON # GIT_REPOSITORY https://github.com/DaveGamble/cJSON.git @@ -36,8 +42,11 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(cJSON) +# Restore original BUILD_SHARED_LIBS setting +set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_BACKUP}) + configure_file(${cJSON_SOURCE_DIR}/cJSON.h ${CMAKE_BINARY_DIR}/include/cJSON/cJSON.h) -target_link_libraries(${PROJECT_NAME} PUBLIC cjson) +target_link_libraries(${PROJECT_NAME} PRIVATE cjson) # Set up include directories properly for both build and install target_include_directories(${PROJECT_NAME} diff --git a/bindings/matlab/README.md b/bindings/matlab/README.md new file mode 100644 index 0000000..ff776f4 --- /dev/null +++ b/bindings/matlab/README.md @@ -0,0 +1,281 @@ + + +# Binsparse MATLAB/Octave Bindings + +This directory contains MATLAB and GNU Octave MEX bindings for the Binsparse C library, providing a simple interface to access Binsparse functionality from both MATLAB and Octave. + +## Quick Start + +### Prerequisites + +**For MATLAB:** +1. **MATLAB** with MEX compiler support +2. **Binsparse C library** headers (included in this repository) + +**For Octave:** +1. **GNU Octave** with mkoctfile (usually included) +2. **C compiler** (gcc recommended) +3. **Binsparse C library** headers (included in this repository) + +### Setup MEX Compiler (if needed) + +**For MATLAB:** +If you haven't configured a MEX compiler yet: + +```matlab +mex -setup +``` + +Choose a compatible C compiler when prompted. + +**For Octave:** +Octave usually comes with mkoctfile pre-configured. Verify it works: + +```bash +mkoctfile --version +``` + +### Build the Bindings + +#### Option 1: MATLAB + +1. Open MATLAB and navigate to this directory: + ```matlab + cd('path/to/binsparse-reference-c/bindings/matlab') + ``` + +2. Build the MEX functions: + ```matlab + build_matlab_bindings() + ``` + +3. Test the installation: + ```matlab + test_binsparse_read() + test_binsparse_write() + ``` + +#### Option 2: Octave (from within Octave) + +1. Open Octave and navigate to this directory: + ```octave + cd('path/to/binsparse-reference-c/bindings/matlab') + ``` + +2. Build the MEX functions: + ```octave + build_octave_bindings() + ``` + +3. Test the installation: + ```octave + test_binsparse_read() + test_binsparse_write() + ``` + +#### Option 3: Octave (from command line) + +1. Navigate to this directory: + ```bash + cd path/to/binsparse-reference-c/bindings/matlab + ``` + +2. Build using the shell script: + ```bash + ./compile_octave.sh + ``` + +3. Test the installation: + ```bash + octave --eval "test_binsparse_read()" + octave --eval "test_binsparse_write()" + ``` + +## Usage Examples + +### Reading Binsparse Files + +**In MATLAB or Octave:** + +```matlab +% Read a Binsparse matrix file +matrix = binsparse_read('path/to/matrix.bsp.h5'); + +% Read from a specific group +matrix = binsparse_read('path/to/matrix.bsp.h5', 'group_name'); + +% Matrix will be a struct with fields: +% - values: array of matrix values +% - indices_0, indices_1: row/column indices +% - pointers_to_1: pointer array (for CSR/CSC formats) +% - nrows, ncols, nnz: matrix dimensions +% - is_iso: boolean for iso-value matrices +% - format: string ('COO', 'CSR', 'CSC', etc.) +% - structure: string ('general', 'symmetric', etc.) +``` + +### Writing Binsparse Files + +```matlab +% Create a matrix struct (example: 3x3 COO matrix) +matrix = struct(); +matrix.values = [1.0; 2.0; 3.0]; +matrix.indices_0 = uint64([0; 1; 2]); % 0-based row indices +matrix.indices_1 = uint64([0; 1; 2]); % 0-based col indices +matrix.pointers_to_1 = uint64([]); % Empty for COO +matrix.nrows = 3; +matrix.ncols = 3; +matrix.nnz = 3; +matrix.is_iso = false; +matrix.format = 'COO'; +matrix.structure = 'general'; + +% Write to file +binsparse_write('output.bsp.h5', matrix); + +% Write with optional parameters +binsparse_write('output.bsp.h5', matrix, 'my_group'); +binsparse_write('output.bsp.h5', matrix, 'my_group', '{"author": "me"}'); +binsparse_write('output.bsp.h5', matrix, 'my_group', '{"author": "me"}', 6); +``` + +### Error Handling + +The MEX functions include proper error handling: + +```matlab +try + matrix = binsparse_read('nonexistent_file.bsp.h5') +catch ME + fprintf('Error: %s\n', ME.message) +end +``` + +## Files Description + +| File | Description | +|------|-------------| +| `binsparse_read.c` | MEX function for reading Binsparse matrix files | +| `binsparse_write.c` | MEX function for writing Binsparse matrix files | +| `build_matlab_bindings.m` | Main build script for MATLAB MEX functions | +| `build_octave_bindings.m` | Main build script for Octave MEX functions | +| `compile_binsparse_read.m` | Simple compilation script for read function (MATLAB) | +| `compile_binsparse_write.m` | Simple compilation script for write function (MATLAB) | +| `compile_binsparse_read_octave.m` | Simple compilation script for read function (Octave) | +| `compile_binsparse_write_octave.m` | Simple compilation script for write function (Octave) | +| `compile_octave.sh` | Shell script for building Octave MEX functions | +| `test_binsparse_read.m` | Test script for read functionality | +| `test_binsparse_write.m` | Test script for write functionality | +| `bsp_matrix_create.m` | Utility function for creating matrix structs | +| `bsp_matrix_info.m` | Utility function for displaying matrix information | +| `README.md` | This documentation file | + +## Technical Details + +### MEX Function Structure + +The Binsparse MEX functions demonstrate: + +1. **Header inclusion**: Proper inclusion of `` +2. **Type conversion**: Complete mapping between MATLAB and Binsparse types +3. **Error handling**: Using Binsparse error types (`bsp_error_t`) +4. **Memory management**: Safe allocation and cleanup +5. **MATLAB interface**: Proper MEX function structure with validation + +### Build Process + +**MATLAB build process:** + +1. Locates Binsparse include directory (`../../include/`) +2. Compiles MEX functions using MATLAB's `mex` command +3. Links against MATLAB MEX libraries +4. Validates compilation with test functions + +**Octave build process:** + +1. Locates Binsparse include directory (`../../include/`) +2. Compiles MEX functions using `mkoctfile --mex` +3. Links against Octave MEX libraries +4. Validates compilation with test functions + +## Extending the Bindings + +To add new Binsparse functionality: + +1. Create a new `.c` file with MEX function structure +2. Include `` and relevant headers +3. Add the filename to `mex_files` list in `build_matlab_bindings.m` +4. Create corresponding test functions + +### Example MEX Function Template + +```c +#include "mex.h" +#include + +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { + // Input validation + if (nrhs != 1) { + mexErrMsgIdAndTxt("BinSparse:InvalidInput", "Expected 1 input argument"); + } + + // Call Binsparse functions + bsp_error_t error = /* your_binsparse_function() */; + + // Handle errors + if (error != BSP_SUCCESS) { + mexErrMsgIdAndTxt("BinSparse:Error", "%s", bsp_get_error_string(error)); + } + + // Return results to MATLAB + plhs[0] = /* create_matlab_output() */; +} +``` + +## Troubleshooting + +### Common Issues + +1. **MEX compiler not found** + - **MATLAB**: Run `mex -setup` to configure a compiler + - **Octave**: Install mkoctfile (usually comes with Octave) + - Ensure you have a compatible C compiler installed + +2. **Include paths not found** + - Verify you're running from the `bindings/matlab` directory + - Check that `../../include/binsparse/binsparse.h` exists + +3. **Compilation errors** + - **MATLAB**: Try building with verbose output: `build_matlab_bindings('verbose')` + - **Octave**: Try building with verbose output: `build_octave_bindings('verbose')` or `./compile_octave.sh --verbose` + - Check compiler compatibility with your MATLAB/Octave version + +### Platform-Specific Notes + +- **Windows**: + - MATLAB: Microsoft Visual Studio or compatible compiler + - Octave: MinGW-w64 (often included with Octave installer) +- **macOS**: Xcode command line tools required for both MATLAB and Octave +- **Linux**: GCC or compatible compiler should work for both MATLAB and Octave + +## Development Status + +This provides complete MATLAB/Octave bindings for Binsparse. Currently implemented: + +- ✅ Matrix reading (`binsparse_read`) +- ✅ Matrix writing (`binsparse_write`) +- ✅ Complete type support (all Binsparse types including complex numbers) +- ✅ Optional parameters (groups, JSON metadata, compression) +- ✅ Comprehensive error handling +- ✅ Build system and testing framework +- ✅ Round-trip compatibility (read → write → read) +- ⏳ Tensor support (future work) +- ⏳ Advanced sparse matrix operations (future work) + +## License + +This code is licensed under the BSD-3-Clause license, same as the main Binsparse project. diff --git a/bindings/matlab/binsparse_read.c b/bindings/matlab/binsparse_read.c new file mode 100644 index 0000000..cf471fe --- /dev/null +++ b/bindings/matlab/binsparse_read.c @@ -0,0 +1,242 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + * binsparse_read.c - Read Binsparse matrices into MATLAB + * + * This MEX function reads Binsparse format matrices and returns them + * as MATLAB structs compatible with bsp_matrix_create. + * + * Usage in MATLAB/Octave: + * matrix = binsparse_read(filename) + * matrix = binsparse_read(filename, group) + */ + +#include "mex.h" +#include +#include + +static inline void* bsp_matlab_malloc(size_t size) { + void* ptr = mxMalloc(size); + mexMakeMemoryPersistent(ptr); + return ptr; +} + +static const bsp_allocator_t bsp_matlab_allocator = { + .malloc = bsp_matlab_malloc, .free = mxFree}; + +static inline mxClassID get_mxClassID(bsp_type_t type) { + switch (type) { + case BSP_UINT8: + return mxUINT8_CLASS; + case BSP_UINT16: + return mxUINT16_CLASS; + case BSP_UINT32: + return mxUINT32_CLASS; + case BSP_UINT64: + return mxUINT64_CLASS; + case BSP_INT8: + return mxINT8_CLASS; + case BSP_INT16: + return mxINT16_CLASS; + case BSP_INT32: + return mxINT32_CLASS; + case BSP_INT64: + return mxINT64_CLASS; + case BSP_FLOAT32: + return mxSINGLE_CLASS; + case BSP_FLOAT64: + return mxDOUBLE_CLASS; + case BSP_BINT8: // Treat BSP_BINT8 as UINT8 + return mxUINT8_CLASS; + case BSP_COMPLEX_FLOAT32: + return mxSINGLE_CLASS; + case BSP_COMPLEX_FLOAT64: + return mxDOUBLE_CLASS; + default: + return mxUNKNOWN_CLASS; + } +} + +static inline mxComplexity get_mxComplexity(bsp_type_t type) { + if (type == BSP_COMPLEX_FLOAT32 || type == BSP_COMPLEX_FLOAT64) { + return mxCOMPLEX; + } else { + return mxREAL; + } +} + +mxArray* bsp_array_to_matlab(bsp_array_t* array) { + if (array->data == NULL || array->size == 0) { + // Return empty array + return mxCreateDoubleMatrix(1, 1, mxREAL); + } + + if (get_mxClassID(array->type) == mxUNKNOWN_CLASS) { + mexWarnMsgIdAndTxt("BinSparse:UnsupportedType", + "Unsupported array type %d, returning empty array", + (int) array->type); + return mxCreateDoubleMatrix(1, 1, mxREAL); + } + + mxArray* mx_array = NULL; + + if ((array->allocator.malloc == bsp_matlab_allocator.malloc && + array->allocator.free == bsp_matlab_allocator.free) && + get_mxComplexity(array->type) == mxREAL) { + // Create mx_array in a zero-copy fashion. + + mx_array = mxCreateNumericMatrix(0, 1, get_mxClassID(array->type), + get_mxComplexity(array->type)); + + mxSetData(mx_array, array->data); + mxSetM(mx_array, array->size); + + array->data = NULL; + array->size = 0; + } else { + mx_array = mxCreateNumericMatrix(array->size, 1, get_mxClassID(array->type), + get_mxComplexity(array->type)); + + if (get_mxComplexity(array->type) == mxREAL) { + memcpy(mxGetData(mx_array), array->data, + array->size * bsp_type_size(array->type)); + } else { + if (array->type == BSP_COMPLEX_FLOAT32) { + float* in_data = + (float*) array->data; // Treat as array of adjacent real/imag pairs + float* real_data = (float*) mxGetData(mx_array); + float* imag_data = (float*) mxGetImagData(mx_array); + for (size_t i = 0; i < array->size; i++) { + real_data[i] = in_data[2 * i]; // Real part + imag_data[i] = in_data[2 * i + 1]; // Imaginary part + } + } else { + double* in_data = + (double*) array->data; // Treat as array of adjacent real/imag pairs + double* real_data = mxGetPr(mx_array); + double* imag_data = mxGetPi(mx_array); + for (size_t i = 0; i < array->size; i++) { + real_data[i] = in_data[2 * i]; // Real part + imag_data[i] = in_data[2 * i + 1]; // Imaginary part + } + } + } + } + + return mx_array; +} + +/** + * Convert bsp_matrix_t to MATLAB struct + */ +mxArray* bsp_matrix_to_matlab_struct(bsp_matrix_t* matrix) { + const char* field_names[] = { + "values", "indices_0", "indices_1", "pointers_to_1", "nrows", + "ncols", "nnz", "is_iso", "format", "structure"}; + + mxArray* mx_struct = mxCreateStructMatrix(1, 1, 10, field_names); + + // Convert arrays + mxSetField(mx_struct, 0, "values", bsp_array_to_matlab(&matrix->values)); + mxSetField(mx_struct, 0, "indices_0", + bsp_array_to_matlab(&matrix->indices_0)); + mxSetField(mx_struct, 0, "indices_1", + bsp_array_to_matlab(&matrix->indices_1)); + mxSetField(mx_struct, 0, "pointers_to_1", + bsp_array_to_matlab(&matrix->pointers_to_1)); + + // Convert scalar fields + mxSetField(mx_struct, 0, "nrows", + mxCreateDoubleScalar((double) matrix->nrows)); + mxSetField(mx_struct, 0, "ncols", + mxCreateDoubleScalar((double) matrix->ncols)); + mxSetField(mx_struct, 0, "nnz", mxCreateDoubleScalar((double) matrix->nnz)); + mxSetField(mx_struct, 0, "is_iso", mxCreateLogicalScalar(matrix->is_iso)); + + // Convert format string + mxSetField(mx_struct, 0, "format", + mxCreateString(bsp_get_matrix_format_string(matrix->format))); + + // Convert structure string + mxSetField(mx_struct, 0, "structure", + mxCreateString(bsp_get_structure_string(matrix->structure))); + + return mx_struct; +} + +/** + * Main MEX function entry point + */ +void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { + char* filename = NULL; + char* group = NULL; + bsp_matrix_t matrix; + bsp_error_t error; + + // Check input arguments + if (nrhs < 1 || nrhs > 2) { + mexErrMsgIdAndTxt("BinSparse:InvalidArgs", + "Usage: matrix = binsparse_read(filename [, group])"); + } + + if (nlhs > 1) { + mexErrMsgIdAndTxt("BinSparse:TooManyOutputs", "Too many output arguments"); + } + + // Get filename + if (!mxIsChar(prhs[0])) { + mexErrMsgIdAndTxt("BinSparse:InvalidFilename", "Filename must be a string"); + } + + filename = mxArrayToString(prhs[0]); + if (!filename) { + mexErrMsgIdAndTxt("BinSparse:MemoryError", + "Failed to convert filename string"); + } + + // Get optional group name + if (nrhs == 2) { + if (!mxIsChar(prhs[1])) { + mxFree(filename); + mexErrMsgIdAndTxt("BinSparse:InvalidGroup", + "Group name must be a string"); + } + + group = mxArrayToString(prhs[1]); + if (!group) { + mxFree(filename); + mexErrMsgIdAndTxt("BinSparse:MemoryError", + "Failed to convert group string"); + } + } + + // Read the matrix using Binsparse + error = + bsp_read_matrix_allocator(&matrix, filename, group, bsp_matlab_allocator); + + if (error != BSP_SUCCESS) { + // Clean up + if (filename) + mxFree(filename); + if (group) + mxFree(group); + + mexErrMsgIdAndTxt("BinSparse:ReadError", "Failed to read matrix: %s", + bsp_get_error_string(error)); + } + + // Convert to MATLAB struct + plhs[0] = bsp_matrix_to_matlab_struct(&matrix); + + // Clean up + bsp_destroy_matrix_t(&matrix); + + if (filename) + mxFree(filename); + if (group) + mxFree(group); +} diff --git a/bindings/matlab/binsparse_write.c b/bindings/matlab/binsparse_write.c new file mode 100644 index 0000000..9e56bd4 --- /dev/null +++ b/bindings/matlab/binsparse_write.c @@ -0,0 +1,357 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + * binsparse_write.c - Write MATLAB structs to Binsparse format + * + * This MEX function writes MATLAB structs (compatible with bsp_matrix_create) + * to Binsparse format files. + * + * Usage in MATLAB/Octave: + * binsparse_write(filename, matrix) + * binsparse_write(filename, matrix, group) + * binsparse_write(filename, matrix, group, json_string) + * binsparse_write(filename, matrix, group, json_string, compression_level) + */ + +#include "mex.h" +#include +#include + +/** + * Convert MATLAB array to bsp_array_t + */ +bsp_error_t matlab_to_bsp_array(const mxArray* mx_array, bsp_array_t* array) { + if (mxIsEmpty(mx_array)) { + bsp_construct_default_array_t(array); + return BSP_SUCCESS; + } + + size_t size = mxGetNumberOfElements(mx_array); + mxClassID class_id = mxGetClassID(mx_array); + bool is_complex = mxIsComplex(mx_array); + + // Determine BSP type from MATLAB class + bsp_type_t bsp_type; + size_t element_size; + + if (is_complex) { + if (class_id == mxDOUBLE_CLASS) { + bsp_type = BSP_COMPLEX_FLOAT64; + element_size = sizeof(double _Complex); + } else if (class_id == mxSINGLE_CLASS) { + bsp_type = BSP_COMPLEX_FLOAT32; + element_size = sizeof(float _Complex); + } else { + return BSP_INVALID_TYPE; + } + } else { + switch (class_id) { + case mxDOUBLE_CLASS: + bsp_type = BSP_FLOAT64; + element_size = sizeof(double); + break; + case mxSINGLE_CLASS: + bsp_type = BSP_FLOAT32; + element_size = sizeof(float); + break; + case mxUINT64_CLASS: + bsp_type = BSP_UINT64; + element_size = sizeof(uint64_t); + break; + case mxUINT32_CLASS: + bsp_type = BSP_UINT32; + element_size = sizeof(uint32_t); + break; + case mxUINT16_CLASS: + bsp_type = BSP_UINT16; + element_size = sizeof(uint16_t); + break; + case mxUINT8_CLASS: + bsp_type = BSP_UINT8; + element_size = sizeof(uint8_t); + break; + case mxINT64_CLASS: + bsp_type = BSP_INT64; + element_size = sizeof(int64_t); + break; + case mxINT32_CLASS: + bsp_type = BSP_INT32; + element_size = sizeof(int32_t); + break; + case mxINT16_CLASS: + bsp_type = BSP_INT16; + element_size = sizeof(int16_t); + break; + case mxINT8_CLASS: + bsp_type = BSP_INT8; + element_size = sizeof(int8_t); + break; + default: + return BSP_INVALID_TYPE; + } + } + + // Allocate BSP array + bsp_error_t error = bsp_construct_array_t(array, size, bsp_type); + if (error != BSP_SUCCESS) { + return error; + } + + // Copy data + if (is_complex) { + // Handle complex numbers: interleave real/imaginary parts + if (class_id == mxDOUBLE_CLASS) { + double* real_data = mxGetPr(mx_array); + double* imag_data = mxGetPi(mx_array); + double* out_data = (double*) array->data; + for (size_t i = 0; i < size; i++) { + out_data[2 * i] = real_data[i]; // Real part + out_data[2 * i + 1] = imag_data[i]; // Imaginary part + } + } else { // mxSINGLE_CLASS + float* real_data = (float*) mxGetData(mx_array); + float* imag_data = (float*) mxGetImagData(mx_array); + float* out_data = (float*) array->data; + for (size_t i = 0; i < size; i++) { + out_data[2 * i] = real_data[i]; // Real part + out_data[2 * i + 1] = imag_data[i]; // Imaginary part + } + } + } else { + // Simple copy for real types + memcpy(array->data, mxGetData(mx_array), size * element_size); + } + + return BSP_SUCCESS; +} + +/** + * Convert MATLAB struct to bsp_matrix_t + */ +bsp_error_t matlab_struct_to_bsp_matrix(const mxArray* mx_struct, + bsp_matrix_t* matrix) { + bsp_construct_default_matrix_t(matrix); + + // Extract and convert arrays + mxArray* values_field = mxGetField(mx_struct, 0, "values"); + mxArray* indices_0_field = mxGetField(mx_struct, 0, "indices_0"); + mxArray* indices_1_field = mxGetField(mx_struct, 0, "indices_1"); + mxArray* pointers_to_1_field = mxGetField(mx_struct, 0, "pointers_to_1"); + + if (!values_field || !indices_0_field || !indices_1_field || + !pointers_to_1_field) { + bsp_destroy_matrix_t(matrix); + return BSP_INVALID_STRUCTURE; + } + + bsp_error_t error; + error = matlab_to_bsp_array(values_field, &matrix->values); + if (error != BSP_SUCCESS) { + bsp_destroy_matrix_t(matrix); + return error; + } + + error = matlab_to_bsp_array(indices_0_field, &matrix->indices_0); + if (error != BSP_SUCCESS) { + bsp_destroy_matrix_t(matrix); + return error; + } + + error = matlab_to_bsp_array(indices_1_field, &matrix->indices_1); + if (error != BSP_SUCCESS) { + bsp_destroy_matrix_t(matrix); + return error; + } + + error = matlab_to_bsp_array(pointers_to_1_field, &matrix->pointers_to_1); + if (error != BSP_SUCCESS) { + bsp_destroy_matrix_t(matrix); + return error; + } + + // Extract scalar fields + mxArray* nrows_field = mxGetField(mx_struct, 0, "nrows"); + mxArray* ncols_field = mxGetField(mx_struct, 0, "ncols"); + mxArray* nnz_field = mxGetField(mx_struct, 0, "nnz"); + mxArray* is_iso_field = mxGetField(mx_struct, 0, "is_iso"); + + if (!nrows_field || !ncols_field || !nnz_field || !is_iso_field) { + bsp_destroy_matrix_t(matrix); + return BSP_INVALID_STRUCTURE; + } + + matrix->nrows = (size_t) mxGetScalar(nrows_field); + matrix->ncols = (size_t) mxGetScalar(ncols_field); + matrix->nnz = (size_t) mxGetScalar(nnz_field); + matrix->is_iso = mxIsLogicalScalarTrue(is_iso_field); + + // Extract format string + mxArray* format_field = mxGetField(mx_struct, 0, "format"); + if (!format_field || !mxIsChar(format_field)) { + bsp_destroy_matrix_t(matrix); + return BSP_INVALID_STRUCTURE; + } + + char* format_str = mxArrayToString(format_field); + if (!format_str) { + bsp_destroy_matrix_t(matrix); + return BSP_INVALID_STRUCTURE; + } + + matrix->format = bsp_get_matrix_format(format_str); + mxFree(format_str); + + if (matrix->format == BSP_INVALID_FORMAT) { + bsp_destroy_matrix_t(matrix); + return BSP_INVALID_FORMAT; + } + + // Extract structure string + mxArray* structure_field = mxGetField(mx_struct, 0, "structure"); + if (!structure_field || !mxIsChar(structure_field)) { + bsp_destroy_matrix_t(matrix); + return BSP_INVALID_STRUCTURE; + } + + char* structure_str = mxArrayToString(structure_field); + if (!structure_str) { + bsp_destroy_matrix_t(matrix); + return BSP_INVALID_STRUCTURE; + } + + matrix->structure = bsp_get_structure(structure_str); + mxFree(structure_str); + + if (matrix->structure == BSP_INVALID_STRUCTURE) { + matrix->structure = BSP_GENERAL; // Default fallback + } + + return BSP_SUCCESS; +} + +/** + * Main MEX function entry point + */ +void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { + char* filename = NULL; + char* group = NULL; + char* json_string = NULL; + int compression_level = 1; // Default compression + bsp_matrix_t matrix; + bsp_error_t error; + + // Check input arguments + if (nrhs < 2 || nrhs > 5) { + mexErrMsgIdAndTxt("BinSparse:InvalidArgs", + "Usage: binsparse_write(filename, matrix [, group [, " + "json_string [, compression_level]]])"); + } + + if (nlhs > 0) { + mexErrMsgIdAndTxt("BinSparse:TooManyOutputs", + "No output arguments expected"); + } + + // Get filename + if (!mxIsChar(prhs[0])) { + mexErrMsgIdAndTxt("BinSparse:InvalidFilename", "Filename must be a string"); + } + + filename = mxArrayToString(prhs[0]); + if (!filename) { + mexErrMsgIdAndTxt("BinSparse:MemoryError", + "Failed to convert filename string"); + } + + // Get matrix struct + if (!mxIsStruct(prhs[1])) { + mxFree(filename); + mexErrMsgIdAndTxt("BinSparse:InvalidMatrix", "Matrix must be a struct"); + } + + // Convert MATLAB struct to bsp_matrix_t + error = matlab_struct_to_bsp_matrix(prhs[1], &matrix); + if (error != BSP_SUCCESS) { + mxFree(filename); + mexErrMsgIdAndTxt("BinSparse:ConversionError", + "Failed to convert MATLAB struct to matrix: %s", + bsp_get_error_string(error)); + } + + // Get optional group name + if (nrhs >= 3 && !mxIsEmpty(prhs[2])) { + if (!mxIsChar(prhs[2])) { + bsp_destroy_matrix_t(&matrix); + mxFree(filename); + mexErrMsgIdAndTxt("BinSparse:InvalidGroup", + "Group name must be a string"); + } + + group = mxArrayToString(prhs[2]); + if (!group) { + bsp_destroy_matrix_t(&matrix); + mxFree(filename); + mexErrMsgIdAndTxt("BinSparse:MemoryError", + "Failed to convert group string"); + } + } + + // Get optional JSON string + if (nrhs >= 4 && !mxIsEmpty(prhs[3])) { + if (!mxIsChar(prhs[3])) { + bsp_destroy_matrix_t(&matrix); + if (group) + mxFree(group); + mxFree(filename); + mexErrMsgIdAndTxt("BinSparse:InvalidJSON", "JSON must be a string"); + } + + json_string = mxArrayToString(prhs[3]); + if (!json_string) { + bsp_destroy_matrix_t(&matrix); + if (group) + mxFree(group); + mxFree(filename); + mexErrMsgIdAndTxt("BinSparse:MemoryError", + "Failed to convert JSON string"); + } + } + + // Get optional compression level + if (nrhs >= 5 && !mxIsEmpty(prhs[4])) { + if (!mxIsNumeric(prhs[4]) || mxIsComplex(prhs[4]) || + mxGetNumberOfElements(prhs[4]) != 1) { + bsp_destroy_matrix_t(&matrix); + if (json_string) + mxFree(json_string); + if (group) + mxFree(group); + mxFree(filename); + mexErrMsgIdAndTxt("BinSparse:InvalidCompression", + "Compression level must be a scalar integer"); + } + + compression_level = (int) mxGetScalar(prhs[4]); + } + + // Write the matrix using Binsparse + error = + bsp_write_matrix(filename, matrix, group, json_string, compression_level); + + // Clean up + bsp_destroy_matrix_t(&matrix); + if (json_string) + mxFree(json_string); + if (group) + mxFree(group); + mxFree(filename); + + if (error != BSP_SUCCESS) { + mexErrMsgIdAndTxt("BinSparse:WriteError", "Failed to write matrix: %s", + bsp_get_error_string(error)); + } +} diff --git a/bindings/matlab/bsp_matrix_create.m b/bindings/matlab/bsp_matrix_create.m new file mode 100644 index 0000000..99a1d56 --- /dev/null +++ b/bindings/matlab/bsp_matrix_create.m @@ -0,0 +1,71 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function matrix = bsp_matrix_create(varargin) +% BSP_MATRIX_CREATE - Create a Binsparse matrix struct +% +% Creates a MATLAB struct analogous to the C bsp_matrix_t structure. +% The struct contains four native MATLAB arrays and metadata fields. +% +% Usage: +% matrix = bsp_matrix_create() % Empty matrix +% matrix = bsp_matrix_create(values, indices_0, indices_1, pointers_to_1, ... +% nrows, ncols, nnz, is_iso, format, structure) +% +% Fields: +% values - MATLAB array of matrix values +% indices_0 - MATLAB array of first dimension indices +% indices_1 - MATLAB array of second dimension indices +% pointers_to_1 - MATLAB array of pointers for compressed formats +% nrows - Number of rows (integer) +% ncols - Number of columns (integer) +% nnz - Number of non-zeros (integer) +% is_iso - Logical indicating if matrix has single value (logical) +% format - Matrix format string ('CSR', 'CSC', 'COO', etc.) +% structure - Matrix structure string ('general', 'symmetric', etc.) +% +% Example: +% % Create empty matrix +% matrix = bsp_matrix_create(); +% +% % Create COO matrix +% values = [1.0, 2.0, 3.0]; +% rows = [1, 2, 3]; +% cols = [1, 2, 3]; +% matrix = bsp_matrix_create(values, rows, cols, [], 3, 3, 3, false, 'COO', 'general'); + +if nargin == 0 + % Create empty/default matrix + matrix = struct(... + 'values', double([]), ... + 'indices_0', uint64([]), ... + 'indices_1', uint64([]), ... + 'pointers_to_1', uint64([]), ... + 'nrows', uint64(0), ... + 'ncols', uint64(0), ... + 'nnz', uint64(0), ... + 'is_iso', false, ... + 'format', '', ... + 'structure', 'general'); + +elseif nargin == 10 + % Create matrix with all fields specified + matrix = struct(... + 'values', varargin{1}, ... + 'indices_0', varargin{2}, ... + 'indices_1', varargin{3}, ... + 'pointers_to_1', varargin{4}, ... + 'nrows', uint64(varargin{5}), ... + 'ncols', uint64(varargin{6}), ... + 'nnz', uint64(varargin{7}), ... + 'is_iso', logical(varargin{8}), ... + 'format', char(varargin{9}), ... + 'structure', char(varargin{10})); + +else + error('bsp_matrix_create:InvalidArgs', ... + 'Expected 0 or 10 arguments, got %d', nargin); +end + +end diff --git a/bindings/matlab/bsp_matrix_info.m b/bindings/matlab/bsp_matrix_info.m new file mode 100644 index 0000000..6cce27e --- /dev/null +++ b/bindings/matlab/bsp_matrix_info.m @@ -0,0 +1,51 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function bsp_matrix_info(matrix) +% BSP_MATRIX_INFO - Display information about a Binsparse matrix struct +% +% Usage: +% bsp_matrix_info(matrix) +% +% Displays: +% - Matrix dimensions and number of non-zeros +% - Format and structure information +% - Array sizes and types for each field + +if ~isstruct(matrix) + error('bsp_matrix_info:InvalidInput', 'Input must be a struct'); +end + +% Display basic matrix information +fprintf('%lu x %lu matrix with %lu nnz\n', ... + matrix.nrows, matrix.ncols, matrix.nnz); +fprintf('%s format with %s structure\n', ... + matrix.format, matrix.structure); + +if matrix.is_iso + fprintf('ISO matrix (single value)\n'); +end + +% Display array information +if ~isempty(matrix.values) + fprintf('%lu values of type %s\n', ... + length(matrix.values), class(matrix.values)); +end + +if ~isempty(matrix.indices_0) + fprintf('%lu indices_0 of type %s\n', ... + length(matrix.indices_0), class(matrix.indices_0)); +end + +if ~isempty(matrix.indices_1) + fprintf('%lu indices_1 of type %s\n', ... + length(matrix.indices_1), class(matrix.indices_1)); +end + +if ~isempty(matrix.pointers_to_1) + fprintf('%lu pointers_to_1 of type %s\n', ... + length(matrix.pointers_to_1), class(matrix.pointers_to_1)); +end + +end diff --git a/bindings/matlab/build_matlab_bindings.m b/bindings/matlab/build_matlab_bindings.m new file mode 100644 index 0000000..8816d0f --- /dev/null +++ b/bindings/matlab/build_matlab_bindings.m @@ -0,0 +1,149 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function build_matlab_bindings(varargin) +% BUILD_MATLAB_BINDINGS - Build Binsparse MATLAB MEX functions +% +% This script provides a simple interface to build MATLAB bindings +% for the Binsparse library. It automatically detects include paths +% and sets up the compilation environment. +% +% Usage: +% build_matlab_bindings() % Build all available MEX functions +% build_matlab_bindings('verbose') % Build with verbose output +% build_matlab_bindings('clean') % Clean compiled MEX files +% +% Prerequisites: +% - MATLAB with working MEX compiler (run 'mex -setup' if needed) +% - Binsparse C library headers (in ../../include/) +% +% Note: This script currently builds a simple demonstration MEX function. +% Additional Binsparse functionality can be added by creating more +% MEX wrapper functions. + +% Parse input arguments +verbose = any(strcmpi(varargin, 'verbose')); +clean_only = any(strcmpi(varargin, 'clean')); + +fprintf('=== Binsparse MATLAB Bindings Build Script ===\n\n'); + +if clean_only + fprintf('Cleaning compiled MEX files...\n'); + clean_mex_files(); + return; +end + +% Check MEX compiler +if ~check_mex_compiler() + error('MEX compiler not properly configured. Run "mex -setup" first.'); +end + +% Find and validate paths +paths = get_build_paths(); +if verbose + fprintf('Build paths:\n'); + fprintf(' MATLAB dir: %s\n', paths.matlab_dir); + fprintf(' Include dir: %s\n', paths.include_dir); + fprintf(' Root dir: %s\n', paths.binsparse_root); + fprintf('\n'); +end + +% Compile MEX functions +compile_mex_functions(paths, verbose); + +fprintf('\n=== Build Complete ===\n'); +fprintf('Run the test functions to verify the installation:\n'); +fprintf(' test_binsparse_read()\n'); +fprintf(' test_binsparse_write()\n\n'); + +end + +function success = check_mex_compiler() + % Check if MEX compiler is configured + try + % Try to get MEX configuration + cc = mex.getCompilerConfigurations('C'); + success = ~isempty(cc); + if success + fprintf('MEX compiler found: %s\n', cc(1).Name); + end + catch + success = false; + end +end + +function paths = get_build_paths() + % Get and validate build paths + paths.matlab_dir = pwd; + paths.binsparse_root = fullfile(paths.matlab_dir, '..', '..'); + paths.include_dir = fullfile(paths.binsparse_root, 'include'); + + if ~exist(paths.include_dir, 'dir') + error('Binsparse include directory not found: %s\nEnsure you are running this script from the bindings/matlab directory.', paths.include_dir); + end + + % Check for main header file + main_header = fullfile(paths.include_dir, 'binsparse', 'binsparse.h'); + if ~exist(main_header, 'file') + error('Main Binsparse header not found: %s', main_header); + end +end + +function compile_mex_functions(paths, verbose) + % Compile all MEX functions + + % List of MEX functions to compile + mex_files = {'binsparse_read.c', 'binsparse_write.c'}; + + fprintf('Compiling MEX functions...\n'); + + for i = 1:length(mex_files) + mex_file = mex_files{i}; + if ~exist(mex_file, 'file') + warning('MEX source file not found: %s', mex_file); + continue; + end + + fprintf(' Compiling %s... ', mex_file); + + % Prepare MEX command with library linking + lib_dir = fullfile(paths.binsparse_root, 'build'); + lib_path = fullfile(lib_dir, 'libbinsparse.a'); + cjson_lib = fullfile(lib_dir, '_deps', 'cjson-build', 'libcjson.so'); + + mex_args = {'-I', paths.include_dir, mex_file, lib_path, cjson_lib, '-lhdf5_serial'}; + if verbose + mex_args = [mex_args, {'-v'}]; + end + + try + mex(mex_args{:}); + fprintf('SUCCESS\n'); + catch ME + fprintf('FAILED\n'); + fprintf(' Error: %s\n', ME.message); + end + end +end + +function clean_mex_files() + % Clean compiled MEX files + + % Get MEX file extension for current platform + mex_ext = mexext(); + + % Find and delete MEX files + mex_files = dir(['*.' mex_ext]); + + if isempty(mex_files) + fprintf('No MEX files found to clean.\n'); + return; + end + + fprintf('Removing %d MEX file(s):\n', length(mex_files)); + for i = 1:length(mex_files) + fprintf(' %s\n', mex_files(i).name); + delete(mex_files(i).name); + end +end diff --git a/bindings/matlab/build_octave_bindings.m b/bindings/matlab/build_octave_bindings.m new file mode 100644 index 0000000..87ba30f --- /dev/null +++ b/bindings/matlab/build_octave_bindings.m @@ -0,0 +1,185 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function build_octave_bindings(varargin) +% BUILD_OCTAVE_BINDINGS - Build Binsparse Octave MEX functions +% +% This script provides a simple interface to build Octave bindings +% for the Binsparse library using mkoctfile. It automatically detects +% include paths and sets up the compilation environment. +% +% Usage: +% build_octave_bindings() % Build all available MEX functions +% build_octave_bindings('verbose') % Build with verbose output +% build_octave_bindings('clean') % Clean compiled MEX files +% +% Prerequisites: +% - GNU Octave with mkoctfile (usually included with Octave) +% - Binsparse C library headers (in ../../include/) +% - C compiler (gcc recommended) +% +% Note: This script builds Octave-compatible MEX functions using mkoctfile +% instead of MATLAB's mex command. + +% Parse input arguments +verbose = any(strcmpi(varargin, 'verbose')); +clean_only = any(strcmpi(varargin, 'clean')); + +fprintf('=== Binsparse Octave Bindings Build Script ===\n\n'); + +if clean_only + fprintf('Cleaning compiled MEX files...\n'); + clean_mex_files(); + return; +end + +% Check if we're running in Octave +if ~is_octave() + warning('This script is designed for Octave. For MATLAB, use build_matlab_bindings.m'); +end + +% Check mkoctfile availability +if ~check_mkoctfile() + error('mkoctfile not found. Please ensure Octave is properly installed.'); +end + +% Find and validate paths +paths = get_build_paths(); +if verbose + fprintf('Build paths:\n'); + fprintf(' Current dir: %s\n', paths.current_dir); + fprintf(' Include dir: %s\n', paths.include_dir); + fprintf(' Root dir: %s\n', paths.binsparse_root); + fprintf('\n'); +end + +% Compile MEX functions +compile_octave_functions(paths, verbose); + +fprintf('\n=== Build Complete ===\n'); +fprintf('Run the test functions to verify the installation:\n'); +fprintf(' test_binsparse_read()\n'); +fprintf(' test_binsparse_write()\n\n'); + +end + +function result = is_octave() + % Check if running in Octave + result = exist('OCTAVE_VERSION', 'builtin') ~= 0; +end + +function success = check_mkoctfile() + % Check if mkoctfile is available + try + [status, ~] = system('mkoctfile --version'); + success = (status == 0); + if success && nargout == 0 + fprintf('mkoctfile found and working\n'); + end + catch + success = false; + end +end + +function paths = get_build_paths() + % Get and validate build paths + paths.current_dir = pwd; + paths.binsparse_root = fullfile(paths.current_dir, '..', '..'); + paths.include_dir = fullfile(paths.binsparse_root, 'include'); + + if ~exist(paths.include_dir, 'dir') + error('Binsparse include directory not found: %s\nEnsure you are running this script from the bindings/matlab directory.', paths.include_dir); + end + + % Check for main header file + main_header = fullfile(paths.include_dir, 'binsparse', 'binsparse.h'); + if ~exist(main_header, 'file') + error('Main Binsparse header not found: %s', main_header); + end +end + +function compile_octave_functions(paths, verbose) + % Compile all MEX functions using mkoctfile + + % List of MEX functions to compile + mex_files = {'binsparse_read.c', 'binsparse_write.c'}; + + fprintf('Compiling MEX functions with mkoctfile...\n'); + + for i = 1:length(mex_files) + mex_file = mex_files{i}; + if ~exist(mex_file, 'file') + warning('MEX source file not found: %s', mex_file); + continue; + end + + 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'); + lib_path = fullfile(lib_dir, 'libbinsparse.a'); + cjson_lib_dir = fullfile(lib_dir, '_deps', 'cjson-build'); + + if verbose + cmd = sprintf('mkoctfile --mex --verbose -fPIC %s %s -Wl,--whole-archive %s -Wl,--no-whole-archive -L%s -lcjson -lhdf5_serial', ... + include_flag, mex_file, lib_path, cjson_lib_dir); + else + cmd = sprintf('mkoctfile --mex -fPIC %s %s -Wl,--whole-archive %s -Wl,--no-whole-archive -L%s -lcjson -lhdf5_serial', ... + include_flag, mex_file, lib_path, cjson_lib_dir); + end + + if verbose + fprintf('\n Command: %s\n', cmd); + end + + % Execute mkoctfile + [status, output] = system(cmd); + + if status == 0 + fprintf('SUCCESS\n'); + if verbose && ~isempty(output) + fprintf(' Output: %s\n', output); + end + else + fprintf('FAILED\n'); + fprintf(' Error output:\n%s\n', output); + end + end +end + +function clean_mex_files() + % Clean compiled MEX files (Octave uses different extensions) + + % Octave MEX extensions vary by platform + if ispc + extensions = {'mexw32', 'mexw64'}; + elseif ismac + extensions = {'mexmaci64'}; + else + extensions = {'mexa64', 'mex'}; + end + + found_files = {}; + + % Find files with any of the MEX extensions + for i = 1:length(extensions) + ext = extensions{i}; + files = dir(['*.' ext]); + for j = 1:length(files) + found_files{end+1} = files(j).name; + end + end + + if isempty(found_files) + fprintf('No MEX files found to clean.\n'); + return; + end + + fprintf('Removing %d MEX file(s):\n', length(found_files)); + for i = 1:length(found_files) + fprintf(' %s\n', found_files{i}); + delete(found_files{i}); + end +end diff --git a/bindings/matlab/compile_binsparse_read.m b/bindings/matlab/compile_binsparse_read.m new file mode 100644 index 0000000..c95f93d --- /dev/null +++ b/bindings/matlab/compile_binsparse_read.m @@ -0,0 +1,53 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function compile_binsparse_read() +% COMPILE_BINSPARSE_READ - Quick compilation script for binsparse_read +% +% This script compiles just the binsparse_read MEX function with proper +% library linking. + +fprintf('Compiling binsparse_read MEX function...\n'); + +% Get paths +matlab_dir = pwd; +binsparse_root = fullfile(matlab_dir, '..', '..'); +include_dir = fullfile(binsparse_root, 'include'); +lib_dir = fullfile(binsparse_root, 'build'); + +% Check for required files +lib_path = fullfile(lib_dir, 'libbinsparse.a'); +cjson_lib = fullfile(lib_dir, '_deps', 'cjson-build', 'libcjson.so'); + +if ~exist(lib_path, 'file') + error('libbinsparse.a not found at: %s\nBuild the library first with cmake.', lib_path); +end + +if ~exist(cjson_lib, 'file') + error('libcjson.so not found at: %s\nBuild the library first with cmake.', cjson_lib); +end + +fprintf('Using libraries:\n'); +fprintf(' libbinsparse.a: %s\n', lib_path); +fprintf(' libcjson.so: %s\n', cjson_lib); + +try + % Compile with linking + mex('-I', include_dir, 'binsparse_read.c', lib_path, cjson_lib, '-lhdf5_serial', '-v'); + fprintf('Successfully compiled binsparse_read!\n'); + + % Test if it loads + fprintf('Testing MEX function...\n'); + if exist('binsparse_read', 'file') + fprintf('binsparse_read MEX function is ready to use.\n'); + else + warning('MEX function compiled but not found in path.'); + end + +catch ME + fprintf('Compilation failed: %s\n', ME.message); + rethrow(ME); +end + +end diff --git a/bindings/matlab/compile_binsparse_read_octave.m b/bindings/matlab/compile_binsparse_read_octave.m new file mode 100644 index 0000000..110f0db --- /dev/null +++ b/bindings/matlab/compile_binsparse_read_octave.m @@ -0,0 +1,76 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function compile_binsparse_read_octave() +% COMPILE_BINSPARSE_READ_OCTAVE - Quick Octave compilation for binsparse_read +% +% This script compiles just the binsparse_read MEX function using mkoctfile +% with proper library linking for Octave. + +fprintf('Compiling binsparse_read MEX function for Octave...\n'); + +% Check if we're in Octave +if ~(exist('OCTAVE_VERSION', 'builtin') ~= 0) + warning('This script is designed for Octave. For MATLAB, use compile_binsparse_read.m'); +end + +% Get paths +matlab_dir = pwd; +binsparse_root = fullfile(matlab_dir, '..', '..'); +include_dir = fullfile(binsparse_root, 'include'); +lib_dir = fullfile(binsparse_root, 'build'); + +% Check for required files +lib_path = fullfile(lib_dir, 'libbinsparse.a'); +cjson_lib_path = fullfile(lib_dir, '_deps', 'cjson-build', 'libcjson.a'); + +if ~exist(lib_path, 'file') + error('libbinsparse.a not found at: %s\nBuild the library first with cmake.', lib_path); +end + +if ~exist(cjson_lib_path, 'file') + error('libcjson.a not found at: %s\nRebuild with static cJSON support.', cjson_lib_path); +end + +fprintf('Using libraries:\n'); +fprintf(' libbinsparse.a: %s\n', lib_path); +fprintf(' libcjson.a: %s\n', cjson_lib_path); + +% Build command for mkoctfile with -fPIC and proper static linking +cmd = sprintf('mkoctfile --mex -fPIC -I%s binsparse_read.c -Wl,--whole-archive %s %s -Wl,--no-whole-archive -lhdf5_serial', ... + include_dir, lib_path, cjson_lib_path); + +fprintf('Running: %s\n', cmd); + +try + % Execute mkoctfile + [status, output] = system(cmd); + + if status == 0 + fprintf('Successfully compiled binsparse_read!\n'); + if ~isempty(output) + fprintf('Output: %s\n', output); + end + + % Test if it loads + fprintf('Testing MEX function...\n'); + if exist('binsparse_read', 'file') + fprintf('binsparse_read MEX function is ready to use.\n'); + else + warning('MEX function compiled but not found in path.'); + end + else + fprintf('Compilation failed with status %d\n', status); + if ~isempty(output) + fprintf('Error output: %s\n', output); + end + error('mkoctfile compilation failed'); + end + +catch ME + fprintf('Compilation failed: %s\n', ME.message); + rethrow(ME); +end + +end diff --git a/bindings/matlab/compile_binsparse_write.m b/bindings/matlab/compile_binsparse_write.m new file mode 100644 index 0000000..d62bb08 --- /dev/null +++ b/bindings/matlab/compile_binsparse_write.m @@ -0,0 +1,53 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function compile_binsparse_write() +% COMPILE_BINSPARSE_WRITE - Quick compilation script for binsparse_write +% +% This script compiles just the binsparse_write MEX function with proper +% library linking. + +fprintf('Compiling binsparse_write MEX function...\n'); + +% Get paths +matlab_dir = pwd; +binsparse_root = fullfile(matlab_dir, '..', '..'); +include_dir = fullfile(binsparse_root, 'include'); +lib_dir = fullfile(binsparse_root, 'build'); + +% Check for required files +lib_path = fullfile(lib_dir, 'libbinsparse.a'); +cjson_lib = fullfile(lib_dir, '_deps', 'cjson-build', 'libcjson.so'); + +if ~exist(lib_path, 'file') + error('libbinsparse.a not found at: %s\nBuild the library first with cmake.', lib_path); +end + +if ~exist(cjson_lib, 'file') + error('libcjson.so not found at: %s\nBuild the library first with cmake.', cjson_lib); +end + +fprintf('Using libraries:\n'); +fprintf(' libbinsparse.a: %s\n', lib_path); +fprintf(' libcjson.so: %s\n', cjson_lib); + +try + % Compile with linking + mex('-I', include_dir, 'binsparse_write.c', lib_path, cjson_lib, '-lhdf5_serial', '-v'); + fprintf('Successfully compiled binsparse_write!\n'); + + % Test if it loads + fprintf('Testing MEX function...\n'); + if exist('binsparse_write', 'file') + fprintf('binsparse_write MEX function is ready to use.\n'); + else + warning('MEX function compiled but not found in path.'); + end + +catch ME + fprintf('Compilation failed: %s\n', ME.message); + rethrow(ME); +end + +end diff --git a/bindings/matlab/compile_binsparse_write_octave.m b/bindings/matlab/compile_binsparse_write_octave.m new file mode 100644 index 0000000..d1fa4ea --- /dev/null +++ b/bindings/matlab/compile_binsparse_write_octave.m @@ -0,0 +1,76 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function compile_binsparse_write_octave() +% COMPILE_BINSPARSE_WRITE_OCTAVE - Quick Octave compilation for binsparse_write +% +% This script compiles just the binsparse_write MEX function using mkoctfile +% with proper library linking for Octave. + +fprintf('Compiling binsparse_write MEX function for Octave...\n'); + +% Check if we're in Octave +if ~(exist('OCTAVE_VERSION', 'builtin') ~= 0) + warning('This script is designed for Octave. For MATLAB, use compile_binsparse_write.m'); +end + +% Get paths +matlab_dir = pwd; +binsparse_root = fullfile(matlab_dir, '..', '..'); +include_dir = fullfile(binsparse_root, 'include'); +lib_dir = fullfile(binsparse_root, 'build'); + +% Check for required files +lib_path = fullfile(lib_dir, 'libbinsparse.a'); +cjson_lib_path = fullfile(lib_dir, '_deps', 'cjson-build', 'libcjson.a'); + +if ~exist(lib_path, 'file') + error('libbinsparse.a not found at: %s\nBuild the library first with cmake.', lib_path); +end + +if ~exist(cjson_lib_path, 'file') + error('libcjson.a not found at: %s\nRebuild with static cJSON support.', cjson_lib_path); +end + +fprintf('Using libraries:\n'); +fprintf(' libbinsparse.a: %s\n', lib_path); +fprintf(' libcjson.a: %s\n', cjson_lib_path); + +% Build command for mkoctfile with -fPIC and proper static linking +cmd = sprintf('mkoctfile --mex -fPIC -I%s binsparse_write.c -Wl,--whole-archive %s %s -Wl,--no-whole-archive -lhdf5_serial', ... + include_dir, lib_path, cjson_lib_path); + +fprintf('Running: %s\n', cmd); + +try + % Execute mkoctfile + [status, output] = system(cmd); + + if status == 0 + fprintf('Successfully compiled binsparse_write!\n'); + if ~isempty(output) + fprintf('Output: %s\n', output); + end + + % Test if it loads + fprintf('Testing MEX function...\n'); + if exist('binsparse_write', 'file') + fprintf('binsparse_write MEX function is ready to use.\n'); + else + warning('MEX function compiled but not found in path.'); + end + else + fprintf('Compilation failed with status %d\n', status); + if ~isempty(output) + fprintf('Error output: %s\n', output); + end + error('mkoctfile compilation failed'); + end + +catch ME + fprintf('Compilation failed: %s\n', ME.message); + rethrow(ME); +end + +end diff --git a/bindings/matlab/compile_octave.sh b/bindings/matlab/compile_octave.sh new file mode 100755 index 0000000..641edf2 --- /dev/null +++ b/bindings/matlab/compile_octave.sh @@ -0,0 +1,181 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: 2024 Binsparse Developers +# +# SPDX-License-Identifier: BSD-3-Clause + +# compile_octave.sh - Build Binsparse Octave MEX functions from command line +# +# This script compiles the Binsparse MEX functions for Octave using mkoctfile +# from the command line, without needing to start Octave first. +# +# Usage: +# ./compile_octave.sh +# ./compile_octave.sh --verbose +# ./compile_octave.sh --clean + +set -e # Exit on any error + +# Color output for better readability +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Parse command line arguments +VERBOSE=false +CLEAN=false + +for arg in "$@"; do + case $arg in + --verbose|-v) + VERBOSE=true + shift + ;; + --clean|-c) + CLEAN=true + shift + ;; + --help|-h) + echo "Usage: $0 [--verbose] [--clean] [--help]" + echo " --verbose, -v: Enable verbose output" + echo " --clean, -c: Clean compiled MEX files" + echo " --help, -h: Show this help message" + exit 0 + ;; + *) + print_error "Unknown option: $arg" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +print_info "Binsparse Octave MEX Compilation Script" +echo "========================================" + +# Check if mkoctfile is available +if ! command -v mkoctfile &> /dev/null; then + print_error "mkoctfile not found. Please install GNU Octave." + exit 1 +fi + +mkoctfile_version=$(mkoctfile --version | head -n1) +print_info "Found mkoctfile: $mkoctfile_version" + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +print_info "Working directory: $SCRIPT_DIR" + +# Find Binsparse include directory +BINSPARSE_ROOT="$(realpath "$SCRIPT_DIR/../..")" +INCLUDE_DIR="$BINSPARSE_ROOT/include" + +if [ ! -d "$INCLUDE_DIR" ]; then + print_error "Binsparse include directory not found: $INCLUDE_DIR" + print_error "Make sure you're running this script from the bindings/matlab directory" + exit 1 +fi + +if [ ! -f "$INCLUDE_DIR/binsparse/binsparse.h" ]; then + print_error "Main Binsparse header not found: $INCLUDE_DIR/binsparse/binsparse.h" + exit 1 +fi + +print_info "Using Binsparse include directory: $INCLUDE_DIR" + +# Change to script directory +cd "$SCRIPT_DIR" + +# Clean function +clean_mex_files() { + print_info "Cleaning compiled MEX files..." + + # MEX file extensions for different platforms + extensions=("mex" "mexa64" "mexw32" "mexw64" "mexmaci64") + + found_files=0 + for ext in "${extensions[@]}"; do + if ls *.${ext} 1> /dev/null 2>&1; then + for file in *.${ext}; do + print_info "Removing $file" + rm "$file" + ((found_files++)) + done + fi + done + + if [ $found_files -eq 0 ]; then + print_info "No MEX files found to clean" + else + print_success "Cleaned $found_files MEX file(s)" + fi +} + +# Handle clean option +if [ "$CLEAN" = true ]; then + clean_mex_files + exit 0 +fi + +# List of MEX files to compile +MEX_FILES=("binsparse_read.c" "binsparse_write.c") + +print_info "Compiling MEX functions..." + +# Compile each MEX file +for mex_file in "${MEX_FILES[@]}"; do + if [ ! -f "$mex_file" ]; then + print_warning "MEX source file not found: $mex_file" + continue + fi + + print_info "Compiling $mex_file..." + + # Build mkoctfile command with library linking + LIB_DIR="$BINSPARSE_ROOT/build" + LIB_PATH="$LIB_DIR/libbinsparse.a" + CJSON_LIB_DIR="$LIB_DIR/_deps/cjson-build" + + CMD="mkoctfile --mex -fPIC -I\"$INCLUDE_DIR\" $mex_file -Wl,--whole-archive \"$LIB_PATH\" -Wl,--no-whole-archive -L\"$CJSON_LIB_DIR\" -lcjson -lhdf5_serial" + + if [ "$VERBOSE" = true ]; then + CMD="$CMD --verbose" + print_info "Command: $CMD" + fi + + # Execute compilation + if eval $CMD; then + print_success "Successfully compiled $mex_file" + else + print_error "Failed to compile $mex_file" + exit 1 + fi +done + +print_success "All MEX functions compiled successfully!" +echo "" +print_info "To test the functions, start Octave and run:" +echo " test_binsparse_read()" +echo " test_binsparse_write()" +echo "" +print_info "Or test from command line:" +echo " octave --eval \"test_binsparse_read()\"" diff --git a/bindings/matlab/test_binsparse_read.m b/bindings/matlab/test_binsparse_read.m new file mode 100644 index 0000000..dc6e479 --- /dev/null +++ b/bindings/matlab/test_binsparse_read.m @@ -0,0 +1,71 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function test_binsparse_read() +% TEST_BINSPARSE_READ - Test the binsparse_read MEX function +% +% This function demonstrates how to use the binsparse_read MEX function +% to read Binsparse format matrices into MATLAB/Octave. + +fprintf('=== Testing Binsparse Read Function ===\n\n'); + +% Check if the MEX function exists +if ~exist('binsparse_read', 'file') + error('binsparse_read MEX function not found. Build it first.'); +end + +try + % Test error handling with invalid inputs + fprintf('Test 1: Error handling\n'); + + try + matrix = binsparse_read(); % No arguments + fprintf('FAILED - Should have thrown error\n'); + catch ME + fprintf(' Correctly caught error for no arguments\n'); + end + + try + matrix = binsparse_read(123); % Non-string argument + fprintf('FAILED - Should have thrown error\n'); + catch ME + fprintf(' Correctly caught error for invalid filename type\n'); + end + + try + matrix = binsparse_read('nonexistent_file.h5'); % File doesn't exist + fprintf('FAILED - Should have thrown error\n'); + catch ME + fprintf(' Correctly caught error for nonexistent file\n'); + end + + fprintf(' Status: PASS\n\n'); + + fprintf('=== Basic Error Handling Tests Passed ===\n'); + fprintf('To test actual file reading, you need a valid Binsparse file.\n\n'); + + fprintf('Usage examples:\n'); + fprintf(' matrix = binsparse_read(''myfile.h5''); %% Read from HDF5\n'); + fprintf(' matrix = binsparse_read(''myfile.h5'', ''group''); %% Read from specific group\n'); + fprintf(' matrix = binsparse_read(''myfile.mtx''); %% Read Matrix Market file\n\n'); + + fprintf('The returned matrix will have these fields:\n'); + fprintf(' matrix.values - Values array\n'); + fprintf(' matrix.indices_0 - First dimension indices\n'); + fprintf(' matrix.indices_1 - Second dimension indices\n'); + fprintf(' matrix.pointers_to_1 - Pointers for compressed formats\n'); + fprintf(' matrix.nrows - Number of rows\n'); + fprintf(' matrix.ncols - Number of columns\n'); + fprintf(' matrix.nnz - Number of non-zeros\n'); + fprintf(' matrix.is_iso - ISO matrix flag\n'); + fprintf(' matrix.format - Matrix format string\n'); + fprintf(' matrix.structure - Matrix structure string\n'); + +catch ME + fprintf('=== TEST FAILED ===\n'); + fprintf('Error: %s\n', ME.message); + rethrow(ME); +end + +end diff --git a/bindings/matlab/test_binsparse_write.m b/bindings/matlab/test_binsparse_write.m new file mode 100644 index 0000000..54ca6be --- /dev/null +++ b/bindings/matlab/test_binsparse_write.m @@ -0,0 +1,122 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function test_binsparse_write() +% TEST_BINSPARSE_WRITE - Test the binsparse_write MEX function +% +% This function creates a simple test matrix and writes it to a temporary +% Binsparse file using the binsparse_write MEX function. + +fprintf('=== Testing binsparse_write MEX function ===\n\n'); + +% Check if binsparse_write exists +if ~exist('binsparse_write', 'file') + error('binsparse_write MEX function not found. Please compile it first.'); +end + +try + % Create a simple test matrix in the expected struct format + fprintf('Creating test matrix...\n'); + + % Simple 3x3 COO matrix: + % [1.0, 0.0, 2.0] + % [0.0, 3.0, 0.0] + % [4.0, 0.0, 5.0] + + matrix = struct(); + matrix.values = [1.0; 3.0; 2.0; 4.0; 5.0]; % Values in COO order + matrix.indices_0 = uint64([0; 1; 0; 2; 2]); % Row indices (0-based) + matrix.indices_1 = uint64([0; 1; 2; 0; 2]); % Column indices (0-based) + matrix.pointers_to_1 = uint64([]); % Empty for COO format + matrix.nrows = 3; + matrix.ncols = 3; + matrix.nnz = 5; + matrix.is_iso = false; + matrix.format = 'COO'; + matrix.structure = 'general'; + + fprintf('Matrix created:\n'); + fprintf(' Format: %s\n', matrix.format); + fprintf(' Size: %dx%d\n', matrix.nrows, matrix.ncols); + fprintf(' NNZ: %d\n', matrix.nnz); + fprintf(' Structure: %s\n', matrix.structure); + + % Create temporary filename + temp_file = tempname(); + temp_file = [temp_file, '.bsp.h5']; + + fprintf('\nWriting matrix to: %s\n', temp_file); + + % Test basic write + binsparse_write(temp_file, matrix); + fprintf('✓ Basic write successful\n'); + + % Test with group + binsparse_write(temp_file, matrix, 'test_group'); + fprintf('✓ Write with group successful\n'); + + % Test with group and JSON metadata + json_metadata = '{"test": "metadata", "created_by": "test_binsparse_write"}'; + binsparse_write(temp_file, matrix, 'test_group_json', json_metadata); + fprintf('✓ Write with group and JSON successful\n'); + + % Test with all parameters + binsparse_write(temp_file, matrix, 'test_group_full', json_metadata, 6); + fprintf('✓ Write with all parameters successful\n'); + + % Verify file exists and has reasonable size + if exist(temp_file, 'file') + info = dir(temp_file); + fprintf('✓ Output file created (size: %d bytes)\n', info.bytes); + else + error('Output file was not created'); + end + + % Test round-trip if binsparse_read is available + if exist('binsparse_read', 'file') + fprintf('\nTesting round-trip (write → read)...\n'); + + % Read back the matrix + read_matrix = binsparse_read(temp_file, 'test_group_full'); + + % Basic verification + if read_matrix.nrows == matrix.nrows && ... + read_matrix.ncols == matrix.ncols && ... + read_matrix.nnz == matrix.nnz + fprintf('✓ Round-trip dimensions match\n'); + else + warning('Round-trip dimensions mismatch'); + end + + if strcmp(read_matrix.format, matrix.format) && ... + strcmp(read_matrix.structure, matrix.structure) + fprintf('✓ Round-trip format/structure match\n'); + else + warning('Round-trip format/structure mismatch'); + end + else + fprintf('\nSkipping round-trip test (binsparse_read not available)\n'); + end + + % Clean up + if exist(temp_file, 'file') + delete(temp_file); + fprintf('✓ Temporary file cleaned up\n'); + end + + fprintf('\n=== All tests passed! ===\n'); + fprintf('binsparse_write is working correctly.\n\n'); + +catch ME + % Clean up on error + if exist('temp_file', 'var') && exist(temp_file, 'file') + delete(temp_file); + end + + fprintf('\n=== Test failed ===\n'); + fprintf('Error: %s\n', ME.message); + rethrow(ME); +end + +end diff --git a/bindings/matlab/test_bsp_matrix_struct.m b/bindings/matlab/test_bsp_matrix_struct.m new file mode 100644 index 0000000..f3f3062 --- /dev/null +++ b/bindings/matlab/test_bsp_matrix_struct.m @@ -0,0 +1,76 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function test_bsp_matrix_struct() +% TEST_BSP_MATRIX_STRUCT - Test the Binsparse matrix struct functionality +% +% This function demonstrates and tests the basic MATLAB struct +% that mirrors the C bsp_matrix_t structure. + +fprintf('=== Testing Binsparse Matrix Struct ===\n\n'); + +try + % Test 1: Create empty matrix + fprintf('Test 1: Creating empty matrix\n'); + empty_matrix = bsp_matrix_create(); + fprintf('Empty matrix created successfully\n'); + bsp_matrix_info(empty_matrix); + fprintf('\n'); + + % Test 2: Create simple COO matrix + fprintf('Test 2: Creating simple COO matrix\n'); + % 3x3 identity matrix in COO format + values = [1.0, 1.0, 1.0]; + rows = uint64([0, 1, 2]); % 0-based indexing like C + cols = uint64([0, 1, 2]); % 0-based indexing like C + pointers = uint64([]); % Empty for COO format + + coo_matrix = bsp_matrix_create(values, rows, cols, pointers, ... + 3, 3, 3, false, 'COO', 'general'); + fprintf('COO matrix created successfully\n'); + bsp_matrix_info(coo_matrix); + fprintf('\n'); + + % Test 3: Create CSR matrix + fprintf('Test 3: Creating simple CSR matrix\n'); + % Same 3x3 identity in CSR format + csr_values = [1.0, 1.0, 1.0]; + csr_cols = uint64([0, 1, 2]); + csr_rows = uint64([]); % Not used in CSR + csr_ptrs = uint64([0, 1, 2, 3]); % Row pointers + + csr_matrix = bsp_matrix_create(csr_values, csr_rows, csr_cols, csr_ptrs, ... + 3, 3, 3, false, 'CSR', 'general'); + fprintf('CSR matrix created successfully\n'); + bsp_matrix_info(csr_matrix); + fprintf('\n'); + + % Test 4: Test field access + fprintf('Test 4: Testing field access\n'); + fprintf('Matrix format: %s\n', csr_matrix.format); + fprintf('Matrix structure: %s\n', csr_matrix.structure); + fprintf('Is ISO: %s\n', mat2str(csr_matrix.is_iso)); + fprintf('First value: %.1f\n', csr_matrix.values(1)); + fprintf('\n'); + + % Test 5: Test error handling + fprintf('Test 5: Testing error handling\n'); + try + invalid_matrix = bsp_matrix_create(1, 2, 3); % Wrong number of args + fprintf('FAILED - Should have thrown error\n'); + catch ME + fprintf('Successfully caught error: %s\n', ME.message); + end + fprintf('\n'); + + fprintf('=== All Tests Passed ===\n'); + fprintf('The Binsparse matrix struct is working correctly!\n'); + +catch ME + fprintf('=== TEST FAILED ===\n'); + fprintf('Error: %s\n', ME.message); + rethrow(ME); +end + +end diff --git a/src/write_matrix.c b/src/write_matrix.c index d87244a..7d1956d 100644 --- a/src/write_matrix.c +++ b/src/write_matrix.c @@ -137,9 +137,6 @@ bsp_error_t bsp_write_matrix_to_group(hid_t f, bsp_matrix_t matrix, const char* user_json, int compression_level) { cJSON* user_json_cjson = cJSON_Parse(user_json); - if (user_json_cjson == NULL) { - return BSP_ERROR_FORMAT; - } bsp_error_t error = bsp_write_matrix_to_group_cjson( f, matrix, user_json_cjson, compression_level); cJSON_Delete(user_json_cjson); @@ -154,6 +151,7 @@ bsp_error_t bsp_write_matrix_cjson(const char* fname, bsp_matrix_t matrix, bsp_error_t error = bsp_write_matrix_to_group_cjson(f, matrix, user_json, compression_level); if (error != BSP_SUCCESS) { + printf("AGH! HDF5!\n"); H5Fclose(f); return error; } @@ -169,6 +167,7 @@ bsp_error_t bsp_write_matrix_cjson(const char* fname, bsp_matrix_t matrix, bsp_error_t error = bsp_write_matrix_to_group_cjson(g, matrix, user_json, compression_level); if (error != BSP_SUCCESS) { + printf("AGH! Inner write!\n"); H5Gclose(g); H5Fclose(f); return error; @@ -183,9 +182,7 @@ bsp_error_t bsp_write_matrix(const char* fname, bsp_matrix_t matrix, const char* group, const char* user_json, int compression_level) { cJSON* user_json_cjson = cJSON_Parse(user_json); - if (user_json_cjson == NULL) { - return BSP_ERROR_FORMAT; - } + bsp_error_t error = bsp_write_matrix_cjson( fname, matrix, group, user_json_cjson, compression_level); cJSON_Delete(user_json_cjson);