From 35bc5cbdc418e06a968543330a90de8b33510dd5 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Tue, 19 Aug 2025 20:52:01 -0700 Subject: [PATCH 1/5] Some Matlab files --- .gitignore | 7 + bindings/matlab/README.md | 236 +++++++++++++++++++++++ bindings/matlab/bsp_hello.c | 103 ++++++++++ bindings/matlab/bsp_matrix_create.m | 71 +++++++ bindings/matlab/bsp_matrix_info.m | 51 +++++ bindings/matlab/compile_bsp_hello.m | 57 ++++++ bindings/matlab/compile_octave.sh | 176 +++++++++++++++++ bindings/matlab/test_bsp_hello.m | 79 ++++++++ bindings/matlab/test_bsp_hello_octave.m | 98 ++++++++++ bindings/matlab/test_bsp_matrix_struct.m | 76 ++++++++ 10 files changed, 954 insertions(+) create mode 100644 bindings/matlab/README.md create mode 100644 bindings/matlab/bsp_hello.c create mode 100644 bindings/matlab/bsp_matrix_create.m create mode 100644 bindings/matlab/bsp_matrix_info.m create mode 100644 bindings/matlab/compile_bsp_hello.m create mode 100755 bindings/matlab/compile_octave.sh create mode 100644 bindings/matlab/test_bsp_hello.m create mode 100644 bindings/matlab/test_bsp_hello_octave.m create mode 100644 bindings/matlab/test_bsp_matrix_struct.m diff --git a/.gitignore b/.gitignore index 8dddad7..2a41683 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,10 @@ build* compile_flags.txt ._* tensor_test_files + +# MATLAB/Octave MEX files +*.mex +*.mexa64 +*.mexw32 +*.mexw64 +*.mexmaci64 diff --git a/bindings/matlab/README.md b/bindings/matlab/README.md new file mode 100644 index 0000000..368d479 --- /dev/null +++ b/bindings/matlab/README.md @@ -0,0 +1,236 @@ + + +# 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_bsp_hello() + ``` + +#### 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_bsp_hello_octave() + ``` + +#### 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_bsp_hello_octave()" + ``` + +## Usage Examples + +### Basic Usage + +**In MATLAB or Octave:** + +```matlab +% Simple greeting +result = bsp_hello() +% Output: 'Binsparse MEX binding is working!' + +% Get Binsparse version +[version, success] = bsp_hello('version') +% Output: version = '0.1', success = true +``` + +### Error Handling + +The MEX functions include proper error handling: + +```matlab +try + result = bsp_hello('invalid_mode') +catch ME + fprintf('Error: %s\n', ME.message) +end +``` + +## Files Description + +| File | Description | +|------|-------------| +| `bsp_hello.c` | Simple MEX function demonstrating Binsparse integration | +| `build_matlab_bindings.m` | Main build script for MATLAB MEX functions | +| `build_octave_bindings.m` | Main build script for Octave MEX functions | +| `compile_bsp_hello.m` | Simple compilation script for the demo function (MATLAB) | +| `compile_octave.sh` | Shell script for building Octave MEX functions | +| `test_bsp_hello.m` | Test script to verify functionality (MATLAB) | +| `test_bsp_hello_octave.m` | Test script to verify functionality (Octave) | +| `README.md` | This documentation file | + +## Technical Details + +### MEX Function Structure + +The `bsp_hello` MEX function demonstrates: + +1. **Header inclusion**: Proper inclusion of `` +2. **Error handling**: Using Binsparse error types (`bsp_error_t`) +3. **Memory management**: Safe allocation and cleanup +4. **MATLAB interface**: Proper MEX function structure + +### 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 is a minimal demonstration of MATLAB/Octave bindings for Binsparse. Currently implemented: + +- ✅ Basic MEX function structure +- ✅ Binsparse header inclusion +- ✅ Error handling with Binsparse error types +- ✅ Build system and testing framework +- ⏳ Matrix reading/writing functions (future work) +- ⏳ Advanced Binsparse features (future work) + +## License + +This code is licensed under the BSD-3-Clause license, same as the main Binsparse project. diff --git a/bindings/matlab/bsp_hello.c b/bindings/matlab/bsp_hello.c new file mode 100644 index 0000000..673893f --- /dev/null +++ b/bindings/matlab/bsp_hello.c @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2024 Binsparse Developers + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/** + * bsp_hello.c - Simple Binsparse MEX function demonstration + * + * This is a minimal MEX function that demonstrates how to: + * 1. Include Binsparse headers + * 2. Use basic Binsparse types and error handling + * 3. Return information to Matlab + * + * Usage in Matlab: + * info = bsp_hello(); + * [version, success] = bsp_hello('version'); + */ + +#include "mex.h" +#include +#include + +/** + * Dummy function that demonstrates basic Binsparse functionality + */ +bsp_error_t dummy_binsparse_function(char** version_out) { + // Simulate some basic Binsparse operation + // In a real function, this might read/write matrices, etc. + + if (!version_out) { + return BSP_ERROR_INVALID_INPUT; + } + + // Allocate memory for version string + *version_out = (char*) malloc(strlen(BINSPARSE_VERSION) + 1); + if (!*version_out) { + return BSP_ERROR_MEMORY; + } + + strcpy(*version_out, BINSPARSE_VERSION); + return BSP_SUCCESS; +} + +/** + * Main MEX function entry point + */ +void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { + char* mode = NULL; + char* version_str = NULL; + bsp_error_t error; + + // Handle different calling modes + if (nrhs == 0) { + // Default mode: return basic info + plhs[0] = mxCreateString("Binsparse MEX binding is working!"); + return; + } + + if (nrhs == 1) { + // Get input argument + if (!mxIsChar(prhs[0])) { + mexErrMsgIdAndTxt("BinSparse:InvalidInput", "Input must be a string"); + } + + mode = mxArrayToString(prhs[0]); + if (!mode) { + mexErrMsgIdAndTxt("BinSparse:MemoryError", + "Failed to convert input string"); + } + + if (strcmp(mode, "version") == 0) { + // Call our dummy Binsparse function + error = dummy_binsparse_function(&version_str); + + if (nlhs >= 1) { + if (error == BSP_SUCCESS) { + plhs[0] = mxCreateString(version_str); + } else { + plhs[0] = mxCreateString(bsp_get_error_string(error)); + } + } + + if (nlhs >= 2) { + plhs[1] = mxCreateLogicalScalar(error == BSP_SUCCESS); + } + + // Clean up + if (version_str) { + free(version_str); + } + } else { + mexErrMsgIdAndTxt("BinSparse:InvalidMode", + "Unknown mode. Valid modes: 'version'"); + } + + // Clean up mode string + mxFree(mode); + } else { + mexErrMsgIdAndTxt("BinSparse:TooManyInputs", + "Too many input arguments. Expected 0 or 1."); + } +} diff --git a/bindings/matlab/bsp_matrix_create.m b/bindings/matlab/bsp_matrix_create.m new file mode 100644 index 0000000..4334774 --- /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 \ No newline at end of file diff --git a/bindings/matlab/bsp_matrix_info.m b/bindings/matlab/bsp_matrix_info.m new file mode 100644 index 0000000..0a4f239 --- /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 \ No newline at end of file diff --git a/bindings/matlab/compile_bsp_hello.m b/bindings/matlab/compile_bsp_hello.m new file mode 100644 index 0000000..23005a2 --- /dev/null +++ b/bindings/matlab/compile_bsp_hello.m @@ -0,0 +1,57 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function compile_bsp_hello() +% COMPILE_BSP_HELLO - Compile the bsp_hello MEX function +% +% This script compiles the bsp_hello.c MEX function with proper +% include paths for the Binsparse library headers. +% +% Prerequisites: +% - MATLAB with MEX compiler configured (run 'mex -setup' if needed) +% - Binsparse C library headers available +% +% Usage: +% compile_bsp_hello() + +fprintf('Compiling bsp_hello MEX function...\n'); + +% Get the current directory(should be bindings / matlab) matlab_dir = pwd; +fprintf('Matlab bindings directory: %s\n', matlab_dir); + +% Find the Binsparse include directory % + Assuming + we're in bindings/matlab, go up two levels to find include/ binsparse_root = fullfile( + matlab_dir, '..', '..'); +include_dir = fullfile(binsparse_root, 'include'); + +if + ~exist(include_dir, 'dir') + error('Binsparse include directory not found: %s', include_dir); +end + + fprintf('Using Binsparse include directory: %s\n', include_dir); + +% MEX compilation command mex_cmd = + sprintf('mex -I"%s" bsp_hello.c', include_dir); + +fprintf('Running: %s\n', mex_cmd); + +try % Compile the MEX function eval(mex_cmd); +fprintf('Successfully compiled bsp_hello MEX function!\n'); + +% Test if the function works fprintf('\nTesting the compiled function:\n'); +result = bsp_hello(); +fprintf('bsp_hello() returned: %s\n', result); + +[ version, success ] = bsp_hello('version'); +fprintf('bsp_hello(' 'version' ') returned: %s (success: %d)\n', version, + success); + +catch ME fprintf('Error during compilation:\n'); +fprintf('%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..ece6669 --- /dev/null +++ b/bindings/matlab/compile_octave.sh @@ -0,0 +1,176 @@ +#!/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=("bsp_hello.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 + CMD="mkoctfile --mex -I\"$INCLUDE_DIR\" $mex_file" + + 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_bsp_hello_octave()" +echo "" +print_info "Or test from command line:" +echo " octave --eval \"test_bsp_hello_octave()\"" diff --git a/bindings/matlab/test_bsp_hello.m b/bindings/matlab/test_bsp_hello.m new file mode 100644 index 0000000..52ff427 --- /dev/null +++ b/bindings/matlab/test_bsp_hello.m @@ -0,0 +1,79 @@ +% SPDX - FileCopyrightText : 2024 Binsparse Developers % % SPDX - License - + Identifier + : BSD - + 3 - + Clause + + function + test_bsp_hello() % + TEST_BSP_HELLO + - + Test the bsp_hello MEX function % % + This function tests the basic + functionality of the bsp_hello MEX function + % to verify that the Binsparse MATLAB bindings are working correctly.% + % Usage : % + test_bsp_hello() + + fprintf( + '=== Testing Binsparse MATLAB Bindings ===\n\n'); + +% Check if the MEX function exists if ~exist('bsp_hello', 'file') error( + 'bsp_hello MEX function not found. Run build_matlab_bindings() first.'); +end + + fprintf('Testing bsp_hello MEX function...\n\n'); + +try % Test 1 + : Basic + call with no arguments fprintf('Test 1: Basic call - bsp_hello()\n'); +result1 = bsp_hello(); +fprintf(' Result: %s\n', result1); +fprintf(' Status: PASS\n\n'); + +% Test 2 + : Version + query fprintf('Test 2: Version query - bsp_hello(' 'version' ')\n'); +[ version, success ] = bsp_hello('version'); +fprintf(' Version: %s\n', version); +fprintf(' Success: %s\n', mat2str(success)); +if success + fprintf(' Status: PASS\n\n'); +else + fprintf(' Status: FAIL - Function reported error\n\n'); +end + + % Test 3 : Error handling - + invalid mode fprintf('Test 3: Error handling - bsp_hello(' 'invalid' ')\n'); +try result3 = bsp_hello('invalid'); +fprintf(' Status: FAIL - Should have thrown an error\n\n'); +catch ME fprintf(' Caught expected error: %s\n', ME.message); +fprintf(' Status: PASS\n\n'); +end + + % Test 4 : Type checking - + numeric input fprintf('Test 4: Type checking - bsp_hello(42)\n'); +try result4 = bsp_hello(42); +fprintf(' Status: FAIL - Should have thrown an error\n\n'); +catch ME fprintf(' Caught expected error: %s\n', ME.message); +fprintf(' Status: PASS\n\n'); +end + + fprintf('=== All Tests Completed ===\n'); +fprintf('The Binsparse MATLAB bindings appear to be working correctly!\n\n'); + +% Display system information fprintf('System Information:\n'); +fprintf(' MATLAB Version: %s\n', version('-release')); +fprintf(' MEX Extension: %s\n', mexext()); +fprintf(' Platform: %s\n', computer()); + +catch ME fprintf('=== TEST FAILED ===\n'); +fprintf('Error: %s\n', ME.message); +fprintf('Stack trace:\n'); + for + i = 1 : length(ME.stack) fprintf(' %s (line %d)\n', ME.stack(i).name, + ME.stack(i).line); + end rethrow(ME); + end + + end diff --git a/bindings/matlab/test_bsp_hello_octave.m b/bindings/matlab/test_bsp_hello_octave.m new file mode 100644 index 0000000..8a8c028 --- /dev/null +++ b/bindings/matlab/test_bsp_hello_octave.m @@ -0,0 +1,98 @@ +% SPDX-FileCopyrightText: 2024 Binsparse Developers +% +% SPDX-License-Identifier: BSD-3-Clause + +function test_bsp_hello_octave() +% TEST_BSP_HELLO_OCTAVE - Test the bsp_hello MEX function in Octave +% +% This function tests the basic functionality of the bsp_hello MEX function +% to verify that the Binsparse Octave bindings are working correctly. +% +% Usage: +% test_bsp_hello_octave() + +fprintf('=== Testing Binsparse Octave Bindings ===\n\n'); + +% Check if we're running in Octave +if ~(exist('OCTAVE_VERSION', 'builtin') ~= 0) + warning('This test is designed for Octave. For MATLAB, use test_bsp_hello.m'); +end + +% Check if the MEX function exists +if ~exist('bsp_hello', 'file') + error('bsp_hello MEX function not found. Run build_octave_bindings() first.'); +end + +fprintf('Testing bsp_hello MEX function in Octave...\n\n'); + +try + % Test 1: Basic call with no arguments + fprintf('Test 1: Basic call - bsp_hello()\n'); + result1 = bsp_hello(); + fprintf(' Result: %s\n', result1); + fprintf(' Status: PASS\n\n'); + + % Test 2: Version query + fprintf('Test 2: Version query - bsp_hello(''version'')\n'); + [version, success] = bsp_hello('version'); + fprintf(' Version: %s\n', version); + fprintf(' Success: %s\n', mat2str(success)); + if success + fprintf(' Status: PASS\n\n'); + else + fprintf(' Status: FAIL - Function reported error\n\n'); + end + + % Test 3: Error handling - invalid mode + fprintf('Test 3: Error handling - bsp_hello(''invalid'')\n'); + try + result3 = bsp_hello('invalid'); + fprintf(' Status: FAIL - Should have thrown an error\n\n'); + catch + lasterr_msg = lasterr(); + fprintf(' Caught expected error: %s\n', lasterr_msg); + fprintf(' Status: PASS\n\n'); + end + + % Test 4: Type checking - numeric input + fprintf('Test 4: Type checking - bsp_hello(42)\n'); + try + result4 = bsp_hello(42); + fprintf(' Status: FAIL - Should have thrown an error\n\n'); + catch + lasterr_msg = lasterr(); + fprintf(' Caught expected error: %s\n', lasterr_msg); + fprintf(' Status: PASS\n\n'); + end + + fprintf('=== All Tests Completed ===\n'); + fprintf('The Binsparse Octave bindings appear to be working correctly!\n\n'); + + % Display system information + fprintf('System Information:\n'); + if exist('OCTAVE_VERSION', 'builtin') + fprintf(' Octave Version: %s\n', OCTAVE_VERSION); + end + fprintf(' Platform: %s\n', computer()); + + % Check for mkoctfile + [status, output] = system('mkoctfile --version 2>/dev/null || mkoctfile --version 2>nul'); + if status == 0 + % Extract version from output (first line usually) + lines = strsplit(output, '\n'); + if ~isempty(lines) + fprintf(' mkoctfile: %s\n', strtrim(lines{1})); + end + end + +catch + lasterr_msg = lasterr(); + fprintf('=== TEST FAILED ===\n'); + fprintf('Error: %s\n', lasterr_msg); + + % In Octave, we don't have ME.stack, so provide simpler error info + fprintf('Last error occurred in test_bsp_hello_octave\n'); + rethrow(lasterr()); +end + +end \ No newline at end of file diff --git a/bindings/matlab/test_bsp_matrix_struct.m b/bindings/matlab/test_bsp_matrix_struct.m new file mode 100644 index 0000000..8d893bf --- /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 \ No newline at end of file From 70426d4abcded0bea30d08e92adba646a2ff5145 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Tue, 19 Aug 2025 20:54:03 -0700 Subject: [PATCH 2/5] Prevent `clang-format` from messing with Matlab files. --- .pre-commit-config.yaml | 1 + bindings/matlab/bsp_matrix_create.m | 14 +++++++------- bindings/matlab/bsp_matrix_info.m | 2 +- bindings/matlab/test_bsp_hello_octave.m | 18 +++++++++--------- bindings/matlab/test_bsp_matrix_struct.m | 18 +++++++++--------- 5 files changed, 27 insertions(+), 26 deletions(-) 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/bindings/matlab/bsp_matrix_create.m b/bindings/matlab/bsp_matrix_create.m index 4334774..99a1d56 100644 --- a/bindings/matlab/bsp_matrix_create.m +++ b/bindings/matlab/bsp_matrix_create.m @@ -15,11 +15,11 @@ % % Fields: % values - MATLAB array of matrix values -% indices_0 - MATLAB array of first dimension indices +% 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) +% 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.) @@ -28,10 +28,10 @@ % Example: % % Create empty matrix % matrix = bsp_matrix_create(); -% +% % % Create COO matrix % values = [1.0, 2.0, 3.0]; -% rows = [1, 2, 3]; +% rows = [1, 2, 3]; % cols = [1, 2, 3]; % matrix = bsp_matrix_create(values, rows, cols, [], 3, 3, 3, false, 'COO', 'general'); @@ -48,7 +48,7 @@ 'is_iso', false, ... 'format', '', ... 'structure', 'general'); - + elseif nargin == 10 % Create matrix with all fields specified matrix = struct(... @@ -62,10 +62,10 @@ '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 \ No newline at end of file +end diff --git a/bindings/matlab/bsp_matrix_info.m b/bindings/matlab/bsp_matrix_info.m index 0a4f239..6cce27e 100644 --- a/bindings/matlab/bsp_matrix_info.m +++ b/bindings/matlab/bsp_matrix_info.m @@ -48,4 +48,4 @@ function bsp_matrix_info(matrix) length(matrix.pointers_to_1), class(matrix.pointers_to_1)); end -end \ No newline at end of file +end diff --git a/bindings/matlab/test_bsp_hello_octave.m b/bindings/matlab/test_bsp_hello_octave.m index 8a8c028..cba190a 100644 --- a/bindings/matlab/test_bsp_hello_octave.m +++ b/bindings/matlab/test_bsp_hello_octave.m @@ -31,7 +31,7 @@ function test_bsp_hello_octave() result1 = bsp_hello(); fprintf(' Result: %s\n', result1); fprintf(' Status: PASS\n\n'); - + % Test 2: Version query fprintf('Test 2: Version query - bsp_hello(''version'')\n'); [version, success] = bsp_hello('version'); @@ -42,7 +42,7 @@ function test_bsp_hello_octave() else fprintf(' Status: FAIL - Function reported error\n\n'); end - + % Test 3: Error handling - invalid mode fprintf('Test 3: Error handling - bsp_hello(''invalid'')\n'); try @@ -53,7 +53,7 @@ function test_bsp_hello_octave() fprintf(' Caught expected error: %s\n', lasterr_msg); fprintf(' Status: PASS\n\n'); end - + % Test 4: Type checking - numeric input fprintf('Test 4: Type checking - bsp_hello(42)\n'); try @@ -64,17 +64,17 @@ function test_bsp_hello_octave() fprintf(' Caught expected error: %s\n', lasterr_msg); fprintf(' Status: PASS\n\n'); end - + fprintf('=== All Tests Completed ===\n'); fprintf('The Binsparse Octave bindings appear to be working correctly!\n\n'); - + % Display system information fprintf('System Information:\n'); if exist('OCTAVE_VERSION', 'builtin') fprintf(' Octave Version: %s\n', OCTAVE_VERSION); end fprintf(' Platform: %s\n', computer()); - + % Check for mkoctfile [status, output] = system('mkoctfile --version 2>/dev/null || mkoctfile --version 2>nul'); if status == 0 @@ -84,15 +84,15 @@ function test_bsp_hello_octave() fprintf(' mkoctfile: %s\n', strtrim(lines{1})); end end - + catch lasterr_msg = lasterr(); fprintf('=== TEST FAILED ===\n'); fprintf('Error: %s\n', lasterr_msg); - + % In Octave, we don't have ME.stack, so provide simpler error info fprintf('Last error occurred in test_bsp_hello_octave\n'); rethrow(lasterr()); end -end \ No newline at end of file +end diff --git a/bindings/matlab/test_bsp_matrix_struct.m b/bindings/matlab/test_bsp_matrix_struct.m index 8d893bf..f3f3062 100644 --- a/bindings/matlab/test_bsp_matrix_struct.m +++ b/bindings/matlab/test_bsp_matrix_struct.m @@ -17,7 +17,7 @@ function test_bsp_matrix_struct() 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 @@ -25,13 +25,13 @@ function test_bsp_matrix_struct() 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 @@ -39,13 +39,13 @@ function test_bsp_matrix_struct() 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); @@ -53,7 +53,7 @@ function test_bsp_matrix_struct() 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 @@ -63,14 +63,14 @@ function test_bsp_matrix_struct() 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 \ No newline at end of file +end From a0898df59c4e36f0b9d1d98572c0c278fa7c8082 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Tue, 19 Aug 2025 21:22:19 -0700 Subject: [PATCH 3/5] Update Matlab bindings, fine-tune `.gitignore`. --- .gitignore | 3 +- CMakeLists.txt | 11 +- bindings/matlab/binsparse_read.c | 204 ++++++++++++++++++ bindings/matlab/bsp_hello.c | 103 --------- bindings/matlab/build_matlab_bindings.m | 148 +++++++++++++ bindings/matlab/build_octave_bindings.m | 184 ++++++++++++++++ bindings/matlab/compile_binsparse_read.m | 53 +++++ .../matlab/compile_binsparse_read_octave.m | 76 +++++++ bindings/matlab/compile_bsp_hello.m | 57 ----- bindings/matlab/compile_octave.sh | 14 +- bindings/matlab/test_binsparse_read.m | 71 ++++++ bindings/matlab/test_bsp_hello.m | 79 ------- bindings/matlab/test_bsp_hello_octave.m | 98 --------- 13 files changed, 757 insertions(+), 344 deletions(-) create mode 100644 bindings/matlab/binsparse_read.c delete mode 100644 bindings/matlab/bsp_hello.c 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 delete mode 100644 bindings/matlab/compile_bsp_hello.m create mode 100644 bindings/matlab/test_binsparse_read.m delete mode 100644 bindings/matlab/test_bsp_hello.m delete mode 100644 bindings/matlab/test_bsp_hello_octave.m diff --git a/.gitignore b/.gitignore index 2a41683..77ff7ad 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,8 @@ scripts venv -build* +build/ +build-*/ compile_flags.txt ._* tensor_test_files 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/binsparse_read.c b/bindings/matlab/binsparse_read.c new file mode 100644 index 0000000..5566b03 --- /dev/null +++ b/bindings/matlab/binsparse_read.c @@ -0,0 +1,204 @@ +/* + * 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 + +/** + * Convert bsp_array_t to MATLAB array + */ +mxArray* bsp_array_to_matlab(const bsp_array_t* array) { + if (array->data == NULL || array->size == 0) { + // Return empty array + return mxCreateDoubleMatrix(0, 1, mxREAL); + } + + mxArray* mx_array = NULL; + + switch (array->type) { + case BSP_FLOAT64: + mx_array = mxCreateDoubleMatrix(array->size, 1, mxREAL); + memcpy(mxGetPr(mx_array), array->data, array->size * sizeof(double)); + break; + + case BSP_FLOAT32: { + mx_array = mxCreateDoubleMatrix(array->size, 1, mxREAL); + double* out_data = mxGetPr(mx_array); + float* in_data = (float*) array->data; + for (size_t i = 0; i < array->size; i++) { + out_data[i] = (double) in_data[i]; + } + break; + } + + case BSP_UINT64: { + mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT64_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(uint64_t)); + break; + } + + case BSP_UINT32: { + mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT64_CLASS, mxREAL); + uint64_t* out_data = (uint64_t*) mxGetData(mx_array); + uint32_t* in_data = (uint32_t*) array->data; + for (size_t i = 0; i < array->size; i++) { + out_data[i] = (uint64_t) in_data[i]; + } + break; + } + + case BSP_UINT16: { + mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT64_CLASS, mxREAL); + uint64_t* out_data = (uint64_t*) mxGetData(mx_array); + uint16_t* in_data = (uint16_t*) array->data; + for (size_t i = 0; i < array->size; i++) { + out_data[i] = (uint64_t) in_data[i]; + } + break; + } + + case BSP_UINT8: { + mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT64_CLASS, mxREAL); + uint64_t* out_data = (uint64_t*) mxGetData(mx_array); + uint8_t* in_data = (uint8_t*) array->data; + for (size_t i = 0; i < array->size; i++) { + out_data[i] = (uint64_t) in_data[i]; + } + break; + } + + default: + // Fallback: create empty array + mx_array = mxCreateDoubleMatrix(0, 1, mxREAL); + mexWarnMsgIdAndTxt("BinSparse:UnsupportedType", + "Unsupported array type %d, returning empty array", + (int) array->type); + break; + } + + return mx_array; +} + +/** + * Convert bsp_matrix_t to MATLAB struct + */ +mxArray* bsp_matrix_to_matlab_struct(const 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(&matrix, filename, group); + + 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/bsp_hello.c b/bindings/matlab/bsp_hello.c deleted file mode 100644 index 673893f..0000000 --- a/bindings/matlab/bsp_hello.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 Binsparse Developers - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -/** - * bsp_hello.c - Simple Binsparse MEX function demonstration - * - * This is a minimal MEX function that demonstrates how to: - * 1. Include Binsparse headers - * 2. Use basic Binsparse types and error handling - * 3. Return information to Matlab - * - * Usage in Matlab: - * info = bsp_hello(); - * [version, success] = bsp_hello('version'); - */ - -#include "mex.h" -#include -#include - -/** - * Dummy function that demonstrates basic Binsparse functionality - */ -bsp_error_t dummy_binsparse_function(char** version_out) { - // Simulate some basic Binsparse operation - // In a real function, this might read/write matrices, etc. - - if (!version_out) { - return BSP_ERROR_INVALID_INPUT; - } - - // Allocate memory for version string - *version_out = (char*) malloc(strlen(BINSPARSE_VERSION) + 1); - if (!*version_out) { - return BSP_ERROR_MEMORY; - } - - strcpy(*version_out, BINSPARSE_VERSION); - return BSP_SUCCESS; -} - -/** - * Main MEX function entry point - */ -void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) { - char* mode = NULL; - char* version_str = NULL; - bsp_error_t error; - - // Handle different calling modes - if (nrhs == 0) { - // Default mode: return basic info - plhs[0] = mxCreateString("Binsparse MEX binding is working!"); - return; - } - - if (nrhs == 1) { - // Get input argument - if (!mxIsChar(prhs[0])) { - mexErrMsgIdAndTxt("BinSparse:InvalidInput", "Input must be a string"); - } - - mode = mxArrayToString(prhs[0]); - if (!mode) { - mexErrMsgIdAndTxt("BinSparse:MemoryError", - "Failed to convert input string"); - } - - if (strcmp(mode, "version") == 0) { - // Call our dummy Binsparse function - error = dummy_binsparse_function(&version_str); - - if (nlhs >= 1) { - if (error == BSP_SUCCESS) { - plhs[0] = mxCreateString(version_str); - } else { - plhs[0] = mxCreateString(bsp_get_error_string(error)); - } - } - - if (nlhs >= 2) { - plhs[1] = mxCreateLogicalScalar(error == BSP_SUCCESS); - } - - // Clean up - if (version_str) { - free(version_str); - } - } else { - mexErrMsgIdAndTxt("BinSparse:InvalidMode", - "Unknown mode. Valid modes: 'version'"); - } - - // Clean up mode string - mxFree(mode); - } else { - mexErrMsgIdAndTxt("BinSparse:TooManyInputs", - "Too many input arguments. Expected 0 or 1."); - } -} diff --git a/bindings/matlab/build_matlab_bindings.m b/bindings/matlab/build_matlab_bindings.m new file mode 100644 index 0000000..460ea37 --- /dev/null +++ b/bindings/matlab/build_matlab_bindings.m @@ -0,0 +1,148 @@ +% 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 function to verify the installation:\n'); +fprintf(' test_binsparse_read()\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'}; + + 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..ccb0fca --- /dev/null +++ b/bindings/matlab/build_octave_bindings.m @@ -0,0 +1,184 @@ +% 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 function to verify the installation:\n'); +fprintf(' test_binsparse_read()\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'}; + + 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_bsp_hello.m b/bindings/matlab/compile_bsp_hello.m deleted file mode 100644 index 23005a2..0000000 --- a/bindings/matlab/compile_bsp_hello.m +++ /dev/null @@ -1,57 +0,0 @@ -% SPDX-FileCopyrightText: 2024 Binsparse Developers -% -% SPDX-License-Identifier: BSD-3-Clause - -function compile_bsp_hello() -% COMPILE_BSP_HELLO - Compile the bsp_hello MEX function -% -% This script compiles the bsp_hello.c MEX function with proper -% include paths for the Binsparse library headers. -% -% Prerequisites: -% - MATLAB with MEX compiler configured (run 'mex -setup' if needed) -% - Binsparse C library headers available -% -% Usage: -% compile_bsp_hello() - -fprintf('Compiling bsp_hello MEX function...\n'); - -% Get the current directory(should be bindings / matlab) matlab_dir = pwd; -fprintf('Matlab bindings directory: %s\n', matlab_dir); - -% Find the Binsparse include directory % - Assuming - we're in bindings/matlab, go up two levels to find include/ binsparse_root = fullfile( - matlab_dir, '..', '..'); -include_dir = fullfile(binsparse_root, 'include'); - -if - ~exist(include_dir, 'dir') - error('Binsparse include directory not found: %s', include_dir); -end - - fprintf('Using Binsparse include directory: %s\n', include_dir); - -% MEX compilation command mex_cmd = - sprintf('mex -I"%s" bsp_hello.c', include_dir); - -fprintf('Running: %s\n', mex_cmd); - -try % Compile the MEX function eval(mex_cmd); -fprintf('Successfully compiled bsp_hello MEX function!\n'); - -% Test if the function works fprintf('\nTesting the compiled function:\n'); -result = bsp_hello(); -fprintf('bsp_hello() returned: %s\n', result); - -[ version, success ] = bsp_hello('version'); -fprintf('bsp_hello(' 'version' ') returned: %s (success: %d)\n', version, - success); - -catch ME fprintf('Error during compilation:\n'); -fprintf('%s\n', ME.message); -rethrow(ME); -end - - end diff --git a/bindings/matlab/compile_octave.sh b/bindings/matlab/compile_octave.sh index ece6669..038e87a 100755 --- a/bindings/matlab/compile_octave.sh +++ b/bindings/matlab/compile_octave.sh @@ -137,7 +137,7 @@ if [ "$CLEAN" = true ]; then fi # List of MEX files to compile -MEX_FILES=("bsp_hello.c") +MEX_FILES=("binsparse_read.c") print_info "Compiling MEX functions..." @@ -150,8 +150,12 @@ for mex_file in "${MEX_FILES[@]}"; do print_info "Compiling $mex_file..." - # Build mkoctfile command - CMD="mkoctfile --mex -I\"$INCLUDE_DIR\" $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" @@ -170,7 +174,7 @@ done print_success "All MEX functions compiled successfully!" echo "" print_info "To test the functions, start Octave and run:" -echo " test_bsp_hello_octave()" +echo " test_binsparse_read()" echo "" print_info "Or test from command line:" -echo " octave --eval \"test_bsp_hello_octave()\"" +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_bsp_hello.m b/bindings/matlab/test_bsp_hello.m deleted file mode 100644 index 52ff427..0000000 --- a/bindings/matlab/test_bsp_hello.m +++ /dev/null @@ -1,79 +0,0 @@ -% SPDX - FileCopyrightText : 2024 Binsparse Developers % % SPDX - License - - Identifier - : BSD - - 3 - - Clause - - function - test_bsp_hello() % - TEST_BSP_HELLO - - - Test the bsp_hello MEX function % % - This function tests the basic - functionality of the bsp_hello MEX function - % to verify that the Binsparse MATLAB bindings are working correctly.% - % Usage : % - test_bsp_hello() - - fprintf( - '=== Testing Binsparse MATLAB Bindings ===\n\n'); - -% Check if the MEX function exists if ~exist('bsp_hello', 'file') error( - 'bsp_hello MEX function not found. Run build_matlab_bindings() first.'); -end - - fprintf('Testing bsp_hello MEX function...\n\n'); - -try % Test 1 - : Basic - call with no arguments fprintf('Test 1: Basic call - bsp_hello()\n'); -result1 = bsp_hello(); -fprintf(' Result: %s\n', result1); -fprintf(' Status: PASS\n\n'); - -% Test 2 - : Version - query fprintf('Test 2: Version query - bsp_hello(' 'version' ')\n'); -[ version, success ] = bsp_hello('version'); -fprintf(' Version: %s\n', version); -fprintf(' Success: %s\n', mat2str(success)); -if success - fprintf(' Status: PASS\n\n'); -else - fprintf(' Status: FAIL - Function reported error\n\n'); -end - - % Test 3 : Error handling - - invalid mode fprintf('Test 3: Error handling - bsp_hello(' 'invalid' ')\n'); -try result3 = bsp_hello('invalid'); -fprintf(' Status: FAIL - Should have thrown an error\n\n'); -catch ME fprintf(' Caught expected error: %s\n', ME.message); -fprintf(' Status: PASS\n\n'); -end - - % Test 4 : Type checking - - numeric input fprintf('Test 4: Type checking - bsp_hello(42)\n'); -try result4 = bsp_hello(42); -fprintf(' Status: FAIL - Should have thrown an error\n\n'); -catch ME fprintf(' Caught expected error: %s\n', ME.message); -fprintf(' Status: PASS\n\n'); -end - - fprintf('=== All Tests Completed ===\n'); -fprintf('The Binsparse MATLAB bindings appear to be working correctly!\n\n'); - -% Display system information fprintf('System Information:\n'); -fprintf(' MATLAB Version: %s\n', version('-release')); -fprintf(' MEX Extension: %s\n', mexext()); -fprintf(' Platform: %s\n', computer()); - -catch ME fprintf('=== TEST FAILED ===\n'); -fprintf('Error: %s\n', ME.message); -fprintf('Stack trace:\n'); - for - i = 1 : length(ME.stack) fprintf(' %s (line %d)\n', ME.stack(i).name, - ME.stack(i).line); - end rethrow(ME); - end - - end diff --git a/bindings/matlab/test_bsp_hello_octave.m b/bindings/matlab/test_bsp_hello_octave.m deleted file mode 100644 index cba190a..0000000 --- a/bindings/matlab/test_bsp_hello_octave.m +++ /dev/null @@ -1,98 +0,0 @@ -% SPDX-FileCopyrightText: 2024 Binsparse Developers -% -% SPDX-License-Identifier: BSD-3-Clause - -function test_bsp_hello_octave() -% TEST_BSP_HELLO_OCTAVE - Test the bsp_hello MEX function in Octave -% -% This function tests the basic functionality of the bsp_hello MEX function -% to verify that the Binsparse Octave bindings are working correctly. -% -% Usage: -% test_bsp_hello_octave() - -fprintf('=== Testing Binsparse Octave Bindings ===\n\n'); - -% Check if we're running in Octave -if ~(exist('OCTAVE_VERSION', 'builtin') ~= 0) - warning('This test is designed for Octave. For MATLAB, use test_bsp_hello.m'); -end - -% Check if the MEX function exists -if ~exist('bsp_hello', 'file') - error('bsp_hello MEX function not found. Run build_octave_bindings() first.'); -end - -fprintf('Testing bsp_hello MEX function in Octave...\n\n'); - -try - % Test 1: Basic call with no arguments - fprintf('Test 1: Basic call - bsp_hello()\n'); - result1 = bsp_hello(); - fprintf(' Result: %s\n', result1); - fprintf(' Status: PASS\n\n'); - - % Test 2: Version query - fprintf('Test 2: Version query - bsp_hello(''version'')\n'); - [version, success] = bsp_hello('version'); - fprintf(' Version: %s\n', version); - fprintf(' Success: %s\n', mat2str(success)); - if success - fprintf(' Status: PASS\n\n'); - else - fprintf(' Status: FAIL - Function reported error\n\n'); - end - - % Test 3: Error handling - invalid mode - fprintf('Test 3: Error handling - bsp_hello(''invalid'')\n'); - try - result3 = bsp_hello('invalid'); - fprintf(' Status: FAIL - Should have thrown an error\n\n'); - catch - lasterr_msg = lasterr(); - fprintf(' Caught expected error: %s\n', lasterr_msg); - fprintf(' Status: PASS\n\n'); - end - - % Test 4: Type checking - numeric input - fprintf('Test 4: Type checking - bsp_hello(42)\n'); - try - result4 = bsp_hello(42); - fprintf(' Status: FAIL - Should have thrown an error\n\n'); - catch - lasterr_msg = lasterr(); - fprintf(' Caught expected error: %s\n', lasterr_msg); - fprintf(' Status: PASS\n\n'); - end - - fprintf('=== All Tests Completed ===\n'); - fprintf('The Binsparse Octave bindings appear to be working correctly!\n\n'); - - % Display system information - fprintf('System Information:\n'); - if exist('OCTAVE_VERSION', 'builtin') - fprintf(' Octave Version: %s\n', OCTAVE_VERSION); - end - fprintf(' Platform: %s\n', computer()); - - % Check for mkoctfile - [status, output] = system('mkoctfile --version 2>/dev/null || mkoctfile --version 2>nul'); - if status == 0 - % Extract version from output (first line usually) - lines = strsplit(output, '\n'); - if ~isempty(lines) - fprintf(' mkoctfile: %s\n', strtrim(lines{1})); - end - end - -catch - lasterr_msg = lasterr(); - fprintf('=== TEST FAILED ===\n'); - fprintf('Error: %s\n', lasterr_msg); - - % In Octave, we don't have ME.stack, so provide simpler error info - fprintf('Last error occurred in test_bsp_hello_octave\n'); - rethrow(lasterr()); -end - -end From 7e18e4d8174575f3006c33cb4ac5a06e562c7a05 Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Wed, 20 Aug 2025 15:05:19 -0700 Subject: [PATCH 4/5] Add support for all types to Matlab. --- bindings/matlab/binsparse_read.c | 89 +++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 29 deletions(-) diff --git a/bindings/matlab/binsparse_read.c b/bindings/matlab/binsparse_read.c index 5566b03..a531c63 100644 --- a/bindings/matlab/binsparse_read.c +++ b/bindings/matlab/binsparse_read.c @@ -32,52 +32,83 @@ mxArray* bsp_array_to_matlab(const bsp_array_t* array) { switch (array->type) { case BSP_FLOAT64: - mx_array = mxCreateDoubleMatrix(array->size, 1, mxREAL); + mx_array = mxCreateNumericMatrix(array->size, 1, mxDOUBLE_CLASS, mxREAL); memcpy(mxGetPr(mx_array), array->data, array->size * sizeof(double)); break; - case BSP_FLOAT32: { - mx_array = mxCreateDoubleMatrix(array->size, 1, mxREAL); - double* out_data = mxGetPr(mx_array); - float* in_data = (float*) array->data; - for (size_t i = 0; i < array->size; i++) { - out_data[i] = (double) in_data[i]; - } + case BSP_FLOAT32: + mx_array = mxCreateNumericMatrix(array->size, 1, mxSINGLE_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(float)); break; - } - case BSP_UINT64: { + case BSP_UINT64: mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT64_CLASS, mxREAL); memcpy(mxGetData(mx_array), array->data, array->size * sizeof(uint64_t)); break; - } - case BSP_UINT32: { - mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT64_CLASS, mxREAL); - uint64_t* out_data = (uint64_t*) mxGetData(mx_array); - uint32_t* in_data = (uint32_t*) array->data; - for (size_t i = 0; i < array->size; i++) { - out_data[i] = (uint64_t) in_data[i]; - } + case BSP_UINT32: + mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT32_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(uint32_t)); break; - } - case BSP_UINT16: { - mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT64_CLASS, mxREAL); - uint64_t* out_data = (uint64_t*) mxGetData(mx_array); - uint16_t* in_data = (uint16_t*) array->data; + case BSP_UINT16: + mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT16_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(uint16_t)); + break; + + case BSP_UINT8: + mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT8_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(uint8_t)); + break; + + case BSP_INT64: + mx_array = mxCreateNumericMatrix(array->size, 1, mxINT64_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(int64_t)); + break; + + case BSP_INT32: + mx_array = mxCreateNumericMatrix(array->size, 1, mxINT32_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(int32_t)); + break; + + case BSP_INT16: + mx_array = mxCreateNumericMatrix(array->size, 1, mxINT16_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(int16_t)); + break; + + case BSP_INT8: + mx_array = mxCreateNumericMatrix(array->size, 1, mxINT8_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(int8_t)); + break; + + case BSP_BINT8: + // Treat BSP_BINT8 as UINT8 as suggested + mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT8_CLASS, mxREAL); + memcpy(mxGetData(mx_array), array->data, array->size * sizeof(int8_t)); + break; + + case BSP_COMPLEX_FLOAT64: { + mx_array = mxCreateNumericMatrix(array->size, 1, mxDOUBLE_CLASS, mxCOMPLEX); + 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++) { - out_data[i] = (uint64_t) in_data[i]; + real_data[i] = in_data[2 * i]; // Real part + imag_data[i] = in_data[2 * i + 1]; // Imaginary part } break; } - case BSP_UINT8: { - mx_array = mxCreateNumericMatrix(array->size, 1, mxUINT64_CLASS, mxREAL); - uint64_t* out_data = (uint64_t*) mxGetData(mx_array); - uint8_t* in_data = (uint8_t*) array->data; + case BSP_COMPLEX_FLOAT32: { + mx_array = mxCreateNumericMatrix(array->size, 1, mxSINGLE_CLASS, mxCOMPLEX); + 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++) { - out_data[i] = (uint64_t) in_data[i]; + real_data[i] = in_data[2 * i]; // Real part + imag_data[i] = in_data[2 * i + 1]; // Imaginary part } break; } From 92194cd2ec65a8316c07018c880a94ea1d9291cd Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Wed, 20 Aug 2025 15:36:11 -0700 Subject: [PATCH 5/5] Implement `binsparse_write` bindings. --- bindings/matlab/README.md | 95 +++-- bindings/matlab/binsparse_write.c | 357 ++++++++++++++++++ bindings/matlab/build_matlab_bindings.m | 7 +- bindings/matlab/build_octave_bindings.m | 7 +- bindings/matlab/compile_binsparse_write.m | 53 +++ .../matlab/compile_binsparse_write_octave.m | 76 ++++ bindings/matlab/compile_octave.sh | 3 +- bindings/matlab/test_binsparse_write.m | 122 ++++++ src/write_matrix.c | 9 +- 9 files changed, 691 insertions(+), 38 deletions(-) create mode 100644 bindings/matlab/binsparse_write.c create mode 100644 bindings/matlab/compile_binsparse_write.m create mode 100644 bindings/matlab/compile_binsparse_write_octave.m create mode 100644 bindings/matlab/test_binsparse_write.m diff --git a/bindings/matlab/README.md b/bindings/matlab/README.md index 368d479..ff776f4 100644 --- a/bindings/matlab/README.md +++ b/bindings/matlab/README.md @@ -55,7 +55,8 @@ mkoctfile --version 3. Test the installation: ```matlab - test_bsp_hello() + test_binsparse_read() + test_binsparse_write() ``` #### Option 2: Octave (from within Octave) @@ -72,7 +73,8 @@ mkoctfile --version 3. Test the installation: ```octave - test_bsp_hello_octave() + test_binsparse_read() + test_binsparse_write() ``` #### Option 3: Octave (from command line) @@ -89,23 +91,56 @@ mkoctfile --version 3. Test the installation: ```bash - octave --eval "test_bsp_hello_octave()" + octave --eval "test_binsparse_read()" + octave --eval "test_binsparse_write()" ``` ## Usage Examples -### Basic Usage +### Reading Binsparse Files **In MATLAB or Octave:** ```matlab -% Simple greeting -result = bsp_hello() -% Output: 'Binsparse MEX binding is working!' +% 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 -% Get Binsparse version -[version, success] = bsp_hello('version') -% Output: version = '0.1', success = true +```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 @@ -114,7 +149,7 @@ The MEX functions include proper error handling: ```matlab try - result = bsp_hello('invalid_mode') + matrix = binsparse_read('nonexistent_file.bsp.h5') catch ME fprintf('Error: %s\n', ME.message) end @@ -124,25 +159,32 @@ end | File | Description | |------|-------------| -| `bsp_hello.c` | Simple MEX function demonstrating Binsparse integration | +| `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_bsp_hello.m` | Simple compilation script for the demo function (MATLAB) | +| `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_bsp_hello.m` | Test script to verify functionality (MATLAB) | -| `test_bsp_hello_octave.m` | Test script to verify functionality (Octave) | +| `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 `bsp_hello` MEX function demonstrates: +The Binsparse MEX functions demonstrate: 1. **Header inclusion**: Proper inclusion of `` -2. **Error handling**: Using Binsparse error types (`bsp_error_t`) -3. **Memory management**: Safe allocation and cleanup -4. **MATLAB interface**: Proper MEX function structure +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 @@ -222,14 +264,17 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { ## Development Status -This is a minimal demonstration of MATLAB/Octave bindings for Binsparse. Currently implemented: +This provides complete MATLAB/Octave bindings for Binsparse. Currently implemented: -- ✅ Basic MEX function structure -- ✅ Binsparse header inclusion -- ✅ Error handling with Binsparse error types +- ✅ 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 -- ⏳ Matrix reading/writing functions (future work) -- ⏳ Advanced Binsparse features (future work) +- ✅ Round-trip compatibility (read → write → read) +- ⏳ Tensor support (future work) +- ⏳ Advanced sparse matrix operations (future work) ## License 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/build_matlab_bindings.m b/bindings/matlab/build_matlab_bindings.m index 460ea37..8816d0f 100644 --- a/bindings/matlab/build_matlab_bindings.m +++ b/bindings/matlab/build_matlab_bindings.m @@ -53,8 +53,9 @@ function build_matlab_bindings(varargin) compile_mex_functions(paths, verbose); fprintf('\n=== Build Complete ===\n'); -fprintf('Run the test function to verify the installation:\n'); -fprintf(' test_binsparse_read()\n\n'); +fprintf('Run the test functions to verify the installation:\n'); +fprintf(' test_binsparse_read()\n'); +fprintf(' test_binsparse_write()\n\n'); end @@ -93,7 +94,7 @@ function compile_mex_functions(paths, verbose) % Compile all MEX functions % List of MEX functions to compile - mex_files = {'binsparse_read.c'}; + mex_files = {'binsparse_read.c', 'binsparse_write.c'}; fprintf('Compiling MEX functions...\n'); diff --git a/bindings/matlab/build_octave_bindings.m b/bindings/matlab/build_octave_bindings.m index ccb0fca..87ba30f 100644 --- a/bindings/matlab/build_octave_bindings.m +++ b/bindings/matlab/build_octave_bindings.m @@ -58,8 +58,9 @@ function build_octave_bindings(varargin) compile_octave_functions(paths, verbose); fprintf('\n=== Build Complete ===\n'); -fprintf('Run the test function to verify the installation:\n'); -fprintf(' test_binsparse_read()\n\n'); +fprintf('Run the test functions to verify the installation:\n'); +fprintf(' test_binsparse_read()\n'); +fprintf(' test_binsparse_write()\n\n'); end @@ -102,7 +103,7 @@ function compile_octave_functions(paths, verbose) % Compile all MEX functions using mkoctfile % List of MEX functions to compile - mex_files = {'binsparse_read.c'}; + mex_files = {'binsparse_read.c', 'binsparse_write.c'}; fprintf('Compiling MEX functions with mkoctfile...\n'); 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 index 038e87a..641edf2 100755 --- a/bindings/matlab/compile_octave.sh +++ b/bindings/matlab/compile_octave.sh @@ -137,7 +137,7 @@ if [ "$CLEAN" = true ]; then fi # List of MEX files to compile -MEX_FILES=("binsparse_read.c") +MEX_FILES=("binsparse_read.c" "binsparse_write.c") print_info "Compiling MEX functions..." @@ -175,6 +175,7 @@ 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_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/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);