From 25bcabf7537c99e99c814f05b5989e803bfdcbc7 Mon Sep 17 00:00:00 2001 From: dance858 Date: Sun, 11 Oct 2026 10:19:09 +0200 Subject: [PATCH] Consume sparsediff_linalg as a git submodule The library now lives in its own repository, https://github.com/SparseDifferentiation/sparsediff_linalg, extracted with its full history. The in-tree copy is replaced by a submodule at the same path, pinned to v0.1.0, so the root CMakeLists.txt still does add_subdirectory(sparsediff_linalg) and nothing else about the build changes. A plain clone leaves the directory empty, so configure now stops with a hint to run `git submodule update --init --recursive`. Every workflow that builds checks out submodules recursively; the formatting check prunes the submodule so it covers engine files only (the library's own CI formats its files). Verified: all 77 Release objects byte-identical to main; 310 + 159 tests, ctest 2/2, 162 with SP_TRACK_MEMORY, 7 profiles; a plain clone fails configure with the hint and succeeds after submodule init from GitHub; SparseDiffPy builds and passes its smoke test against this checkout. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/cmake.yml | 2 + .github/workflows/conda-forge.yml | 2 + .github/workflows/formatting.yml | 4 +- .github/workflows/guard-malloc.yml | 2 + .github/workflows/release.yml | 2 + .github/workflows/sanitizer.yml | 2 + .github/workflows/valgrind.yml | 2 + .gitmodules | 3 + CMakeLists.txt | 8 + sparsediff_linalg | 1 + sparsediff_linalg/CMakeLists.txt | 193 -- .../include/sparsediff_linalg/COO_matrix.h | 59 - .../include/sparsediff_linalg/CSC_matrix.h | 80 - .../include/sparsediff_linalg/CSR_matrix.h | 78 - .../include/sparsediff_linalg/CSR_sum.h | 69 - .../include/sparsediff_linalg/Vec_macros.h | 150 - .../include/sparsediff_linalg/cblas_wrapper.h | 36 - .../include/sparsediff_linalg/iVec.h | 56 - .../sparsediff_linalg/int_double_pair.h | 34 - .../linalg_dense_sparse_matmuls.h | 48 - .../sparsediff_linalg/linalg_sparse_matmuls.h | 58 - .../sparsediff_linalg/matmul_dispatchers.h | 62 - .../include/sparsediff_linalg/matrix.h | 168 -- .../include/sparsediff_linalg/matrix_sum.h | 34 - .../include/sparsediff_linalg/mini_numpy.h | 54 - .../sparsediff_linalg/permuted_dense.h | 120 - .../include/sparsediff_linalg/sparse_matrix.h | 64 - .../include/sparsediff_linalg/stacked_pd.h | 117 - .../include/sparsediff_linalg/tracked_alloc.h | 128 - .../include/sparsediff_linalg/utils.h | 66 - sparsediff_linalg/src/COO_matrix.c | 110 - sparsediff_linalg/src/CSC_matrix.c | 550 ---- sparsediff_linalg/src/CSR_matrix.c | 309 -- sparsediff_linalg/src/CSR_sum.c | 298 -- sparsediff_linalg/src/int_double_pair.c | 55 - .../src/internal/permuted_dense_linalg.h | 103 - .../src/internal/stacked_pd_kron_linalg.h | 55 - .../src/internal/stacked_pd_linalg.h | 155 - .../src/linalg_dense_sparse_matmuls.c | 350 --- sparsediff_linalg/src/linalg_sparse_matmuls.c | 376 --- sparsediff_linalg/src/matmul_dispatchers.c | 352 --- sparsediff_linalg/src/matrix_sum.c | 39 - sparsediff_linalg/src/mini_numpy.c | 118 - sparsediff_linalg/src/permuted_dense.c | 484 --- sparsediff_linalg/src/permuted_dense_linalg.c | 603 ---- sparsediff_linalg/src/sparse_matrix.c | 400 --- sparsediff_linalg/src/stacked_pd.c | 449 --- sparsediff_linalg/src/stacked_pd_coalesce.c | 382 --- .../src/stacked_pd_kron_linalg.c | 287 -- sparsediff_linalg/src/stacked_pd_linalg.c | 723 ----- sparsediff_linalg/src/tracked_alloc.c | 23 - sparsediff_linalg/src/utils.c | 126 - sparsediff_linalg/tests/minunit.h | 40 - .../tests/sparsediff_linalg_tests.c | 203 -- sparsediff_linalg/tests/test_COO_matrix.h | 104 - sparsediff_linalg/tests/test_alloc_overflow.h | 83 - sparsediff_linalg/tests/test_cblas.h | 18 - sparsediff_linalg/tests/test_csc_matrix.h | 211 -- .../tests/test_csr_csc_conversion.h | 171 -- sparsediff_linalg/tests/test_csr_matrix.h | 76 - sparsediff_linalg/tests/test_helpers.c | 158 - sparsediff_linalg/tests/test_helpers.h | 51 - .../tests/test_linalg_sparse_matmuls.h | 649 ----- .../test_linalg_utils_matmul_chain_rule.h | 228 -- .../tests/test_matmul_dispatchers.h | 2592 ----------------- sparsediff_linalg/tests/test_matrix.h | 155 - sparsediff_linalg/tests/test_permuted_dense.h | 932 ------ sparsediff_linalg/tests/test_row_gather.h | 238 -- sparsediff_linalg/tests/test_row_reduce.h | 286 -- sparsediff_linalg/tests/test_stacked_pd.h | 1966 ------------- 70 files changed, 27 insertions(+), 16183 deletions(-) create mode 100644 .gitmodules create mode 160000 sparsediff_linalg delete mode 100644 sparsediff_linalg/CMakeLists.txt delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/COO_matrix.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/CSC_matrix.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/CSR_matrix.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/CSR_sum.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/Vec_macros.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/cblas_wrapper.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/iVec.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/int_double_pair.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/linalg_dense_sparse_matmuls.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/linalg_sparse_matmuls.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/matmul_dispatchers.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/matrix.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/matrix_sum.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/mini_numpy.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/permuted_dense.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/sparse_matrix.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/stacked_pd.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/tracked_alloc.h delete mode 100644 sparsediff_linalg/include/sparsediff_linalg/utils.h delete mode 100644 sparsediff_linalg/src/COO_matrix.c delete mode 100644 sparsediff_linalg/src/CSC_matrix.c delete mode 100644 sparsediff_linalg/src/CSR_matrix.c delete mode 100644 sparsediff_linalg/src/CSR_sum.c delete mode 100644 sparsediff_linalg/src/int_double_pair.c delete mode 100644 sparsediff_linalg/src/internal/permuted_dense_linalg.h delete mode 100644 sparsediff_linalg/src/internal/stacked_pd_kron_linalg.h delete mode 100644 sparsediff_linalg/src/internal/stacked_pd_linalg.h delete mode 100644 sparsediff_linalg/src/linalg_dense_sparse_matmuls.c delete mode 100644 sparsediff_linalg/src/linalg_sparse_matmuls.c delete mode 100644 sparsediff_linalg/src/matmul_dispatchers.c delete mode 100644 sparsediff_linalg/src/matrix_sum.c delete mode 100644 sparsediff_linalg/src/mini_numpy.c delete mode 100644 sparsediff_linalg/src/permuted_dense.c delete mode 100644 sparsediff_linalg/src/permuted_dense_linalg.c delete mode 100644 sparsediff_linalg/src/sparse_matrix.c delete mode 100644 sparsediff_linalg/src/stacked_pd.c delete mode 100644 sparsediff_linalg/src/stacked_pd_coalesce.c delete mode 100644 sparsediff_linalg/src/stacked_pd_kron_linalg.c delete mode 100644 sparsediff_linalg/src/stacked_pd_linalg.c delete mode 100644 sparsediff_linalg/src/tracked_alloc.c delete mode 100644 sparsediff_linalg/src/utils.c delete mode 100644 sparsediff_linalg/tests/minunit.h delete mode 100644 sparsediff_linalg/tests/sparsediff_linalg_tests.c delete mode 100644 sparsediff_linalg/tests/test_COO_matrix.h delete mode 100644 sparsediff_linalg/tests/test_alloc_overflow.h delete mode 100644 sparsediff_linalg/tests/test_cblas.h delete mode 100644 sparsediff_linalg/tests/test_csc_matrix.h delete mode 100644 sparsediff_linalg/tests/test_csr_csc_conversion.h delete mode 100644 sparsediff_linalg/tests/test_csr_matrix.h delete mode 100644 sparsediff_linalg/tests/test_helpers.c delete mode 100644 sparsediff_linalg/tests/test_helpers.h delete mode 100644 sparsediff_linalg/tests/test_linalg_sparse_matmuls.h delete mode 100644 sparsediff_linalg/tests/test_linalg_utils_matmul_chain_rule.h delete mode 100644 sparsediff_linalg/tests/test_matmul_dispatchers.h delete mode 100644 sparsediff_linalg/tests/test_matrix.h delete mode 100644 sparsediff_linalg/tests/test_permuted_dense.h delete mode 100644 sparsediff_linalg/tests/test_row_gather.h delete mode 100644 sparsediff_linalg/tests/test_row_reduce.h delete mode 100644 sparsediff_linalg/tests/test_stacked_pd.h diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index aecc4a1c..87f38e8c 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -16,6 +16,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + submodules: recursive - name: Install BLAS (Ubuntu) if: runner.os == 'Linux' diff --git a/.github/workflows/conda-forge.yml b/.github/workflows/conda-forge.yml index 155e6fd7..7abc4cf5 100644 --- a/.github/workflows/conda-forge.yml +++ b/.github/workflows/conda-forge.yml @@ -24,6 +24,8 @@ jobs: steps: - uses: actions/checkout@v5 + with: + submodules: recursive - uses: conda-incubator/setup-miniconda@v3 with: diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index 24e518c6..a49dac3d 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -12,6 +12,8 @@ jobs: steps: - uses: actions/checkout@v5 + with: + submodules: recursive - name: Install clang-format run: brew install clang-format || true @@ -25,7 +27,7 @@ jobs: # the *.h branch and .c files are never checked. - name: Check C formatting run: | - misformatted=$(find . \( -name '*.c' -o -name '*.h' \) -print0 | xargs -0 clang-format -style=file -output-replacements-xml | grep ".h", under -# include/) or a private one ("internal/.h", under src/internal/, which -# only the library and its own test suite can resolve). A public header must -# never include a private one, or "public" would stop meaning anything. System -# headers use <...>. Checked here so a violation fails every configure, locally -# and in CI. -foreach(_sdl_file IN LISTS SPARSEDIFF_LINALG_SOURCES SPARSEDIFF_LINALG_HEADERS) - file(STRINGS "${_sdl_file}" _sdl_includes REGEX "^[ \t]*#[ \t]*include[ \t]*\"") - foreach(_sdl_line IN LISTS _sdl_includes) - if(NOT _sdl_line MATCHES "\"(sparsediff_linalg|internal)/[A-Za-z0-9_]+\\.h\"") - message(FATAL_ERROR - "Layering violation in ${_sdl_file}:\n" - " ${_sdl_line}\n" - "sparsediff_linalg may include only its own headers " - "(#include \"sparsediff_linalg/.h\" or " - "\"internal/.h\") and system headers.") - endif() - if(_sdl_file MATCHES "/include/sparsediff_linalg/" AND _sdl_line MATCHES "\"internal/") - message(FATAL_ERROR - "Public header ${_sdl_file} includes a private one:\n" - " ${_sdl_line}\n" - "Headers under include/sparsediff_linalg must not include " - "internal/ headers.") - endif() - endforeach() -endforeach() - -# Sparse linear-algebra library. Built position-independent so it can be linked -# into shared objects such as a Python extension module. -add_library(sparsediff_linalg ${SPARSEDIFF_LINALG_SOURCES}) -add_library(sparsediff_linalg::sparsediff_linalg ALIAS sparsediff_linalg) -target_include_directories(sparsediff_linalg PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) -# src/ resolves the private headers ("internal/.h"); it is PRIVATE, so -# consumers cannot include them at all. -target_include_directories(sparsediff_linalg PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) -set_property(TARGET sparsediff_linalg PROPERTY POSITION_INDEPENDENT_CODE ON) -sparsediff_linalg_warnings(sparsediff_linalg) - -if(SP_TRACK_MEMORY) - target_compile_definitions(sparsediff_linalg PUBLIC SP_TRACK_MEMORY) -endif() - -# Link math library (Unix/Linux only) -if(NOT MSVC) - target_link_libraries(sparsediff_linalg PUBLIC m) -endif() - -# Find and link BLAS. Linked PUBLIC: the library's own kernels call CBLAS, and so -# may consumers, which inherit it through the target. -if(APPLE) - find_library(ACCELERATE_FRAMEWORK Accelerate) - target_link_libraries(sparsediff_linalg PUBLIC ${ACCELERATE_FRAMEWORK}) -elseif(MSVC) - find_package(OpenBLAS CONFIG QUIET) - if(TARGET OpenBLAS::OpenBLAS) - # vcpkg - target_link_libraries(sparsediff_linalg PUBLIC OpenBLAS::OpenBLAS) - else() - # e.g. conda-forge's openblas ships openblas.lib and - # include/openblas/cblas.h, but no imported target - find_library(OPENBLAS_LIBRARY NAMES openblas libopenblas REQUIRED) - get_filename_component(_sdl_openblas_libdir "${OPENBLAS_LIBRARY}" DIRECTORY) - get_filename_component(_sdl_openblas_prefix "${_sdl_openblas_libdir}" DIRECTORY) - find_path(OPENBLAS_INCLUDE_DIR cblas.h - HINTS "${_sdl_openblas_prefix}/include" - PATH_SUFFIXES openblas REQUIRED) - target_link_libraries(sparsediff_linalg PUBLIC ${OPENBLAS_LIBRARY}) - target_include_directories(sparsediff_linalg PUBLIC ${OPENBLAS_INCLUDE_DIR}) - endif() -else() - find_package(BLAS REQUIRED) - # conda-forge splits the CBLAS interface into libcblas next to libblas. Link - # it explicitly (and before BLAS) so the package keeps working when users - # switch BLAS implementations, even if the build-time libblas exports cblas_*. - set(_sdl_cblas_include_hints) - if(BLAS_LIBRARIES) - list(GET BLAS_LIBRARIES 0 _sdl_blas_lib) - get_filename_component(_sdl_blas_dir "${_sdl_blas_lib}" DIRECTORY) - find_library(CBLAS_LIBRARY NAMES cblas - HINTS "${_sdl_blas_dir}" NO_DEFAULT_PATH) - list(APPEND _sdl_cblas_include_hints "${_sdl_blas_dir}/../include") - endif() - if(CBLAS_LIBRARY) - target_link_libraries(sparsediff_linalg PUBLIC ${CBLAS_LIBRARY}) - endif() - target_link_libraries(sparsediff_linalg PUBLIC ${BLAS_LIBRARIES}) - # Some distributions ship cblas.h only under include/openblas. Export the - # directory when it is found so library and consumer sources both compile; a - # miss is not an error because cblas.h is normally on the default path. - find_path(CBLAS_INCLUDE_DIR cblas.h - HINTS ${_sdl_cblas_include_hints} - PATH_SUFFIXES openblas) - if(CBLAS_INCLUDE_DIR) - target_include_directories(sparsediff_linalg PUBLIC ${CBLAS_INCLUDE_DIR}) - endif() -endif() - -# ============================================================================= -# Tests -# ============================================================================= -if(SPARSEDIFF_LINALG_BUILD_TESTS) - enable_testing() - - # The suite links sparsediff_linalg alone, so a test that reaches outside the - # library fails to link. include/ is public on the target, so the linker - # would not catch a header-only reach; this check does. Quoted includes may - # name a library header (public or private: the suite is white-box), - # minunit/test_helpers, or a sibling test_.h. - file(GLOB_RECURSE _sdl_test_files "tests/*.c" "tests/*.h") - foreach(_sdl_file IN LISTS _sdl_test_files) - file(STRINGS "${_sdl_file}" _sdl_includes REGEX "^[ \t]*#[ \t]*include[ \t]*\"") - foreach(_sdl_line IN LISTS _sdl_includes) - if(NOT _sdl_line MATCHES - "\"((sparsediff_linalg|internal)/[A-Za-z0-9_]+|minunit|test_[A-Za-z0-9_]+)\\.h\"") - message(FATAL_ERROR - "Layering violation in ${_sdl_file}:\n" - " ${_sdl_line}\n" - "sparsediff_linalg tests may include only sparsediff_linalg/.h, " - "internal/.h, minunit.h, test_helpers.h and sibling " - "test_.h files.") - endif() - endforeach() - endforeach() - - add_executable(sparsediff_linalg_tests - tests/sparsediff_linalg_tests.c - tests/test_helpers.c - ) - target_include_directories(sparsediff_linalg_tests PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/tests - ${CMAKE_CURRENT_SOURCE_DIR}/src - ) - target_link_libraries(sparsediff_linalg_tests sparsediff_linalg) - sparsediff_linalg_warnings(sparsediff_linalg_tests) - - add_test(NAME SparseDiffLinalgTests COMMAND sparsediff_linalg_tests) -endif() diff --git a/sparsediff_linalg/include/sparsediff_linalg/COO_matrix.h b/sparsediff_linalg/include/sparsediff_linalg/COO_matrix.h deleted file mode 100644 index f71a07b8..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/COO_matrix.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef COO_matrix_H -#define COO_matrix_H - -#include "sparsediff_linalg/CSR_matrix.h" - -/* COO (Coordinate) Sparse matrix Format - * - * For an m x n matrix with nnz nonzeros: - * - rows: array of size nnz containing row indices - * - cols: array of size nnz containing column indices - * - x: array of size nnz containing values - * - value_map: array of size nnz mapping CSR_matrix entries to COO entries (for - * lower-triangular COO) - * - m: number of rows - * - n: number of columns - * - nnz: number of nonzero entries - */ -typedef struct COO_matrix -{ - int *rows; - int *cols; - double *x; - int *value_map; - int m; - int n; - int nnz; -} COO_matrix; - -/* COO from CSR */ -COO_matrix *new_COO_matrix(const CSR_matrix *A); - -/* Construct COO containing only the lower-triangular entries (col <= row) of a - symmetric CSR. Populates value_map so that refresh_lower_triangular_coo can - update values without recomputing structure. */ -COO_matrix *new_COO_matrix_lower_triangular(const CSR_matrix *A); - -/* Refresh COO values from a new CSR_matrix value array using value_map */ -void refresh_lower_triangular_coo(COO_matrix *coo, const double *vals); - -void free_COO_matrix(COO_matrix *matrix); - -#endif /* COO_matrix_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/CSC_matrix.h b/sparsediff_linalg/include/sparsediff_linalg/CSC_matrix.h deleted file mode 100644 index 41d26790..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/CSC_matrix.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef CSC_matrix_H -#define CSC_matrix_H - -#include "sparsediff_linalg/CSR_matrix.h" - -/* CSC_matrix (Compressed Sparse Column) matrix Format - * - * For an m x n matrix with nnz nonzeros: - * - p: array of size (n + 1) indicating start of each column - * - i: array of size nnz containing row indices - * - x: array of size nnz containing values - * - m: number of rows - * - n: number of columns - * - nnz: number of nonzero entries - */ -typedef struct CSC_matrix -{ - int *p; - int *i; - double *x; - int m; - int n; - int nnz; -} CSC_matrix; - -/* constructor and destructor */ -CSC_matrix *new_CSC_matrix(int m, int n, int nnz); -void free_CSC_matrix(CSC_matrix *matrix); - -/* Fill sparsity of C = A^T D A for diagonal D */ -CSR_matrix *ATA_alloc(const CSC_matrix *A); - -/* Fill sparsity of C = B^T D A for diagonal D */ -CSR_matrix *BTA_alloc(const CSC_matrix *A, const CSC_matrix *B); - -/* Fill sparsity of C = BA, where B is symmetric. */ -CSC_matrix *symBA_alloc(const CSR_matrix *B, const CSC_matrix *A); - -/* Compute values for C = A^T D A (null d corresponds to D as identity) */ -void ATDA_fill_values(const CSC_matrix *A, const double *d, CSR_matrix *C); - -/* Compute values for C = B^T D A (null d corresonds to D as identity) */ -void BTDA_fill_values(const CSC_matrix *A, const CSC_matrix *B, const double *d, - CSR_matrix *C); - -/* Fill values of C = BA. The matrix B does not have to be symmetric */ -void BA_fill_values(const CSR_matrix *B, const CSC_matrix *A, CSC_matrix *C); - -/* Fill values of C = x^T A. The matrix C must have filled sparsity. */ -void yTA_fill_values(const CSC_matrix *A, const double *x, CSR_matrix *C); - -/* Count nonzero columns of a CSC_matrix matrix */ -int count_nonzero_cols_csc(const CSC_matrix *A); - -/* convert from CSR_matrix to CSC_matrix format */ -CSC_matrix *csr_to_csc_alloc(const CSR_matrix *A, int *iwork); -void csr_to_csc_fill_values(const CSR_matrix *A, CSC_matrix *C, int *iwork); - -/* convert from CSC_matrix to CSR_matrix format */ -CSR_matrix *csc_to_csr_alloc(const CSC_matrix *A, int *iwork); -void csc_to_csr_fill_values(const CSC_matrix *A, CSR_matrix *C, int *iwork); - -#endif /* CSC_matrix_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/CSR_matrix.h b/sparsediff_linalg/include/sparsediff_linalg/CSR_matrix.h deleted file mode 100644 index 2999c31f..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/CSR_matrix.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef CSR_matrix_H -#define CSR_matrix_H -#include - -/* CSR_matrix (Compressed Sparse Row) matrix Format - * - * For an m x n matrix with nnz nonzeros: - * - p: array of size (m + 1) indicating start of each row - * - i: array of size nnz containing column indices - * - x: array of size nnz containing values - * - m: number of rows - * - n: number of columns - * - nnz: number of nonzero entries - */ -typedef struct CSR_matrix -{ - int *p; - int *i; - double *x; - int m; - int n; - int nnz; -} CSR_matrix; - -/* constructors and destructors */ -CSR_matrix *new_CSR_matrix(int m, int n, int nnz); -CSR_matrix *new_csr(const CSR_matrix *A); -CSR_matrix *new_csr_copy_sparsity(const CSR_matrix *A); -void free_CSR_matrix(CSR_matrix *matrix); -void copy_CSR_matrix(const CSR_matrix *A, CSR_matrix *C); - -/* Shrink a capacity-built CSR to its true size: realloc i and x down to - p[m] and set nnz. Call once, after the pattern is complete and before - the matrix is published to any consumer. */ -void CSR_trim(CSR_matrix *A); - -/* transpose functionality (iwork must be of size A->n) */ -CSR_matrix *transpose(const CSR_matrix *A, int *iwork); -CSR_matrix *AT_alloc(const CSR_matrix *A, int *iwork); -void AT_fill_values(const CSR_matrix *A, CSR_matrix *AT, int *iwork); - -/* computes dense y = Ax */ -void Ax_csr(const CSR_matrix *A, const double *x, double *y, int col_offset); - -/* fills values of C = diag(d) @ A */ -void DA_fill_values(const double *d, const CSR_matrix *A, CSR_matrix *C); - -/* Count number of columns with nonzero entries in A and marks them in col_nz */ -int count_nonzero_cols(const CSR_matrix *A, bool *col_nz); - -/* inserts 'idx' into array 'arr' in sorted order, and moves the other elements */ -void insert_idx(int idx, int *arr, int len); - -/* get value at position (row, col) in A */ -double csr_get_value(const CSR_matrix *A, int row, int col); - -/* Expand symmetric CSR_matrix matrix A to full matrix C. A is assumed to store - only upper triangle. C must be pre-allocated with sufficient nnz */ -void symmetrize_csr(const int *Ap, const int *Ai, int m, CSR_matrix *C); - -#endif /* CSR_matrix_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/CSR_sum.h b/sparsediff_linalg/include/sparsediff_linalg/CSR_sum.h deleted file mode 100644 index 23738114..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/CSR_sum.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef CSR_SUM_H -#define CSR_SUM_H - -#include "sparsediff_linalg/CSR_matrix.h" - -/* forward declaration */ -struct int_double_pair; - -/* Compute sparsity pattern of C = A + B (and sets C->nnz) */ -void sum_csr_alloc(const CSR_matrix *A, const CSR_matrix *B, CSR_matrix *C); - -/* Fills values of C = A + B (assuming C's sparsity pattern is set) */ -void sum_csr_fill_values(const CSR_matrix *A, const CSR_matrix *B, CSR_matrix *C); - -/* Fills values of C = diag(d1) * A + diag(d2) * B (assuming C's sparsity is set)*/ -void sum_scaled_csr_matrices_fill_values(const CSR_matrix *A, const CSR_matrix *B, - CSR_matrix *C, const double *d1, - const double *d2); - -/* The following two functions are used for summing either more than two CSR_matrix - matrices or rows of CSR_matrix matrices. To implement the filling of values - efficiently, we compute an idx_map when we fill the sparsity pattern of the output - matrix, which maps each nonzero entry in the input matrix to its position in the - output matrix. This allows us to fill the values with a single pass of the output - matrix through the input matrices, without needing to search for the position of - each entry in the output matrix. So each idx_map should have size equal to the - number of nonzeros in the corresponding input matrix, and idx_map[j] should give - the index in the output matrix of the entry (in the value array of the output - matrix) corresponding to the j-th nonzero in the input matrix. - - Output matrix C, input matrix A, iwork->size = max(A->n, A->nnz) for the first - function. The last function allocates the output matrix and returns it. */ -// ------------------------------------------------------------------------------------ -void sum_spaced_rows_into_row_csr_alloc(const CSR_matrix *A, CSR_matrix *C, - int spacing, int *iwork, int *idx_map); - -/* Compute sparsity pattern of out = A + B + C + D */ -CSR_matrix *sum_4_csr_alloc(const CSR_matrix *A, const CSR_matrix *B, - const CSR_matrix *C, const CSR_matrix *D, - int *idx_maps[4]); -// ------------------------------------------------------------------------------------ - -/* Accumulates `nnz` values from `vals` into `out` at the positions given by - `idx_map` (length `nnz`). Caller must zero `out` before calling. */ -void accumulator(const double *vals, int nnz, const int *idx_map, double *out); - -/* Accumulates values from A according to map with spacing. Must memset to zero - * before calling. */ -void accumulator_with_spacing(const CSR_matrix *A, const int *idx_map, double *out, - int spacing); - -#endif /* CSR_SUM_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/Vec_macros.h b/sparsediff_linalg/include/sparsediff_linalg/Vec_macros.h deleted file mode 100644 index b02ba82f..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/Vec_macros.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* - * Copyright 2025 Daniel Cederberg - * - * This file is part of the PSLP project (LP Presolver). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef VEC_MACROS_H -#define VEC_MACROS_H - -#include "sparsediff_linalg/tracked_alloc.h" -#include -#include -#include -#include - -// Portable UNUSED macro -#if defined(_MSC_VER) -#define PSLP_UNUSED -#else -#define PSLP_UNUSED __attribute__((unused)) -#endif - -// Macro to define a generic vector class -#define DEFINE_VECTOR(TYPE, TYPE_NAME) \ - typedef struct TYPE_NAME##Vec \ - { \ - TYPE *data; \ - int len; \ - int capacity; \ - } TYPE_NAME##Vec; \ - \ - PSLP_UNUSED static TYPE_NAME##Vec *TYPE_NAME##Vec_new(int capacity) \ - { \ - assert(capacity > 0); \ - TYPE_NAME##Vec *vec = (TYPE_NAME##Vec *) sp_malloc(sizeof(TYPE_NAME##Vec)); \ - if (vec == NULL) return NULL; \ - vec->data = (TYPE *) sp_malloc(capacity * sizeof(TYPE)); \ - if (vec->data == NULL) \ - { \ - sp_free(vec); \ - return NULL; \ - } \ - \ - vec->len = 0; \ - vec->capacity = capacity; \ - return vec; \ - } \ - \ - static inline void TYPE_NAME##Vec_free(TYPE_NAME##Vec *vec) \ - { \ - sp_free(vec->data); \ - sp_free(vec); \ - } \ - \ - static inline void TYPE_NAME##Vec_clear_no_resize(TYPE_NAME##Vec *vec) \ - { \ - vec->len = 0; \ - } \ - \ - static inline void TYPE_NAME##Vec_append(TYPE_NAME##Vec *vec, TYPE value) \ - { \ - if (vec->len >= vec->capacity) \ - { \ - vec->capacity *= 2; \ - assert(vec->capacity > 0); \ - TYPE *temp = (TYPE *) sp_realloc(vec->data, (size_t) vec->capacity * \ - sizeof(TYPE)); \ - if (temp == NULL) \ - { \ - TYPE_NAME##Vec_free(vec); \ - fprintf(stderr, "Error: realloc failed\n"); \ - exit(1); \ - } \ - \ - vec->data = temp; \ - } \ - \ - vec->data[vec->len++] = value; \ - } \ - \ - static inline void TYPE_NAME##Vec_append_array(TYPE_NAME##Vec *vec, \ - const TYPE *values, int n) \ - { \ - if (vec->len + n > vec->capacity) \ - { \ - int new_capacity = vec->capacity > 0 ? vec->capacity : 1; \ - while (vec->len + n > new_capacity) \ - { \ - new_capacity *= 2; \ - } \ - \ - TYPE *temp = (TYPE *) sp_realloc(vec->data, \ - (size_t) new_capacity * sizeof(TYPE)); \ - if (temp == NULL) \ - { \ - TYPE_NAME##Vec_free(vec); \ - fprintf(stderr, "Error: realloc failed\n"); \ - exit(1); \ - } \ - \ - vec->data = temp; \ - vec->capacity = new_capacity; \ - } \ - \ - memcpy(vec->data + vec->len, values, (size_t) n * sizeof(TYPE)); \ - vec->len += n; \ - } \ - PSLP_UNUSED static int TYPE_NAME##Vec_contains(const TYPE_NAME##Vec *vec, \ - TYPE value) \ - { \ - for (int i = 0; i < vec->len; ++i) \ - { \ - if (vec->data[i] == value) \ - { \ - return 1; /* Element found */ \ - } \ - } \ - return 0; /* Element not found */ \ - } - -#endif diff --git a/sparsediff_linalg/include/sparsediff_linalg/cblas_wrapper.h b/sparsediff_linalg/include/sparsediff_linalg/cblas_wrapper.h deleted file mode 100644 index c3d35281..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/cblas_wrapper.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef CBLAS_WRAPPER_H -#define CBLAS_WRAPPER_H - -#ifdef __APPLE__ -#define ACCELERATE_NEW_LAPACK -/* Apple's Accelerate/LAPACK/Sparse headers trip a large, SDK-dependent set - * of -Wpedantic/GNU-extension warnings (nullability qualifiers, zero-arg - * variadic macros, etc.) that we cannot fix and do not want in our build - * output. Treat everything included below as a system header so all of its - * warnings are suppressed, while our own code keeps full -Wpedantic - * strictness. This file contains nothing but the include, so the marker is - * effectively scoped to Accelerate. */ -#pragma clang system_header -#include -#else -#include -#endif - -#endif /* CBLAS_WRAPPER_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/iVec.h b/sparsediff_linalg/include/sparsediff_linalg/iVec.h deleted file mode 100644 index d49a5a6b..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/iVec.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* - * Copyright 2025 Daniel Cederberg - * - * This file is part of the PSLP project (LP Presolver). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IVEC_H -#define IVEC_H - -#include - -#include "sparsediff_linalg/Vec_macros.h" - -// This macro defines a vector of integers. -DEFINE_VECTOR(int, i) - -/* -__attribute__((unused)) static void print_ivec(iVec *vec) -{ - for (size_t i = 0; i < vec->len; ++i) - { - printf("%d ", vec->data[i]); - } - printf("\n"); -} -*/ -#endif // IVEC_H diff --git a/sparsediff_linalg/include/sparsediff_linalg/int_double_pair.h b/sparsediff_linalg/include/sparsediff_linalg/int_double_pair.h deleted file mode 100644 index 5170363f..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/int_double_pair.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef INT_DOUBLE_PAIR_H -#define INT_DOUBLE_PAIR_H - -typedef struct int_double_pair -{ - int col; - double val; -} int_double_pair; - -int_double_pair *new_int_double_pair_array(int size); -void set_int_double_pair_array(int_double_pair *pair, int *ints, double *doubles, - int size); -void free_int_double_pair_array(int_double_pair *array); -void sort_int_double_pair_array(int_double_pair *array, int size); - -#endif /* INT_DOUBLE_PAIR_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/linalg_dense_sparse_matmuls.h b/sparsediff_linalg/include/sparsediff_linalg/linalg_dense_sparse_matmuls.h deleted file mode 100644 index df026d24..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/linalg_dense_sparse_matmuls.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef LINALG_DENSE_SPARSE_H -#define LINALG_DENSE_SPARSE_H - -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/matrix.h" - -/* C = (I_p kron A) @ J via the polymorphic matrix interface. - * A is dense m x n, J is (n*p) x k in CSC_matrix, C is (m*p) x k in CSC_matrix. - * `work` must be sized at least A->n doubles — used as a scratch buffer when - * a sparse column of J needs to be densified before dgemv. The caller is - * responsible for sizing it (typically pre-sized in the corresponding - * sparsity-build step). */ -// TODO: maybe we can replace these with I_kron_X functionality? -CSC_matrix *I_kron_A_alloc(const matrix *A, const CSC_matrix *J, int p); -void I_kron_A_fill_values(const matrix *A, const CSC_matrix *J, CSC_matrix *C, - double *work); - -/* Sparsity and values of C = (Y^T kron I_m) @ J where Y is k x n, J is (m*k) x p, - and C is (m*n) x p. Y is given in column-major dense format. */ -CSR_matrix *YT_kron_I_alloc(int m, int k, int n, const CSC_matrix *J); -void YT_kron_I_fill_values(int m, int k, int n, const double *Y, const CSC_matrix *J, - CSR_matrix *C); - -/* Sparsity and values of C = (I_n kron X) @ J where X is m x k (col-major dense), - J is (k*n) x p, and C is (m*n) x p. */ -CSR_matrix *I_kron_X_alloc(int m, int k, int n, const CSC_matrix *J); -void I_kron_X_fill_values(int m, int k, int n, const double *X, const CSC_matrix *J, - CSR_matrix *C); - -#endif /* LINALG_DENSE_SPARSE_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/linalg_sparse_matmuls.h b/sparsediff_linalg/include/sparsediff_linalg/linalg_sparse_matmuls.h deleted file mode 100644 index 6b4fef7a..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/linalg_sparse_matmuls.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef LINALG_H -#define LINALG_H - -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" - -/* Compute sparsity pattern and values for the matrix-matrix multiplication - C = (I_p kron A) @ J where A is m x n, J is (n*p) x k, and C is (m*p) x k, - without relying on generic sparse matrix-matrix multiplication. Specialized - logic for this is much faster (50-100x) than generic sparse matmul. - - * J is provided in CSC_matrix format and is split into p blocks of n rows each - * C is returned in CSC_matrix format - * Mathematically it corresponds to C = [A @ J1; A @ J2; ...; A @ Jp], - where J = [J1; J2; ...; Jp] -*/ -CSC_matrix *block_left_multiply_fill_sparsity(const CSR_matrix *A, - const CSC_matrix *J, int p); - -void block_left_multiply_fill_values(const CSR_matrix *A, const CSC_matrix *J, - CSC_matrix *C); - -/* Compute y = kron(I_p, A) @ x where A is m x n and x is(n*p)-length vector. - The output y is m*p-length vector corresponding to - y = [A @ x1; A @ x2; ...; A @ xp] where x is divided into p blocks of n - elements. -*/ -void block_left_multiply_vec(const CSR_matrix *A, const double *x, double *y, int p); - -/* Fill values of C = A @ B where A is CSR_matrix, B is CSC_matrix. - * C must have sparsity pattern already computed. - */ -void csr_csc_matmul_fill_values(const CSR_matrix *A, const CSC_matrix *B, - CSR_matrix *C); - -/* C = A @ B where A is CSR_matrix, B is CSC_matrix. Result C is CSR_matrix. - * Allocates and precomputes sparsity pattern. No workspace required. - */ -CSR_matrix *csr_csc_matmul_alloc(const CSR_matrix *A, const CSC_matrix *B); - -#endif /* LINALG_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/matmul_dispatchers.h b/sparsediff_linalg/include/sparsediff_linalg/matmul_dispatchers.h deleted file mode 100644 index 84e2934a..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/matmul_dispatchers.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - */ -#ifndef MATMUL_DISPATCHERS_H -#define MATMUL_DISPATCHERS_H - -#include "sparsediff_linalg/matrix.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/sparse_matrix.h" -#include "sparsediff_linalg/stacked_pd.h" - -/* Polymorphic dispatchers for C = BT @ A and C = BT @ diag(d) @ A, where A - and B are any matrix type (permuted_dense, stacked_pd, or sparse_matrix). - - Output type varies with the operand pair: - - B is PD -> output is permuted_dense. - - B is stacked_pd -> output is stacked_pd. - - B is sparse_matrix, A is PD -> output is permuted_dense. - - B is sparse_matrix, A is spd -> output is stacked_pd. - - B is sparse_matrix, A is sparse -> output is sparse_matrix. - - Contract: for sparse_matrix operands (A or B), the caller is responsible for - refreshing csc_cache values via refresh_csc_values before - BTA_matrices_fill_values / BTDA_matrices_fill_values. */ -matrix *BTA_matrices_alloc(matrix *A, matrix *B); -void BTA_matrices_fill_values(matrix *A, matrix *B, matrix *C); -void BTDA_matrices_fill_values(matrix *A, const double *d, matrix *B, matrix *C); - -/* Polymorphic dispatchers for C = B @ A, where B is permuted_dense and A is any - matrix type (permuted_dense, stacked_pd, or sparse_matrix). The output C is - always permuted_dense. - - Contract: for sparse_matrix A, the caller is responsible for refreshing - sm_A->csc_cache values via refresh_csc_values before - BA_pd_matrices_fill_values. */ -matrix *BA_pd_matrices_alloc(const permuted_dense *B, matrix *A); -void BA_pd_matrices_fill_values(const permuted_dense *B, const matrix *A, - permuted_dense *C); - -/* Polymorphic dispatchers for C = kron(I_p, A) @ J, where A is a local dense - matrix and J is any matrix type (permuted_dense, stacked_pd, or - sparse_matrix). The output C is always stacked_pd. - - Contract 1: A must be a "full" permuted_dense (m0 == base.m, n0 == base.n, - and row_perm / col_perm are the identity). - - Contract 2: for sparse_matrix J, the caller is responsible for refreshing - sm_J->csc_cache values via refresh_csc_values before - BA_dense_kron_matrices_fill_values. */ -matrix *BA_dense_kron_matrices_alloc(const permuted_dense *A, int p, matrix *J); -void BA_dense_kron_matrices_fill_values(const permuted_dense *A, int p, - const matrix *J, stacked_pd *C); - -#endif /* MATMUL_DISPATCHERS_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/matrix.h b/sparsediff_linalg/include/sparsediff_linalg/matrix.h deleted file mode 100644 index 03b4dc09..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/matrix.h +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MATRIX_H -#define MATRIX_H - -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include - -/* Polymorphic matrix base. Concrete types embed `matrix` as their first - member and implement the vtable slots below. Currently implemented: - 1. sparse_matrix — generic CSR_matrix-backed matrix. - 2. permuted_dense — matrix whose nonzeros lie in a single dense block - located at chosen rows and columns of the global - index space. - 3. stacked_pd — vertical stack of permuted_dense blocks. - `kind` tags which one a handle is. */ - -typedef struct matrix matrix; - -/* Concrete type behind a matrix handle. MATRIX_SPARSE is 0 so a zeroed struct - is a sparse_matrix. */ -typedef enum -{ - MATRIX_SPARSE = 0, /* sparse_matrix: CSR-backed */ - MATRIX_PERMUTED_DENSE, /* permuted_dense: dense block at permuted rows/cols */ - MATRIX_STACKED_PD /* stacked_pd: vertical stack of permuted_dense blocks */ -} matrix_kind; - -/* y = kron(I_p, A) @ x */ -typedef void (*matrix_block_left_mult_vec_fn)(const matrix *A, const double *x, - double *y, int p); - -/* Allocate sparsity of C = kron(I_p, A) @ J */ -typedef CSC_matrix *(*matrix_block_left_mult_sparsity_fn)(const matrix *A, - const CSC_matrix *J, - int p); - -/* Fill values of C = kron(I_p, A) @ J */ -typedef void (*matrix_block_left_mult_values_fn)(const matrix *A, - const CSC_matrix *J, CSC_matrix *C); - -/* Allocate a new matrix with the same sparsity as A */ -typedef matrix *(*matrix_copy_sparsity_fn)(const matrix *A); - -/* Fill values of C = diag(d) @ A */ -typedef void (*matrix_DA_fill_values_fn)(const double *d, const matrix *A, - matrix *C); - -/* Allocate C = AT @ A */ -typedef matrix *(*matrix_ATA_alloc_fn)(matrix *A); - -/* Fill values of C = AT @ diag(d) @ A */ -typedef void (*matrix_ATDA_fill_values_fn)(const matrix *A, const double *d, - matrix *C); - -/* Allocate AT = transpose(A) */ -typedef matrix *(*matrix_transpose_alloc_fn)(const matrix *A); - -/* Fill values of AT = transpose(A) */ -typedef void (*matrix_transpose_fill_values_fn)(const matrix *A, matrix *AT); - -/* Returns a CSR_matrix view of A */ -typedef CSR_matrix *(*matrix_to_csr_fn)(matrix *A); - -/* Refresh any internal caches (e.g. a CSC_matrix mirror) so subsequent ATA / - ATDA calls reflect the current values. Version-guarded: a no-op when the - cache already matches values_version, so it is cheap to call when fresh. */ -typedef void (*matrix_refresh_csc_values_fn)(matrix *A); - -/* Allocate C = A[map, :]. C stores map internally, so the fill takes none. */ -typedef matrix *(*matrix_row_gather_alloc_fn)(const matrix *A, const int *map, - int m_out); - -/* Fill values of C = A[map, :] */ -typedef void (*matrix_row_gather_fill_values_fn)(const matrix *A, matrix *C); - -/* diag_vec: A is an (n, A->n) Jacobian for a length-n vector; output is - (n*n, A->n) where row i lands at output row i*(n+1) (column-major - diagonal positions). Other output rows are structurally zero. */ -typedef matrix *(*matrix_diag_vec_alloc_fn)(matrix *A); -typedef void (*matrix_diag_vec_fill_values_fn)(matrix *A, matrix *out); - -/* Allocate C = row-reduce of A: C[j, :] = sum of rows i with group[i] == j, - group[i] in [0, m_out). C stores the reduction map internally, so the fill - takes none. */ -typedef matrix *(*matrix_row_reduce_alloc_fn)(const matrix *A, const int *group, - int m_out); - -/* Fill values of C = row-reduce of A. */ -typedef void (*matrix_row_reduce_fill_values_fn)(const matrix *A, matrix *C); - -typedef void (*matrix_free_fn)(matrix *self); - -struct matrix -{ - int m, n, nnz; - double *x; /* non-owning pointer to the value buffer */ - matrix_kind kind; - - /* Monotone counter bumped whenever the matrix's values change. Consumers - that mirror the values into a cache (CSC mirror, CSR view, ...) record - the version they last saw and refresh iff it differs. Code that writes - x directly must call matrix_values_changed on the OWNER of the buffer — - aliased children (spd blocks, cache views) have no version of their - own. */ - uint64_t values_version; - - /* Operator ops */ - matrix_block_left_mult_vec_fn block_left_mult_vec; - matrix_block_left_mult_sparsity_fn block_left_mult_sparsity; - matrix_block_left_mult_values_fn block_left_mult_values; - - /* Chain-rule ops */ - matrix_copy_sparsity_fn copy_sparsity; - matrix_DA_fill_values_fn DA_fill_values; - matrix_ATA_alloc_fn ATA_alloc; - matrix_ATDA_fill_values_fn ATDA_fill_values; - matrix_transpose_alloc_fn transpose_alloc; - matrix_transpose_fill_values_fn transpose_fill_values; - - /* Views and cache */ - matrix_to_csr_fn to_csr; - matrix_refresh_csc_values_fn refresh_csc_values; - - /* Atom-specific ops */ - matrix_row_gather_alloc_fn row_gather_alloc; - matrix_row_gather_fill_values_fn row_gather_fill_values; - matrix_diag_vec_alloc_fn diag_vec_alloc; - matrix_diag_vec_fill_values_fn diag_vec_fill_values; - matrix_row_reduce_alloc_fn row_reduce_alloc; - matrix_row_reduce_fill_values_fn row_reduce_fill_values; - - /* Lifecycle */ - matrix_free_fn free_fn; -}; - -/* Notify the library after writing A->x directly. */ -static inline void matrix_values_changed(matrix *A) -{ - A->values_version++; -} - -/* Free helper */ -static inline void free_matrix(matrix *m) -{ - if (m) - { - m->free_fn(m); - } -} - -#endif /* MATRIX_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/matrix_sum.h b/sparsediff_linalg/include/sparsediff_linalg/matrix_sum.h deleted file mode 100644 index 86cd7ef2..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/matrix_sum.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MATRIX_SUM_H -#define MATRIX_SUM_H - -#include "sparsediff_linalg/matrix.h" - -/* Polymorphic wrappers for allocating C = A + B. Right now we always - convert to CSR matrices internally for the sum. */ -void sum_matrices_alloc(matrix *A, matrix *B, matrix *C); - -/* Fill values of C = A + B. Uses CSR matrices internally. */ -void sum_matrices_fill_values(matrix *A, matrix *B, matrix *C); - -/* Fill values of C = diag(d1) * A + diag(d2) * B. Uses CSR matrices internally. */ -void sum_scaled_matrices_fill_values(matrix *A, matrix *B, matrix *C, - const double *d1, const double *d2); - -#endif /* MATRIX_SUM_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/mini_numpy.h b/sparsediff_linalg/include/sparsediff_linalg/mini_numpy.h deleted file mode 100644 index 6324a49b..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/mini_numpy.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef MINI_NUMPY_H -#define MINI_NUMPY_H - -#include "sparsediff_linalg/CSR_matrix.h" - -/* Example: a = [1, 2], len = 2, repeats = 3, result = [1, 1, 1, 2, 2, 2] */ -void repeat(double *result, const double *a, int len, int repeats); - -/* Example: a = [1, 2], len = 2, tiles = 3, result = [1, 2, 1, 2, 1, 2] */ -void tile_double(double *result, const double *a, int len, int tiles); - -/* Example: size = 5, value = 3.0, result = [3.0, 3.0, 3.0, 3.0, 3.0] */ -void scaled_ones(double *result, int size, double value); - -/* Row-major dense transpose: AT[j*m + i] = A[i*n + j] for an m x n A. */ -void A_transpose(double *AT, const double *A, int m, int n); - -/* Compute v = (Y kron I_m) @ w where Y is k x n (col-major), len(w) = m * n, and - len(v) = m * k. Equivalently, reshape w as the m x n matrix W (col-major) and - compute v = vec(W @ Y^T). */ -void Y_kron_I_vec(int m, int k, int n, const double *Y, const double *w, double *v); - -/* Compute v = (I_n kron X^T) @ w where X is m x k (col-major), len(w) = m * n, and - len(v) = k * n. Equivalently, reshape w as the m x n matrix W (col-major) and - compute v = vec(X^T @ W). */ -void I_kron_XT_vec(int m, int k, int n, const double *X, const double *w, double *v); - -/* Fill T_csr's row pointers and column indices for the 1D full-convolution - Toeplitz matrix T(a), sized (m+n-1) x n with m*n nonzeros. Values (x) are - not written; call conv_matrix_fill_values to populate them. */ -void conv_matrix_fill_sparsity(CSR_matrix *T_csr, int m, int n); - -/* Overwrite T_csr->x from kernel a, using the sparsity already written by - conv_matrix_fill_sparsity. T[r, col] = a[r - col]. */ -void conv_matrix_fill_values(CSR_matrix *T_csr, const double *a); - -#endif /* MINI_NUMPY_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/permuted_dense.h b/sparsediff_linalg/include/sparsediff_linalg/permuted_dense.h deleted file mode 100644 index 82e37c71..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/permuted_dense.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef PERMUTED_DENSE_H -#define PERMUTED_DENSE_H - -#include "sparsediff_linalg/matrix.h" -#include -#include - -/* permuted_dense represents a matrix whose only nonzeros lie in a dense block - of size m0 x n0, after rows and columns are restricted to chosen subsets of - the global index space. For local indices (ii, jj) with 0 <= ii < m0 and 0 <= - jj < n0, - - M[row_perm[ii], col_perm[jj]] = X[ii, jj]. - - All other entries of M are zero. row_perm and col_perm are stored in strictly - increasing order; the constructor asserts this. */ -typedef struct permuted_dense -{ - matrix base; - int m0; - int n0; - int *row_perm; - int *col_perm; - int *col_inv; /* col_inv[col_perm[jj]] = jj, otherwise -1 */ - int *row_inv; /* row_inv[row_perm[ii]] = ii, otherwise -1 */ - - /* Row-major block of size m0 x n0. Owned by this PD when owns_X == true, - and otherwise X is a view into a buffer (eg., stacked_pd's shared values - buffer) and must not be freed by this PD. */ - double *X; - bool owns_X; - - /* Cached CSR_matrix view of the permuted dense, built on demand by to_csr. - The structure (p/i arrays) is allocated on the first call to to_csr and - reused thereafter; the values array aliases self->X so is always live with - no separate fill needed. */ - CSR_matrix *csr_cache; - - /* Workspace used for BLAS operations. Allocated lazily by the first kernel - that needs it, and resizes if another kernel needs more space than - currently allocated (that's why we store dwork_size) */ - double *kernel_dwork; - size_t kernel_dwork_size; - - /* Private buffers owned by the kernel that produced this PD. The - producing kernel may use them for per-call scratch and/or small - persistent state (e.g. cached config the fill_values needs to - read). Allocated lazily. */ - int *kernel_iwork; - size_t kernel_iwork_size; - - /* Int state bound by the alloc that produced this PD and read by the - matching fill: row_gather_alloc stores, per dense row of this PD, the - dense row of the source it copies (length m0); row_reduce_alloc stores, - per dense row of the source, the dense row of this PD it adds into - (length source m0). NULL otherwise; never touched by any other kernel - (unlike kernel_iwork). */ - int *bound_iwork; - - /* Cached transpose of this PD as another permuted_dense, allocated lazily - on first call to permuted_dense_ensure_transpose_cache. On the cache PD - itself, transpose_seen records the source's base.values_version whose - values the cache holds; consumers refill iff it is stale. */ - struct permuted_dense *transpose_cache; - uint64_t transpose_seen; -} permuted_dense; - -/* Constructor. row_perm and col_perm must be strictly increasing in their - respective ranges. If X_data is NULL the value buffer is allocated but left - uninitialized. Otherwise m0 * n0 entries are copied. */ -matrix *new_permuted_dense(int m, int n, int m0, int n0, const int *row_perm, - const int *col_perm, const double *X_data); - -/* Convenience constructor for the trivial-perm case: row_perm = [0..m-1], - col_perm = [0..n-1], dense block fills the full (m, n) shape. */ -matrix *new_permuted_dense_full(int m, int n, const double *data); - -/* Ensure A->kernel_dwork is sized at least 'size' doubles. Grows in - place; contents are NOT preserved. */ -void permuted_dense_ensure_kernel_dwork(const permuted_dense *A, size_t size); - -/* Allocate C = A[map, :], where A and C are permuted dense. C stores map - internally, so the fill takes none. */ -matrix *row_gather_pd_alloc(const permuted_dense *A, const int *map, int m_out); - -/* Fill values of C = A[map, :], where A and C are permuted dense. */ -void row_gather_pd_fill_values(const permuted_dense *A, permuted_dense *C); - -/* Allocate C = row-reduce of A (C[j, :] = sum of rows i with group[i] == j, - group[i] in [0, m_out)), where A and C are permuted dense. C stores the - reduction map internally, so the fill takes none. */ -matrix *row_reduce_pd_alloc(const permuted_dense *A, const int *group, int m_out); - -/* Fill values of C = row-reduce of A, where A and C are permuted dense. */ -void row_reduce_pd_fill_values(const permuted_dense *A, permuted_dense *C); - -/* Allocate C = diag_vec(A), where A and C are permuted dense. */ -matrix *diag_vec_pd_alloc(const permuted_dense *A); - -/* Fill values of C = diag_vec(A). */ -void diag_vec_pd_fill_values(const permuted_dense *A, permuted_dense *C); - -#endif /* PERMUTED_DENSE_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/sparse_matrix.h b/sparsediff_linalg/include/sparsediff_linalg/sparse_matrix.h deleted file mode 100644 index 1ade9ca5..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/sparse_matrix.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef SPARSE_MATRIX_H -#define SPARSE_MATRIX_H - -#include "sparsediff_linalg/matrix.h" - -/* Sparse matrix wrapping CSR_matrix. csc_cache is a lazily-built CSC_matrix mirror - used by the chain-rule ATA / ATDA paths; it's allocated on first need and refilled - by refresh_csc_values. csc_iwork is the workspace for csr_to_csc. */ -typedef struct sparse_matrix -{ - matrix base; - CSR_matrix *csr; - CSC_matrix *csc_cache; - uint64_t csc_seen; /* base.values_version the csc_cache values reflect */ - int *csc_iwork; - /* Int state bound by the alloc that produced this matrix and read by the - matching fill: transpose_alloc stores csr->n scratch for - transpose_fill_values; row_gather_alloc stores the base.m-long row map - for row_gather_fill_values; row_reduce_alloc stores the source-nnz-long - position map for row_reduce_fill_values. NULL otherwise; never touched - by any other kernel. */ - int *bound_iwork; -} sparse_matrix; - -/* Constructor. Takes ownership of A; the caller must not free A separately - (free_matrix on the returned matrix frees A). */ -matrix *new_sparse_matrix(CSR_matrix *A); - -/* Convenience: allocate a sparse_matrix of shape (m, n) with capacity for - nnz entries. Equivalent to new_sparse_matrix(new_CSR_matrix(m, n, nnz)). - Sparsity pattern and values are uninitialized. */ -matrix *new_sparse_matrix_alloc(int m, int n, int nnz); - -/* Trim a wrapped capacity-built CSR to its true size (CSR_trim) and re-sync - the base's cached x pointer and nnz, since realloc may move the buffer. */ -void sparse_matrix_trim(matrix *M); - -/* Transpose helper */ -matrix *sparse_matrix_trans(const sparse_matrix *self, int *iwork); - -/* Build the CSC_matrix cache structure if absent. Idempotent; structure-only, - values are NOT filled (use refresh_csc_values for that). Exposed so the - bivariate dispatchers in matrix_BTA can prepare sparsity without touching - uninitialized values during the init phase. */ -void sparse_matrix_ensure_csc_cache(sparse_matrix *sm); - -#endif /* SPARSE_MATRIX_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/stacked_pd.h b/sparsediff_linalg/include/sparsediff_linalg/stacked_pd.h deleted file mode 100644 index bd61d069..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/stacked_pd.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef STACKED_PD_H -#define STACKED_PD_H - -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/matrix.h" -#include "sparsediff_linalg/permuted_dense.h" - -/* stacked_pd represents a matrix that is the (vertical) union of 'n_blocks' - permuted_dense blocks. Two different blocks have disjoint row permutations, - but the column permutations may overlap across blocks. - - `src_block_idx_p` and `src_block_idx` form a CSR-style map (offsets + flat - data) from each output block to the source block indices that contribute to - it in the spd-producing operations that built this spd. For operations with - one source per output every entry of `src_block_idx_p` increments by one. For - coalesce a single output block can be fed by multiple source blocks. */ -typedef struct stacked_pd -{ - matrix base; - int n_blocks; - permuted_dense **blocks; - int *src_block_idx_p; - int *src_block_idx; - - /* Some operations on stacked_pds (like transpose) may in an intermediate - step result in a stacked_pd with blocks that do not have disjoint row - permutations. Such operations must incorporate a coalesce step that - merges rows that appear in several blocks into a single block. This pointer - holds the raw spd before the coalesce step. */ - struct stacked_pd *pre_coalesce; - - /* lazily built CSR view */ - CSR_matrix *csr_cache; - uint64_t csr_seen; /* base.values_version the csr_cache values reflect */ - - /* Private permuted_dense scratch owned by the kernel that produced - this spd. Allocated by the producing _alloc, used (without - further allocation) by the matching _fill_values, freed by - stacked_pd_free. Currently used by BA_dense_kron_spd to cache - the kron-expanded transpose-of-A buffer across fills. NULL when - not used. */ - permuted_dense *kernel_pd_scratch; -} stacked_pd; - -/* Constructor for stacked_pd. Takes ownership of every block in 'blocks'. The - arrays 'src_block_idx_p' and 'src_block_idx' map each block in the new - stacked_pd (ie. the block in 'blocks') to the source block(s) that - contributed to it in the spd-producing operations that built this spd. If - src_block_idx_p and src_block_idx are NULL, then src_block_idx is ignored and - the constructor produces the identity mapping (each output block has itself - as its one source). */ -matrix *new_stacked_pd(int m, int n, int n_blocks, permuted_dense **blocks, - const int *src_block_idx_p, const int *src_block_idx); - -/* Same as new_stacked_pd but skips a pairwise-disjoint row_permutation check in - debug mode. Use only when the input intentionally has blocks with overlapping - row permutations. */ -matrix *new_stacked_pd_unchecked(int m, int n, int n_blocks, permuted_dense **blocks, - const int *src_block_idx_p, - const int *src_block_idx); - -/* Filter-map over a stacked_pd's blocks. For each block of B, calls op(Bk, - ctx). Drop blocks whose result has nnz == 0 and assembles the survivors into - a new stacked_pd (dimensions Cm x Cn) with one source per output block. */ -typedef matrix *(*spd_block_op)(permuted_dense *Bk, const void *ctx); -matrix *spd_map_filter_blocks(const stacked_pd *B, int Cm, int Cn, spd_block_op op, - const void *ctx); - -/* Coalesce takes a stacked_pd whose blocks are disjoint but may have - overlapping row permuations and produces an equivalent stacked_pd with blocks - that have disjoint row permutations. This function allocates the output - stacked_pd. */ -matrix *coalesce_spd_alloc(const stacked_pd *A); - -/* Same as coalesce_spd_alloc but skips a pairwise-disjoint row_permutation - check in debug mode. Use only when the input intentionally has blocks with - overlapping row permutations. */ -matrix *coalesce_spd_alloc_unchecked(const stacked_pd *A); - -/* Fill values of C = coalesce(A). */ -void coalesce_spd_fill_values(const stacked_pd *A, stacked_pd *C); - -/* Same scatter as coalesce_spd_fill_values but with += instead of =. Caller is - responsible for zeroing C->base.x first. */ -void coalesce_spd_fill_values_accumulate(const stacked_pd *A, stacked_pd *C); - -/* Re-index `idx_map` in place from CSR ordering to spd-native ordering - (block-major). Useful for atoms that build idx_maps from a `to_csr` - view at init time and want to read spd values directly at eval time, - avoiding the per-call csr_cache->x refresh. - - - `spd` and `csr` must correspond (csr is the result of spd->to_csr). - - `idx_map` has length `csr->nnz` (= `spd->base.nnz`); on entry each - entry is indexed by CSR position, on exit each entry is indexed by - `spd->base.x` position. The buffer is reused; the caller's pointer - stays valid. */ -void compose_csr_idx_map_for_spd(const stacked_pd *spd, const CSR_matrix *csr, - int *idx_map); - -#endif /* STACKED_PD_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/tracked_alloc.h b/sparsediff_linalg/include/sparsediff_linalg/tracked_alloc.h deleted file mode 100644 index c5f7b389..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/tracked_alloc.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef TRACKED_ALLOC_H -#define TRACKED_ALLOC_H - -#include -#include - -/* Peak-memory tracking is only compiled in with -DSP_TRACK_MEMORY=ON (CMake - option). Otherwise sp_* are plain malloc/calloc/realloc/free. */ -#ifdef SP_TRACK_MEMORY - -/* Platform shim for "how many usable bytes are at this malloc'd pointer". - Used to track total live bytes */ -#if defined(__APPLE__) -#include -#define TRACKED_BLOCK_SIZE(p) malloc_size(p) -#elif defined(_WIN32) || defined(_WIN64) -#include -#define TRACKED_BLOCK_SIZE(p) _msize(p) -#else -#include -#define TRACKED_BLOCK_SIZE(p) malloc_usable_size(p) -#endif - -extern size_t g_allocated_bytes; /* current live bytes */ -extern size_t g_peak_bytes; /* high-water mark since last reset */ - -/* All allocations in src/ must go through these wrappers (and pair sp_free - with sp_malloc / sp_calloc). Tests may use plain malloc/free — those - bytes are simply not tracked. */ -static inline void *sp_malloc(size_t size) -{ - void *ptr = malloc(size); - if (ptr) - { - g_allocated_bytes += TRACKED_BLOCK_SIZE(ptr); - if (g_allocated_bytes > g_peak_bytes) - { - g_peak_bytes = g_allocated_bytes; - } - } - return ptr; -} - -static inline void *sp_calloc(size_t count, size_t size) -{ - void *ptr = calloc(count, size); - if (ptr) - { - g_allocated_bytes += TRACKED_BLOCK_SIZE(ptr); - if (g_allocated_bytes > g_peak_bytes) - { - g_peak_bytes = g_allocated_bytes; - } - } - return ptr; -} - -static inline void sp_free(void *ptr) -{ - if (ptr) - { - g_allocated_bytes -= TRACKED_BLOCK_SIZE(ptr); - free(ptr); - } -} - -static inline void *sp_realloc(void *ptr, size_t size) -{ - size_t old_block_size = ptr ? TRACKED_BLOCK_SIZE(ptr) : 0; - void *new_ptr = realloc(ptr, size); - if (new_ptr) - { - g_allocated_bytes = - g_allocated_bytes - old_block_size + TRACKED_BLOCK_SIZE(new_ptr); - if (g_allocated_bytes > g_peak_bytes) - { - g_peak_bytes = g_allocated_bytes; - } - } - /* realloc returning NULL means failure — original block is still live - per C standard, so no counter adjustment. */ - return new_ptr; -} - -#else /* !SP_TRACK_MEMORY */ - -/* Tracking disabled (default): the wrappers forward straight to libc and the - library has no mutable global state. */ -static inline void *sp_malloc(size_t size) -{ - return malloc(size); -} - -static inline void *sp_calloc(size_t count, size_t size) -{ - return calloc(count, size); -} - -static inline void sp_free(void *ptr) -{ - free(ptr); -} - -static inline void *sp_realloc(void *ptr, size_t size) -{ - return realloc(ptr, size); -} - -#endif /* SP_TRACK_MEMORY */ - -#endif /* TRACKED_ALLOC_H */ diff --git a/sparsediff_linalg/include/sparsediff_linalg/utils.h b/sparsediff_linalg/include/sparsediff_linalg/utils.h deleted file mode 100644 index 0058be28..00000000 --- a/sparsediff_linalg/include/sparsediff_linalg/utils.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef UTILS_H -#define UTILS_H - -#include "sparsediff_linalg/iVec.h" -#include -#include - -#ifndef MAX -#define MAX(a, b) ((a) > (b) ? (a) : (b)) -#endif -#ifndef MIN -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#endif - -/* Dense cell count a*b for allocation upper bounds, clamped to INT_MAX on overflow. - Used as MIN(loose_nnz_bound, sat_mul_int(rows, cols)): a real nnz bound is always - < INT_MAX, so on overflow the MIN correctly falls back to the loose bound instead - of selecting a wrapped (negative or small-positive) product. */ -static inline int sat_mul_int(int a, int b) -{ - if (a <= 0 || b <= 0) return 0; - if (a > INT_MAX / b) return INT_MAX; - return a * b; -} - -/* Sort an array of integers in ascending order */ -void sort_int_array(int *array, int size); - -/* Return true if sorted index arrays a_idx and b_idx (lengths a_len, b_len) - share any value, where b_idx entries are shifted by b_offset before - comparison (a_idx[ai] == b_idx[bi] - b_offset). */ -bool has_overlap(const int *a_idx, int a_len, const int *b_idx, int b_len, - int b_offset); - -/* Find positions where two sorted, ascending int arrays match. For each match - (a[ii] == b[jj]) writes ii into idx_a and jj into idx_b. Returns the count. */ -int sorted_intersect_indices(const int *a, int a_len, const int *b, int b_len, - int *idx_a, int *idx_b); - -/* Merges 'n_arrs' sorted, ascending integer arrays into 'out', 'producing the - sorted deduplicated union. 'arrs[a]' has length 'lens[a]'. 'out' is cleared - (length reset, capacity reused) and populated. */ -void sorted_union_int_arrays(const int *const *arrs, const int *lens, int n_arrs, - iVec *out); - -/* in-place cumulative sum */ -void cumsum(int *p, int n); - -#endif // UTILS_H diff --git a/sparsediff_linalg/src/COO_matrix.c b/sparsediff_linalg/src/COO_matrix.c deleted file mode 100644 index ef6940e0..00000000 --- a/sparsediff_linalg/src/COO_matrix.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/COO_matrix.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include -#include - -COO_matrix *new_COO_matrix(const CSR_matrix *A) -{ - COO_matrix *coo = (COO_matrix *) sp_malloc(sizeof(COO_matrix)); - coo->m = A->m; - coo->n = A->n; - coo->nnz = A->nnz; - coo->rows = (int *) sp_malloc(A->nnz * sizeof(int)); - coo->cols = (int *) sp_malloc(A->nnz * sizeof(int)); - coo->x = (double *) sp_malloc(A->nnz * sizeof(double)); - coo->value_map = NULL; - - for (int r = 0; r < A->m; r++) - { - for (int k = A->p[r]; k < A->p[r + 1]; k++) - { - coo->rows[k] = r; - } - } - - memcpy(coo->cols, A->i, A->nnz * sizeof(int)); - memcpy(coo->x, A->x, A->nnz * sizeof(double)); - - return coo; -} - -COO_matrix *new_COO_matrix_lower_triangular(const CSR_matrix *A) -{ - /* Pass 1: count lower-triangular entries (col <= row) */ - int count = 0; - for (int r = 0; r < A->m; r++) - { - for (int k = A->p[r]; k < A->p[r + 1]; k++) - { - if (A->i[k] <= r) - { - count++; - } - } - } - - COO_matrix *coo = (COO_matrix *) sp_malloc(sizeof(COO_matrix)); - coo->m = A->m; - coo->n = A->n; - coo->nnz = count; - coo->rows = (int *) sp_malloc(count * sizeof(int)); - coo->cols = (int *) sp_malloc(count * sizeof(int)); - coo->x = (double *) sp_malloc(count * sizeof(double)); - coo->value_map = (int *) sp_malloc(count * sizeof(int)); - - /* Pass 2: fill arrays */ - int idx = 0; - for (int r = 0; r < A->m; r++) - { - for (int k = A->p[r]; k < A->p[r + 1]; k++) - { - if (A->i[k] <= r) - { - coo->rows[idx] = r; - coo->cols[idx] = A->i[k]; - coo->x[idx] = A->x[k]; - coo->value_map[idx] = k; - idx++; - } - } - } - - return coo; -} - -void refresh_lower_triangular_coo(COO_matrix *coo, const double *vals) -{ - for (int i = 0; i < coo->nnz; i++) - { - coo->x[i] = vals[coo->value_map[i]]; - } -} - -void free_COO_matrix(COO_matrix *matrix) -{ - if (matrix) - { - sp_free(matrix->rows); - sp_free(matrix->cols); - sp_free(matrix->x); - sp_free(matrix->value_map); - sp_free(matrix); - } -} diff --git a/sparsediff_linalg/src/CSC_matrix.c b/sparsediff_linalg/src/CSC_matrix.c deleted file mode 100644 index c613f54f..00000000 --- a/sparsediff_linalg/src/CSC_matrix.c +++ /dev/null @@ -1,550 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/iVec.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include -#include -#include - -CSC_matrix *new_CSC_matrix(int m, int n, int nnz) -{ - CSC_matrix *matrix = (CSC_matrix *) sp_malloc(sizeof(CSC_matrix)); - if (!matrix) return NULL; - - matrix->p = (int *) sp_malloc((n + 1) * sizeof(int)); - matrix->i = (int *) sp_malloc(nnz * sizeof(int)); - matrix->x = (double *) sp_malloc(nnz * sizeof(double)); - - if (!matrix->p || !matrix->i || !matrix->x) - { - sp_free(matrix->p); - sp_free(matrix->i); - sp_free(matrix->x); - sp_free(matrix); - return NULL; - } - - matrix->m = m; - matrix->n = n; - matrix->nnz = nnz; - - return matrix; -} - -void free_CSC_matrix(CSC_matrix *matrix) -{ - if (matrix) - { - sp_free(matrix->p); - sp_free(matrix->i); - sp_free(matrix->x); - sp_free(matrix); - } -} - -CSR_matrix *ATA_alloc(const CSC_matrix *A) -{ - /* A is m x n, A^T A is n x n */ - int n = A->n; - int m = A->m; - int nnz = 0; - int i, j, ii, jj; - - /* row ptr and column idxs for upper triangular part of C = A^T A */ - int *Cp = (int *) sp_malloc((n + 1) * sizeof(int)); - iVec *Ci = iVec_new(m); - Cp[0] = 0; - - /* compute sparsity pattern, only storing upper triangular part */ - for (i = 0; i < n; i++) - { - /* check if Cij != 0 */ - for (j = i; j < n; j++) - { - ii = A->p[i]; - jj = A->p[j]; - - while (ii < A->p[i + 1] && jj < A->p[j + 1]) - { - if (A->i[ii] == A->i[jj]) - { - nnz += (j != i) ? 2 : 1; - iVec_append(Ci, j); - break; - } - else if (A->i[ii] < A->i[jj]) - { - ii++; - } - else - { - jj++; - } - } - } - Cp[i + 1] = Ci->len; - } - - /* Allocate C and symmetrize it */ - CSR_matrix *C = new_CSR_matrix(n, n, nnz); - symmetrize_csr(Cp, Ci->data, n, C); - - /* free workspace */ - sp_free(Cp); - iVec_free(Ci); - - return C; -} - -static inline double sparse_dot(const double *a_x, const int *a_i, int a_nnz, - const double *b_x, const int *b_i, int b_nnz) -{ - int ii = 0; - int jj = 0; - double sum = 0.0; - - while (ii < a_nnz && jj < b_nnz) - { - if (a_i[ii] == b_i[jj]) - { - sum += a_x[ii] * b_x[jj]; - ii++; - jj++; - } - else if (a_i[ii] < b_i[jj]) - { - ii++; - } - else - { - jj++; - } - } - - return sum; -} - -static inline double sparse_wdot(const double *a_x, const int *a_i, int a_nnz, - const double *b_x, const int *b_i, int b_nnz, - const double *d) -{ - int ii = 0; - int jj = 0; - double sum = 0.0; - - while (ii < a_nnz && jj < b_nnz) - { - if (a_i[ii] == b_i[jj]) - { - sum += a_x[ii] * b_x[jj] * d[a_i[ii]]; - ii++; - jj++; - } - else if (a_i[ii] < b_i[jj]) - { - ii++; - } - else - { - jj++; - } - } - - return sum; -} - -void ATDA_fill_values(const CSC_matrix *A, const double *d, CSR_matrix *C) -{ - int j, ii, jj; - for (ii = 0; ii < C->m; ii++) - { - for (jj = C->p[ii]; jj < C->p[ii + 1]; jj++) - { - j = C->i[jj]; - - if (j < ii) - { - C->x[jj] = csr_get_value(C, j, ii); - } - else - { - int nnz_ai = A->p[ii + 1] - A->p[ii]; - int nnz_aj = A->p[j + 1] - A->p[j]; - - if (d != NULL) - { - C->x[jj] = - sparse_wdot(A->x + A->p[ii], A->i + A->p[ii], nnz_ai, - A->x + A->p[j], A->i + A->p[j], nnz_aj, d); - } - else - { - C->x[jj] = sparse_dot(A->x + A->p[ii], A->i + A->p[ii], nnz_ai, - A->x + A->p[j], A->i + A->p[j], nnz_aj); - } - } - } - } -} - -CSC_matrix *csr_to_csc_alloc(const CSR_matrix *A, int *iwork) -{ - CSC_matrix *C = new_CSC_matrix(A->m, A->n, A->nnz); - - int i, j; - int *count = iwork; - memset(count, 0, A->n * sizeof(int)); - - // ------------------------------------------------------------------- - // compute nnz in each column of A - // ------------------------------------------------------------------- - for (i = 0; i < A->m; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; ++j) - { - count[A->i[j]]++; - } - } - - // ------------------------------------------------------------------ - // compute column pointers - // ------------------------------------------------------------------ - C->p[0] = 0; - for (i = 0; i < A->n; ++i) - { - C->p[i + 1] = C->p[i] + count[i]; - count[i] = C->p[i]; - } - - // ------------------------------------------------------------------ - // fill row indices - // ------------------------------------------------------------------ - for (i = 0; i < A->m; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; ++j) - { - C->i[count[A->i[j]]] = i; - count[A->i[j]]++; - } - } - - return C; -} - -void csr_to_csc_fill_values(const CSR_matrix *A, CSC_matrix *C, int *iwork) -{ - int i, j; - int *count = iwork; - memcpy(count, C->p, A->n * sizeof(int)); - - // ------------------------------------------------------------------ - // fill values - // ------------------------------------------------------------------ - for (i = 0; i < A->m; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; ++j) - { - C->x[count[A->i[j]]] = A->x[j]; - count[A->i[j]]++; - } - } -} - -CSR_matrix *csc_to_csr_alloc(const CSC_matrix *A, int *iwork) -{ - CSR_matrix *C = new_CSR_matrix(A->m, A->n, A->nnz); - - int i, j; - int *count = iwork; - memset(count, 0, A->m * sizeof(int)); - - // ------------------------------------------------------------------- - // compute nnz in each row of A, store in count - // ------------------------------------------------------------------- - for (i = 0; i < A->n; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; ++j) - { - assert(A->i[j] < A->m); - count[A->i[j]]++; - } - } - - // ------------------------------------------------------------------ - // compute row pointers - // ------------------------------------------------------------------ - C->p[0] = 0; - for (i = 0; i < A->m; ++i) - { - C->p[i + 1] = C->p[i] + count[i]; - count[i] = C->p[i]; - } - - // ------------------------------------------------------------------ - // fill column indices - // ------------------------------------------------------------------ - for (i = 0; i < A->n; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; ++j) - { - assert(A->i[j] < A->m); - C->i[count[A->i[j]]] = i; - count[A->i[j]]++; - } - } - - return C; -} - -void csc_to_csr_fill_values(const CSC_matrix *A, CSR_matrix *C, int *iwork) -{ - int i, j; - int *count = iwork; - memcpy(count, C->p, A->m * sizeof(int)); - - // ------------------------------------------------------------------ - // fill values - // ------------------------------------------------------------------ - for (i = 0; i < A->n; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; ++j) - { - assert(A->i[j] < A->m); - C->x[count[A->i[j]]] = A->x[j]; - count[A->i[j]]++; - } - } -} - -CSR_matrix *BTA_alloc(const CSC_matrix *A, const CSC_matrix *B) -{ - /* A is m x n, B is m x p, C = B^T A is p x n */ - int n = A->n; - int p = B->n; - int nnz = 0; - int i, j, ii, jj; - - /* row ptr and column idxs for C = B^T A */ - int *Cp = (int *) sp_malloc((p + 1) * sizeof(int)); - iVec *Ci = iVec_new(n); - Cp[0] = 0; - - /* compute sparsity pattern */ - for (i = 0; i < p; i++) - { - /* check if Cij != 0 for each column j of A */ - for (j = 0; j < n; j++) - { - ii = B->p[i]; - jj = A->p[j]; - - /* check if row i of B^T (column i of B) has common row with column j of - * A */ - while (ii < B->p[i + 1] && jj < A->p[j + 1]) - { - if (B->i[ii] == A->i[jj]) - { - nnz++; - iVec_append(Ci, j); - break; - } - else if (B->i[ii] < A->i[jj]) - { - ii++; - } - else - { - jj++; - } - } - } - Cp[i + 1] = Ci->len; - } - - /* Allocate C */ - CSR_matrix *C = new_CSR_matrix(p, n, nnz); - memcpy(C->p, Cp, (p + 1) * sizeof(int)); - memcpy(C->i, Ci->data, nnz * sizeof(int)); - - /* free workspace */ - sp_free(Cp); - iVec_free(Ci); - - return C; -} - -void yTA_fill_values(const CSC_matrix *A, const double *y, CSR_matrix *C) -{ - for (int col = 0; col < A->n; col++) - { - double val = 0; - for (int j = A->p[col]; j < A->p[col + 1]; j++) - { - val += y[A->i[j]] * A->x[j]; - } - - if (A->p[col + 1] - A->p[col] == 0) continue; - - /* find position in C and fill value */ - for (int k = 0; k < C->nnz; k++) - { - if (C->i[k] == col) - { - C->x[k] = val; - break; - } - } - } -} - -/* computes C = B^T * D * A in CSR_matrix */ -void BTDA_fill_values(const CSC_matrix *A, const CSC_matrix *B, const double *d, - CSR_matrix *C) -{ - int i, j, jj; - for (i = 0; i < C->m; i++) - { - for (jj = C->p[i]; jj < C->p[i + 1]; jj++) - { - j = C->i[jj]; - - int nnz_bi = B->p[i + 1] - B->p[i]; - int nnz_aj = A->p[j + 1] - A->p[j]; - - if (d != NULL) - { - C->x[jj] = sparse_wdot(B->x + B->p[i], B->i + B->p[i], nnz_bi, - A->x + A->p[j], A->i + A->p[j], nnz_aj, d); - } - else - { - C->x[jj] = sparse_dot(B->x + B->p[i], B->i + B->p[i], nnz_bi, - A->x + A->p[j], A->i + A->p[j], nnz_aj); - } - } - } -} - -/* NOTE: an alternative marker-based approach (scatter A_{k,j} * Q[k,:] - * into column j of C using a marker array for position lookup) may be - * faster when Q is dense, since it touches each Q entry exactly once. - * The sparse_dot approach below is simpler but redundantly scans - * column j of A for each nonzero row of C. */ -void BA_fill_values(const CSR_matrix *Q, const CSC_matrix *A, CSC_matrix *C) -{ - /* fill values of C = Q * A, given the sparsity pattern of C. */ - int i, j, ii; - - /* for each column j of C */ - for (j = 0; j < C->n; j++) - { - for (ii = C->p[j]; ii < C->p[j + 1]; ii++) - { - i = C->i[ii]; - int nnz_q = Q->p[i + 1] - Q->p[i]; - int nnz_a = A->p[j + 1] - A->p[j]; - - /* inner product between row i of Q and column j of A */ - C->x[ii] = sparse_dot(Q->x + Q->p[i], Q->i + Q->p[i], nnz_q, - A->x + A->p[j], A->i + A->p[j], nnz_a); - } - } -} - -CSC_matrix *symBA_alloc(const CSR_matrix *B, const CSC_matrix *A) -{ - /* Allocate C = B * A (sparsity only). B must be symmetric. - * B is CSR_matrix (m x m), A is CSC_matrix (m x n), C is CSC_matrix (m x n). - * - * Column j of C is B * a_j = sum_k A_{k,j} B[:, k], so the nonzero - * rows of column j of C are the union of the nonzero rows of B[:, k]. - * - * Since B is symmetric, we can find the nonzero rows of B[:, k] by - * finding the nonzero columns of B in row k. - * - * We use a marker array to avoid duplicates: marker[l] stores the - * last column j that registered l as nonzero, so checking - * marker[l] != j avoids duplicates. */ - - int m = B->m; - int n = A->n; - int i, j, k, jj, ii, ell; - - /* marker[row] = last column j that registered row as nonzero */ - int *marker = (int *) sp_malloc(m * sizeof(int)); - for (i = 0; i < m; i++) - { - marker[i] = -1; - } - - int *Cp = (int *) sp_malloc((n + 1) * sizeof(int)); - iVec *Ci = iVec_new(A->nnz); - Cp[0] = 0; - - /* for each column j of C */ - for (j = 0; j < n; j++) - { - int col_nnz = 0; - - /* iterate over nonzero rows k in column j of A */ - for (ii = A->p[j]; ii < A->p[j + 1]; ii++) - { - k = A->i[ii]; - - /* find nonzero rows ell of column k of B */ - for (jj = B->p[k]; jj < B->p[k + 1]; jj++) - { - ell = B->i[jj]; - if (marker[ell] != j) - { - marker[ell] = j; - iVec_append(Ci, ell); - col_nnz++; - } - } - } - - Cp[j + 1] = Cp[j] + col_nnz; - } - - /* allocate C and copy the computed structure */ - int total_nnz = Cp[n]; - CSC_matrix *C = new_CSC_matrix(m, n, total_nnz); - memcpy(C->p, Cp, (n + 1) * sizeof(int)); - memcpy(C->i, Ci->data, total_nnz * sizeof(int)); - - sp_free(marker); - sp_free(Cp); - iVec_free(Ci); - - return C; -} - -int count_nonzero_cols_csc(const CSC_matrix *A) -{ - int count = 0; - for (int j = 0; j < A->n; j++) - { - if (A->p[j + 1] > A->p[j]) - { - count++; - } - } - return count; -} diff --git a/sparsediff_linalg/src/CSR_matrix.c b/sparsediff_linalg/src/CSR_matrix.c deleted file mode 100644 index 933c0134..00000000 --- a/sparsediff_linalg/src/CSR_matrix.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/int_double_pair.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include -#include -#include - -CSR_matrix *new_CSR_matrix(int m, int n, int nnz) -{ - CSR_matrix *matrix = (CSR_matrix *) sp_malloc(sizeof(CSR_matrix)); - matrix->p = (int *) sp_calloc(m + 1, sizeof(int)); - matrix->i = (int *) sp_calloc(nnz, sizeof(int)); - matrix->x = (double *) sp_malloc(nnz * sizeof(double)); - matrix->m = m; - matrix->n = n; - matrix->nnz = nnz; - return matrix; -} - -CSR_matrix *new_csr(const CSR_matrix *A) -{ - CSR_matrix *copy = new_CSR_matrix(A->m, A->n, A->nnz); - memcpy(copy->p, A->p, (A->m + 1) * sizeof(int)); - memcpy(copy->i, A->i, A->nnz * sizeof(int)); - memcpy(copy->x, A->x, A->nnz * sizeof(double)); - return copy; -} - -CSR_matrix *new_csr_copy_sparsity(const CSR_matrix *A) -{ - CSR_matrix *copy = new_CSR_matrix(A->m, A->n, A->nnz); - memcpy(copy->p, A->p, (A->m + 1) * sizeof(int)); - memcpy(copy->i, A->i, A->nnz * sizeof(int)); - return copy; -} - -void CSR_trim(CSR_matrix *A) -{ - int nnz = A->p[A->m]; - A->nnz = nnz; - if (nnz == 0) return; /* realloc(ptr, 0) frees and returns NULL on MSVC */ - A->i = (int *) sp_realloc(A->i, nnz * sizeof(int)); - A->x = (double *) sp_realloc(A->x, nnz * sizeof(double)); -} - -void free_CSR_matrix(CSR_matrix *matrix) -{ - if (matrix) - { - sp_free(matrix->p); - sp_free(matrix->i); - sp_free(matrix->x); - sp_free(matrix); - } -} - -void copy_CSR_matrix(const CSR_matrix *A, CSR_matrix *C) -{ - C->m = A->m; - C->n = A->n; - C->nnz = A->nnz; - memcpy(C->p, A->p, (A->m + 1) * sizeof(int)); - memcpy(C->i, A->i, A->nnz * sizeof(int)); - memcpy(C->x, A->x, A->nnz * sizeof(double)); -} - -void Ax_csr(const CSR_matrix *A, const double *x, double *y, int col_offset) -{ - for (int row = 0; row < A->m; row++) - { - double sum = 0.0; - for (int j = A->p[row]; j < A->p[row + 1]; j++) - { - sum += A->x[j] * x[A->i[j] - col_offset]; - } - y[row] = sum; - } -} - -int count_nonzero_cols(const CSR_matrix *A, bool *col_nz) -{ - for (int row = 0; row < A->m; row++) - { - for (int j = A->p[row]; j < A->p[row + 1]; j++) - { - col_nz[A->i[j]] = true; - } - } - int count = 0; - for (int col = 0; col < A->n; col++) - { - if (col_nz[col]) count++; - } - - return count; -} - -void insert_idx(int idx, int *arr, int len) -{ - int j = 0; - - while (j < len && arr[j] < idx) - { - j++; - } - - // move elements to the right - memmove(arr + (j + 1), arr + j, (len - j) * sizeof(int)); - arr[j] = idx; -} - -void DA_fill_values(const double *d, const CSR_matrix *A, CSR_matrix *C) -{ - memcpy(C->x, A->x, A->nnz * sizeof(double)); - - for (int row = 0; row < C->m; row++) - { - for (int j = C->p[row]; j < C->p[row + 1]; j++) - { - C->x[j] *= d[row]; - } - } -} - -CSR_matrix *transpose(const CSR_matrix *A, int *iwork) -{ - CSR_matrix *AT = new_CSR_matrix(A->n, A->m, A->nnz); - - int i, j; - int *count = iwork; - memset(count, 0, A->n * sizeof(int)); - - // ------------------------------------------------------------------- - // compute nnz in each column of A - // ------------------------------------------------------------------- - for (i = 0; i < A->m; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; ++j) - { - count[A->i[j]]++; - } - } - - // ------------------------------------------------------------------ - // compute row pointers - // ------------------------------------------------------------------ - AT->p[0] = 0; - for (i = 0; i < A->n; i++) - { - AT->p[i + 1] = AT->p[i] + count[i]; - iwork[i] = AT->p[i]; - } - - // ------------------------------------------------------------------ - // fill transposed matrix - // ------------------------------------------------------------------ - for (i = 0; i < A->m; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; j++) - { - AT->x[count[A->i[j]]] = A->x[j]; - AT->i[count[A->i[j]]] = i; - count[A->i[j]]++; - } - } - - return AT; -} - -CSR_matrix *AT_alloc(const CSR_matrix *A, int *iwork) -{ - /* Allocate A^T and compute sparsity pattern without filling values */ - CSR_matrix *AT = new_CSR_matrix(A->n, A->m, A->nnz); - - int i, j; - int *count = iwork; - memset(count, 0, A->n * sizeof(int)); - - // ------------------------------------------------------------------- - // compute nnz in each column of A - // ------------------------------------------------------------------- - for (i = 0; i < A->m; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; ++j) - { - count[A->i[j]]++; - } - } - - // ------------------------------------------------------------------ - // compute row pointers - // ------------------------------------------------------------------ - AT->p[0] = 0; - for (i = 0; i < A->n; i++) - { - AT->p[i + 1] = AT->p[i] + count[i]; - count[i] = AT->p[i]; - } - - // ------------------------------------------------------------------ - // fill column indices - // ------------------------------------------------------------------ - for (i = 0; i < A->m; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; j++) - { - AT->i[count[A->i[j]]] = i; - count[A->i[j]]++; - } - } - - return AT; -} - -void AT_fill_values(const CSR_matrix *A, CSR_matrix *AT, int *iwork) -{ - /* Fill values of A^T given sparsity pattern is already computed */ - int i, j; - int *count = iwork; - memcpy(count, AT->p, A->n * sizeof(int)); - - /* Fill values by placing each element of A into its transposed position */ - for (i = 0; i < A->m; ++i) - { - for (j = A->p[i]; j < A->p[i + 1]; j++) - { - AT->x[count[A->i[j]]] = A->x[j]; - count[A->i[j]]++; - } - } -} - -double csr_get_value(const CSR_matrix *A, int row, int col) -{ - for (int j = A->p[row]; j < A->p[row + 1]; j++) - { - if (A->i[j] == col) - { - return A->x[j]; - } - } - return 0.0; -} - -void symmetrize_csr(const int *Ap, const int *Ai, int m, CSR_matrix *C) -{ - int i, j, col; - - /* Count entries per row */ - int *counts = (int *) sp_calloc(m, sizeof(int)); - for (i = 0; i < m; i++) - { - for (j = Ap[i]; j < Ap[i + 1]; j++) - { - counts[i]++; - if (Ai[j] != i) counts[Ai[j]]++; - } - } - - /* Build row pointers */ - C->p[0] = 0; - for (i = 0; i < m; i++) - { - C->p[i + 1] = C->p[i] + counts[i]; - } - - /* Fill column indices */ - memset(counts, 0, m * sizeof(int)); - for (i = 0; i < m; i++) - { - for (j = Ap[i]; j < Ap[i + 1]; j++) - { - col = Ai[j]; - - /* Add to row i */ - C->i[C->p[i] + counts[i]] = col; - counts[i]++; - - /* Add symmetric entry to row col */ - if (col != i) - { - C->i[C->p[col] + counts[col]] = i; - counts[col]++; - } - } - } - - sp_free(counts); -} diff --git a/sparsediff_linalg/src/CSR_sum.c b/sparsediff_linalg/src/CSR_sum.c deleted file mode 100644 index 105835d0..00000000 --- a/sparsediff_linalg/src/CSR_sum.c +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/CSR_sum.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/int_double_pair.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include - -void sum_csr_alloc(const CSR_matrix *A, const CSR_matrix *B, CSR_matrix *C) -{ - /* A and B must be different from C */ - assert(A != C && B != C); - - int cursor = 0; - - for (int row = 0; row < A->m; row++) - { - int a_ptr = A->p[row]; - int a_end = A->p[row + 1]; - int b_ptr = B->p[row]; - int b_end = B->p[row + 1]; - C->p[row] = cursor; - - /* Merge while both have elements (only column indices) */ - while (a_ptr < a_end && b_ptr < b_end) - { - if (A->i[a_ptr] < B->i[b_ptr]) - { - C->i[cursor] = A->i[a_ptr]; - a_ptr++; - } - else if (B->i[b_ptr] < A->i[a_ptr]) - { - C->i[cursor] = B->i[b_ptr]; - b_ptr++; - } - else - { - C->i[cursor] = A->i[a_ptr]; - a_ptr++; - b_ptr++; - } - cursor++; - } - - /* Copy remaining elements from A */ - if (a_ptr < a_end) - { - int a_remaining = a_end - a_ptr; - memcpy(C->i + cursor, A->i + a_ptr, a_remaining * sizeof(int)); - cursor += a_remaining; - } - - /* Copy remaining elements from B */ - if (b_ptr < b_end) - { - int b_remaining = b_end - b_ptr; - memcpy(C->i + cursor, B->i + b_ptr, b_remaining * sizeof(int)); - cursor += b_remaining; - } - } - - C->p[A->m] = cursor; - C->nnz = cursor; -} - -void sum_csr_fill_values(const CSR_matrix *A, const CSR_matrix *B, CSR_matrix *C) -{ - /* Assumes C->p and C->i already contain the sparsity pattern of A+B. - Fills only C->x accordingly. */ - for (int row = 0; row < A->m; row++) - { - int a_ptr = A->p[row]; - int a_end = A->p[row + 1]; - int b_ptr = B->p[row]; - int b_end = B->p[row + 1]; - - for (int c_ptr = C->p[row]; c_ptr < C->p[row + 1]; c_ptr++) - { - int col = C->i[c_ptr]; - double val = 0.0; - - if (a_ptr < a_end && A->i[a_ptr] == col) - { - val += A->x[a_ptr]; - a_ptr++; - } - - if (b_ptr < b_end && B->i[b_ptr] == col) - { - val += B->x[b_ptr]; - b_ptr++; - } - C->x[c_ptr] = val; - } - } -} - -void sum_scaled_csr_matrices_fill_values(const CSR_matrix *A, const CSR_matrix *B, - CSR_matrix *C, const double *d1, - const double *d2) -{ - /* Assumes C->p and C->i already contain the sparsity pattern of A+B. - Fills only C->x accordingly with scaling. */ - for (int row = 0; row < A->m; row++) - { - int a_ptr = A->p[row]; - int a_end = A->p[row + 1]; - int b_ptr = B->p[row]; - int b_end = B->p[row + 1]; - - for (int c_ptr = C->p[row]; c_ptr < C->p[row + 1]; c_ptr++) - { - int col = C->i[c_ptr]; - double val = 0.0; - - if (a_ptr < a_end && A->i[a_ptr] == col) - { - val += d1[row] * A->x[a_ptr]; - a_ptr++; - } - - if (b_ptr < b_end && B->i[b_ptr] == col) - { - val += d2[row] * B->x[b_ptr]; - b_ptr++; - } - C->x[c_ptr] = val; - } - } -} - -void accumulator(const double *vals, int nnz, const int *idx_map, double *out) -{ - /* don't forget to initialize accumulator to 0 before calling this */ - for (int j = 0; j < nnz; j++) - { - out[idx_map[j]] += vals[j]; - } -} - -void accumulator_with_spacing(const CSR_matrix *A, const int *idx_map, double *out, - int spacing) -{ - /* don't forget to initialze accumulator to 0 before calling this */ - for (int row = 0; row < A->m; row += spacing) - { - for (int j = A->p[row]; j < A->p[row + 1]; j++) - { - out[idx_map[j]] += A->x[j]; - } - } -} - -/* - * Sums evenly spaced rows from A into a single row in C and fills an index map. - * A: input CSR_matrix matrix - * C: output CSR_matrix matrix (must have m=1) - * spacing: row spacing - * iwork: workspace of size at least max(A->n, A->nnz) - * idx_map: output index map, size at least A->nnz - */ -CSR_matrix *sum_4_csr_alloc(const CSR_matrix *A, const CSR_matrix *B, - const CSR_matrix *C, const CSR_matrix *D, - int *idx_maps[4]) -{ - const CSR_matrix *inputs[4] = {A, B, C, D}; - int m = A->m; - int n = A->n; - int nnz_ub = MIN(A->nnz + B->nnz + C->nnz + D->nnz, sat_mul_int(m, n)); - - /* allocate output and index maps */ - CSR_matrix *out = new_CSR_matrix(m, n, nnz_ub); - for (int k = 0; k < 4; k++) - { - idx_maps[k] = (int *) sp_malloc(inputs[k]->nnz * sizeof(int)); - } - - /* 4-way sorted merge per row */ - int ptrs[4], ends[4]; - int nnz = 0; - - for (int row = 0; row < m; row++) - { - out->p[row] = nnz; - for (int k = 0; k < 4; k++) - { - ptrs[k] = inputs[k]->p[row]; - ends[k] = inputs[k]->p[row + 1]; - } - - for (;;) - { - - /* find minimum column in the current row among the 4 inputs */ - int min_col = -1; - for (int k = 0; k < 4; k++) - { - if (ptrs[k] < ends[k]) - { - int col = inputs[k]->i[ptrs[k]]; - if (min_col == -1 || col < min_col) - { - min_col = col; - } - } - } - - /* if no elements in the current row, the output row will be empty */ - if (min_col == -1) - { - break; - } - - /* insert min_col into output and update idx_maps */ - out->i[nnz] = min_col; - for (int k = 0; k < 4; k++) - { - if (ptrs[k] < ends[k] && inputs[k]->i[ptrs[k]] == min_col) - { - idx_maps[k][ptrs[k]++] = nnz; - } - } - nnz++; - } - } - - out->p[m] = nnz; - out->nnz = nnz; - CSR_trim(out); - return out; -} - -void sum_spaced_rows_into_row_csr_alloc(const CSR_matrix *A, CSR_matrix *C, - int spacing, int *iwork, int *idx_map) -{ - assert(C->m == 1); - C->n = A->n; - - /* gather all column indices from the spaced rows */ - int count = 0; - for (int row = 0; row < A->m; row += spacing) - { - int len = A->p[row + 1] - A->p[row]; - memcpy(iwork + count, A->i + A->p[row], len * sizeof(int)); - count += len; - } - - /* fill sparsity pattern */ - sort_int_array(iwork, count); - int unique_nnz = 0; - int prev_col = -1; - for (int i = 0; i < count; i++) - { - int col = iwork[i]; - if (col != prev_col) - { - C->i[unique_nnz++] = col; - prev_col = col; - } - } - C->nnz = unique_nnz; - C->p[0] = 0; - C->p[1] = C->nnz; - - /* fill idx_map */ - int *col_to_pos = iwork; - for (int idx = 0; idx < unique_nnz; idx++) - { - col_to_pos[C->i[idx]] = idx; - } - - for (int row = 0; row < A->m; row += spacing) - { - for (int j = A->p[row]; j < A->p[row + 1]; j++) - { - idx_map[j] = col_to_pos[A->i[j]]; - } - } -} diff --git a/sparsediff_linalg/src/int_double_pair.c b/sparsediff_linalg/src/int_double_pair.c deleted file mode 100644 index da050588..00000000 --- a/sparsediff_linalg/src/int_double_pair.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/int_double_pair.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include - -static int compare_int_double_pair(const void *a, const void *b) -{ - const int_double_pair *pair_a = (const int_double_pair *) a; - const int_double_pair *pair_b = (const int_double_pair *) b; - - if (pair_a->col < pair_b->col) return -1; - if (pair_a->col > pair_b->col) return 1; - return 0; -} - -int_double_pair *new_int_double_pair_array(int size) -{ - return (int_double_pair *) sp_malloc(size * sizeof(int_double_pair)); -} - -void set_int_double_pair_array(int_double_pair *pair, int *ints, double *doubles, - int size) -{ - for (int k = 0; k < size; k++) - { - pair[k].col = ints[k]; - pair[k].val = doubles[k]; - } -} - -void free_int_double_pair_array(int_double_pair *array) -{ - sp_free(array); -} - -void sort_int_double_pair_array(int_double_pair *array, int size) -{ - qsort(array, size, sizeof(int_double_pair), compare_int_double_pair); -} diff --git a/sparsediff_linalg/src/internal/permuted_dense_linalg.h b/sparsediff_linalg/src/internal/permuted_dense_linalg.h deleted file mode 100644 index 235c4edc..00000000 --- a/sparsediff_linalg/src/internal/permuted_dense_linalg.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef PERMUTED_DENSE_LINALG_H -#define PERMUTED_DENSE_LINALG_H - -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/permuted_dense.h" - -/* Fill values of C = diag(d) @ A where len(d) = number of (global) rows of A */ -void DA_pd_fill_values(const double *d, const permuted_dense *A, permuted_dense *C); - -/* Allocate new permuted dense for C = AT @ A */ -matrix *ATA_pd_alloc(const permuted_dense *A); - -/* Fill values of C = AT @ diag(d) @ A */ -void ATDA_pd_fill_values(const permuted_dense *A, const double *d, - permuted_dense *C); - -/* Allocate new permuted dense forC = BT @ A where A and B are both permuted_dense. - (If B and A have no overlapping rows, then C is empty) */ -matrix *BTA_pd_pd_alloc(const permuted_dense *B, const permuted_dense *A); - -/* Fill values of C = BT @ A where A and B are both permuted dense. */ -void BTA_pd_pd_fill_values(const permuted_dense *B, const permuted_dense *A, - permuted_dense *C); - -/* Fill values of C = BT @ diag(d) @ A where A and B are both permuted dense. */ -void BTDA_pd_pd_fill_values(const permuted_dense *B, const double *d, - const permuted_dense *A, permuted_dense *C); - -/* Allocate new permuted dense for C = B @ A where B is PD and A is CSC. */ -matrix *BA_pd_csc_alloc(const permuted_dense *B, const CSC_matrix *A); - -/* Fill values of C = B @ A where B is value buffer to permuted dense and A is CSC. - - The raw-buffer signature for B lets callers pass a transposed dense block - (e.g. (diag(d) B)^T stored in B->dwork) without needing to build a transposed - permuted dense. */ -void BA_pd_csc_fill_values(const double *B, int n0_B, const int *inv, - const CSC_matrix *A, permuted_dense *C); - -/* Allocate new permuted dense for C = B @ A where B and A are both PD */ -matrix *BA_pd_pd_alloc(const permuted_dense *B, const permuted_dense *A); - -/* Fill values of C = B @ A where A, B, and C are all permuted dense */ -void BA_pd_pd_fill_values(const permuted_dense *B, const permuted_dense *A, - permuted_dense *C); - -/* Allocate new permuted dense for C = B^T @ A where B is PD and A is CSC */ -matrix *BTA_pd_csc_alloc(const permuted_dense *B, const CSC_matrix *A); - -/* Fill values of C = B^T @ A where B is PD and A is CSC. */ -void BTA_pd_csc_fill_values(const permuted_dense *B, const CSC_matrix *A, - permuted_dense *C); - -/* Fill values of C = B^T @ diag(d) @ A where B is PD and A is CSC */ -void BTDA_pd_csc_fill_values(const permuted_dense *B, const double *d, - const CSC_matrix *A, permuted_dense *C); - -/* Allocate new permuted_dense for C = B^T @ A where B is Sparse CSC and A is PD. */ -matrix *BTA_csc_pd_alloc(const CSC_matrix *B, const permuted_dense *A); - -/* Fill values of C = B^T @ A where B is CSC and A is PD. */ -void BTA_csc_pd_fill_values(const CSC_matrix *B, const permuted_dense *A, - permuted_dense *C); - -/* Fill values of C = B^T @ diag(d) @ A where B is CSC and A is PD */ -void BTDA_csc_pd_fill_values(const CSC_matrix *B, const double *d, - const permuted_dense *A, permuted_dense *C); - -/* Allocate a new PD C with the same sparsity as A */ -matrix *copy_sparsity_pd_alloc(const permuted_dense *A); - -/* Allocate C = A^T (transpose). */ -matrix *transpose_pd_alloc(const permuted_dense *A); - -/* Fill values of C = A^T. */ -void transpose_pd_fill_values(const permuted_dense *A, permuted_dense *C); - -/* Lazily allocate B's transpose_cache (a PD with swapped (m,n), (m0,n0), - (row_perm,col_perm)) and return it. Idempotent: on subsequent calls - the existing cache is returned. The cache's X buffer is allocated but - uninitialized — callers must refresh it via transpose_pd_fill_values(B, - B->transpose_cache) before reading X. Const-casts B internally; mirrors - sparse_matrix_ensure_csc_cache's convention. */ -permuted_dense *permuted_dense_ensure_transpose_cache(const permuted_dense *B); - -#endif /* PERMUTED_DENSE_LINALG_H */ diff --git a/sparsediff_linalg/src/internal/stacked_pd_kron_linalg.h b/sparsediff_linalg/src/internal/stacked_pd_kron_linalg.h deleted file mode 100644 index c5938693..00000000 --- a/sparsediff_linalg/src/internal/stacked_pd_kron_linalg.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef STACKED_PD_KRON_LINALG_H -#define STACKED_PD_KRON_LINALG_H - -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/matrix.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/stacked_pd.h" - -/* All three families compute C = kron(I_p, A) @ J as a stacked_pd, without - materializing kron(I_p, A). - - Contract on A: A must be a "full" permuted_dense — m0 == base.m, - n0 == base.n, and row_perm / col_perm are the identity — i.e., a - local dense matrix wrapped in the permuted_dense struct. */ - -/* Allocate sparsity for stacked_pd C = kron(I_p, A) @ J where J is CSC. */ -matrix *BA_dense_kron_csc_alloc(const permuted_dense *A, int p, const CSC_matrix *J); - -/* Fill values of stacked_pd C = kron(I_p, A) @ J where J is CSC. */ -void BA_dense_kron_csc_fill_values(const permuted_dense *A, int p, - const CSC_matrix *J, stacked_pd *C); - -/* Allocate sparsity for stacked_pd C = kron(I_p, A) @ J where J is PD. */ -matrix *BA_dense_kron_pd_alloc(const permuted_dense *A, int p, - const permuted_dense *J); - -/* Fill values of stacked_pd C = kron(I_p, A) @ J where J is permuted_dense. */ -void BA_dense_kron_pd_fill_values(const permuted_dense *A, int p, - const permuted_dense *J, stacked_pd *C); - -/* Allocate sparsity for stacked_pd C = kron(I_p, A) @ J where J is stacked_pd. */ -matrix *BA_dense_kron_spd_alloc(const permuted_dense *A, int p, const stacked_pd *J); - -/* Fill values of stacked_pd C = kron(I_p, A) @ J where J is stacked_pd. */ -void BA_dense_kron_spd_fill_values(const permuted_dense *A, int p, - const stacked_pd *J, stacked_pd *C); - -#endif /* STACKED_PD_KRON_LINALG_H */ diff --git a/sparsediff_linalg/src/internal/stacked_pd_linalg.h b/sparsediff_linalg/src/internal/stacked_pd_linalg.h deleted file mode 100644 index 9d747943..00000000 --- a/sparsediff_linalg/src/internal/stacked_pd_linalg.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef STACKED_PD_LINALG_H -#define STACKED_PD_LINALG_H - -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/stacked_pd.h" - -/* Allocate a new spd C with the same sparsity as A. */ -matrix *copy_sparsity_spd_alloc(const stacked_pd *A); - -/* Allocate a new stacked pd C = transpose(A). C is guaranteed to be a valid - stacked_pd (ie. it has disjoint row permutations across blocks). The raw spd - is stored on `C->pre_coalesce` for reuse by `transpose_spd_fill_values`. */ -matrix *transpose_spd_alloc(const stacked_pd *A); - -/* Fill values of C = transpose(A). */ -void transpose_spd_fill_values(const stacked_pd *A, stacked_pd *C); - -/* Allocate C = row-reduce of A (C[j, :] = sum of rows i with group[i] == j, - group[i] in [0, m_out)); C is a stacked_pd. C stores the reduction map - internally, so the fill takes none. */ -matrix *row_reduce_spd_alloc(const stacked_pd *A, const int *group, int m_out); - -/* Fill values of C = row-reduce of A. */ -void row_reduce_spd_fill_values(const stacked_pd *A, stacked_pd *C); - -/* Fill values of C = diag(d) @ A, where 'd' is 'global' with length A->m. */ -void DA_spd_fill_values(const double *d, const stacked_pd *A, stacked_pd *C); - -/* Allocate sparsity for C = B @ A where B is a permuted_dense and A is spd. - The output C is permuted_dense. */ -matrix *BA_pd_spd_alloc(const permuted_dense *B, const stacked_pd *A); - -/* Fill values of C = B @ A where C and B are permuted_dense and A is spd */ -void BA_pd_spd_fill_values(const permuted_dense *B, const stacked_pd *A, - permuted_dense *C); - -/* Allocate sparsity for C = B^T @ A where B is a permuted_dense and A is spd. - The output C is permuted_dense. */ -matrix *BTA_pd_spd_alloc(const permuted_dense *B, const stacked_pd *A); - -/* Fill values of C = B^T @ A where B is permuted_dense, A is spd and C is - permuted_dense. */ -void BTA_pd_spd_fill_values(const permuted_dense *B, const stacked_pd *A, - permuted_dense *C); - -/* Fill values of C = B^T @ diag(d) @ A where B is permuted_dense, A is spd, - d is a global vector of length A->m, and C is permuted_dense. */ -void BTDA_pd_spd_fill_values(const permuted_dense *B, const double *d, - const stacked_pd *A, permuted_dense *C); - -/* Allocate sparsity for C = B^T @ A where B is stacked_pd and A is - permuted_dense. The output C is stacked_pd. */ -matrix *BTA_spd_pd_alloc(const stacked_pd *B, const permuted_dense *A); - -/* Fill values of C = B^T @ A where B is stacked_pd, A is permuted_dense, and - C is stacked_pd. */ -void BTA_spd_pd_fill_values(const stacked_pd *B, const permuted_dense *A, - stacked_pd *C); - -/* Fill values of C = B^T @ diag(d) @ A where B is stacked_pd, A is - permuted_dense, d is a global vector of length B->m, and C is stacked_pd. */ -void BTDA_spd_pd_fill_values(const stacked_pd *B, const double *d, - const permuted_dense *A, stacked_pd *C); - -/* Allocate sparsity for C = B^T @ A where B is stacked_pd and A is CSC. - The output C is stacked_pd. */ -matrix *BTA_spd_csc_alloc(const stacked_pd *B, const CSC_matrix *A); - -/* Fill values of C = B^T @ A where B is stacked_pd and A is CSC, and C is - stacked_pd. C must be pre-allocated via BTA_spd_csc_alloc. */ -void BTA_spd_csc_fill_values(const stacked_pd *B, const CSC_matrix *A, - stacked_pd *C); - -/* Fill values of C = B^T @ diag(d) @ A where B is stacked_pd, A is CSC, - d is a global vector of length B->m, and C is stacked_pd. */ -void BTDA_spd_csc_fill_values(const stacked_pd *B, const double *d, - const CSC_matrix *A, stacked_pd *C); - -/* Allocate sparsity for C = B^T @ A where both B and A are stacked_pd. - The output C is stacked_pd. */ -matrix *BTA_spd_spd_alloc(const stacked_pd *B, const stacked_pd *A); - -/* Fill values of C = B^T @ A where both B and A are stacked_pd, and C is - stacked_pd. C must be pre-allocated via BTA_spd_spd_alloc. */ -void BTA_spd_spd_fill_values(const stacked_pd *B, const stacked_pd *A, - stacked_pd *C); - -/* Fill values of C = B^T @ diag(d) @ A where both B and A are stacked_pd, - d is a global vector of length B->m, and C is stacked_pd. C must be - pre-allocated via BTA_spd_spd_alloc. */ -void BTDA_spd_spd_fill_values(const stacked_pd *B, const double *d, - const stacked_pd *A, stacked_pd *C); - -/* Allocate sparsity for C = B^T @ A where B is CSC and A is stacked_pd. - The output C is stacked_pd. */ -matrix *BTA_csc_spd_alloc(const CSC_matrix *B, const stacked_pd *A); - -/* Fill values of C = B^T @ A where B is CSC and A is stacked_pd, and C is - stacked_pd. C must be pre-allocated via BTA_csc_spd_alloc. */ -void BTA_csc_spd_fill_values(const CSC_matrix *B, const stacked_pd *A, - stacked_pd *C); - -/* Fill values of C = B^T @ diag(d) @ A where B is CSC, A is stacked_pd, - d is a global vector of length A->m, and C is stacked_pd. C must be - pre-allocated via BTA_csc_spd_alloc. */ -void BTDA_csc_spd_fill_values(const CSC_matrix *B, const double *d, - const stacked_pd *A, stacked_pd *C); - -/* Allocate C = B @ A where B is spd, A is CSC. Not used in production. */ -matrix *BA_spd_csc_alloc(const stacked_pd *B, const CSC_matrix *A); - -/* Fill values of C = B @ A where B is spd and A is CSC. Not used in production. */ -void BA_spd_csc_fill_values(const stacked_pd *B, const CSC_matrix *A, stacked_pd *C); - -/* Allocate sparsity for C = B @ A where B is spd and A is permuted_dense. Not - * used in production. */ -matrix *BA_spd_pd_alloc(const stacked_pd *B, const permuted_dense *A); - -/* Fill values of C = B @ A where B is spd and A is permuted_dense. Not used in - * production. */ -void BA_spd_pd_fill_values(const stacked_pd *B, const permuted_dense *A, - stacked_pd *C); - -/* Allocate sparsity for C = B @ A where both B and A are stacked_pd. Not used in - * production. */ -matrix *BA_spd_spd_alloc(const stacked_pd *B, const stacked_pd *A); - -/* Fill values of C = B @ A where both B and A are stacked_pd. Not used in - * production. */ -void BA_spd_spd_fill_values(const stacked_pd *B, const stacked_pd *A, stacked_pd *C); - -/* Allocate sparsity for a new stacked_pd C = A^T A. Not used in production. */ -matrix *ATA_spd_alloc(const stacked_pd *A); - -/* Fill values of C = A^T diag(d) A. Not used in production. */ -void ATDA_spd_fill_values(const stacked_pd *A, const double *d, stacked_pd *C); - -#endif /* STACKED_PD_LINALG_H */ diff --git a/sparsediff_linalg/src/linalg_dense_sparse_matmuls.c b/sparsediff_linalg/src/linalg_dense_sparse_matmuls.c deleted file mode 100644 index 9578bccc..00000000 --- a/sparsediff_linalg/src/linalg_dense_sparse_matmuls.c +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/linalg_dense_sparse_matmuls.h" -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/cblas_wrapper.h" -#include "sparsediff_linalg/iVec.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include -#include -#include - -/* --------------------------------------------------------------- - * C = (I_p kron A) @ J via the polymorphic matrix interface. - * A is dense m x n, J is (n*p) x k CSC_matrix, C is (m*p) x k CSC_matrix. - * --------------------------------------------------------------- */ -CSC_matrix *I_kron_A_alloc(const matrix *A, const CSC_matrix *J, int p) -{ - int m = A->m; - int n = A->n; - int i, j, jj, block, block_start, block_end, block_jj_start, row_offset; - - /* capacity hint based on J->nnz and not on the dense product */ - int *Cp = (int *) sp_malloc((J->n + 1) * sizeof(int)); - iVec *Ci = iVec_new(J->nnz > 0 ? J->nnz : 1); - Cp[0] = 0; - - /* for each column of J */ - for (j = 0; j < J->n; j++) - { - /* if empty we continue */ - if (J->p[j] == J->p[j + 1]) - { - Cp[j + 1] = Cp[j]; - continue; - } - - /* process each of p blocks of rows in this column of J */ - jj = J->p[j]; - for (block = 0; block < p; block++) - { - block_start = block * n; - block_end = block_start + n; - while (jj < J->p[j + 1] && J->i[jj] < block_start) - { - jj++; - } - - block_jj_start = jj; - while (jj < J->p[j + 1] && J->i[jj] < block_end) - { - jj++; - } - - /* if no entries in this block, continue */ - if (jj == block_jj_start) - { - continue; - } - - /* dense A: all m rows contribute */ - row_offset = block * m; - for (i = 0; i < m; i++) - { - iVec_append(Ci, row_offset + i); - } - } - Cp[j + 1] = Ci->len; - } - - CSC_matrix *C = new_CSC_matrix(m * p, J->n, Ci->len); - memcpy(C->p, Cp, (J->n + 1) * sizeof(int)); - memcpy(C->i, Ci->data, Ci->len * sizeof(int)); - sp_free(Cp); - iVec_free(Ci); - - return C; -} - -void I_kron_A_fill_values(const matrix *A, const CSC_matrix *J, CSC_matrix *C, - double *work) -{ - int m = A->m; - int n = A->n; - int k = J->n; - - int i, j, s, block, block_start, block_end, start, end; - - double *j_dense = work; - - /* for each column of J (and C) */ - for (j = 0; j < k; j++) - { - for (i = C->p[j]; i < C->p[j + 1]; i += m) - { - block = C->i[i] / m; - block_start = block * n; - block_end = block_start + n; - - start = J->p[j]; - end = J->p[j + 1]; - - while (start < J->p[j + 1] && J->i[start] < block_start) - { - start++; - } - - while (end > start && J->i[end - 1] >= block_end) - { - end--; - } - - int count = end - start; - - if (count == 1) - { - /* Fast path: C column segment = val * A[:, row_in_block] */ - int row_in_block = J->i[start] - block_start; - double val = J->x[start]; - cblas_dcopy(m, A->x + row_in_block, n, C->x + i, 1); - if (val != 1.0) - { - cblas_dscal(m, val, C->x + i, 1); - } - } - else - { - /* scatter sparse J col into dense vector and then - * compute A @ j_dense */ - memset(j_dense, 0, n * sizeof(double)); - for (s = start; s < end; s++) - { - j_dense[J->i[s] - block_start] = J->x[s]; - } - - cblas_dgemv(CblasRowMajor, CblasNoTrans, m, n, 1.0, A->x, n, j_dense, - 1, 0.0, C->x + i, 1); - } - } - } -} - -/* --------------------------------------------------------------- - * C = (Y^T kron I_m) @ J - * Y is k x n (col-major), J is (m*k) x p CSC_matrix, C is (m*n) x p CSR_matrix - * --------------------------------------------------------------- */ -CSR_matrix *YT_kron_I_alloc(int m, int k, int n, const CSC_matrix *J) -{ - (void) k; - /* C has n blocks of m rows. All rows at the same position within - their block (same blk_row) share the same column sparsity. - - Step 1: for each blk_row, find which columns of J contribute. - Column j contributes iff J[:,j] has any nonzero r - with r % m == blk_row. - Step 2: replicate each blk_row's pattern across all n blocks. */ - - int i, j, ii, blk_row, total_nnz; - - // --------------------------------------------------------------- - // build sparsity pattern per blk_row - // --------------------------------------------------------------- - iVec **pattern = (iVec **) sp_malloc(m * sizeof(iVec *)); - total_nnz = 0; - for (blk_row = 0; blk_row < m; blk_row++) - { - pattern[blk_row] = iVec_new(J->n); - - /* check each column of J */ - for (j = 0; j < J->n; j++) - { - for (ii = J->p[j]; ii < J->p[j + 1]; ii++) - { - if (J->i[ii] % m == blk_row) - { - iVec_append(pattern[blk_row], j); - break; - } - } - } - total_nnz += pattern[blk_row]->len * n; - } - - // --------------------------------------------------------------- - // replicate sparsity pattern across blocks - // --------------------------------------------------------------- - CSR_matrix *C = new_CSR_matrix(m * n, J->n, total_nnz); - int idx = 0; - for (i = 0; i < m * n; i++) - { - blk_row = i % m; - C->p[i] = idx; - int len = pattern[blk_row]->len; - memcpy(C->i + idx, pattern[blk_row]->data, len * sizeof(int)); - idx += len; - } - C->p[m * n] = idx; - assert(idx == total_nnz); - - for (blk_row = 0; blk_row < m; blk_row++) - { - iVec_free(pattern[blk_row]); - } - sp_free(pattern); - return C; -} - -void YT_kron_I_fill_values(int m, int k, int n, const double *Y, const CSC_matrix *J, - CSR_matrix *C) -{ - (void) n; - assert(C->m == m * n); - /* C[i, j] = sum_l Y[l, blk] * J[blk_row + l*m, j] - * where blk_row = i % m, blk = i / m */ - - int i, j, ii, jj, blk, blk_row; - double sum; - - /* for each row i of C */ - for (i = 0; i < C->m; i++) - { - blk = i / m; /* which block of C */ - blk_row = i % m; /* which row within block */ - const double *Y_col = Y + blk * k; /* column blk of Y */ - - /* for each column j in row i of C */ - for (jj = C->p[i]; jj < C->p[i + 1]; jj++) - { - j = C->i[jj]; - sum = 0.0; - - /* matching J nonzeros in this column */ - for (ii = J->p[j]; ii < J->p[j + 1]; ii++) - { - if (J->i[ii] % m == blk_row) - { - sum += Y_col[J->i[ii] / m] * J->x[ii]; - } - } - C->x[jj] = sum; - } - } -} - -CSR_matrix *I_kron_X_alloc(int m, int k, int n, const CSC_matrix *J) -{ - /* Step 1: for each block, find which columns of J have any - * nonzero in row range [blk*k, blk*k + k). */ - int i, j, ii, blk; - - iVec **pattern = (iVec **) sp_malloc(n * sizeof(iVec *)); - int total_nnz = 0; - for (blk = 0; blk < n; blk++) - { - int blk_start = blk * k; - int blk_end = blk_start + k; - pattern[blk] = iVec_new(J->n); - - /* check each column of J */ - for (j = 0; j < J->n; j++) - { - for (ii = J->p[j]; ii < J->p[j + 1]; ii++) - { - if (J->i[ii] >= blk_start && J->i[ii] < blk_end) - { - iVec_append(pattern[blk], j); - break; - } - } - } - total_nnz += (int) pattern[blk]->len * m; - } - - /* Step 2: replicate each block's pattern for all m rows - * within that block. */ - CSR_matrix *C = new_CSR_matrix(m * n, J->n, total_nnz); - int idx = 0; - for (i = 0; i < m * n; i++) - { - blk = i / m; - C->p[i] = idx; - int len = (int) pattern[blk]->len; - memcpy(C->i + idx, pattern[blk]->data, len * sizeof(int)); - idx += len; - } - C->p[m * n] = idx; - assert(idx == total_nnz); - - for (blk = 0; blk < n; blk++) - { - iVec_free(pattern[blk]); - } - sp_free(pattern); - return C; -} - -void I_kron_X_fill_values(int m, int k, int n, const double *X, const CSC_matrix *J, - CSR_matrix *C) -{ - (void) n; - assert(C->m == m * n); - /* C[i, j] = sum_l X[blk_row + l*m] * J[blk*k + l, j] - * where blk = i / m, blk_row = i % m */ - - int i, j, ii, jj, blk, blk_row; - double sum; - - /* for each row i of C */ - for (i = 0; i < C->m; i++) - { - blk = i / m; /* which block of C */ - blk_row = i % m; /* which row within block */ - int blk_start = blk * k; - int blk_end = blk_start + k; - - /* for each column j in row i of C */ - for (jj = C->p[i]; jj < C->p[i + 1]; jj++) - { - j = C->i[jj]; - sum = 0.0; - - /* J nonzeros in column j within this block's row range */ - for (ii = J->p[j]; ii < J->p[j + 1]; ii++) - { - int r = J->i[ii]; - if (r >= blk_start && r < blk_end) - { - int l = r - blk_start; - sum += X[blk_row + l * m] * J->x[ii]; - } - } - C->x[jj] = sum; - } - } -} diff --git a/sparsediff_linalg/src/linalg_sparse_matmuls.c b/sparsediff_linalg/src/linalg_sparse_matmuls.c deleted file mode 100644 index c460a353..00000000 --- a/sparsediff_linalg/src/linalg_sparse_matmuls.c +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/iVec.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include -#include -#include - -/* Unweighted sparse dot product of two sorted index arrays */ -static inline double sparse_dot(const double *a_x, const int *a_i, int a_nnz, - const double *b_x, const int *b_i, int b_nnz, - int b_offset) -{ - int ii = 0; - int jj = 0; - double sum = 0.0; - - while (ii < a_nnz && jj < b_nnz) - { - if (a_i[ii] == b_i[jj] - b_offset) - { - sum += a_x[ii] * b_x[jj]; - ii++; - jj++; - } - else if (a_i[ii] < b_i[jj] - b_offset) - { - ii++; - } - else - { - jj++; - } - } - - return sum; -} - -static inline double sparse_dot_offset(const double *a_x, const int *a_idx, - int a_nnz, const double *b_x, - const int *b_idx, int b_nnz, int b_offset) -{ - int ii = 0, jj = 0; - double sum = 0.0; - - while (ii < a_nnz && jj < b_nnz) - { - int b_col = b_idx[jj] - b_offset; - - if (a_idx[ii] == b_col) - { - sum += a_x[ii] * b_x[jj]; - ii++; - jj++; - } - else if (a_idx[ii] < b_col) - { - ii++; - } - else - { - jj++; - } - } - - return sum; -} - -/* Symbolic phase of Gustavson's sparse matmul: an output row's (or column's) - sparsity pattern is the union of the operand index lists selected by keys, - i.e. the lists list_i[list_p[k] .. list_p[k+1]] for each k = keys[t] - - key_offset. Compute that union into cand, deduplicated with a - generation-stamped workspace and sorted ascending (the order the downstream - sparse dot products require). Returns the number of gathered indices. */ -static int sorted_pattern_union(const int *keys, int n_keys, int key_offset, - const int *list_p, const int *list_i, int *stamp, - int *cand, int *gen) -{ - int g = ++(*gen); - int n_cand = 0; - for (int t = 0; t < n_keys; t++) - { - int k = keys[t] - key_offset; - for (int q = list_p[k]; q < list_p[k + 1]; q++) - { - int i = list_i[q]; - if (stamp[i] != g) - { - stamp[i] = g; - cand[n_cand++] = i; - } - } - } - sort_int_array(cand, n_cand); - return n_cand; -} - -CSC_matrix *block_left_multiply_fill_sparsity(const CSR_matrix *A, - const CSC_matrix *J, int p) -{ - /* A is m x n, J is (n*p) x k, C is (m*p) x k */ - int m = A->m; - int n = A->n; - int j, jj, block, block_start, block_end, block_jj_start, block_jj_end, - row_offset; - - /* Allocate column pointers and an estimate of row indices. Capacity hint - based on J->nnz and not on the dense product */ - int *Cp = (int *) sp_malloc((J->n + 1) * sizeof(int)); - iVec *Ci = iVec_new(J->nnz > 0 ? J->nnz : 1); - Cp[0] = 0; - - /* Output-driven: row i of A intersects a block's child entries iff i - appears in the CSC column list of one of those entries, so gather those - columns instead of testing all m rows per (column, block). */ - int *iwork = (int *) sp_malloc((n > 0 ? n : 1) * sizeof(int)); - CSC_matrix *A_csc = csr_to_csc_alloc(A, iwork); - sp_free(iwork); - - int *stamp = (int *) sp_calloc(m > 0 ? m : 1, sizeof(int)); /* 0 = unseen */ - int *cand = (int *) sp_malloc((m > 0 ? m : 1) * sizeof(int)); - int gen = 0; - - /* for each column of J */ - for (j = 0; j < J->n; j++) - { - /* if empty we continue */ - if (J->p[j] == J->p[j + 1]) - { - Cp[j + 1] = Cp[j]; - continue; - } - - /* process each of p blocks of rows in this column of J */ - jj = J->p[j]; - for (block = 0; block < p; block++) - { - - // ----------------------------------------------------------------- - // find start and end indices of rows of J in this block - // ----------------------------------------------------------------- - block_start = block * n; - block_end = block_start + n; - while (jj < J->p[j + 1] && J->i[jj] < block_start) - { - jj++; - } - - block_jj_start = jj; - - while (jj < J->p[j + 1] && J->i[jj] < block_end) - { - jj++; - } - - block_jj_end = jj; - if (block_jj_end == block_jj_start) - { - continue; - } - - // ----------------------------------------------------------------- - // gather the rows of A that hit this block's child entries - // ----------------------------------------------------------------- - int n_cand = sorted_pattern_union( - J->i + block_jj_start, block_jj_end - block_jj_start, block_start, - A_csc->p, A_csc->i, stamp, cand, &gen); - row_offset = block * m; - for (int t = 0; t < n_cand; t++) - { - iVec_append(Ci, row_offset + cand[t]); - } - } - Cp[j + 1] = Ci->len; - } - - free_CSC_matrix(A_csc); - sp_free(stamp); - sp_free(cand); - - CSC_matrix *C = new_CSC_matrix(m * p, J->n, Ci->len); - memcpy(C->p, Cp, (J->n + 1) * sizeof(int)); - memcpy(C->i, Ci->data, Ci->len * sizeof(int)); - sp_free(Cp); - iVec_free(Ci); - - return C; -} - -void block_left_multiply_fill_values(const CSR_matrix *A, const CSC_matrix *J, - CSC_matrix *C) -{ - /* A is m x n, J is (n*p) x k, C is (m*p) x k */ - int m = A->m; - int n = A->n; - int k = J->n; - - int i, j, row_a, block, block_start, block_end, start, end; - - /* to get rid of unitialized warnings */ - block = 0; - block_start = 0; - block_end = 0; - start = 0; - end = 0; - - /* for each column of J (and C) */ - for (j = 0; j < k; j++) - { - int previous_block = -1; - - for (i = C->p[j]; i < C->p[j + 1]; i++) - { - /* choose row of A and block of column of J */ - row_a = C->i[i] % m; - block = C->i[i] / m; - - // ------------------------------------------------------------------------- - // find the part of the column of J in the current block - // ------------------------------------------------------------------------- - if (block != previous_block) - { - previous_block = block; - block_start = block * n; - block_end = block_start + n; - start = J->p[j]; - end = J->p[j + 1]; - - while (start < J->p[j + 1] && J->i[start] < block_start) - { - start++; - } - - while (end > start && J->i[end - 1] >= block_end) - { - end--; - } - } - - // ------------------------------------------------------------------------------ - // compute value as sparse dot product of row of A and column of J in - // this block - // ------------------------------------------------------------------------------ - int a_len = A->p[row_a + 1] - A->p[row_a]; - C->x[i] = - sparse_dot(A->x + A->p[row_a], A->i + A->p[row_a], a_len, - J->x + start, J->i + start, end - start, block_start); - } - } -} - -/* Fill values of C = A @ B where A is CSR_matrix, B is CSC_matrix. */ -void csr_csc_matmul_fill_values(const CSR_matrix *A, const CSC_matrix *B, - CSR_matrix *C) -{ - for (int i = 0; i < A->m; i++) - { - for (int jj = C->p[i]; jj < C->p[i + 1]; jj++) - { - int j = C->i[jj]; - - int a_nnz = A->p[i + 1] - A->p[i]; - int b_nnz = B->p[j + 1] - B->p[j]; - - /* Compute dot product of row i of A and column j of B */ - double sum = sparse_dot(A->x + A->p[i], A->i + A->p[i], a_nnz, - B->x + B->p[j], B->i + B->p[j], b_nnz, 0); - - C->x[jj] = sum; - } - } -} - -/* C = A @ B where A is CSR_matrix (m x n), B is CSC_matrix (n x p). Result C is - CSR_matrix (m x p) with precomputed sparsity pattern */ -CSR_matrix *csr_csc_matmul_alloc(const CSR_matrix *A, const CSC_matrix *B) -{ - int m = A->m; - int p = B->n; - - int *Cp = (int *) sp_malloc((m + 1) * sizeof(int)); - iVec *Ci = iVec_new(m); - - Cp[0] = 0; - - /* Output-driven: column j of B intersects row i of A iff j appears in the - CSR row list of one of A's row-i column indices, so gather those rows - instead of testing all p columns per row of A. */ - int *iwork = (int *) sp_malloc((B->m > 0 ? B->m : 1) * sizeof(int)); - CSR_matrix *B_csr = csc_to_csr_alloc(B, iwork); - sp_free(iwork); - - int *stamp = (int *) sp_calloc(p > 0 ? p : 1, sizeof(int)); /* 0 = unseen */ - int *cand = (int *) sp_malloc((p > 0 ? p : 1) * sizeof(int)); - int gen = 0; - - // -------------------------------------------------------------- - // count nnz and fill column indices - // -------------------------------------------------------------- - int nnz = 0; - for (int i = 0; i < A->m; i++) - { - int n_cand = sorted_pattern_union(A->i + A->p[i], A->p[i + 1] - A->p[i], 0, - B_csr->p, B_csr->i, stamp, cand, &gen); - for (int t = 0; t < n_cand; t++) - { - iVec_append(Ci, cand[t]); - } - nnz += n_cand; - - Cp[i + 1] = nnz; - } - - free_CSR_matrix(B_csr); - sp_free(stamp); - sp_free(cand); - - CSR_matrix *C = new_CSR_matrix(m, p, nnz); - memcpy(C->p, Cp, (m + 1) * sizeof(int)); - memcpy(C->i, Ci->data, nnz * sizeof(int)); - sp_free(Cp); - iVec_free(Ci); - - return C; -} - -/* Compute block-wise matrix-vector products. - * y = [A @ x1; A @ x2; ...; A @ xp] where A is m x n and x is (n*p)-length vector. - * x is split into p blocks of n elements each. - */ -void block_left_multiply_vec(const struct CSR_matrix *A, const double *x, double *y, - int p) -{ - /* For each block */ - for (int block = 0; block < p; block++) - { - int block_start = block * A->n; - int y_offset = block * A->m; - - /* For each row of A */ - for (int i = 0; i < A->m; i++) - { - double row_sum = 0.0; - int row_nnz = A->p[i + 1] - A->p[i]; - - /* Compute sparse dot product of A[i,:] with x_block */ - for (int idx = 0; idx < row_nnz; idx++) - { - int col = A->i[A->p[i] + idx]; - row_sum += A->x[A->p[i] + idx] * x[block_start + col]; - } - - y[y_offset + i] = row_sum; - } - } -} diff --git a/sparsediff_linalg/src/matmul_dispatchers.c b/sparsediff_linalg/src/matmul_dispatchers.c deleted file mode 100644 index 2f66ab07..00000000 --- a/sparsediff_linalg/src/matmul_dispatchers.c +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - */ -#include "sparsediff_linalg/matmul_dispatchers.h" - -#include "internal/permuted_dense_linalg.h" -#include "internal/stacked_pd_kron_linalg.h" -#include "internal/stacked_pd_linalg.h" -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/sparse_matrix.h" -#include "sparsediff_linalg/stacked_pd.h" -#include - -/* Forward declarations of the fixed-B dispatchers used internally by - BTA_matrices_alloc / BTA_matrices_fill_values / BTDA_matrices_fill_values. */ -static matrix *BTA_pd_matrices_alloc(const permuted_dense *B, matrix *A); -static void BTA_pd_matrices_fill_values(const permuted_dense *B, const matrix *A, - permuted_dense *C); -static void BTDA_pd_matrices_fill_values(const permuted_dense *B, const double *d, - const matrix *A, permuted_dense *C); -static matrix *BTA_spd_matrices_alloc(const stacked_pd *B, matrix *A); -static void BTA_spd_matrices_fill_values(const stacked_pd *B, const matrix *A, - stacked_pd *C); -static void BTDA_spd_matrices_fill_values(const stacked_pd *B, const double *d, - const matrix *A, stacked_pd *C); -static matrix *BTA_sparse_matrices_alloc(const sparse_matrix *B, matrix *A); -static void BTA_sparse_matrices_fill_values(const sparse_matrix *B, const matrix *A, - matrix *C); -static void BTDA_sparse_matrices_fill_values(const sparse_matrix *B, const double *d, - const matrix *A, matrix *C); - -matrix *BTA_matrices_alloc(matrix *A, matrix *B) -{ - if (B->kind == MATRIX_PERMUTED_DENSE) - { - return BTA_pd_matrices_alloc((const permuted_dense *) B, A); - } - if (B->kind == MATRIX_STACKED_PD) - { - return BTA_spd_matrices_alloc((const stacked_pd *) B, A); - } - /* B is sparse_matrix */ - return BTA_sparse_matrices_alloc((const sparse_matrix *) B, A); -} - -void BTDA_matrices_fill_values(matrix *A, const double *d, matrix *B, matrix *C) -{ - if (B->kind == MATRIX_PERMUTED_DENSE) - { - BTDA_pd_matrices_fill_values((const permuted_dense *) B, d, A, - (permuted_dense *) C); - return; - } - if (B->kind == MATRIX_STACKED_PD) - { - BTDA_spd_matrices_fill_values((const stacked_pd *) B, d, A, - (stacked_pd *) C); - return; - } - /* B is sparse_matrix */ - BTDA_sparse_matrices_fill_values((const sparse_matrix *) B, d, A, C); -} - -void BTA_matrices_fill_values(matrix *A, matrix *B, matrix *C) -{ - if (B->kind == MATRIX_PERMUTED_DENSE) - { - BTA_pd_matrices_fill_values((const permuted_dense *) B, A, - (permuted_dense *) C); - return; - } - if (B->kind == MATRIX_STACKED_PD) - { - BTA_spd_matrices_fill_values((const stacked_pd *) B, A, (stacked_pd *) C); - return; - } - /* B is sparse_matrix */ - BTA_sparse_matrices_fill_values((const sparse_matrix *) B, A, C); -} - -matrix *BA_pd_matrices_alloc(const permuted_dense *B, matrix *A) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - return BA_pd_pd_alloc(B, (const permuted_dense *) A); - } - if (A->kind == MATRIX_STACKED_PD) - { - return BA_pd_spd_alloc(B, (const stacked_pd *) A); - } - - /* A is sparse */ - sparse_matrix *sm_A = (sparse_matrix *) A; - sparse_matrix_ensure_csc_cache(sm_A); - return BA_pd_csc_alloc(B, sm_A->csc_cache); -} - -void BA_pd_matrices_fill_values(const permuted_dense *B, const matrix *A, - permuted_dense *C) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - BA_pd_pd_fill_values(B, (const permuted_dense *) A, C); - return; - } - if (A->kind == MATRIX_STACKED_PD) - { - BA_pd_spd_fill_values(B, (const stacked_pd *) A, C); - return; - } - - /* A is sparse */ - const sparse_matrix *sm_A = (const sparse_matrix *) A; - BA_pd_csc_fill_values(B->X, B->n0, B->col_inv, sm_A->csc_cache, C); -} - -static matrix *BTA_pd_matrices_alloc(const permuted_dense *B, matrix *A) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - return BTA_pd_pd_alloc(B, (const permuted_dense *) A); - } - if (A->kind == MATRIX_STACKED_PD) - { - return BTA_pd_spd_alloc(B, (const stacked_pd *) A); - } - - /* A is sparse */ - sparse_matrix *sm_A = (sparse_matrix *) A; - sparse_matrix_ensure_csc_cache(sm_A); - return BTA_pd_csc_alloc(B, sm_A->csc_cache); -} - -static void BTDA_pd_matrices_fill_values(const permuted_dense *B, const double *d, - const matrix *A, permuted_dense *C) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - BTDA_pd_pd_fill_values(B, d, (const permuted_dense *) A, C); - return; - } - if (A->kind == MATRIX_STACKED_PD) - { - BTDA_pd_spd_fill_values(B, d, (const stacked_pd *) A, C); - return; - } - - /* A is sparse */ - const sparse_matrix *sm_A = (const sparse_matrix *) A; - BTDA_pd_csc_fill_values(B, d, sm_A->csc_cache, C); -} - -static void BTA_pd_matrices_fill_values(const permuted_dense *B, const matrix *A, - permuted_dense *C) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - BTA_pd_pd_fill_values(B, (const permuted_dense *) A, C); - return; - } - if (A->kind == MATRIX_STACKED_PD) - { - BTA_pd_spd_fill_values(B, (const stacked_pd *) A, C); - return; - } - - /* A is sparse */ - const sparse_matrix *sm_A = (const sparse_matrix *) A; - BTA_pd_csc_fill_values(B, sm_A->csc_cache, C); -} - -static matrix *BTA_spd_matrices_alloc(const stacked_pd *B, matrix *A) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - return BTA_spd_pd_alloc(B, (const permuted_dense *) A); - } - if (A->kind == MATRIX_STACKED_PD) - { - return BTA_spd_spd_alloc(B, (const stacked_pd *) A); - } - - /* A is sparse */ - sparse_matrix *sm_A = (sparse_matrix *) A; - sparse_matrix_ensure_csc_cache(sm_A); - return BTA_spd_csc_alloc(B, sm_A->csc_cache); -} - -static void BTDA_spd_matrices_fill_values(const stacked_pd *B, const double *d, - const matrix *A, stacked_pd *C) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - BTDA_spd_pd_fill_values(B, d, (const permuted_dense *) A, C); - return; - } - if (A->kind == MATRIX_STACKED_PD) - { - BTDA_spd_spd_fill_values(B, d, (const stacked_pd *) A, C); - return; - } - - /* A is sparse */ - const sparse_matrix *sm_A = (const sparse_matrix *) A; - BTDA_spd_csc_fill_values(B, d, sm_A->csc_cache, C); -} - -static void BTA_spd_matrices_fill_values(const stacked_pd *B, const matrix *A, - stacked_pd *C) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - BTA_spd_pd_fill_values(B, (const permuted_dense *) A, C); - return; - } - if (A->kind == MATRIX_STACKED_PD) - { - BTA_spd_spd_fill_values(B, (const stacked_pd *) A, C); - return; - } - - /* A is sparse */ - const sparse_matrix *sm_A = (const sparse_matrix *) A; - BTA_spd_csc_fill_values(B, sm_A->csc_cache, C); -} - -static matrix *BTA_sparse_matrices_alloc(const sparse_matrix *B, matrix *A) -{ - /* Ensure B's csc_cache structure exists. */ - sparse_matrix_ensure_csc_cache((sparse_matrix *) B); - - if (A->kind == MATRIX_PERMUTED_DENSE) - { - return BTA_csc_pd_alloc(B->csc_cache, (const permuted_dense *) A); - } - if (A->kind == MATRIX_STACKED_PD) - { - return BTA_csc_spd_alloc(B->csc_cache, (const stacked_pd *) A); - } - - /* A is sparse */ - sparse_matrix *sm_A = (sparse_matrix *) A; - sparse_matrix_ensure_csc_cache(sm_A); - /* BTA_alloc takes (A_csc, B_csc) — computes B^T A and returns CSR. */ - CSR_matrix *C_csr = BTA_alloc(sm_A->csc_cache, B->csc_cache); - return new_sparse_matrix(C_csr); -} - -static void BTDA_sparse_matrices_fill_values(const sparse_matrix *B, const double *d, - const matrix *A, matrix *C) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - BTDA_csc_pd_fill_values(B->csc_cache, d, (const permuted_dense *) A, - (permuted_dense *) C); - return; - } - if (A->kind == MATRIX_STACKED_PD) - { - BTDA_csc_spd_fill_values(B->csc_cache, d, (const stacked_pd *) A, - (stacked_pd *) C); - return; - } - - /* A is sparse */ - const sparse_matrix *sm_A = (const sparse_matrix *) A; - BTDA_fill_values(sm_A->csc_cache, B->csc_cache, d, ((sparse_matrix *) C)->csr); -} - -static void BTA_sparse_matrices_fill_values(const sparse_matrix *B, const matrix *A, - matrix *C) -{ - if (A->kind == MATRIX_PERMUTED_DENSE) - { - BTA_csc_pd_fill_values(B->csc_cache, (const permuted_dense *) A, - (permuted_dense *) C); - return; - } - if (A->kind == MATRIX_STACKED_PD) - { - BTA_csc_spd_fill_values(B->csc_cache, (const stacked_pd *) A, - (stacked_pd *) C); - return; - } - - /* A is sparse */ - const sparse_matrix *sm_A = (const sparse_matrix *) A; - BTDA_fill_values(sm_A->csc_cache, B->csc_cache, NULL, - ((sparse_matrix *) C)->csr); -} - -/* Debug-only check that A is the "full" permuted_dense shape the kron - helpers require: m0 == base.m, n0 == base.n, identity inner perms. - Called once at alloc; not repeated at fill (caller would have to - reuse the same A). */ -#ifndef NDEBUG -static void assert_dense_kron_A_is_full(const permuted_dense *A) -{ - assert(A->m0 == A->base.m); - assert(A->n0 == A->base.n); - for (int i = 0; i < A->m0; i++) assert(A->row_perm[i] == i); - for (int j = 0; j < A->n0; j++) assert(A->col_perm[j] == j); -} -#endif - -matrix *BA_dense_kron_matrices_alloc(const permuted_dense *A, int p, matrix *J) -{ -#ifndef NDEBUG - assert_dense_kron_A_is_full(A); -#endif - - if (J->kind == MATRIX_PERMUTED_DENSE) - { - return BA_dense_kron_pd_alloc(A, p, (const permuted_dense *) J); - } - if (J->kind == MATRIX_STACKED_PD) - { - return BA_dense_kron_spd_alloc(A, p, (const stacked_pd *) J); - } - /* J is sparse */ - sparse_matrix *sm_J = (sparse_matrix *) J; - sparse_matrix_ensure_csc_cache(sm_J); - return BA_dense_kron_csc_alloc(A, p, sm_J->csc_cache); -} - -void BA_dense_kron_matrices_fill_values(const permuted_dense *A, int p, - const matrix *J, stacked_pd *C) -{ - if (J->kind == MATRIX_PERMUTED_DENSE) - { - BA_dense_kron_pd_fill_values(A, p, (const permuted_dense *) J, C); - return; - } - if (J->kind == MATRIX_STACKED_PD) - { - BA_dense_kron_spd_fill_values(A, p, (const stacked_pd *) J, C); - return; - } - /* J is sparse */ - const sparse_matrix *sm_J = (const sparse_matrix *) J; - BA_dense_kron_csc_fill_values(A, p, sm_J->csc_cache, C); -} diff --git a/sparsediff_linalg/src/matrix_sum.c b/sparsediff_linalg/src/matrix_sum.c deleted file mode 100644 index c0e21fe8..00000000 --- a/sparsediff_linalg/src/matrix_sum.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/matrix_sum.h" -#include "sparsediff_linalg/CSR_sum.h" -#include "sparsediff_linalg/sparse_matrix.h" - -void sum_matrices_alloc(matrix *A, matrix *B, matrix *C) -{ - CSR_matrix *cc = C->to_csr(C); - sum_csr_alloc(A->to_csr(A), B->to_csr(B), cc); - sparse_matrix_trim(C); -} - -void sum_matrices_fill_values(matrix *A, matrix *B, matrix *C) -{ - sum_csr_fill_values(A->to_csr(A), B->to_csr(B), C->to_csr(C)); -} - -void sum_scaled_matrices_fill_values(matrix *A, matrix *B, matrix *C, - const double *d1, const double *d2) -{ - sum_scaled_csr_matrices_fill_values(A->to_csr(A), B->to_csr(B), C->to_csr(C), d1, - d2); -} diff --git a/sparsediff_linalg/src/mini_numpy.c b/sparsediff_linalg/src/mini_numpy.c deleted file mode 100644 index 8b57daab..00000000 --- a/sparsediff_linalg/src/mini_numpy.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/mini_numpy.h" -#include - -void repeat(double *result, const double *a, int len, int repeats) -{ - int idx = 0; - for (int i = 0; i < len; i++) - { - for (int j = 0; j < repeats; j++) - { - result[idx++] = a[i]; - } - } -} - -void tile_double(double *result, const double *a, int len, int tiles) -{ - for (int i = 0; i < tiles; i++) - { - memcpy(result + i * len, a, len * sizeof(double)); - } -} - -void scaled_ones(double *result, int size, double value) -{ - for (int i = 0; i < size; i++) - { - result[i] = value; - } -} - -void A_transpose(double *AT, const double *A, int m, int n) -{ - for (int i = 0; i < m; i++) - { - for (int j = 0; j < n; j++) - { - AT[j * m + i] = A[i * n + j]; - } - } -} - -void Y_kron_I_vec(int m, int k, int n, const double *Y, const double *w, double *v) -{ - for (int j = 0; j < k; j++) - { - for (int row = 0; row < m; row++) - { - double sum = 0.0; - for (int col = 0; col < n; col++) - { - sum += Y[j + col * k] * w[row + col * m]; - } - v[row + j * m] = sum; - } - } -} - -void I_kron_XT_vec(int m, int k, int n, const double *X, const double *w, double *v) -{ - for (int col = 0; col < n; col++) - { - for (int j = 0; j < k; j++) - { - double sum = 0.0; - for (int row = 0; row < m; row++) - { - sum += X[row + j * m] * w[row + col * m]; - } - v[j + col * k] = sum; - } - } -} - -void conv_matrix_fill_sparsity(CSR_matrix *T_csr, int m, int n) -{ - int nnz = 0; - for (int r = 0; r < T_csr->m; r++) - { - T_csr->p[r] = nnz; - int col_lo = r - m + 1 > 0 ? r - m + 1 : 0; - int col_hi = r < n - 1 ? r : n - 1; - for (int col = col_lo; col <= col_hi; col++) - { - T_csr->i[nnz] = col; - nnz++; - } - } - T_csr->p[T_csr->m] = nnz; -} - -void conv_matrix_fill_values(CSR_matrix *T_csr, const double *a) -{ - for (int r = 0; r < T_csr->m; r++) - { - for (int k = T_csr->p[r]; k < T_csr->p[r + 1]; k++) - { - T_csr->x[k] = a[r - T_csr->i[k]]; - } - } -} diff --git a/sparsediff_linalg/src/permuted_dense.c b/sparsediff_linalg/src/permuted_dense.c deleted file mode 100644 index 17d42325..00000000 --- a/sparsediff_linalg/src/permuted_dense.c +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/permuted_dense.h" - -#include "internal/permuted_dense_linalg.h" -#include "sparsediff_linalg/cblas_wrapper.h" -#include "sparsediff_linalg/linalg_dense_sparse_matmuls.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include -#include -#include - -static void permuted_dense_free(matrix *self) -{ - permuted_dense *pd = (permuted_dense *) self; - sp_free(pd->row_perm); - sp_free(pd->col_perm); - sp_free(pd->col_inv); - sp_free(pd->row_inv); - /* csr_cache->x aliases pd->X (set in permuted_dense_to_csr_alloc); NULL it - so free_CSR_matrix doesn't double-free the shared buffer. */ - if (pd->csr_cache != NULL) - { - pd->csr_cache->x = NULL; - } - free_CSR_matrix(pd->csr_cache); - if (pd->owns_X) - { - sp_free(pd->X); - } - sp_free(pd->kernel_dwork); - sp_free(pd->kernel_iwork); - sp_free(pd->bound_iwork); - free_matrix((matrix *) pd->transpose_cache); - sp_free(pd); -} - -/* permuted_dense has no CSC_matrix mirror; chain-rule kernels operate on X directly. - */ -static void permuted_dense_refresh_csc_values(matrix *self) -{ - (void) self; -} - -/* Vtable adapters — each delegates to the existing permuted_dense_* kernel. */ -static matrix *permuted_dense_vtable_copy_sparsity(const matrix *self) -{ - return copy_sparsity_pd_alloc((const permuted_dense *) self); -} - -static void permuted_dense_vtable_DA_fill_values(const double *d, const matrix *self, - matrix *out) -{ - DA_pd_fill_values(d, (const permuted_dense *) self, (permuted_dense *) out); -} - -static matrix *permuted_dense_vtable_ATA_alloc(matrix *self) -{ - return ATA_pd_alloc((const permuted_dense *) self); -} - -static void permuted_dense_vtable_ATDA_fill_values(const matrix *self, - const double *d, matrix *out) -{ - ATDA_pd_fill_values((const permuted_dense *) self, d, (permuted_dense *) out); -} - -/* Forward decl; definition lower in the file. */ -static CSR_matrix *permuted_dense_to_csr_alloc(const permuted_dense *A); - -/* Lazy CSR_matrix view: allocate structure on first call, then return the cache. - The cache's x array aliases pd->X (see permuted_dense_to_csr_alloc), so - values are always live without a per-call refresh. */ -static CSR_matrix *permuted_dense_to_csr(matrix *self) -{ - permuted_dense *pd = (permuted_dense *) self; - if (pd->csr_cache == NULL) - { - pd->csr_cache = permuted_dense_to_csr_alloc(pd); - } - return pd->csr_cache; -} - -static matrix *permuted_dense_vtable_transpose_alloc(const matrix *self) -{ - return transpose_pd_alloc((const permuted_dense *) self); -} - -static void permuted_dense_vtable_transpose_fill_values(const matrix *self, - matrix *out) -{ - transpose_pd_fill_values((const permuted_dense *) self, (permuted_dense *) out); -} - -matrix *row_gather_pd_alloc(const permuted_dense *A, const int *map, int m_out) -{ - /* Output position i is dense iff map[i] hits a row in A->row_perm. The kept - positions form C's row_perm (strictly increasing by construction, repeats - in map included); src[k] is the dense row of A that C's row k copies. */ - int *new_row_perm = (int *) sp_malloc(m_out * sizeof(int)); - int *src = (int *) sp_malloc(m_out * sizeof(int)); - int new_m0 = 0; - for (int i = 0; i < m_out; i++) - { - int ii = A->row_inv[map[i]]; - if (ii >= 0) - { - new_row_perm[new_m0] = i; - src[new_m0] = ii; - new_m0++; - } - } - - matrix *out = new_permuted_dense(m_out, A->base.n, new_m0, A->n0, new_row_perm, - A->col_perm, NULL); - if (new_m0 > 0) - { - permuted_dense *C = (permuted_dense *) out; - C->bound_iwork = (int *) sp_malloc(new_m0 * sizeof(int)); - memcpy(C->bound_iwork, src, new_m0 * sizeof(int)); - } - sp_free(new_row_perm); - sp_free(src); - return out; -} - -void row_gather_pd_fill_values(const permuted_dense *A, permuted_dense *C) -{ - assert(A->base.n == C->base.n && (C->m0 == 0 || C->bound_iwork != NULL)); - int n0 = A->n0; - for (int k = 0; k < C->m0; k++) - { - memcpy(C->X + k * n0, A->X + C->bound_iwork[k] * n0, n0 * sizeof(double)); - } -} - -static matrix *permuted_dense_vtable_row_gather_alloc(const matrix *self, - const int *map, int m_out) -{ - return row_gather_pd_alloc((const permuted_dense *) self, map, m_out); -} - -static void permuted_dense_vtable_row_gather_fill_values(const matrix *self, - matrix *out) -{ - row_gather_pd_fill_values((const permuted_dense *) self, (permuted_dense *) out); -} - -matrix *diag_vec_pd_alloc(const permuted_dense *A) -{ - int n = A->base.m; - int out_m = n * n; - - if (A->m0 == 0) - { - return new_permuted_dense(out_m, A->base.n, 0, A->n0, NULL, A->col_perm, - NULL); - } - - int *new_row_perm = (int *) sp_malloc(A->m0 * sizeof(int)); - for (int ii = 0; ii < A->m0; ii++) - { - new_row_perm[ii] = A->row_perm[ii] * (n + 1); - } - matrix *out = new_permuted_dense(out_m, A->base.n, A->m0, A->n0, new_row_perm, - A->col_perm, NULL); - sp_free(new_row_perm); - return out; -} - -void diag_vec_pd_fill_values(const permuted_dense *A, permuted_dense *C) -{ - if (A->m0 == 0) - { - return; - } - memcpy(C->X, A->X, A->m0 * A->n0 * sizeof(double)); -} - -static matrix *permuted_dense_vtable_diag_vec_alloc(matrix *self) -{ - return diag_vec_pd_alloc((const permuted_dense *) self); -} - -static void permuted_dense_vtable_diag_vec_fill_values(matrix *self, matrix *out) -{ - diag_vec_pd_fill_values((const permuted_dense *) self, (permuted_dense *) out); -} - -/* ===== Operator-role adapters: PD acting as the constant left operand of - left_matmul. Currently restricted to full-block PDs (m0 == m, n0 == n, - identity perms) — the only operator shape any caller needs today. */ - -static void permuted_dense_vtable_block_left_mult_vec(const matrix *A, - const double *x, double *y, - int p) -{ - /* Full-block precondition: A->x is a single contiguous row-major m x n - block (perms are identity). For a non-trivial PD, A->x still points - at pd->X but X only stores the values at the permuted positions; the - layout below assumes a full m x n matrix, hence the assert. */ - assert(((const permuted_dense *) A)->m0 == A->m && - ((const permuted_dense *) A)->n0 == A->n); - - /* y = kron(I_p, A) @ x via a single dgemm. - Input x is p blocks of length n (block-interleaved); output y is p - blocks of length m. That's identical in memory to row-major matrices - of shape (p, n) and (p, m) respectively, so we can compute - y (p x m) = x (p x n) * A^T (n x m) - in one shot. CblasRowMajor + CblasNoTrans on x + CblasTrans on A - gives exactly that. */ - cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasTrans, p, A->m, A->n, 1.0, x, A->n, - A->x, A->n, 0.0, y, A->m); -} - -static CSC_matrix * -permuted_dense_vtable_block_left_mult_sparsity(const matrix *A, const CSC_matrix *J, - int p) -{ - const permuted_dense *pd = (const permuted_dense *) A; - assert(pd->m0 == A->m && pd->n0 == A->n); - /* Pre-size dwork for the subsequent block_left_mult_values fill, which - densifies a sparse column of J (size A->n) before applying A. Honors - the no-alloc-in-fill rule. */ - permuted_dense_ensure_kernel_dwork(pd, A->n); - return I_kron_A_alloc(A, J, p); -} - -static void permuted_dense_vtable_block_left_mult_values(const matrix *A, - const CSC_matrix *J, - CSC_matrix *C) -{ - const permuted_dense *pd = (const permuted_dense *) A; - assert(pd->m0 == A->m && pd->n0 == A->n); - I_kron_A_fill_values(A, J, C, pd->kernel_dwork); -} - -matrix *row_reduce_pd_alloc(const permuted_dense *A, const int *group, int m_out) -{ - /* C's dense rows are the groups hit by A's dense rows, in increasing order. - bound_iwork[ii] is the dense row of C that source dense row ii adds into. */ - int *group_to_out = (int *) sp_malloc(m_out * sizeof(int)); - for (int g = 0; g < m_out; g++) - { - group_to_out[g] = -1; - } - for (int ii = 0; ii < A->m0; ii++) - { - int g = group[A->row_perm[ii]]; - assert(g >= 0 && g < m_out); - group_to_out[g] = 0; /* hit; compacted index assigned below */ - } - int *C_row_perm = (int *) sp_malloc(m_out * sizeof(int)); - int new_m0 = 0; - for (int g = 0; g < m_out; g++) - { - if (group_to_out[g] >= 0) - { - group_to_out[g] = new_m0; - C_row_perm[new_m0++] = g; - } - } - - matrix *out = new_permuted_dense(m_out, A->base.n, new_m0, A->n0, C_row_perm, - A->col_perm, NULL); - if (A->m0 > 0) - { - permuted_dense *C = (permuted_dense *) out; - C->bound_iwork = (int *) sp_malloc(A->m0 * sizeof(int)); - for (int ii = 0; ii < A->m0; ii++) - { - C->bound_iwork[ii] = group_to_out[group[A->row_perm[ii]]]; - } - } - sp_free(group_to_out); - sp_free(C_row_perm); - return out; -} - -void row_reduce_pd_fill_values(const permuted_dense *A, permuted_dense *C) -{ - if (C->base.nnz == 0) return; - assert(A->base.n == C->base.n && C->bound_iwork != NULL); - int n0 = A->n0; - memset(C->X, 0, (size_t) C->m0 * n0 * sizeof(double)); - for (int ii = 0; ii < A->m0; ii++) - { - const double *src = A->X + ii * n0; - double *dst = C->X + C->bound_iwork[ii] * n0; - for (int jj = 0; jj < n0; jj++) - { - dst[jj] += src[jj]; - } - } -} - -static matrix *permuted_dense_vtable_row_reduce_alloc(const matrix *self, - const int *group, int m_out) -{ - return row_reduce_pd_alloc((const permuted_dense *) self, group, m_out); -} - -static void permuted_dense_vtable_row_reduce_fill_values(const matrix *self, - matrix *out) -{ - row_reduce_pd_fill_values((const permuted_dense *) self, (permuted_dense *) out); -} - -static void wire_vtable(permuted_dense *pd) -{ - pd->base.kind = MATRIX_PERMUTED_DENSE; - pd->base.free_fn = permuted_dense_free; - pd->base.block_left_mult_vec = permuted_dense_vtable_block_left_mult_vec; - pd->base.block_left_mult_sparsity = - permuted_dense_vtable_block_left_mult_sparsity; - pd->base.block_left_mult_values = permuted_dense_vtable_block_left_mult_values; - pd->base.copy_sparsity = permuted_dense_vtable_copy_sparsity; - pd->base.DA_fill_values = permuted_dense_vtable_DA_fill_values; - pd->base.ATA_alloc = permuted_dense_vtable_ATA_alloc; - pd->base.ATDA_fill_values = permuted_dense_vtable_ATDA_fill_values; - pd->base.to_csr = permuted_dense_to_csr; - pd->base.transpose_alloc = permuted_dense_vtable_transpose_alloc; - pd->base.transpose_fill_values = permuted_dense_vtable_transpose_fill_values; - pd->base.row_gather_alloc = permuted_dense_vtable_row_gather_alloc; - pd->base.row_gather_fill_values = permuted_dense_vtable_row_gather_fill_values; - pd->base.diag_vec_alloc = permuted_dense_vtable_diag_vec_alloc; - pd->base.diag_vec_fill_values = permuted_dense_vtable_diag_vec_fill_values; - pd->base.row_reduce_alloc = permuted_dense_vtable_row_reduce_alloc; - pd->base.row_reduce_fill_values = permuted_dense_vtable_row_reduce_fill_values; - pd->base.refresh_csc_values = permuted_dense_refresh_csc_values; -} - -matrix *new_permuted_dense(int m, int n, int m0, int n0, const int *row_perm, - const int *col_perm, const double *X_data) -{ - /* Validate sorted invariants. */ - for (int ii = 1; ii < m0; ii++) - { - assert(row_perm[ii] > row_perm[ii - 1]); - } - for (int jj = 1; jj < n0; jj++) - { - assert(col_perm[jj] > col_perm[jj - 1]); - } - if (m0 > 0) - { - assert(row_perm[0] >= 0 && row_perm[m0 - 1] < m); - } - if (n0 > 0) - { - assert(col_perm[0] >= 0 && col_perm[n0 - 1] < n); - } - - permuted_dense *pd = (permuted_dense *) sp_calloc(1, sizeof(permuted_dense)); - pd->base.m = m; - pd->base.n = n; - if (m0 > 0 && n0 > INT_MAX / m0) - { - fprintf(stderr, "Error in new_permuted_dense: dense block m0 * n0 " - "exceeds INT_MAX.\n"); - exit(1); - } - pd->base.nnz = m0 * n0; - wire_vtable(pd); - - pd->m0 = m0; - pd->n0 = n0; - - int sz = m0 * n0; - pd->row_perm = (int *) sp_malloc(m0 * sizeof(int)); - pd->col_perm = (int *) sp_malloc(n0 * sizeof(int)); - pd->X = (double *) sp_malloc(sz * sizeof(double)); - pd->base.x = pd->X; - pd->owns_X = true; - pd->col_inv = (int *) sp_malloc(n * sizeof(int)); - pd->row_inv = (int *) sp_malloc(m * sizeof(int)); - - if (m0 > 0) - { - memcpy(pd->row_perm, row_perm, m0 * sizeof(int)); - } - if (n0 > 0) - { - memcpy(pd->col_perm, col_perm, n0 * sizeof(int)); - } - - for (int j = 0; j < n; j++) - { - pd->col_inv[j] = -1; - } - for (int jj = 0; jj < n0; jj++) - { - pd->col_inv[col_perm[jj]] = jj; - } - - for (int i = 0; i < m; i++) - { - pd->row_inv[i] = -1; - } - for (int ii = 0; ii < m0; ii++) - { - pd->row_inv[row_perm[ii]] = ii; - } - - if (X_data != NULL && sz > 0) - { - memcpy(pd->X, X_data, sz * sizeof(double)); - } - - return &pd->base; -} - -matrix *new_permuted_dense_full(int m, int n, const double *data) -{ - int *row_perm = (int *) sp_malloc(m * sizeof(int)); - int *col_perm = (int *) sp_malloc(n * sizeof(int)); - for (int i = 0; i < m; i++) row_perm[i] = i; - for (int j = 0; j < n; j++) col_perm[j] = j; - matrix *out = new_permuted_dense(m, n, m, n, row_perm, col_perm, data); - sp_free(row_perm); - sp_free(col_perm); - return out; -} - -static CSR_matrix *permuted_dense_to_csr_alloc(const permuted_dense *A) -{ - int m0 = A->m0; - int n0 = A->n0; - int m = A->base.m; - CSR_matrix *C = new_CSR_matrix(m, A->base.n, m0 * n0); - - /* Alias C->x to A->X: the dense block layout already matches what the - CSR_matrix view's value array would hold, so values are always live with no - memcpy needed. The PD owns the buffer; permuted_dense_free nulls - C->x before free_CSR_matrix to avoid double-free. */ - sp_free(C->x); - C->x = A->X; - - /* fill column indices (each dense row contributes a copy of col_perm) */ - for (int ii = 0; ii < m0; ii++) - { - memcpy(C->i + ii * n0, A->col_perm, n0 * sizeof(int)); - } - - /* set row pointers via count and then cumulative sum */ - memset(C->p, 0, (m + 1) * sizeof(int)); - for (int ii = 0; ii < m0; ii++) - { - C->p[A->row_perm[ii] + 1] = n0; - } - - cumsum(C->p, m); - - return C; -} - -void permuted_dense_ensure_kernel_dwork(const permuted_dense *pd_const, size_t size) -{ - /* TODO: refactor this - maybe pass in kernel and size instead? Then this is - * a general function, not necessarily tied to permuted_dense*/ - permuted_dense *pd = (permuted_dense *) pd_const; - if (pd->kernel_dwork_size >= size) return; - sp_free(pd->kernel_dwork); - pd->kernel_dwork = (double *) sp_malloc(size * sizeof(double)); - pd->kernel_dwork_size = size; -} diff --git a/sparsediff_linalg/src/permuted_dense_linalg.c b/sparsediff_linalg/src/permuted_dense_linalg.c deleted file mode 100644 index e475fb35..00000000 --- a/sparsediff_linalg/src/permuted_dense_linalg.c +++ /dev/null @@ -1,603 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "internal/permuted_dense_linalg.h" - -#include "sparsediff_linalg/cblas_wrapper.h" -#include "sparsediff_linalg/iVec.h" -#include "sparsediff_linalg/mini_numpy.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include - -matrix *copy_sparsity_pd_alloc(const permuted_dense *A) -{ - return new_permuted_dense(A->base.m, A->base.n, A->m0, A->n0, A->row_perm, - A->col_perm, NULL); -} - -matrix *transpose_pd_alloc(const permuted_dense *A) -{ - /* swap (m, n), (m0, n0), and (row_perm, col_perm) */ - return new_permuted_dense(A->base.n, A->base.m, A->n0, A->m0, A->col_perm, - A->row_perm, NULL); -} - -void transpose_pd_fill_values(const permuted_dense *A, permuted_dense *C) -{ - A_transpose(C->X, A->X, A->m0, A->n0); -} - -permuted_dense *permuted_dense_ensure_transpose_cache(const permuted_dense *B_const) -{ - /* const-cast: we don't modify B's externally observable state, just - lazily populate the cache slot. Same convention as - permuted_dense_ensure_kernel_dwork and sparse_matrix_ensure_csc_cache. */ - permuted_dense *B = (permuted_dense *) B_const; - if (B->transpose_cache != NULL) - { - return B->transpose_cache; - } - permuted_dense *BT = (permuted_dense *) transpose_pd_alloc(B); - B->transpose_cache = BT; - /* Deliberately stale so the first fill always refreshes the cache. */ - BT->transpose_seen = B->base.values_version - 1; - return BT; -} - -void DA_pd_fill_values(const double *d, const permuted_dense *A, permuted_dense *C) -{ - int m0 = A->m0; - int n0 = A->n0; - cblas_dcopy(m0 * n0, A->X, 1, C->X, 1); - for (int ii = 0; ii < m0; ii++) - { - cblas_dscal(n0, d[A->row_perm[ii]], C->X + ii * n0, 1); - } -} - -matrix *ATA_pd_alloc(const permuted_dense *A) -{ - int n = A->base.n; - /* C = AT @ A has a dense block of size n0 x n0, with row and column index - sets given by A's col_perm. (This follows from Cij = ai^T aj where - ai and aj are columns of A. Here, ai and aj always have overlapping entries, - so Cij != 0 for (i, j) in A->col_perm x A->col_perm) */ - - /* Pre-size A's dwork for the ATDA fill (Y-buffer = diag(d_perm) X). */ - permuted_dense_ensure_kernel_dwork(A, (size_t) A->m0 * A->n0); - - return new_permuted_dense(n, n, A->n0, A->n0, A->col_perm, A->col_perm, NULL); -} - -void ATDA_pd_fill_values(const permuted_dense *A, const double *d, permuted_dense *C) -{ - int m0 = A->m0; - int n0 = A->n0; - - /* dwork = diag(d_perm) @ X, where d_perm[ii] = d[row_perm[ii]]. */ - cblas_dcopy(m0 * n0, A->X, 1, A->kernel_dwork, 1); - for (int ii = 0; ii < m0; ii++) - { - cblas_dscal(n0, d[A->row_perm[ii]], A->kernel_dwork + ii * n0, 1); - } - - /* C = XT @ dwork = XT @ diag(d_perm) @ X */ - cblas_dgemm(CblasRowMajor, CblasTrans, CblasNoTrans, n0, n0, m0, 1.0, A->X, n0, - A->kernel_dwork, n0, 0.0, C->X, n0); -} - -matrix *BTA_pd_pd_alloc(const permuted_dense *B, const permuted_dense *A) -{ - /* if A and B have no overlapping rows, then C = BT @ A is empty */ - if (!has_overlap(A->row_perm, A->m0, B->row_perm, B->m0, 0)) - { - return new_permuted_dense(B->base.n, A->base.n, 0, 0, NULL, NULL, NULL); - } - - /* otherwise C has a dense block of size B->n0 x A->n0, with row and column - index sets given by B->col_perm and A->col_perm, respectively */ - matrix *C = new_permuted_dense(B->base.n, A->base.n, B->n0, A->n0, B->col_perm, - A->col_perm, NULL); - - /* Pre-size A's and B's dwork for the BTA fill slow path (gathered row - buffers). Each operand needs s_max rows of its own n0 doubles, where - s_max = MIN(A->m0, B->m0) bounds the intersection of row_perms. */ - int s_max = MIN(A->m0, B->m0); - permuted_dense_ensure_kernel_dwork(A, (size_t) s_max * A->n0); - permuted_dense_ensure_kernel_dwork(B, (size_t) s_max * B->n0); - - /* Pre-allocate C->kernel_iwork for idx_A + idx_B in BTA / BTDA_pd_pd slow paths - (each needs at most s_max ints; we store both arrays back-to-back - in iwork, hence 2 * s_max). */ - permuted_dense *C_pd = (permuted_dense *) C; - C_pd->kernel_iwork_size = (size_t) 2 * s_max; - C_pd->kernel_iwork = (int *) sp_malloc(C_pd->kernel_iwork_size * sizeof(int)); - - return C; -} - -/* Return 1 iff arrays a and b of length n are element-wise equal. */ -static int int_arrays_equal(const int *a, const int *b, int n) -{ - for (int i = 0; i < n; i++) - { - if (a[i] != b[i]) return 0; - } - return 1; -} - -/* Shared core for C = B^T @ diag(d) @ A with d == NULL meaning identity. - diag(d) is folded into A's copy into kernel_dwork (d indexed by global row), - so no intermediate is ever allocated: all buffers are pre-sized by - BTA_pd_pd_alloc. Aliasing invariant: B == A implies identical row_perms and - therefore the single-matmul path (a dwork write vs an X read — safe); the - gather path must never run with B == A sharing one kernel_dwork. */ -static void BTA_pd_pd_core(const permuted_dense *B, const double *d, - const permuted_dense *A, permuted_dense *C) -{ - /* C may be empty if there is no overlap in row permutations */ - if (C->base.nnz == 0) - { - return; - } - - /* if B and A have identical row_perms, one matmul suffices */ - if (A->m0 == B->m0 && int_arrays_equal(A->row_perm, B->row_perm, A->m0)) - { - const double *A_rows = A->X; - if (d != NULL) - { - /* A->kernel_dwork = diag(d) X_A. Pre-sized by BTA_pd_pd_alloc to - MIN(A->m0, B->m0) * A->n0 = A->m0 * A->n0 on this path. */ - memcpy(A->kernel_dwork, A->X, (size_t) A->m0 * A->n0 * sizeof(double)); - for (int ii = 0; ii < A->m0; ii++) - { - cblas_dscal(A->n0, d[A->row_perm[ii]], A->kernel_dwork + ii * A->n0, - 1); - } - A_rows = A->kernel_dwork; - } - cblas_dgemm(CblasRowMajor, CblasTrans, CblasNoTrans, B->n0, A->n0, A->m0, - 1.0, B->X, B->n0, A_rows, A->n0, 0.0, C->X, A->n0); - return; - } - - // ----------------------------------------------------------------------- - // find intersection of row permutations. C->kernel_iwork was pre-sized by - // BTA_pd_pd_alloc to 2 * MIN(A->m0, B->m0) ints (idx_A | idx_B back- - // to-back), so no allocation here. - // ----------------------------------------------------------------------- - int s_max = MIN(A->m0, B->m0); - int *idx_A = C->kernel_iwork; - int *idx_B = C->kernel_iwork + s_max; - int s = sorted_intersect_indices(A->row_perm, A->m0, B->row_perm, B->m0, idx_A, - idx_B); - assert(s > 0); - - // ------------------------------------------------------------------------ - // Gather the matching rows into A->kernel_dwork and B->kernel_dwork, - // scaling A's rows by diag(d) on the way when d is given. dwork is - // pre-sized by BTA_pd_pd_alloc (one ensure_dwork call per operand at - // alloc time). - // ------------------------------------------------------------------------ - for (int k = 0; k < s; k++) - { - memcpy(A->kernel_dwork + k * A->n0, A->X + idx_A[k] * A->n0, - A->n0 * sizeof(double)); - memcpy(B->kernel_dwork + k * B->n0, B->X + idx_B[k] * B->n0, - B->n0 * sizeof(double)); - } - if (d != NULL) - { - for (int k = 0; k < s; k++) - { - cblas_dscal(A->n0, d[A->row_perm[idx_A[k]]], A->kernel_dwork + k * A->n0, - 1); - } - } - - /* matmul on the gathered rows */ - cblas_dgemm(CblasRowMajor, CblasTrans, CblasNoTrans, B->n0, A->n0, s, 1.0, - B->kernel_dwork, B->n0, A->kernel_dwork, A->n0, 0.0, C->X, A->n0); -} - -void BTA_pd_pd_fill_values(const permuted_dense *B, const permuted_dense *A, - permuted_dense *C) -{ - BTA_pd_pd_core(B, NULL, A, C); -} - -void BTDA_pd_pd_fill_values(const permuted_dense *B, const double *d, - const permuted_dense *A, permuted_dense *C) -{ - BTA_pd_pd_core(B, d, A, C); -} - -/* The CSR-flavored kernels for (B=Sparse, A=PD) live in src/old-code; the - production path uses BTA_csc_pd_alloc / BTDA_csc_pd_fill_values defined - further below, which delegate to BTA_pd_csc via the (A^T B)^T identity. */ - -/* Return true if any of the 'len' integers in 'indices' exist in the set - marked by 'inv' (inv[k] != -1 iff k is in the set). */ -static inline bool idxs_hits_set(const int *idxs, int len, const int *inv) -{ - for (int ii = 0; ii < len; ii++) - { - if (inv[idxs[ii]] != -1) - { - return true; - } - } - return false; -} - -/* Inner product of a sparse vector (vals[0..len) at positions idxs[0..len)) - with a dense vector, where inv maps each idxs value to a position in - 'dense' (inv[k] == -1 means skip that entry). */ -static inline double sparse_dot_dense(const double *vals, const int *idxs, int len, - const int *inv, const double *dense) -{ - double sum = 0.0; - for (int e = 0; e < len; e++) - { - int kk = inv[idxs[e]]; - if (kk == -1) - { - continue; - } - sum += vals[e] * dense[kk]; - } - return sum; -} - -matrix *BA_pd_csc_alloc(const permuted_dense *B, const CSC_matrix *A) -{ - /* Cij != 0 if row i of B overlaps with column j of A. So we loop through - the columns of A. For each column of A, we check if it has any nonzeros in - rows that are in B's col_perm. If yes, column j of C will have a nonzero - block corresponding to the rows of B */ - iVec *col_perm_C = iVec_new(10); - for (int j = 0; j < A->n; j++) - { - int start = A->p[j]; - int len = A->p[j + 1] - start; - if (idxs_hits_set(A->i + start, len, B->col_inv)) - { - iVec_append(col_perm_C, j); - } - } - - matrix *C = new_permuted_dense(B->base.m, A->n, B->m0, col_perm_C->len, - B->row_perm, col_perm_C->data, NULL); - iVec_free(col_perm_C); - return C; -} - -void BA_pd_csc_fill_values(const double *B, int n0_B, const int *inv, - const CSC_matrix *A, permuted_dense *C) -{ - /* C[i, j] = bi^T @ ajj, where bi is the ith row of B_X (length n0_B, - row stride n0_B) and ajj is the jjth column of A's sparse block - (column jj = C->col_perm[j]). inv maps A's row indices to positions - in B_X (entries with inv[r] == -1 are skipped). */ - - /* row i of C */ - for (int i = 0; i < C->m0; i++) - { - double *ci = C->X + i * C->n0; - - /* col j of C */ - for (int j = 0; j < C->n0; j++) - { - - int jj = C->col_perm[j]; - int start = A->p[jj]; - int len = A->p[jj + 1] - start; - /* we compute entry C[i, j] */ - ci[j] = - sparse_dot_dense(A->x + start, A->i + start, len, inv, B + i * n0_B); - } - } -} - -matrix *BA_pd_pd_alloc(const permuted_dense *B, const permuted_dense *A) -{ - /* if B's columns don't overlap with A's rows, C = B @ A is empty */ - if (!has_overlap(B->col_perm, B->n0, A->row_perm, A->m0, 0)) - { - return new_permuted_dense(B->base.m, A->base.n, 0, 0, NULL, NULL, NULL); - } - - /* otherwise C has a dense block of size B->m0 x A->n0, with row index - set B->row_perm and column index set A->col_perm. */ - matrix *C = new_permuted_dense(B->base.m, A->base.n, B->m0, A->n0, B->row_perm, - A->col_perm, NULL); - - int s_max = MIN(B->n0, A->m0); - - /* Pre-size B's and A's dwork for the gathers in fill. Worst-case - intersection size is s_max; B_sub is (m0, s) and A_sub is (s, n0). */ - permuted_dense_ensure_kernel_dwork(A, (size_t) s_max * A->n0); - permuted_dense_ensure_kernel_dwork(B, (size_t) s_max * B->m0); - - /* Pre-allocate C->kernel_iwork for idx_B + idx_A back-to-back (2 * s_max ints), - same idiom as BTA_pd_pd_alloc. */ - permuted_dense *C_pd = (permuted_dense *) C; - C_pd->kernel_iwork_size = (size_t) 2 * s_max; - C_pd->kernel_iwork = (int *) sp_malloc(C_pd->kernel_iwork_size * sizeof(int)); - - return C; -} - -/* TODO: do we want to reuse BTA_pd_pd_fill_values? */ -void BA_pd_pd_fill_values(const permuted_dense *B, const permuted_dense *A, - permuted_dense *C) -{ - /* C may be empty when B->col_perm and A->row_perm don't overlap. */ - if (C->base.nnz == 0) - { - return; - } - - /* if B's col_perm and A's row_perm are identical, one matmul suffices */ - if (B->n0 == A->m0 && int_arrays_equal(B->col_perm, A->row_perm, B->n0)) - { - cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, B->m0, A->n0, B->n0, - 1.0, B->X, B->n0, A->X, A->n0, 0.0, C->X, A->n0); - return; - } - - // ----------------------------------------------------------------------- - // find intersection of B's col_perm and A's row_perm. C->kernel_iwork was - // pre-sized by BA_pd_pd_alloc to 2 * MIN(B->n0, A->m0) ints (idx_B | - // idx_A back-to-back), so no allocation here. - // ----------------------------------------------------------------------- - int s_max = MIN(B->n0, A->m0); - int *idx_B = C->kernel_iwork; - int *idx_A = C->kernel_iwork + s_max; - int s = sorted_intersect_indices(B->col_perm, B->n0, A->row_perm, A->m0, idx_B, - idx_A); - assert(s > 0); - - // ------------------------------------------------------------------------ - // Gather the matching slices into B->kernel_dwork (column gather) and - // A->kernel_dwork (row gather). dwork is pre-sized by BA_pd_pd_alloc (one - // ensure_dwork call per operand at alloc time). - // ------------------------------------------------------------------------ - /* B_sub shape (B->m0, s) row-major: B_sub[ii, kk] = B->X[ii, idx_B[kk]]. */ - for (int ii = 0; ii < B->m0; ii++) - { - for (int kk = 0; kk < s; kk++) - { - B->kernel_dwork[ii * s + kk] = B->X[ii * B->n0 + idx_B[kk]]; - } - } - /* A_sub shape (s, A->n0) row-major: A_sub[kk, :] = A->X[idx_A[kk], :]. */ - for (int kk = 0; kk < s; kk++) - { - memcpy(A->kernel_dwork + kk * A->n0, A->X + idx_A[kk] * A->n0, - A->n0 * sizeof(double)); - } - - /* matmul on the gathered slices */ - cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, B->m0, A->n0, s, 1.0, - B->kernel_dwork, s, A->kernel_dwork, A->n0, 0.0, C->X, A->n0); -} - -matrix *BTA_pd_csc_alloc(const permuted_dense *B, const CSC_matrix *A) -{ - /* Cij != 0 if column i of B overlaps with column j of A. So we loop - through the columns of A. For each column of A, we check if it has any - nonzeros in rows that are in B's row_perm. If yes, column j of C will - have a nonzero block corresponding to the columns of B */ - iVec *col_active = iVec_new(8); - for (int j = 0; j < A->n; j++) - { - int start = A->p[j]; - int len = A->p[j + 1] - start; - if (idxs_hits_set(A->i + start, len, B->row_inv)) - { - iVec_append(col_active, j); - } - } - - matrix *C = new_permuted_dense(B->base.n, A->n, B->n0, col_active->len, - B->col_perm, col_active->data, NULL); - iVec_free(col_active); - - /* Pre-size B's dwork for the BTDA fill (holds (diag(d) B)^T). */ - permuted_dense_ensure_kernel_dwork(B, (size_t) B->m0 * B->n0); - - return C; -} - -/* C = B^T @ A = (B^T) A, with B^T materialized into B->kernel_dwork. The no- - diagonal analogue of BTDA_pd_csc_fill_values. */ -void BTA_pd_csc_fill_values(const permuted_dense *B, const CSC_matrix *A, - permuted_dense *C) -{ - /* C may be empty */ - if (C->base.nnz == 0) - { - return; - } - - A_transpose(B->kernel_dwork, B->X, B->m0, B->n0); - BA_pd_csc_fill_values(B->kernel_dwork, B->m0, B->row_inv, A, C); -} - -/* C = B^T diag(d) A = (diag (d) B)^T A */ -void BTDA_pd_csc_fill_values(const permuted_dense *B, const double *d, - const CSC_matrix *A, permuted_dense *C) -{ - /* C may be empty */ - if (C->base.nnz == 0) - { - return; - } - - int m0 = B->m0; - int n0 = B->n0; - - /* conpute B->kernel_dwork = (diag(d) B)^T */ - for (int kk = 0; kk < m0; kk++) - { - double dk = d[B->row_perm[kk]]; - for (int ii = 0; ii < n0; ii++) - { - B->kernel_dwork[ii * m0 + kk] = dk * B->X[kk * n0 + ii]; - } - } - - BA_pd_csc_fill_values(B->kernel_dwork, m0, B->row_inv, A, C); -} - -matrix *BTA_csc_pd_alloc(const CSC_matrix *B, const permuted_dense *A) -{ - /* Cij != 0 if column i of B overlaps with row j of A. So we loop through the - columns of B. For each column of B, we check if it has any nonzeros in rows - that are in A->row_perm. If yes, column i of C will have a nonzero block - corresponding to the columns of A */ - - iVec *row_active = iVec_new(10); - for (int i = 0; i < B->n; i++) - { - int start = B->p[i]; - int len = B->p[i + 1] - start; - if (idxs_hits_set(B->i + start, len, A->row_inv)) - { - iVec_append(row_active, i); - } - } - - matrix *C = new_permuted_dense(B->n, A->base.n, row_active->len, A->n0, - row_active->data, A->col_perm, NULL); - iVec_free(row_active); - - /* Pre-size A's dwork for the BTDA fill (holds (diag(d_perm) X_A)^T). */ - permuted_dense_ensure_kernel_dwork(A, (size_t) A->m0 * A->n0); - - return C; -} - -/* Internal core for BTA_csc_pd_fill_values / BTDA_csc_pd_fill_values: C = B^T @ A - where B is CSC and the right operand A is supplied as a transposed-layout raw - buffer (row j of A_T = m0_A contiguous doubles = the j-th column of A's dense - block). Transposed-output sibling of BA_pd_csc_fill_values. */ -static void BTA_csc_denseT_fill_values(const CSC_matrix *B, const double *A_T, - int m0_A, const int *inv, permuted_dense *C) -{ - /* C[i_C, j_C] = dot(col C->row_perm[i_C] of B, row j_C of A_T). */ - for (int i_C = 0; i_C < C->m0; i_C++) - { - int B_col = C->row_perm[i_C]; - int start = B->p[B_col]; - int len = B->p[B_col + 1] - start; - double *ci = C->X + i_C * C->n0; - for (int j_C = 0; j_C < C->n0; j_C++) - { - ci[j_C] = sparse_dot_dense(B->x + start, B->i + start, len, inv, - A_T + j_C * m0_A); - } - } -} - -/* C = B^T @ A where B is CSC and A is permuted_dense. The csc_pd analogue of - BTDA_csc_pd_fill_values without the diagonal: transpose A's dense block into - A->kernel_dwork (the column-contiguous layout the core wants) and delegate. */ -void BTA_csc_pd_fill_values(const CSC_matrix *B, const permuted_dense *A, - permuted_dense *C) -{ - if (C->base.nnz == 0) - { - return; - } - - /* A->kernel_dwork = X_A^T, row-major shape (n0_A, m0_A). */ - A_transpose(A->kernel_dwork, A->X, A->m0, A->n0); - BTA_csc_denseT_fill_values(B, A->kernel_dwork, A->m0, A->row_inv, C); -} - -/* C = B^T diag(d) A. Folds diag(d) into A's dense block (writing - (diag(d_perm) X_A)^T into A->kernel_dwork) and delegates to - BTA_csc_denseT_fill_values. Mirrors how BTDA_pd_csc_fill_values wraps - BA_pd_csc_fill_values. */ -void BTDA_csc_pd_fill_values(const CSC_matrix *B, const double *d, - const permuted_dense *A, permuted_dense *C) -{ - if (C->base.nnz == 0) - { - return; - } - - int m0_A = A->m0; - int n0_A = A->n0; - - /* A->kernel_dwork = (diag(d_perm) X_A)^T, row-major shape (n0_A, m0_A). - Pre-sized by BTA_csc_pd_alloc; no allocation in fill. - Column j of (diag(d) X_A) lives contiguously in dwork as row j — - which is exactly the layout BTA_csc_denseT_fill_values wants. */ - for (int kk = 0; kk < m0_A; kk++) - { - double dk = d[A->row_perm[kk]]; - for (int jj = 0; jj < n0_A; jj++) - { - A->kernel_dwork[jj * m0_A + kk] = dk * A->X[kk * n0_A + jj]; - } - } - - BTA_csc_denseT_fill_values(B, A->kernel_dwork, m0_A, A->row_inv, C); -} - -/* Original transpose-via-Cprime implementation of BTDA_csc_pd_fill_values. - No longer linked; preserved here as in-file reference for the math - identity C = (A^T diag(d) B)^T and the BA_pd_csc_fill_values delegation. */ -#if defined(__GNUC__) || defined(__clang__) -__attribute__((unused)) -#endif -static void BTDA_csc_pd_fill_values_via_transpose_dead(const CSC_matrix *B, - const double *d, - const permuted_dense *A, - permuted_dense *C) -{ - if (C->base.nnz == 0) - { - return; - } - - /* Cprime has shape (A->n0, |row_active|) — i.e. C transposed. */ - matrix *Cprime_m = BTA_pd_csc_alloc(A, B); - permuted_dense *Cprime = (permuted_dense *) Cprime_m; - BTDA_pd_csc_fill_values(A, d, B, Cprime); - - /* C->X = Cprime->X^T. Cprime has dims (C->n0, C->m0). */ - int m0 = C->m0; - int n0 = C->n0; - for (int i = 0; i < m0; i++) - { - for (int j = 0; j < n0; j++) - { - C->X[i * n0 + j] = Cprime->X[j * m0 + i]; - } - } - - free_matrix(Cprime_m); -} diff --git a/sparsediff_linalg/src/sparse_matrix.c b/sparsediff_linalg/src/sparse_matrix.c deleted file mode 100644 index a87e8812..00000000 --- a/sparsediff_linalg/src/sparse_matrix.c +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/sparse_matrix.h" - -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_sum.h" -#include "sparsediff_linalg/linalg_sparse_matmuls.h" -#include "sparsediff_linalg/matrix.h" -#include "sparsediff_linalg/mini_numpy.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include -#include - -static void sparse_block_left_mult_vec(const matrix *self, const double *x, - double *y, int p) -{ - const sparse_matrix *sm = (const sparse_matrix *) self; - block_left_multiply_vec(sm->csr, x, y, p); -} - -static CSC_matrix *sparse_block_left_mult_sparsity(const matrix *self, - const CSC_matrix *J, int p) -{ - const sparse_matrix *sm = (const sparse_matrix *) self; - return block_left_multiply_fill_sparsity(sm->csr, J, p); -} - -static void sparse_block_left_mult_values(const matrix *self, const CSC_matrix *J, - CSC_matrix *C) -{ - const sparse_matrix *sm = (const sparse_matrix *) self; - block_left_multiply_fill_values(sm->csr, J, C); -} - -static void sparse_free(matrix *self) -{ - sparse_matrix *sm = (sparse_matrix *) self; - free_CSR_matrix(sm->csr); - free_CSC_matrix(sm->csc_cache); - sp_free(sm->csc_iwork); - sp_free(sm->bound_iwork); - sp_free(sm); -} - -/* Forward decl: ctor is referenced by copy_sparsity below. */ -matrix *new_sparse_matrix(CSR_matrix *A); - -/* Build the CSC_matrix cache structure if absent. Values are NOT filled here; call - refresh_csc_values before consuming (it no-ops when the cache is already - fresh). ATA_alloc only needs structure, so it's safe to call without a - subsequent refresh. */ -void sparse_matrix_ensure_csc_cache(sparse_matrix *sm) -{ - if (sm->csc_cache != NULL) return; - sm->csc_iwork = (int *) sp_malloc(sm->csr->n * sizeof(int)); - sm->csc_cache = csr_to_csc_alloc(sm->csr, sm->csc_iwork); - - /* Deliberately stale: the structure is built during the symbolic phase, - before values are meaningful, so the first refresh must fill. */ - sm->csc_seen = sm->base.values_version - 1; -} - -static matrix *sparse_copy_sparsity(const matrix *self) -{ - const sparse_matrix *sm = (const sparse_matrix *) self; - return new_sparse_matrix(new_csr_copy_sparsity(sm->csr)); -} - -static void sparse_DA_fill_values(const double *d, const matrix *self, matrix *out) -{ - const sparse_matrix *sm = (const sparse_matrix *) self; - sparse_matrix *sm_out = (sparse_matrix *) out; - DA_fill_values(d, sm->csr, sm_out->csr); -} - -static matrix *sparse_ATA_alloc(matrix *self) -{ - sparse_matrix *sm = (sparse_matrix *) self; - sparse_matrix_ensure_csc_cache(sm); - return new_sparse_matrix(ATA_alloc(sm->csc_cache)); -} - -/* Call refresh_csc_values before consuming; it no-ops when the cache is - already fresh. */ -static void sparse_ATDA_fill_values(const matrix *self, const double *d, matrix *out) -{ - const sparse_matrix *sm = (const sparse_matrix *) self; - sparse_matrix *sm_out = (sparse_matrix *) out; - ATDA_fill_values(sm->csc_cache, d, sm_out->csr); -} - -static CSR_matrix *sparse_to_csr(matrix *self) -{ - return ((sparse_matrix *) self)->csr; -} - -static matrix *sparse_transpose_alloc(const matrix *self) -{ - const sparse_matrix *sm = (const sparse_matrix *) self; - int *iwork = (int *) sp_malloc(sm->csr->n * sizeof(int)); - CSR_matrix *AT = AT_alloc(sm->csr, iwork); - sparse_matrix *out = (sparse_matrix *) new_sparse_matrix(AT); - out->bound_iwork = iwork; - return &out->base; -} - -static void sparse_transpose_fill_values(const matrix *self, matrix *out) -{ - const sparse_matrix *sm_in = (const sparse_matrix *) self; - sparse_matrix *sm_out = (sparse_matrix *) out; - AT_fill_values(sm_in->csr, sm_out->csr, sm_out->bound_iwork); -} - -static matrix *sparse_row_gather_alloc(const matrix *self, const int *map, int m_out) -{ - const CSR_matrix *Jx = ((const sparse_matrix *) self)->csr; - - /* Exact output nnz: sum the selected rows' nnz. Jx->nnz is NOT an upper - bound — repeated map entries select the same source row more than once - (cvxpy#3442). Repeated gathers of dense rows can push the true count - past INT_MAX, which a CSR cannot represent, so fail before wrapping. */ - int nnz = 0; - for (int i = 0; i < m_out; i++) - { - int row = map[i]; - int len = Jx->p[row + 1] - Jx->p[row]; - if (len > INT_MAX - nnz) - { - fprintf(stderr, "Error in sparse_row_gather_alloc: gathered nnz " - "exceeds INT_MAX.\n"); - exit(1); - } - nnz += len; - } - CSR_matrix *J = new_CSR_matrix(m_out, self->n, nnz); - - J->p[0] = 0; - for (int i = 0; i < m_out; i++) - { - int row = map[i]; - int len = Jx->p[row + 1] - Jx->p[row]; - memcpy(J->i + J->p[i], Jx->i + Jx->p[row], len * sizeof(int)); - J->p[i + 1] = J->p[i] + len; - } - J->nnz = J->p[m_out]; - - sparse_matrix *out = (sparse_matrix *) new_sparse_matrix(J); - out->bound_iwork = (int *) sp_malloc(m_out * sizeof(int)); - if (m_out > 0) - { - memcpy(out->bound_iwork, map, m_out * sizeof(int)); - } - return &out->base; -} - -static void sparse_row_gather_fill_values(const matrix *self, matrix *out) -{ - const CSR_matrix *Jx = ((const sparse_matrix *) self)->csr; - sparse_matrix *sm_out = (sparse_matrix *) out; - CSR_matrix *J = sm_out->csr; - const int *map = sm_out->bound_iwork; - assert(map != NULL && self->n == out->n); - for (int i = 0; i < J->m; i++) - { - int len = J->p[i + 1] - J->p[i]; - memcpy(J->x + J->p[i], Jx->x + Jx->p[map[i]], len * sizeof(double)); - } -} - -static matrix *sparse_diag_vec_alloc(matrix *self) -{ - CSR_matrix *Jx = ((sparse_matrix *) self)->csr; - int n = self->m; - int out_m = n * n; - CSR_matrix *J = new_CSR_matrix(out_m, self->n, Jx->nnz); - - int nnz = 0; - int next_diag = 0; - for (int row = 0; row < out_m; row++) - { - J->p[row] = nnz; - if (row == next_diag) - { - int child_row = row / (n + 1); - int len = Jx->p[child_row + 1] - Jx->p[child_row]; - memcpy(J->i + nnz, Jx->i + Jx->p[child_row], len * sizeof(int)); - nnz += len; - next_diag += n + 1; - } - } - J->p[out_m] = nnz; - J->nnz = nnz; - return new_sparse_matrix(J); -} - -static void sparse_diag_vec_fill_values(matrix *self, matrix *out) -{ - CSR_matrix *Jx = ((sparse_matrix *) self)->csr; - CSR_matrix *J = ((sparse_matrix *) out)->csr; - int n = self->m; - for (int i = 0; i < n; i++) - { - int out_row = i * (n + 1); - int len = J->p[out_row + 1] - J->p[out_row]; - memcpy(J->x + J->p[out_row], Jx->x + Jx->p[i], len * sizeof(double)); - } -} - -/* Build CSC_matrix structure on first call; refill values from csr->x iff they - changed since the last refresh (tracked via base.values_version). */ -static void sparse_refresh_csc_values(matrix *self) -{ - sparse_matrix *sm = (sparse_matrix *) self; - sparse_matrix_ensure_csc_cache(sm); - if (sm->csc_seen == sm->base.values_version) return; - csr_to_csc_fill_values(sm->csr, sm->csc_cache, sm->csc_iwork); - sm->csc_seen = sm->base.values_version; -} - -static matrix *sparse_row_reduce_alloc(const matrix *self, const int *group, - int m_out) -{ - const CSR_matrix *A = ((const sparse_matrix *) self)->csr; - int m = A->m; - int n = A->n; - - /* Inverse of group by counting sort: rows[start[k] .. start[k+1]) lists the - source rows of output row k. */ - int *start = (int *) sp_calloc(m_out + 1, sizeof(int)); - int *fill = (int *) sp_malloc(m_out * sizeof(int)); - int *rows = (int *) sp_malloc(m * sizeof(int)); - for (int i = 0; i < m; i++) - { - assert(group[i] >= 0 && group[i] < m_out); - start[group[i] + 1]++; - } - for (int k = 0; k < m_out; k++) - { - start[k + 1] += start[k]; - } - memcpy(fill, start, m_out * sizeof(int)); - for (int i = 0; i < m; i++) - { - rows[fill[group[i]]++] = i; - } - - /* Output row k is the sorted union of its source rows' columns. Summing can - only merge entries, so A->nnz bounds the output nnz. pos_of[j] is the - position in J of column j within the output row being built; anything - below row_start is left over from an earlier row and means "not yet in - this row". */ - int cap = MIN(A->nnz, sat_mul_int(m_out, n)); - CSR_matrix *J = new_CSR_matrix(m_out, n, cap); - int *map = (int *) sp_malloc(A->nnz * sizeof(int)); - int *pos_of = (int *) sp_malloc(n * sizeof(int)); - for (int j = 0; j < n; j++) - { - pos_of[j] = -1; - } - - int nnz = 0; - J->p[0] = 0; - for (int k = 0; k < m_out; k++) - { - int row_start = nnz; - for (int ii = start[k]; ii < start[k + 1]; ii++) - { - int i = rows[ii]; - for (int jj = A->p[i]; jj < A->p[i + 1]; jj++) - { - int j = A->i[jj]; - if (pos_of[j] < row_start) - { - pos_of[j] = nnz; - J->i[nnz++] = j; - } - } - } - if (nnz > row_start) - { - sort_int_array(J->i + row_start, nnz - row_start); - } - J->p[k + 1] = nnz; - - /* sorting moved the columns: record final positions, then map every - source entry of this output row to its output position */ - for (int jj = row_start; jj < nnz; jj++) - { - pos_of[J->i[jj]] = jj; - } - for (int ii = start[k]; ii < start[k + 1]; ii++) - { - int i = rows[ii]; - for (int jj = A->p[i]; jj < A->p[i + 1]; jj++) - { - map[jj] = pos_of[A->i[jj]]; - } - } - } - J->nnz = nnz; - CSR_trim(J); - - sp_free(pos_of); - sp_free(rows); - sp_free(fill); - sp_free(start); - - sparse_matrix *out = (sparse_matrix *) new_sparse_matrix(J); - out->bound_iwork = map; - return &out->base; -} - -static void sparse_row_reduce_fill_values(const matrix *self, matrix *out) -{ - if (out->nnz == 0) return; - const CSR_matrix *A = ((const sparse_matrix *) self)->csr; - sparse_matrix *sm_out = (sparse_matrix *) out; - assert(sm_out->bound_iwork != NULL && self->n == out->n); - memset(out->x, 0, out->nnz * sizeof(double)); - accumulator(A->x, A->nnz, sm_out->bound_iwork, out->x); -} - -static void wire_vtable(sparse_matrix *sm) -{ - sm->base.kind = MATRIX_SPARSE; - sm->base.block_left_mult_vec = sparse_block_left_mult_vec; - sm->base.block_left_mult_sparsity = sparse_block_left_mult_sparsity; - sm->base.block_left_mult_values = sparse_block_left_mult_values; - sm->base.copy_sparsity = sparse_copy_sparsity; - sm->base.DA_fill_values = sparse_DA_fill_values; - sm->base.ATA_alloc = sparse_ATA_alloc; - sm->base.ATDA_fill_values = sparse_ATDA_fill_values; - sm->base.to_csr = sparse_to_csr; - sm->base.transpose_alloc = sparse_transpose_alloc; - sm->base.transpose_fill_values = sparse_transpose_fill_values; - sm->base.row_gather_alloc = sparse_row_gather_alloc; - sm->base.row_gather_fill_values = sparse_row_gather_fill_values; - sm->base.diag_vec_alloc = sparse_diag_vec_alloc; - sm->base.diag_vec_fill_values = sparse_diag_vec_fill_values; - sm->base.row_reduce_alloc = sparse_row_reduce_alloc; - sm->base.row_reduce_fill_values = sparse_row_reduce_fill_values; - sm->base.refresh_csc_values = sparse_refresh_csc_values; - sm->base.free_fn = sparse_free; -} - -matrix *new_sparse_matrix(CSR_matrix *A) -{ - sparse_matrix *sm = (sparse_matrix *) sp_calloc(1, sizeof(sparse_matrix)); - sm->base.m = A->m; - sm->base.n = A->n; - sm->base.nnz = A->nnz; - sm->base.x = A->x; - wire_vtable(sm); - sm->csr = A; - return &sm->base; -} - -matrix *new_sparse_matrix_alloc(int m, int n, int nnz) -{ - return new_sparse_matrix(new_CSR_matrix(m, n, nnz)); -} - -void sparse_matrix_trim(matrix *M) -{ - CSR_matrix *csr = ((sparse_matrix *) M)->csr; - CSR_trim(csr); - M->x = csr->x; - M->nnz = csr->nnz; -} - -matrix *sparse_matrix_trans(const sparse_matrix *self, int *iwork) -{ - CSR_matrix *AT = transpose(self->csr, iwork); - sparse_matrix *sm = (sparse_matrix *) sp_calloc(1, sizeof(sparse_matrix)); - sm->base.m = AT->m; - sm->base.n = AT->n; - sm->base.nnz = AT->nnz; - sm->base.x = AT->x; - wire_vtable(sm); - sm->csr = AT; - return &sm->base; -} diff --git a/sparsediff_linalg/src/stacked_pd.c b/sparsediff_linalg/src/stacked_pd.c deleted file mode 100644 index ea462a2a..00000000 --- a/sparsediff_linalg/src/stacked_pd.c +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/stacked_pd.h" - -#include "internal/stacked_pd_linalg.h" -#include "sparsediff_linalg/matrix.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/sparse_matrix.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include - -static void stacked_pd_free(matrix *self) -{ - stacked_pd *spd = (stacked_pd *) self; - for (int k = 0; k < spd->n_blocks; k++) - { - /* spd owns the blocks */ - free_matrix((matrix *) spd->blocks[k]); - } - - sp_free(spd->blocks); - sp_free(spd->src_block_idx_p); - sp_free(spd->src_block_idx); - - if (spd->pre_coalesce != NULL) - { - free_matrix((matrix *) spd->pre_coalesce); - } - - if (spd->kernel_pd_scratch != NULL) - { - free_matrix(&spd->kernel_pd_scratch->base); - } - - free_CSR_matrix(spd->csr_cache); - sp_free(spd->base.x); - sp_free(spd); -} - -/* Vtable adapters — each downcasts matrix * to stacked_pd * and - delegates to the corresponding public spd kernel. */ - -static matrix *stacked_pd_vtable_copy_sparsity(const matrix *self) -{ - return copy_sparsity_spd_alloc((const stacked_pd *) self); -} - -static void stacked_pd_vtable_DA_fill_values(const double *d, const matrix *self, - matrix *out) -{ - DA_spd_fill_values(d, (const stacked_pd *) self, (stacked_pd *) out); - matrix_values_changed(out); -} - -static matrix *stacked_pd_vtable_ATA_alloc(matrix *self) -{ - return ATA_spd_alloc((const stacked_pd *) self); -} - -static void stacked_pd_vtable_ATDA_fill_values(const matrix *self, const double *d, - matrix *out) -{ - ATDA_spd_fill_values((const stacked_pd *) self, d, (stacked_pd *) out); - matrix_values_changed(out); -} - -static matrix *stacked_pd_vtable_transpose_alloc(const matrix *self) -{ - return transpose_spd_alloc((const stacked_pd *) self); -} - -static void stacked_pd_vtable_transpose_fill_values(const matrix *self, matrix *out) -{ - transpose_spd_fill_values((const stacked_pd *) self, (stacked_pd *) out); - matrix_values_changed(out); -} - -static void stacked_pd_vtable_refresh_csc_values(matrix *self) -{ - (void) self; /* spd has no CSC cache to refresh */ -} - -static CSR_matrix *stacked_pd_to_csr_alloc(const stacked_pd *spd) -{ - int m = spd->base.m; - CSR_matrix *C = new_CSR_matrix(m, spd->base.n, spd->base.nnz); - - /* fill row ptrs by first setting the length of each row and then cumsum */ - int *Cp = C->p; - for (int k = 0; k < spd->n_blocks; k++) - { - permuted_dense *blk = spd->blocks[k]; - for (int i = 0; i < blk->m0; i++) - { - Cp[blk->row_perm[i] + 1] = blk->n0; - } - } - cumsum(Cp, m); - - /* fill column indices */ - for (int k = 0; k < spd->n_blocks; k++) - { - permuted_dense *blk = spd->blocks[k]; - for (int i = 0; i < blk->m0; i++) - { - int row = blk->row_perm[i]; - memcpy(C->i + Cp[row], blk->col_perm, blk->n0 * sizeof(int)); - } - } - return C; -} - -static CSR_matrix *stacked_pd_to_csr(matrix *self) -{ - stacked_pd *spd = (stacked_pd *) self; - - if (spd->csr_cache == NULL) - { - spd->csr_cache = stacked_pd_to_csr_alloc(spd); - - /* Deliberately stale: force the first call to copy the values. */ - spd->csr_seen = spd->base.values_version - 1; - } - - /* refresh values iff the block X buffers changed since the last call */ - if (spd->csr_seen == spd->base.values_version) - { - return spd->csr_cache; - } - spd->csr_seen = spd->base.values_version; - int *p_arr = spd->csr_cache->p; - for (int k = 0; k < spd->n_blocks; k++) - { - permuted_dense *blk = spd->blocks[k]; - for (int i = 0; i < blk->m0; i++) - { - int row = blk->row_perm[i]; - memcpy(spd->csr_cache->x + p_arr[row], blk->X + i * blk->n0, - blk->n0 * sizeof(double)); - } - } - return spd->csr_cache; -} - -matrix *spd_map_filter_blocks(const stacked_pd *B, int Cm, int Cn, spd_block_op op, - const void *ctx) -{ - if (B->n_blocks == 0) - { - return new_stacked_pd(Cm, Cn, 0, NULL, NULL, NULL); - } - - permuted_dense **C_blocks = - (permuted_dense **) sp_malloc(B->n_blocks * sizeof(permuted_dense *)); - int *C_src = (int *) sp_malloc(B->n_blocks * sizeof(int)); - - int out_nb = 0; - for (int k = 0; k < B->n_blocks; k++) - { - matrix *Ck = op(B->blocks[k], ctx); - if (Ck->nnz == 0) - { - free_matrix(Ck); - } - else - { - C_blocks[out_nb] = (permuted_dense *) Ck; - C_src[out_nb] = k; - out_nb++; - } - } - - int *C_src_p = (int *) sp_malloc((out_nb + 1) * sizeof(int)); - for (int k = 0; k <= out_nb; k++) - { - C_src_p[k] = k; - } - - matrix *C = new_stacked_pd(Cm, Cn, out_nb, C_blocks, C_src_p, C_src); - sp_free(C_blocks); - sp_free(C_src); - sp_free(C_src_p); - return C; -} - -void compose_csr_idx_map_for_spd(const stacked_pd *spd, const CSR_matrix *csr, - int *idx_map) -{ - /* spd's base.x is block-major (block 0's m0*n0 values, then block 1's, - ...); csr->x is row-major sorted. Because spd blocks have pairwise- - disjoint row perms and stacked_pd_to_csr_alloc memcpys each block's - col_perm straight into csr->i, the n0 CSR entries for any row are - exactly that block's columns in order — CSR position is just - csr->p[row] + jj. */ - int *scratch = (int *) sp_malloc(spd->base.nnz * sizeof(int)); - int native = 0; - for (int k = 0; k < spd->n_blocks; k++) - { - permuted_dense *blk = spd->blocks[k]; - for (int ii = 0; ii < blk->m0; ii++) - { - int row_start = csr->p[blk->row_perm[ii]]; - for (int jj = 0; jj < blk->n0; jj++) - { - scratch[native++] = idx_map[row_start + jj]; - } - } - } - memcpy(idx_map, scratch, spd->base.nnz * sizeof(int)); - sp_free(scratch); -} - -// ----------------------------------------------------------------------------- -// row gather of stacked_pd: C = A[map, :] where A is stacked_pd -// ----------------------------------------------------------------------------- -typedef struct -{ - const int *map; - int m_out; -} pd_row_gather_ctx; - -static matrix *wrapper_pd_row_gather(permuted_dense *Bk, const void *ctx) -{ - const pd_row_gather_ctx *c = (const pd_row_gather_ctx *) ctx; - return row_gather_pd_alloc(Bk, c->map, c->m_out); -} - -/* Every output row has exactly one source row, which lives in exactly one - block, so the per-block results stay row-disjoint even when map repeats. */ -static matrix *stacked_pd_vtable_row_gather_alloc(const matrix *self, const int *map, - int m_out) -{ - const stacked_pd *src = (const stacked_pd *) self; - pd_row_gather_ctx ctx = {map, m_out}; - return spd_map_filter_blocks(src, m_out, src->base.n, wrapper_pd_row_gather, - &ctx); -} - -static void stacked_pd_vtable_row_gather_fill_values(const matrix *self, matrix *out) -{ - const stacked_pd *src = (const stacked_pd *) self; - stacked_pd *out_spd = (stacked_pd *) out; - for (int k = 0; k < out_spd->n_blocks; k++) - { - int sk = out_spd->src_block_idx[k]; - row_gather_pd_fill_values(src->blocks[sk], out_spd->blocks[k]); - } -} - -// ----------------------------------------------------------------------------- -// diag_vec: C = diag(vec(A)) where A is stacked_pd -// ----------------------------------------------------------------------------- -static matrix *stacked_pd_vtable_diag_vec_alloc(matrix *self) -{ - stacked_pd *A = (stacked_pd *) self; - permuted_dense **tmp_blocks = - (permuted_dense **) sp_malloc(A->n_blocks * sizeof(permuted_dense *)); - for (int k = 0; k < A->n_blocks; k++) - { - tmp_blocks[k] = (permuted_dense *) diag_vec_pd_alloc(A->blocks[k]); - } - - matrix *out = new_stacked_pd(A->base.m * A->base.m, A->base.n, A->n_blocks, - tmp_blocks, NULL, NULL); - sp_free(tmp_blocks); - return out; -} - -static void stacked_pd_vtable_diag_vec_fill_values(matrix *self, matrix *out) -{ - stacked_pd *src = (stacked_pd *) self; - stacked_pd *out_spd = (stacked_pd *) out; - for (int k = 0; k < out_spd->n_blocks; k++) - { - diag_vec_pd_fill_values(src->blocks[k], out_spd->blocks[k]); - } -} - -// -------------------------------------------------------------------------------- -// Constructor below -// -------------------------------------------------------------------------------- -#ifndef NDEBUG -/* Pairwise verify that block row permutations are disjoint via a - two-pointer merge over the sorted arrays. */ -static void assert_disjoint_row_perms(int n_blocks, permuted_dense *const *blocks) -{ - for (int a = 0; a < n_blocks; a++) - { - for (int b = a + 1; b < n_blocks; b++) - { - const int *ra = blocks[a]->row_perm; - int la = blocks[a]->m0; - const int *rb = blocks[b]->row_perm; - int lb = blocks[b]->m0; - int i = 0; - int j = 0; - while (i < la && j < lb) - { - assert(ra[i] != rb[j] && "stacked_pd: block row_perms overlap"); - if (ra[i] < rb[j]) - { - i++; - } - else - { - j++; - } - } - } - } -} -#endif - -static matrix *stacked_pd_vtable_row_reduce_alloc(const matrix *self, - const int *group, int m_out) -{ - return row_reduce_spd_alloc((const stacked_pd *) self, group, m_out); -} - -static void stacked_pd_vtable_row_reduce_fill_values(const matrix *self, matrix *out) -{ - row_reduce_spd_fill_values((const stacked_pd *) self, (stacked_pd *) out); -} - -static void wire_vtable(stacked_pd *spd) -{ - spd->base.kind = MATRIX_STACKED_PD; - spd->base.free_fn = stacked_pd_free; - spd->base.copy_sparsity = stacked_pd_vtable_copy_sparsity; - spd->base.DA_fill_values = stacked_pd_vtable_DA_fill_values; - spd->base.ATA_alloc = stacked_pd_vtable_ATA_alloc; - spd->base.ATDA_fill_values = stacked_pd_vtable_ATDA_fill_values; - spd->base.transpose_alloc = stacked_pd_vtable_transpose_alloc; - spd->base.transpose_fill_values = stacked_pd_vtable_transpose_fill_values; - spd->base.refresh_csc_values = stacked_pd_vtable_refresh_csc_values; - spd->base.to_csr = stacked_pd_to_csr; - spd->base.row_gather_alloc = stacked_pd_vtable_row_gather_alloc; - spd->base.row_gather_fill_values = stacked_pd_vtable_row_gather_fill_values; - spd->base.diag_vec_alloc = stacked_pd_vtable_diag_vec_alloc; - spd->base.diag_vec_fill_values = stacked_pd_vtable_diag_vec_fill_values; - spd->base.row_reduce_alloc = stacked_pd_vtable_row_reduce_alloc; - spd->base.row_reduce_fill_values = stacked_pd_vtable_row_reduce_fill_values; -} - -matrix *new_stacked_pd_unchecked(int m, int n, int n_blocks, permuted_dense **blocks, - const int *src_block_idx_p, - const int *src_block_idx) -{ - assert((src_block_idx_p == NULL) == (src_block_idx == NULL)); - - // -------------------------------------------------------------------------------- - // Set up basic fields - // -------------------------------------------------------------------------------- - stacked_pd *spd = (stacked_pd *) sp_calloc(1, sizeof(stacked_pd)); - spd->base.m = m; - spd->base.n = n; - int nnz = 0; - for (int k = 0; k < n_blocks; k++) - { - nnz += blocks[k]->base.nnz; - } - spd->base.nnz = nnz; - wire_vtable(spd); - - spd->n_blocks = n_blocks; - spd->blocks = (permuted_dense **) sp_malloc(n_blocks * sizeof(permuted_dense *)); - spd->src_block_idx_p = (int *) sp_malloc((n_blocks + 1) * sizeof(int)); - spd->src_block_idx_p[0] = 0; - if (n_blocks > 0) - { - memcpy(spd->blocks, blocks, n_blocks * sizeof(permuted_dense *)); - } - - // -------------------------------------------------------------------------------- - // Set up source block index mapping - // -------------------------------------------------------------------------------- - if (src_block_idx_p == NULL) - { - /* identity: each output block has itself as its one source */ - spd->src_block_idx = (int *) sp_malloc(n_blocks * sizeof(int)); - for (int k = 0; k < n_blocks; k++) - { - spd->src_block_idx_p[k + 1] = k + 1; - spd->src_block_idx[k] = k; - } - } - else - { - int total = src_block_idx_p[n_blocks]; - memcpy(spd->src_block_idx_p, src_block_idx_p, (n_blocks + 1) * sizeof(int)); - spd->src_block_idx = (int *) sp_malloc(total * sizeof(int)); - if (total > 0) - { - memcpy(spd->src_block_idx, src_block_idx, total * sizeof(int)); - } - } - - // --------------------------------------------------------------------------- - // Absorb each block's X into a single shared values buffer owned by this spd. - // ---------------------------------------------------------------------------- - spd->base.x = (double *) sp_malloc(spd->base.nnz * sizeof(double)); - int offset = 0; - for (int k = 0; k < n_blocks; k++) - { - int nnz_block = spd->blocks[k]->base.nnz; - if (nnz_block == 0) - { - continue; - } - - memcpy(spd->base.x + offset, spd->blocks[k]->X, nnz_block * sizeof(double)); - sp_free(spd->blocks[k]->X); - spd->blocks[k]->X = spd->base.x + offset; - spd->blocks[k]->base.x = spd->blocks[k]->X; - spd->blocks[k]->owns_X = false; - offset += nnz_block; - } - - return &spd->base; -} - -matrix *new_stacked_pd(int m, int n, int n_blocks, permuted_dense **blocks, - const int *src_block_idx_p, const int *src_block_idx) -{ - matrix *out = new_stacked_pd_unchecked(m, n, n_blocks, blocks, src_block_idx_p, - src_block_idx); -#ifndef NDEBUG - assert_disjoint_row_perms(n_blocks, blocks); -#endif - return out; -} diff --git a/sparsediff_linalg/src/stacked_pd_coalesce.c b/sparsediff_linalg/src/stacked_pd_coalesce.c deleted file mode 100644 index b6ea0493..00000000 --- a/sparsediff_linalg/src/stacked_pd_coalesce.c +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/stacked_pd.h" - -#include "sparsediff_linalg/iVec.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include -#include - -/* A CSR-style ragged 2D int array, indexed by an integer key: - csr->data[csr->p[k]..csr->p[k+1]) — the int list for key k - Invariants: csr->p has length n+1, csr->p[0] == 0, csr->data has - length csr->p[n]. Read-only after construction. */ -typedef struct -{ - int *p; - int *data; - int n; -} int_csr; - -static int_csr *int_csr_create(int *p, int *data, int n) -{ - int_csr *csr = (int_csr *) sp_malloc(sizeof(int_csr)); - csr->p = p; - csr->data = data; - csr->n = n; - return csr; -} - -static void int_csr_free(int_csr *csr) -{ - sp_free(csr->p); - sp_free(csr->data); - sp_free(csr); -} - -/* Slice of csr->data for key k. Sets *out_len. */ -static inline const int *int_csr_get(const int_csr *csr, int k, int *out_len) -{ - int lo = csr->p[k]; - int hi = csr->p[k + 1]; - *out_len = hi - lo; - return csr->data + lo; -} - -/* Look up 'sig' in the catalog represented as the CSR pair (cat_p, cat_data). Return - the existing signature index if found; otherwise append it and return the new - index. */ -static int lookup_or_append_signature(iVec *cat_p, iVec *cat_data, const int *sig, - int sig_len) -{ - int n_sigs = cat_p->len - 1; - - /* compare the signature 'sig' with all the existing signatures */ - for (int s = 0; s < n_sigs; s++) - { - int lo = cat_p->data[s]; - int hi = cat_p->data[s + 1]; - if (hi - lo == sig_len && - memcmp(cat_data->data + lo, sig, sig_len * sizeof(int)) == 0) - { - return s; - } - } - - /* signature not found, append it */ - iVec_append_array(cat_data, sig, sig_len); - iVec_append(cat_p, cat_data->len); - return n_sigs; -} - -#ifndef NDEBUG -/* Assert pairwise-disjoint cells: any two source blocks sharing a row - must have disjoint col_perms. We only need to check pairs within the - same signature (others share no row by construction). */ -static void assert_disjoint_cells(const stacked_pd *src, const int *cat_sig_p, - const int *cat_sig_data, int n_sigs) -{ - for (int s = 0; s < n_sigs; s++) - { - int sig_lo = cat_sig_p[s]; - int sig_hi = cat_sig_p[s + 1]; - for (int u = sig_lo; u < sig_hi; u++) - { - for (int v = u + 1; v < sig_hi; v++) - { - permuted_dense *bu = src->blocks[cat_sig_data[u]]; - permuted_dense *bv = src->blocks[cat_sig_data[v]]; - assert(!has_overlap(bu->col_perm, bu->n0, bv->col_perm, bv->n0, 0) && - "coalesce: source blocks share both a row and a col"); - } - } - } -} -#endif - -static int_csr *build_row_to_blocks_csr(const stacked_pd *A, int *scratch) -{ - int m = A->base.m; - int n_blocks = A->n_blocks; - - int *p = (int *) sp_calloc(m + 1, sizeof(int)); - for (int k = 0; k < n_blocks; k++) - { - permuted_dense *Ak = A->blocks[k]; - for (int i = 0; i < Ak->m0; i++) - { - p[Ak->row_perm[i] + 1]++; - } - } - cumsum(p, m); - - int *data = (int *) sp_malloc(p[m] * sizeof(int)); - int *blocks_added_per_row = scratch; - for (int k = 0; k < n_blocks; k++) - { - permuted_dense *Ak = A->blocks[k]; - for (int i = 0; i < Ak->m0; i++) - { - int row = Ak->row_perm[i]; - data[p[row] + blocks_added_per_row[row]++] = k; - } - } - - return int_csr_create(p, data, m); -} - -static void catalog_signatures(const int_csr *row_to_blocks, iVec *catalog_sig_p, - iVec *catalog_sig_data, int **row_to_sig_out) -{ - int m = row_to_blocks->n; - int *row_to_sig = (int *) sp_malloc(m * sizeof(int)); - for (int i = 0; i < m; i++) - { - int signature_len; - const int *sig = int_csr_get(row_to_blocks, i, &signature_len); - - /* no block contains row i */ - if (signature_len == 0) - { - row_to_sig[i] = -1; - continue; - } - row_to_sig[i] = lookup_or_append_signature(catalog_sig_p, catalog_sig_data, - sig, signature_len); - } - *row_to_sig_out = row_to_sig; -} - -static int_csr *group_rows_by_signature(int m, int n_sigs, const int *row_to_sig, - int *scratch) -{ - int *p = (int *) sp_calloc(n_sigs + 1, sizeof(int)); - for (int i = 0; i < m; i++) - { - if (row_to_sig[i] >= 0) - { - p[row_to_sig[i] + 1]++; - } - } - cumsum(p, n_sigs); - - int *data = (int *) sp_malloc(p[n_sigs] * sizeof(int)); - int *rows_added_per_sig = scratch; - for (int i = 0; i < m; i++) - { - int s = row_to_sig[i]; - if (s >= 0) - { - data[p[s] + rows_added_per_sig[s]++] = i; - } - } - - return int_csr_create(p, data, n_sigs); -} - -/* Allocate one output PD per signature. The PD for signature s has - row_perm = sig_to_rows[s] and col_perm = sorted union of - A->blocks[k]->col_perm for k ranging over the signature's source-block - IDs. Returns a newly-allocated array of n_sigs PD pointers; caller - owns the array (PDs themselves are taken over by the eventual - stacked_pd). */ -static permuted_dense **build_output_blocks(const stacked_pd *A, - const iVec *catalog_sig_p, - const iVec *catalog_sig_data, - const int_csr *sig_to_rows) -{ - int m = A->base.m; - int n = A->base.n; - int n_sigs = catalog_sig_p->len - 1; - - permuted_dense **out_blocks = (permuted_dense **) sp_malloc( - (n_sigs > 0 ? n_sigs : 1) * sizeof(permuted_dense *)); - iVec *col_union = iVec_new(8); - /* col_arrs / col_lens: scratch passed to sorted_union_int_arrays. - Max sig size is bounded by A->n_blocks, so size once outside the loop. */ - const int **col_arrs = (const int **) sp_malloc(A->n_blocks * sizeof(int *)); - int *col_lens = (int *) sp_malloc(A->n_blocks * sizeof(int)); - - for (int s = 0; s < n_sigs; s++) - { - int sig_lo = catalog_sig_p->data[s]; - int sig_sz = catalog_sig_p->data[s + 1] - sig_lo; - const int *sig_ids = catalog_sig_data->data + sig_lo; - - for (int t = 0; t < sig_sz; t++) - { - permuted_dense *blk = A->blocks[sig_ids[t]]; - col_arrs[t] = blk->col_perm; - col_lens[t] = blk->n0; - } - sorted_union_int_arrays(col_arrs, col_lens, sig_sz, col_union); - - int m0; - const int *row_perm = int_csr_get(sig_to_rows, s, &m0); - out_blocks[s] = (permuted_dense *) new_permuted_dense( - m, n, m0, col_union->len, row_perm, col_union->data, NULL); - } - - iVec_free(col_union); - sp_free(col_arrs); - sp_free(col_lens); - return out_blocks; -} - -static matrix *coalesce_spd_alloc_impl(const stacked_pd *A, - bool check_disjoint_cells) -{ - int m = A->base.m; - int n = A->base.n; - int *iwork = (int *) sp_calloc(m, sizeof(int)); - - // --------------------------------------------------------------------------- - // For each global row in A, catalog which blocks contain it. - // --------------------------------------------------------------------------- - int_csr *row_to_blocks = build_row_to_blocks_csr(A, iwork); - - // ------------------------------------------------------------------------------ - // Catalog unique signatures (sets of source blocks sharing each row), and assign - // each row to a signature id. Each unique signature will become one output - // block. The array 'row_to_sig' maps each row to its signature id; rows with the - // same signature have the same set of source blocks and thus land in the same - // output block. - // ------------------------------------------------------------------------------ - iVec *catalog_sig_p = iVec_new(8); - iVec_append(catalog_sig_p, 0); - iVec *catalog_sig_data = iVec_new(16); - int *row_to_sig; - catalog_signatures(row_to_blocks, catalog_sig_p, catalog_sig_data, &row_to_sig); - int n_sigs = catalog_sig_p->len - 1; - -#ifndef NDEBUG - if (check_disjoint_cells) - { - assert_disjoint_cells(A, catalog_sig_p->data, catalog_sig_data->data, - n_sigs); - } -#else - (void) check_disjoint_cells; -#endif - - // ----------------------------------------------------------------------------- - // Group rows by signature. For each unique signature s, which rows have it? - // ----------------------------------------------------------------------------- - memset(iwork, 0, n_sigs * sizeof(int)); - int_csr *sig_to_rows = group_rows_by_signature(m, n_sigs, row_to_sig, iwork); - - // ----------------------------------------------------------------------------- - // Build one output PD per signature, then hand off to new_stacked_pd. Catalog - // order is already sorted by min row (pass 2 introduces signatures in row - // scan order), so the output uses catalog order directly — no reorder needed. - // new_stacked_pd memcpy's src_block_idx_p/_data, so we pass the catalog's - // arrays straight through. - // ----------------------------------------------------------------------------- - permuted_dense **out_blocks = - build_output_blocks(A, catalog_sig_p, catalog_sig_data, sig_to_rows); - - matrix *C = new_stacked_pd(m, n, n_sigs, out_blocks, catalog_sig_p->data, - catalog_sig_data->data); - - sp_free(out_blocks); - int_csr_free(sig_to_rows); - sp_free(row_to_sig); - iVec_free(catalog_sig_p); - iVec_free(catalog_sig_data); - int_csr_free(row_to_blocks); - sp_free(iwork); - - return C; -} - -matrix *coalesce_spd_alloc(const stacked_pd *src) -{ - return coalesce_spd_alloc_impl(src, true); -} - -matrix *coalesce_spd_alloc_unchecked(const stacked_pd *src) -{ - return coalesce_spd_alloc_impl(src, false); -} - -/* Scatter src's blocks into out's blocks, following out's src_block_idx_* - map. If accumulate is true, += into out (caller must zero out->base.x - first); otherwise = (precondition: source cells are pairwise disjoint - across blocks targeting the same out block). `accumulate` is a - compile-time constant at every call site, so the inner-loop branch is - folded away when this is inlined into the public wrappers. */ -static inline void coalesce_spd_scatter(const stacked_pd *src, stacked_pd *out, - bool accumulate) -{ - /* for each block out_k of out */ - for (int k = 0; k < out->n_blocks; k++) - { - permuted_dense *out_k = out->blocks[k]; - int s_lo = out->src_block_idx_p[k]; - int s_hi = out->src_block_idx_p[k + 1]; - - /* for each block src_k in src contributing to out_k */ - for (int t = s_lo; t < s_hi; t++) - { - const permuted_dense *src_k = src->blocks[out->src_block_idx[t]]; - - /* for each row in src_k */ - for (int i = 0; i < src_k->m0; i++) - { - int out_i = out_k->row_inv[src_k->row_perm[i]]; - if (out_i < 0) - { - /* this row in src_k does not contribute to out_k */ - continue; - } - - double *out_row = out_k->X + out_i * out_k->n0; - const double *src_row = src_k->X + i * src_k->n0; - - /* place each col in src_row at its correct position in out_row */ - for (int j = 0; j < src_k->n0; j++) - { - int out_j = out_k->col_inv[src_k->col_perm[j]]; - assert(out_j >= 0); - if (accumulate) - { - out_row[out_j] += src_row[j]; - } - else - { - out_row[out_j] = src_row[j]; - } - } - } - } - } -} - -void coalesce_spd_fill_values(const stacked_pd *src, stacked_pd *out) -{ - coalesce_spd_scatter(src, out, false); -} - -void coalesce_spd_fill_values_accumulate(const stacked_pd *src, stacked_pd *out) -{ - coalesce_spd_scatter(src, out, true); -} diff --git a/sparsediff_linalg/src/stacked_pd_kron_linalg.c b/sparsediff_linalg/src/stacked_pd_kron_linalg.c deleted file mode 100644 index 086e9267..00000000 --- a/sparsediff_linalg/src/stacked_pd_kron_linalg.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "internal/stacked_pd_kron_linalg.h" - -#include "internal/permuted_dense_linalg.h" -#include "internal/stacked_pd_linalg.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/stacked_pd.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include - -// ==================================================================================== -// All three BA_dense_kron_{pd,csc,spd}_* pairs share the same blockwise skeleton: -// -// 1. At alloc, build a persistent permuted_dense "state" (the scratch view of -// kron(I_p, A), or its transpose BT for the spd kernel). -// 2. Loop k = 0..p-1: kron_scratch_set_block(state, k, last_k); call the -// per-kernel alloc op to produce Ck; collect non-empty Ck's. -// 3. Assemble the survivors into a stacked_pd C and stash `state` on -// C->kernel_pd_scratch so fill can reuse it. -// 4. At fill, read state from C->kernel_pd_scratch (plus any kernel-specific -// value refresh) and run the per-output-block loop. -// -// Each loop ends with one extra kron_scratch_set_block(state, 0, last_k) call -// to restore "block 0 set" so the next entry's last_k = 0 invariant holds. -// (Only the csc kernel actually reads state->col_inv; the reset is harmless -// for the pd / spd kernels and keeps the helper agnostic.) -// -// The state for pd/csc is the scratch (kron_scratch_init); for spd it is the -// transposed scratch BT (kron_BT_alloc). One set_block function works for -// both because under the "full A" contract, kron_scratch_set_block's col_inv -// writes stay within bounds even for BT's transposed shape. -// ==================================================================================== - -/* Build the scratch "block view" of kron(I_p, A): a permuted_dense with X - aliasing A->X (owns_X = false). Initial perms encode block 0; callers - mutate via kron_scratch_set_block. Free with free_matrix when done. */ -static permuted_dense *kron_scratch_init(const permuted_dense *A, int p) -{ - int m = A->m0; - int n = A->n0; - - /* Initial block-0 perms. */ - int *row_perm = (int *) sp_malloc(m * sizeof(int)); - int *col_perm = (int *) sp_malloc(n * sizeof(int)); - for (int i = 0; i < m; i++) row_perm[i] = i; - for (int j = 0; j < n; j++) col_perm[j] = j; - - /* X_data=NULL leaves the buffer uninitialized; we drop it and alias A->X. */ - matrix *pd_m = new_permuted_dense(m * p, n * p, m, n, row_perm, col_perm, NULL); - sp_free(row_perm); - sp_free(col_perm); - - permuted_dense *pd = (permuted_dense *) pd_m; - sp_free(pd->X); - pd->X = A->X; - pd->base.x = A->X; - pd->owns_X = false; - return pd; -} - -/* Allocate the transposed scratch BT for the spd kernel: shape (A.n0*p, A.m0*p), - inner block (A.n0, A.m0). Owns its X buffer (filled at fill time by - transpose_pd_fill_values). Initial perms are placeholders — kron_scratch_set_block - overwrites them before BT is read. */ -static permuted_dense *kron_BT_alloc(const permuted_dense *A, int p) -{ - int n = A->n0; - int m = A->m0; - int *row_perm = (int *) sp_malloc(n * sizeof(int)); - int *col_perm = (int *) sp_malloc(m * sizeof(int)); - for (int j = 0; j < n; j++) row_perm[j] = j; - for (int i = 0; i < m; i++) col_perm[i] = i; - matrix *BT_m = new_permuted_dense(n * p, m * p, n, m, row_perm, col_perm, NULL); - sp_free(row_perm); - sp_free(col_perm); - return (permuted_dense *) BT_m; -} - -/* Mutate the state to represent kron block k. last_k is the block currently - encoded (0 immediately after _init); the call clears last_k's col_inv - entries and sets block-k's, plus refreshes row_perm and col_perm. O(m + n) - work. row_inv is left stale — no per-block kernel reads it. Works on both - scratch (pd/csc) and BT (spd) under the "full A" contract. */ -static void kron_scratch_set_block(permuted_dense *state, int k, int last_k) -{ - int m = state->m0; - int n = state->n0; - for (int i = 0; i < m; i++) - { - state->row_perm[i] = k * m + i; - } - for (int j = 0; j < n; j++) - { - state->col_inv[last_k * n + j] = -1; - } - for (int j = 0; j < n; j++) - { - state->col_perm[j] = k * n + j; - state->col_inv[k * n + j] = j; - } -} - -/* Per-block callback signatures. */ -typedef matrix *(*kron_block_alloc_fn)(permuted_dense *state, const void *ctx); -typedef void (*kron_block_fill_fn)(permuted_dense *state, const void *ctx, - permuted_dense *Ck); - -/* Generic alloc loop: iterate p kron-blocks of A on `state`, call `op` per - block, drop blocks whose Ck has nnz == 0, assemble the survivors into a - stacked_pd of shape (Cm, Cn). Ownership of `state` transfers to - C->kernel_pd_scratch on return. Restores state to "block 0 set" at the - end for the next call's invariant. */ -static matrix *kron_alloc_blockwise(permuted_dense *state, int p, int Cm, int Cn, - kron_block_alloc_fn op, const void *ctx) -{ - if (p == 0) - { - free_matrix(&state->base); - return new_stacked_pd(Cm, Cn, 0, NULL, NULL, NULL); - } - - permuted_dense **C_blocks = - (permuted_dense **) sp_malloc(p * sizeof(permuted_dense *)); - int *C_src = (int *) sp_malloc(p * sizeof(int)); - - int out_nb = 0; - int last_k = 0; - for (int k = 0; k < p; k++) - { - kron_scratch_set_block(state, k, last_k); - last_k = k; - - matrix *Ck = op(state, ctx); - if (Ck->nnz == 0) - { - free_matrix(Ck); - } - else - { - C_blocks[out_nb] = (permuted_dense *) Ck; - C_src[out_nb] = k; - out_nb++; - } - } - - int *C_src_p = (int *) sp_malloc((out_nb + 1) * sizeof(int)); - for (int k = 0; k <= out_nb; k++) C_src_p[k] = k; - - matrix *C = new_stacked_pd(Cm, Cn, out_nb, C_blocks, C_src_p, C_src); - sp_free(C_blocks); - sp_free(C_src); - sp_free(C_src_p); - - /* Restore "block 0 set" invariant for the next call. */ - kron_scratch_set_block(state, 0, last_k); - - /* Hand state to C for reuse at fill time. stacked_pd_free releases it. */ - ((stacked_pd *) C)->kernel_pd_scratch = state; - return C; -} - -/* Generic fill loop: iterate C's output blocks, set state to the source kron - block, call `op` per block. Caller has already done any kernel-specific - value refresh on `state` (e.g. transpose_pd_fill_values for spd). */ -static void kron_fill_blockwise(stacked_pd *C, kron_block_fill_fn op, - const void *ctx) -{ - if (C->n_blocks == 0) return; - - permuted_dense *state = C->kernel_pd_scratch; - int last_k = 0; - for (int k = 0; k < C->n_blocks; k++) - { - int q = C->src_block_idx[C->src_block_idx_p[k]]; - kron_scratch_set_block(state, q, last_k); - last_k = q; - - op(state, ctx, C->blocks[k]); - } - - /* Restore "block 0 set" invariant for the next fill call. */ - kron_scratch_set_block(state, 0, last_k); -} - -// ---------------------------------------------------------------------------------- -// Per-kernel ops + public functions. -// ---------------------------------------------------------------------------------- - -/* pd: state is the (untransposed) scratch. */ -static matrix *kron_pd_alloc_op(permuted_dense *state, const void *ctx) -{ - return BA_pd_pd_alloc(state, (const permuted_dense *) ctx); -} -static void kron_pd_fill_op(permuted_dense *state, const void *ctx, - permuted_dense *Ck) -{ - BA_pd_pd_fill_values(state, (const permuted_dense *) ctx, Ck); -} - -matrix *BA_dense_kron_pd_alloc(const permuted_dense *A, int p, - const permuted_dense *J) -{ - permuted_dense *scratch = kron_scratch_init(A, p); - return kron_alloc_blockwise(scratch, p, A->m0 * p, J->base.n, kron_pd_alloc_op, - J); -} - -void BA_dense_kron_pd_fill_values(const permuted_dense *A, int p, - const permuted_dense *J, stacked_pd *C) -{ - (void) A; - (void) p; - kron_fill_blockwise(C, kron_pd_fill_op, J); -} - -/* csc: state is the (untransposed) scratch; BA_pd_csc_* reads col_inv. */ -static matrix *kron_csc_alloc_op(permuted_dense *state, const void *ctx) -{ - return BA_pd_csc_alloc(state, (const CSC_matrix *) ctx); -} -static void kron_csc_fill_op(permuted_dense *state, const void *ctx, - permuted_dense *Ck) -{ - BA_pd_csc_fill_values(state->X, state->n0, state->col_inv, - (const CSC_matrix *) ctx, Ck); -} - -matrix *BA_dense_kron_csc_alloc(const permuted_dense *A, int p, const CSC_matrix *J) -{ - permuted_dense *scratch = kron_scratch_init(A, p); - return kron_alloc_blockwise(scratch, p, A->m0 * p, J->n, kron_csc_alloc_op, J); -} - -void BA_dense_kron_csc_fill_values(const permuted_dense *A, int p, - const CSC_matrix *J, stacked_pd *C) -{ - (void) A; - (void) p; - kron_fill_blockwise(C, kron_csc_fill_op, J); -} - -/* spd: state is the transposed scratch BT. BT.X needs to be refreshed each - fill call (A's values may have changed); pd/csc don't, since their state's - X aliases A->X. */ -static matrix *kron_spd_alloc_op(permuted_dense *state, const void *ctx) -{ - return BTA_pd_spd_alloc(state, (const stacked_pd *) ctx); -} -static void kron_spd_fill_op(permuted_dense *state, const void *ctx, - permuted_dense *Ck) -{ - BTA_pd_spd_fill_values(state, (const stacked_pd *) ctx, Ck); -} - -matrix *BA_dense_kron_spd_alloc(const permuted_dense *A, int p, const stacked_pd *J) -{ - permuted_dense *BT = kron_BT_alloc(A, p); - return kron_alloc_blockwise(BT, p, A->m0 * p, J->base.n, kron_spd_alloc_op, J); -} - -void BA_dense_kron_spd_fill_values(const permuted_dense *A, int p, - const stacked_pd *J, stacked_pd *C) -{ - (void) p; - if (C->n_blocks == 0) return; - - /* spd-only prelude: BT.X = transpose(A.X). A's values may have changed - between alloc and fill, or between fills (parameter case). */ - transpose_pd_fill_values(A, C->kernel_pd_scratch); - kron_fill_blockwise(C, kron_spd_fill_op, J); -} diff --git a/sparsediff_linalg/src/stacked_pd_linalg.c b/sparsediff_linalg/src/stacked_pd_linalg.c deleted file mode 100644 index dfbde8d7..00000000 --- a/sparsediff_linalg/src/stacked_pd_linalg.c +++ /dev/null @@ -1,723 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "internal/stacked_pd_linalg.h" - -#include "internal/permuted_dense_linalg.h" -#include "sparsediff_linalg/cblas_wrapper.h" -#include "sparsediff_linalg/iVec.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/stacked_pd.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include - -// ---------------------------------------------------------------------------------- -// copy_sparsity and DA: C = D @ A where A is stacked_pd. The logic is blockwise. -// ---------------------------------------------------------------------------------- -matrix *copy_sparsity_spd_alloc(const stacked_pd *A) -{ - int n_blocks = A->n_blocks; - permuted_dense **C_blocks = - (permuted_dense **) sp_malloc(n_blocks * sizeof(permuted_dense *)); - for (int k = 0; k < n_blocks; k++) - { - C_blocks[k] = (permuted_dense *) copy_sparsity_pd_alloc(A->blocks[k]); - } - - matrix *C = new_stacked_pd(A->base.m, A->base.n, n_blocks, C_blocks, NULL, NULL); - sp_free(C_blocks); - return C; -} - -void DA_spd_fill_values(const double *d, const stacked_pd *A, stacked_pd *C) -{ - for (int k = 0; k < A->n_blocks; k++) - { - DA_pd_fill_values(d, A->blocks[k], C->blocks[k]); - } -} - -// ---------------------------------------------------------------------------------- -// Transpose C = A^T where A is stacked_pd. Blockwise logic followed by coalescing: -// we first transpose each block independently, yielding a raw spd with the same -// block count and swapped row/col perms as the final output but potentially -// overlapping row perms. We then coalesce that raw spd to get the final output with -// disjoint row perms. We keep the raw spd around on `out->pre_coalesce` for reuse -// by `transpose_spd_fill_values` since its per-block transposes are also needed -// there. -// ---------------------------------------------------------------------------------- -matrix *transpose_spd_alloc(const stacked_pd *A) -{ - int n_blocks = A->n_blocks; - permuted_dense **AT_blocks = - (permuted_dense **) sp_malloc(n_blocks * sizeof(permuted_dense *)); - for (int k = 0; k < n_blocks; k++) - { - AT_blocks[k] = (permuted_dense *) transpose_pd_alloc(A->blocks[k]); - } - - /* Raw spd: dimensions swapped, identity src_block_idx_*; rows may - overlap so use the unchecked constructor. */ - matrix *raw = new_stacked_pd_unchecked(A->base.n, A->base.m, n_blocks, AT_blocks, - NULL, NULL); - sp_free(AT_blocks); - - matrix *C = coalesce_spd_alloc((stacked_pd *) raw); - ((stacked_pd *) C)->pre_coalesce = (stacked_pd *) raw; - return C; -} - -void transpose_spd_fill_values(const stacked_pd *A, stacked_pd *C) -{ - stacked_pd *raw = C->pre_coalesce; - for (int k = 0; k < A->n_blocks; k++) - { - transpose_pd_fill_values(A->blocks[k], raw->blocks[k]); - } - coalesce_spd_fill_values(raw, C); -} - -// ==================================================================================== -// Shared "spd blockwise + coalesce-accumulate" skeleton. -// -// Used by kernels of the shape C = f(spd_iter, X) where spd_iter is a -// stacked_pd we iterate, and each per-block partial f(spd_iter->blocks[k], X) -// is a PD. The partials' row_perms (and cells) may overlap across k, so we -// collect them via new_stacked_pd_unchecked, coalesce with the _unchecked -// variant, and use the accumulating scatter at fill time. The raw spd is -// stashed on out->pre_coalesce so fill can refresh per-block values without -// reallocating structure. -// -// (Cm, Cn) is the output global shape. For "spd is the BTA's left operand" -// callers (BTA_spd_pd / BTA_spd_csc / BTA_spd_spd) and the ATA self-case, -// Cm = spd_iter->base.n. For BTA_csc_spd we iterate A but output rows come -// from B_csc, so the caller passes Cm = B_csc->n explicitly. -// -// Kernel pairs build on this skeleton: BTA_spd_pd / BTDA_spd_pd, -// BTA_spd_csc / BTDA_spd_csc, BTA_spd_spd / BTDA_spd_spd, -// BTA_csc_spd / BTDA_csc_spd, and ATA_spd / ATDA_spd. -// ==================================================================================== -typedef matrix *(*spd_per_block_alloc_fn)(const permuted_dense *blk, - const void *ctx); - -typedef void (*spd_per_block_fill_fn)(const permuted_dense *blk, const double *d, - const void *ctx, permuted_dense *Ck); - -static matrix *spd_blockwise_alloc_coalesce(const stacked_pd *spd_iter, int Cm, - int Cn, spd_per_block_alloc_fn op, - const void *ctx) -{ - int n_blocks = spd_iter->n_blocks; - permuted_dense **partials = - (permuted_dense **) sp_malloc(n_blocks * sizeof(permuted_dense *)); - for (int k = 0; k < n_blocks; k++) - { - partials[k] = (permuted_dense *) op(spd_iter->blocks[k], ctx); - } - matrix *raw = new_stacked_pd_unchecked(Cm, Cn, n_blocks, partials, NULL, NULL); - sp_free(partials); - matrix *C = coalesce_spd_alloc_unchecked((stacked_pd *) raw); - ((stacked_pd *) C)->pre_coalesce = (stacked_pd *) raw; - return C; -} - -static void spd_blockwise_fill_coalesce_accumulate(const stacked_pd *spd_iter, - const double *d, const void *ctx, - stacked_pd *C, - spd_per_block_fill_fn op) -{ - if (C->base.nnz == 0) return; - - stacked_pd *raw = C->pre_coalesce; - for (int k = 0; k < spd_iter->n_blocks; k++) - { - op(spd_iter->blocks[k], d, ctx, raw->blocks[k]); - } - memset(C->base.x, 0, C->base.nnz * sizeof(double)); - coalesce_spd_fill_values_accumulate(raw, C); -} - -// ---------------------------------------------------------------------------------- -// C = row-reduce of stacked_pd A: C[j, :] = sum of rows i with group[i] == j. -// Each block is reduced on its own into a PD (rows of one block that share a -// group are summed there); the partials may overlap in rows and cells across -// blocks, so they go through the coalesce-accumulate skeleton above. The result -// is always a stacked_pd. -// ---------------------------------------------------------------------------------- -typedef struct -{ - const int *group; - int m_out; -} row_reduce_ctx; - -static matrix *row_reduce_partial_alloc(const permuted_dense *blk, const void *ctx) -{ - const row_reduce_ctx *c = (const row_reduce_ctx *) ctx; - return row_reduce_pd_alloc(blk, c->group, c->m_out); -} - -matrix *row_reduce_spd_alloc(const stacked_pd *A, const int *group, int m_out) -{ - row_reduce_ctx ctx = {group, m_out}; - return spd_blockwise_alloc_coalesce(A, m_out, A->base.n, - row_reduce_partial_alloc, &ctx); -} - -void row_reduce_spd_fill_values(const stacked_pd *A, stacked_pd *C) -{ - if (C->base.nnz == 0) return; - stacked_pd *raw = C->pre_coalesce; - for (int k = 0; k < A->n_blocks; k++) - { - row_reduce_pd_fill_values(A->blocks[k], raw->blocks[k]); - } - memset(C->base.x, 0, C->base.nnz * sizeof(double)); - coalesce_spd_fill_values_accumulate(raw, C); -} - -// ------------------------------------------------------------------------------------ -// C = ATDA for stacked_pd A. Let A = [A1; A2; A3] where Ai has n columns (the same -// number as A). Then ATDA = A1^T D1 A1 + A2^T D2 A2 + A3^T D3 A3. Term i and j -// overlap if Ai and Aj have overlapping column entries. We therefore first compute -// Ai^T Di Ai and then take care of the coalescing. -// ------------------------------------------------------------------------------------ - -/* -A ^ T A decomposes as Σ_k B_k ^ T B_k, where summands with overlapping col_perms(C_k) -share cells.The output groups cols of A by signature sig_C(c) = {k: c ∈ C_k}; each - unique signature becomes one output PD with row_perm = group cols - and col_perm = ⋃ C_k for k in the signature. No structural zeros. - The output's `pre_coalesce` slot holds per-source scratch PDs (one symmetric - PD per source block, row_perm = col_perm = C_k) that - ATDA_spd_fill_values writes into. - - Note on per-block kernel choice: we use ATA_pd_alloc (rather than - new_permuted_dense directly) because it also pre-sizes - A->blocks[k]->kernel_dwork — that scratch buffer is what - ATDA_pd_fill_values reads from during the fill phase. */ -static matrix *wrapper_ATA_pd(const permuted_dense *Ak, const void *ctx) -{ - (void) ctx; - return ATA_pd_alloc(Ak); -} - -static void wrapper_ATDA_pd(const permuted_dense *Ak, const double *d, - const void *ctx, permuted_dense *Ck) -{ - (void) ctx; - ATDA_pd_fill_values(Ak, d, Ck); -} - -matrix *ATA_spd_alloc(const stacked_pd *A) -{ - return spd_blockwise_alloc_coalesce(A, A->base.n, A->base.n, wrapper_ATA_pd, - NULL); -} - -void ATDA_spd_fill_values(const stacked_pd *A, const double *d, stacked_pd *C) -{ - spd_blockwise_fill_coalesce_accumulate(A, d, NULL, C, wrapper_ATDA_pd); -} - -// --------------------------------------------------------------------------------- -// BTA_pd_spd: C = B^T @ A where B is a permuted_dense and A is a stacked_pd. C is -// also permuted_dense. C has row_perm = B->col_perm, and its col_perm is the sorted -// union of the col_perms of the contributing A-blocks. An A-block contributes if its -// row_perm overlaps B's row_perm. We compute this as -// C = \sum_k (B[r_k, :])^T @ A_k, where r_k = A_k->row_perm. -// -// This is the canonical (cache-friendly) kernel: the gather of B is a single -// memcpy per gathered row, since B is row-major. BA_pd_spd_* is a thin wrapper -// that transposes its B argument and calls into this kernel. -// ---------------------------------------------------------------------------------- -matrix *BTA_pd_spd_alloc(const permuted_dense *B, const stacked_pd *A) -{ - int n_blocks = A->n_blocks; - - // ------------------------------------------------------------------------------ - // Find column permutations of C. An A-block contributes if its row_perm overlaps - // B's row_perm. - // ------------------------------------------------------------------------------ - const int **col_perms = (const int **) sp_malloc(n_blocks * sizeof(int *)); - int *lens = (int *) sp_malloc(n_blocks * sizeof(int)); - - int n_contributing_A_blocks = 0; - for (int k = 0; k < n_blocks; k++) - { - permuted_dense *Ak = A->blocks[k]; - if (has_overlap(B->row_perm, B->m0, Ak->row_perm, Ak->m0, 0)) - { - col_perms[n_contributing_A_blocks] = Ak->col_perm; - lens[n_contributing_A_blocks] = Ak->n0; - n_contributing_A_blocks++; - } - } - - iVec *col_union = iVec_new(8); - sorted_union_int_arrays(col_perms, lens, n_contributing_A_blocks, col_union); - - // ------------------------------------------------------------------------------- - // Allocate C with row_perm = B->col_perm and col_perm = col_union. - // ------------------------------------------------------------------------------- - matrix *C = new_permuted_dense(B->base.n, A->base.n, B->n0, col_union->len, - B->col_perm, col_union->data, NULL); - - iVec_free(col_union); - sp_free(col_perms); - sp_free(lens); - - // ------------------------------------------------------------------------------- - // Set up workspace. We need: - // 1. (s_max, max_n0_A) - worst-case intersection size and worst-case A-block - // column count across all k. Used to size all other scratch buffers. - // 2. (idx_B, idx_A) - arrays to find rows of B that overlap with rows of A_k. - // 3. Bg and Ag to hold the gathered rows of B and gathered rows of Ak. - // 4. Cg = Bg^T @ Ag which is then scattered into C. - // 5. out_cols - work array for cache-friendly scattering - // ------------------------------------------------------------------------------- - permuted_dense *C_pd = (permuted_dense *) C; - int s_max = 0; - int max_n0_A = 0; - for (int k = 0; k < n_blocks; k++) - { - permuted_dense *Ak = A->blocks[k]; - int s = B->m0 < Ak->m0 ? B->m0 : Ak->m0; - if (s > s_max) s_max = s; - if (Ak->n0 > max_n0_A) max_n0_A = Ak->n0; - } - - C_pd->kernel_iwork_size = (size_t) 2 + (size_t) 2 * s_max + (size_t) max_n0_A; - C_pd->kernel_iwork = (int *) sp_malloc(C_pd->kernel_iwork_size * sizeof(int)); - C_pd->kernel_iwork[0] = s_max; - C_pd->kernel_iwork[1] = max_n0_A; - - size_t dwork_total = (size_t) B->n0 * s_max + (size_t) s_max * max_n0_A + - (size_t) B->n0 * max_n0_A; - permuted_dense_ensure_kernel_dwork(C_pd, dwork_total); - - return C; -} - -/* Shared core for C = B^T @ diag(d) @ A with d == NULL meaning identity. - diag(d) is folded into the per-block gather of A_k's rows (d indexed by - global row), so no intermediate is ever allocated: Bg | Ag | Cg live in - C->kernel_dwork, pre-sized by BTA_pd_spd_alloc. */ -static void BTA_pd_spd_core(const permuted_dense *B, const double *d, - const stacked_pd *A, permuted_dense *C) -{ - /* return if C is empty */ - if (C->base.nnz == 0) - { - return; - } - - /* reset values of C */ - memset(C->X, 0, (size_t) C->m0 * C->n0 * sizeof(double)); - - /* partition workspace according to how it was set up */ - int s_max = C->kernel_iwork[0]; - int max_n0_A = C->kernel_iwork[1]; - int *idx_B = C->kernel_iwork + 2; - int *idx_A = idx_B + s_max; - int *out_cols = idx_A + s_max; - double *Bg = C->kernel_dwork; - double *Ag = Bg + (size_t) s_max * B->n0; - double *Cg = Ag + (size_t) s_max * max_n0_A; - - /* for each block Ak of A */ - for (int k = 0; k < A->n_blocks; k++) - { - permuted_dense *Ak = A->blocks[k]; - - /* find rows of B that overlap with rows of A_k */ - int s = sorted_intersect_indices(B->row_perm, B->m0, Ak->row_perm, Ak->m0, - idx_B, idx_A); - if (s == 0) - { - continue; - } - - /* Bg = B[idx_B, :] where idx_B contains the overlapping row indices. One - contiguous memcpy per gathered row (row-major-friendly). */ - for (int p = 0; p < s; p++) - { - memcpy(Bg + p * B->n0, B->X + idx_B[p] * B->n0, B->n0 * sizeof(double)); - } - - /* Ag = (diag(d)A)[idx_A, :] where idx_A contains the overlapping row - indices */ - for (int p = 0; p < s; p++) - { - memcpy(Ag + p * Ak->n0, Ak->X + idx_A[p] * Ak->n0, - Ak->n0 * sizeof(double)); - } - if (d != NULL) - { - for (int p = 0; p < s; p++) - { - cblas_dscal(Ak->n0, d[Ak->row_perm[idx_A[p]]], Ag + p * Ak->n0, 1); - } - } - - /* Cg = Bg^T @ Ag. Bg is (s, B->n0) row-major (lda = B->n0); we want - output Cg = (B->n0, Ak->n0). */ - cblas_dgemm(CblasRowMajor, CblasTrans, CblasNoTrans, B->n0, Ak->n0, s, 1.0, - Bg, B->n0, Ag, Ak->n0, 0.0, Cg, Ak->n0); - - /* precompute scatter positions once per block */ - for (int j = 0; j < Ak->n0; j++) - { - out_cols[j] = C->col_inv[Ak->col_perm[j]]; - } - - /* C += Cg (scatter + add into C) */ - for (int i = 0; i < B->n0; i++) - { - double *C_row = C->X + i * C->n0; - const double *Cg_row = Cg + i * Ak->n0; - for (int j = 0; j < Ak->n0; j++) - { - C_row[out_cols[j]] += Cg_row[j]; - } - } - } -} - -void BTA_pd_spd_fill_values(const permuted_dense *B, const stacked_pd *A, - permuted_dense *C) -{ - BTA_pd_spd_core(B, NULL, A, C); -} - -// --------------------------------------------------------------------------------- -// BTDA_pd_spd: C = B^T @ diag(d) @ A. No separate alloc — output sparsity -// is identical to BTA_pd_spd (D doesn't add/remove nonzeros), so callers -// pre-allocate C via BTA_pd_spd_alloc, same convention as BTA_pd_pd / -// BTDA_pd_pd. -// --------------------------------------------------------------------------------- -void BTDA_pd_spd_fill_values(const permuted_dense *B, const double *d, - const stacked_pd *A, permuted_dense *C) -{ - BTA_pd_spd_core(B, d, A, C); -} - -// --------------------------------------------------------------------------------- -// BTA_spd_pd / BTDA_spd_pd: C = B^T @ (diag(d) @) A where B is stacked_pd, A is -// permuted_dense. Output C is stacked_pd. We implement this using blockwise -// logic followed by coalescing. The blockwise logic is not obvious -// mathematically, but it is correct when we think of each of B's blocks as a -// global matrix and B as a sum of these blocks. -// -// B = B1 + B2 + B3 where each Bi is a global permuted dense. Then -// C = B^T D A = C1 + C2 + C3 where Ci = Bi^T D A. -// --------------------------------------------------------------------------------- -static matrix *wrapper_BTA_pd_pd(const permuted_dense *Bk, const void *ctx) -{ - return BTA_pd_pd_alloc(Bk, (const permuted_dense *) ctx); -} - -static void wrapper_BTDA_pd_pd(const permuted_dense *Bk, const double *d, - const void *ctx, permuted_dense *Ck) -{ - BTDA_pd_pd_fill_values(Bk, d, (const permuted_dense *) ctx, Ck); -} - -/* BTA per-block fill: plain B_k^T @ A, no diagonal. The shared skeleton - threads d through verbatim and never dereferences it, so the caller passes - d = NULL and we ignore it here. Calling BTA_pd_pd_fill_values directly (vs. - the BTDA wrapper) also sidesteps the per-block DA temp. */ -static void wrapper_BTA_pd_pd_fill(const permuted_dense *Bk, const double *d, - const void *ctx, permuted_dense *Ck) -{ - (void) d; - BTA_pd_pd_fill_values(Bk, (const permuted_dense *) ctx, Ck); -} - -matrix *BTA_spd_pd_alloc(const stacked_pd *B, const permuted_dense *A) -{ - return spd_blockwise_alloc_coalesce(B, B->base.n, A->base.n, wrapper_BTA_pd_pd, - A); -} - -void BTA_spd_pd_fill_values(const stacked_pd *B, const permuted_dense *A, - stacked_pd *C) -{ - spd_blockwise_fill_coalesce_accumulate(B, NULL, A, C, wrapper_BTA_pd_pd_fill); -} - -void BTDA_spd_pd_fill_values(const stacked_pd *B, const double *d, - const permuted_dense *A, stacked_pd *C) -{ - spd_blockwise_fill_coalesce_accumulate(B, d, A, C, wrapper_BTDA_pd_pd); -} - -// --------------------------------------------------------------------------------- -// BTA_spd_csc / BTDA_spd_csc: C = B^T @ (diag(d) @) A where B is stacked_pd, -// A is CSC. Output C is stacked_pd. Same two-stage skeleton as BTA_spd_pd / -// BTDA_spd_pd, just with BTA_pd_csc / BTDA_pd_csc as the per-block kernel. -// Unlike the pd-on-the-right path, BTDA_pd_csc reuses the per-block -// B_k->kernel_dwork sized by BTA_pd_csc_alloc — no temp DA allocation. -// --------------------------------------------------------------------------------- -static matrix *wrapper_BTA_pd_csc(const permuted_dense *Bk, const void *ctx) -{ - return BTA_pd_csc_alloc(Bk, (const CSC_matrix *) ctx); -} - -static void wrapper_BTDA_pd_csc(const permuted_dense *Bk, const double *d, - const void *ctx, permuted_dense *Ck) -{ - BTDA_pd_csc_fill_values(Bk, d, (const CSC_matrix *) ctx, Ck); -} - -/* BTA per-block fill: plain B_k^T @ A, no diagonal. The skeleton forwards d - verbatim and never dereferences it, so the caller passes d = NULL and we - ignore it. */ -static void wrapper_BTA_pd_csc_fill(const permuted_dense *Bk, const double *d, - const void *ctx, permuted_dense *Ck) -{ - (void) d; - BTA_pd_csc_fill_values(Bk, (const CSC_matrix *) ctx, Ck); -} - -matrix *BTA_spd_csc_alloc(const stacked_pd *B, const CSC_matrix *A) -{ - return spd_blockwise_alloc_coalesce(B, B->base.n, A->n, wrapper_BTA_pd_csc, A); -} - -void BTA_spd_csc_fill_values(const stacked_pd *B, const CSC_matrix *A, stacked_pd *C) -{ - spd_blockwise_fill_coalesce_accumulate(B, NULL, A, C, wrapper_BTA_pd_csc_fill); -} - -void BTDA_spd_csc_fill_values(const stacked_pd *B, const double *d, - const CSC_matrix *A, stacked_pd *C) -{ - spd_blockwise_fill_coalesce_accumulate(B, d, A, C, wrapper_BTDA_pd_csc); -} - -// --------------------------------------------------------------------------------- -// BTA_spd_spd / BTDA_spd_spd: C = B^T @ (diag(d) @) A where both B and A are -// stacked_pd. Output C is stacked_pd. Same skeleton as BTA_spd_pd/csc — per -// B-block partial is B_k^T @ A_spd via BTA_pd_spd / BTDA_pd_spd, which -// internally handles the iteration over A's blocks. The "double nesting" -// over (B_k, A_j) pairs is hidden inside BTA_pd_spd_alloc. -// --------------------------------------------------------------------------------- -static matrix *wrapper_BTA_pd_spd(const permuted_dense *Bk, const void *ctx) -{ - return BTA_pd_spd_alloc(Bk, (const stacked_pd *) ctx); -} - -static void wrapper_BTDA_pd_spd(const permuted_dense *Bk, const double *d, - const void *ctx, permuted_dense *Ck) -{ - BTDA_pd_spd_fill_values(Bk, d, (const stacked_pd *) ctx, Ck); -} - -/* BTA per-block fill: plain B_k^T @ A_spd, no diagonal. The skeleton forwards d - verbatim and never dereferences it, so the caller passes d = NULL and we - ignore it. Calling BTA_pd_spd_fill_values directly (vs. the BTDA wrapper) also - avoids the per-block DA temp. */ -static void wrapper_BTA_pd_spd_fill(const permuted_dense *Bk, const double *d, - const void *ctx, permuted_dense *Ck) -{ - (void) d; - BTA_pd_spd_fill_values(Bk, (const stacked_pd *) ctx, Ck); -} - -matrix *BTA_spd_spd_alloc(const stacked_pd *B, const stacked_pd *A) -{ - return spd_blockwise_alloc_coalesce(B, B->base.n, A->base.n, wrapper_BTA_pd_spd, - A); -} - -void BTA_spd_spd_fill_values(const stacked_pd *B, const stacked_pd *A, stacked_pd *C) -{ - spd_blockwise_fill_coalesce_accumulate(B, NULL, A, C, wrapper_BTA_pd_spd_fill); -} - -void BTDA_spd_spd_fill_values(const stacked_pd *B, const double *d, - const stacked_pd *A, stacked_pd *C) -{ - spd_blockwise_fill_coalesce_accumulate(B, d, A, C, wrapper_BTDA_pd_spd); -} - -// --------------------------------------------------------------------------------- -// BTA_csc_spd / BTDA_csc_spd: C = B^T @ (diag(d) @) A where B is CSC and A is -// stacked_pd. Output C is stacked_pd. -// -// Math: A = Σ_k S_{r_k} A_k S_{c_k}^T, so -// B^T A = Σ_k B^T A_k -// where each per-block partial B^T @ A_k is computed by BTA_csc_pd. Unlike -// the spd-on-the-left family, we iterate A's blocks (not B's), and output -// rows come from B's cols — hence the explicit Cm = B->n passed to the -// shared helper. Cells overlap across k (both row_perms and col_perms can -// collide), so we use the unchecked raw + accumulating coalesce. -// --------------------------------------------------------------------------------- -static matrix *wrapper_BTA_csc_pd(const permuted_dense *Ak, const void *ctx) -{ - return BTA_csc_pd_alloc((const CSC_matrix *) ctx, Ak); -} - -static void wrapper_BTDA_csc_pd(const permuted_dense *Ak, const double *d, - const void *ctx, permuted_dense *Ck) -{ - BTDA_csc_pd_fill_values((const CSC_matrix *) ctx, d, Ak, Ck); -} - -/* BTA per-block fill: plain B^T @ A_k, no diagonal. The skeleton forwards d - verbatim and never dereferences it, so the caller passes d = NULL and we - ignore it. */ -static void wrapper_BTA_csc_pd_fill(const permuted_dense *Ak, const double *d, - const void *ctx, permuted_dense *Ck) -{ - (void) d; - BTA_csc_pd_fill_values((const CSC_matrix *) ctx, Ak, Ck); -} - -matrix *BTA_csc_spd_alloc(const CSC_matrix *B, const stacked_pd *A) -{ - return spd_blockwise_alloc_coalesce(A, B->n, A->base.n, wrapper_BTA_csc_pd, B); -} - -void BTA_csc_spd_fill_values(const CSC_matrix *B, const stacked_pd *A, stacked_pd *C) -{ - spd_blockwise_fill_coalesce_accumulate(A, NULL, B, C, wrapper_BTA_csc_pd_fill); -} - -void BTDA_csc_spd_fill_values(const CSC_matrix *B, const double *d, - const stacked_pd *A, stacked_pd *C) -{ - spd_blockwise_fill_coalesce_accumulate(A, d, B, C, wrapper_BTDA_csc_pd); -} - -// --------------------------------------------------------------------------------- -// BA_pd_spd: C = B @ A where B is permuted_dense and A is stacked_pd. Thin -// wrapper over the canonical BTA_pd_spd_* kernel: use B's lazily-cached -// transpose and call BTA. The cache is populated on first call (in alloc) -// and reused across subsequent fills; its values are refreshed only when -// B's values_version has moved since the last fill (transpose_seen). -// -// Contract: B's perms must be immutable between alloc and fill (the cache -// records B's perms at alloc time and is not re-validated at fill). For -// callers where B's perms change between calls — notably the kron-spd path -// that reuses a mutating scratch — bypass this wrapper and call -// BTA_pd_spd_* directly. BA_dense_kron_spd does exactly that -// (stacked_pd_kron_linalg.c). The values_version guard additionally -// requires B to be the owner of its value buffer; BA_spd_spd_fill_values -// passes spd blocks (no version of their own) and therefore also bypasses -// the wrapper for its fills. -// --------------------------------------------------------------------------------- -matrix *BA_pd_spd_alloc(const permuted_dense *B, const stacked_pd *A) -{ - permuted_dense *BT = permuted_dense_ensure_transpose_cache(B); - return BTA_pd_spd_alloc(BT, A); -} - -void BA_pd_spd_fill_values(const permuted_dense *B, const stacked_pd *A, - permuted_dense *C) -{ - permuted_dense *BT = B->transpose_cache; - if (BT->transpose_seen != B->base.values_version) - { - transpose_pd_fill_values(B, BT); - BT->transpose_seen = B->base.values_version; - } - BTA_pd_spd_fill_values(BT, A, C); -} - -// ==================================================================================== -// "B is stacked_pd on the LEFT" BA kernels: C = B @ A for A in {CSC, PD, spd}. -// Output is always stacked_pd, with each output block = B_k @ A. Because B's -// blocks have disjoint row_perms (spd invariant) and the output rows come -// directly from B's rows, the output's blocks are also disjoint by construction -// — no coalesce needed. We use spd_map_filter_blocks (stacked_pd.h) which builds -// a valid stacked_pd directly, filtering out empty contributions. -// -// Not used in any production atom today (stacked_pds appear as derived Jacobians, -// never as constant left operands of B @ A). Kept as siblings of the BTA_spd_* -// family for API symmetry and because BA_spd_matrices is the natural counterpart -// to BTA_spd_matrices. Cost of carrying them is small. -// ==================================================================================== -static matrix *wrapper_BA_pd_csc(permuted_dense *Bk, const void *ctx) -{ - return BA_pd_csc_alloc(Bk, (const CSC_matrix *) ctx); -} - -matrix *BA_spd_csc_alloc(const stacked_pd *B, const CSC_matrix *A) -{ - return spd_map_filter_blocks(B, B->base.m, A->n, wrapper_BA_pd_csc, A); -} - -void BA_spd_csc_fill_values(const stacked_pd *B, const CSC_matrix *A, stacked_pd *C) -{ - for (int k = 0; k < C->n_blocks; k++) - { - /* Bq is the block in B that contributes to Ck */ - int q = C->src_block_idx[C->src_block_idx_p[k]]; - const permuted_dense *Bq = B->blocks[q]; - BA_pd_csc_fill_values(Bq->X, Bq->n0, Bq->col_inv, A, C->blocks[k]); - } -} - -static matrix *wrapper_BA_pd_pd(permuted_dense *Bk, const void *ctx) -{ - return BA_pd_pd_alloc(Bk, (const permuted_dense *) ctx); -} - -matrix *BA_spd_pd_alloc(const stacked_pd *B, const permuted_dense *A) -{ - return spd_map_filter_blocks(B, B->base.m, A->base.n, wrapper_BA_pd_pd, A); -} - -void BA_spd_pd_fill_values(const stacked_pd *B, const permuted_dense *A, - stacked_pd *C) -{ - for (int k = 0; k < C->n_blocks; k++) - { - int q = C->src_block_idx[C->src_block_idx_p[k]]; - const permuted_dense *Bq = B->blocks[q]; - BA_pd_pd_fill_values(Bq, A, C->blocks[k]); - } -} - -static matrix *wrapper_BA_pd_spd(permuted_dense *Bk, const void *ctx) -{ - return BA_pd_spd_alloc(Bk, (const stacked_pd *) ctx); -} - -matrix *BA_spd_spd_alloc(const stacked_pd *B, const stacked_pd *A) -{ - return spd_map_filter_blocks(B, B->base.m, A->base.n, wrapper_BA_pd_spd, A); -} - -void BA_spd_spd_fill_values(const stacked_pd *B, const stacked_pd *A, stacked_pd *C) -{ - for (int k = 0; k < C->n_blocks; k++) - { - int q = C->src_block_idx[C->src_block_idx_p[k]]; - const permuted_dense *Bq = B->blocks[q]; - /* Bypass BA_pd_spd_fill_values' version guard: spd blocks have no - values_version of their own (writers bump the owning spd), so the - cached transpose must be refreshed unconditionally here. */ - permuted_dense *BqT = Bq->transpose_cache; - transpose_pd_fill_values(Bq, BqT); - BTA_pd_spd_fill_values(BqT, A, C->blocks[k]); - } -} diff --git a/sparsediff_linalg/src/tracked_alloc.c b/sparsediff_linalg/src/tracked_alloc.c deleted file mode 100644 index 6c7be041..00000000 --- a/sparsediff_linalg/src/tracked_alloc.c +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/tracked_alloc.h" - -#ifdef SP_TRACK_MEMORY -size_t g_allocated_bytes = 0; -size_t g_peak_bytes = 0; -#endif diff --git a/sparsediff_linalg/src/utils.c b/sparsediff_linalg/src/utils.c deleted file mode 100644 index 554e59fb..00000000 --- a/sparsediff_linalg/src/utils.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "sparsediff_linalg/utils.h" - -#include "sparsediff_linalg/iVec.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include - -/* Helper function to compare integers for qsort */ -static int compare_int_asc(const void *a, const void *b) -{ - int ia = *((const int *) a); - int ib = *((const int *) b); - return (ia > ib) - (ia < ib); -} - -void sort_int_array(int *array, int size) -{ - qsort(array, size, sizeof(int), compare_int_asc); -} - -bool has_overlap(const int *a_idx, int a_len, const int *b_idx, int b_len, - int b_offset) -{ - int ai = 0, bi = 0; - while (ai < a_len && bi < b_len) - { - if (a_idx[ai] == b_idx[bi] - b_offset) return true; - if (a_idx[ai] < b_idx[bi] - b_offset) - { - ai++; - } - else - { - bi++; - } - } - return false; -} - -int sorted_intersect_indices(const int *a, int a_len, const int *b, int b_len, - int *idx_a, int *idx_b) -{ - int s = 0; - int ii = 0, jj = 0; - while (ii < a_len && jj < b_len) - { - int ra = a[ii]; - int rb = b[jj]; - if (ra == rb) - { - idx_a[s] = ii; - idx_b[s] = jj; - s++; - ii++; - jj++; - } - else if (ra < rb) - { - ii++; - } - else - { - jj++; - } - } - return s; -} - -void sorted_union_int_arrays(const int *const *arrs, const int *lens, int n_arrs, - iVec *out) -{ - iVec_clear_no_resize(out); - int *cursor = (int *) sp_calloc(n_arrs, sizeof(int)); - while (1) - { - int min_val = 0; - int min_arr = -1; - for (int a = 0; a < n_arrs; a++) - { - if (cursor[a] >= lens[a]) - { - continue; - } - int v = arrs[a][cursor[a]]; - if (min_arr == -1 || v < min_val) - { - min_val = v; - min_arr = a; - } - } - if (min_arr == -1) - { - break; - } - if (out->len == 0 || out->data[out->len - 1] != min_val) - { - iVec_append(out, min_val); - } - cursor[min_arr]++; - } - sp_free(cursor); -} - -void cumsum(int *p, int n) -{ - for (int i = 0; i < n; i++) - { - p[i + 1] += p[i]; - } -} diff --git a/sparsediff_linalg/tests/minunit.h b/sparsediff_linalg/tests/minunit.h deleted file mode 100644 index cd6e1b31..00000000 --- a/sparsediff_linalg/tests/minunit.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef MINUNIT_H -#define MINUNIT_H -/* Modified from http://www.jera.com/techinfo/jtns/jtn002.html */ - -/* Simple Macros for testing */ -#define mu_assert(message, test) \ - do \ - { \ - if (!(test)) return message; \ - } while (0) - -#define mu_assert_less(message, a, b) \ - do \ - { \ - if (a > b) \ - { \ - printf("%s: %1.3e > %1.3e\n", message, a, b); \ - return message; \ - } \ - } while (0) - -#define mu_run_test(test, var) _mu_run_test(#test, test, var) - -#define _mu_run_test(name, test, var) \ - do \ - { \ - printf("*********************************************************\n"); \ - printf("Running test: %s\n", name); \ - printf("*********************************************************\n"); \ - const char *message = test(); \ - var++; \ - if (message) \ - { \ - printf("FAILED: %s\n", message); \ - return 1; \ - } \ - printf("PASSED\n\n"); \ - } while (0) - -#endif diff --git a/sparsediff_linalg/tests/sparsediff_linalg_tests.c b/sparsediff_linalg/tests/sparsediff_linalg_tests.c deleted file mode 100644 index 4f46ac71..00000000 --- a/sparsediff_linalg/tests/sparsediff_linalg_tests.c +++ /dev/null @@ -1,203 +0,0 @@ -#include - -#include "minunit.h" - -/* Library-only suite: this executable links sparsediff_linalg alone, so every - header below may include only sparsediff_linalg/.h, internal/.h, - minunit.h, test_helpers.h and sibling test_.h files (checked at - configure time in ../CMakeLists.txt). Tests that need old-code or engine - kernels live in the engine's tests/old-code and run from all_tests. */ -#include "test_COO_matrix.h" -#include "test_alloc_overflow.h" -#include "test_cblas.h" -#include "test_csc_matrix.h" -#include "test_csr_csc_conversion.h" -#include "test_csr_matrix.h" -#include "test_linalg_sparse_matmuls.h" -#include "test_linalg_utils_matmul_chain_rule.h" -#include "test_matmul_dispatchers.h" -#include "test_matrix.h" -#include "test_permuted_dense.h" -#include "test_row_gather.h" -#include "test_row_reduce.h" -#include "test_stacked_pd.h" - -int main(void) -{ - printf("=== Running sparsediff_linalg Tests ===\n\n"); - - int tests_run = 0; - - printf("--- sparsediff_linalg Tests ---\n"); - mu_run_test(test_sat_mul_int_clamps_on_overflow, tests_run); - mu_run_test(test_row_gather_alloc_no_int_overflow, tests_run); - mu_run_test(test_cblas_ddot, tests_run); - mu_run_test(test_transpose, tests_run); - mu_run_test(test_AT_alloc_and_fill, tests_run); - mu_run_test(test_csr_to_csc_split, tests_run); - mu_run_test(test_csc_to_csr_sparsity, tests_run); - mu_run_test(test_csc_to_csr_values, tests_run); - mu_run_test(test_csr_csc_csr_roundtrip, tests_run); - mu_run_test(test_block_left_multiply_single_block, tests_run); - mu_run_test(test_block_left_multiply_two_blocks, tests_run); - mu_run_test(test_block_left_multiply_zero_column, tests_run); - mu_run_test(test_block_left_multiply_dedup_order, tests_run); - mu_run_test(test_block_left_multiply_matches_reference_random, tests_run); - mu_run_test(test_csr_csc_matmul_alloc_basic, tests_run); - mu_run_test(test_csr_csc_matmul_alloc_sparse, tests_run); - mu_run_test(test_csr_csc_matmul_alloc_dedup_order, tests_run); - mu_run_test(test_csr_csc_matmul_alloc_matches_reference_random, tests_run); - mu_run_test(test_block_left_multiply_vec_single_block, tests_run); - mu_run_test(test_block_left_multiply_vec_two_blocks, tests_run); - mu_run_test(test_block_left_multiply_vec_sparse, tests_run); - mu_run_test(test_block_left_multiply_vec_three_blocks, tests_run); - mu_run_test(test_ATA_alloc_simple, tests_run); - mu_run_test(test_ATA_alloc_diagonal_like, tests_run); - mu_run_test(test_ATA_alloc_random, tests_run); - mu_run_test(test_ATA_alloc_random2, tests_run); - mu_run_test(test_BTA_alloc_and_BTDA_fill, tests_run); - mu_run_test(test_csr_to_coo, tests_run); - mu_run_test(test_csr_to_coo_lower_triangular, tests_run); - mu_run_test(test_refresh_lower_triangular_coo, tests_run); - mu_run_test(test_pd_mult_vec_basic, tests_run); - mu_run_test(test_pd_mult_vec_blocks, tests_run); - mu_run_test(test_sparse_vs_pd_mult_vec, tests_run); - mu_run_test(test_pd_trans_full_block, tests_run); - mu_run_test(test_sparse_vs_pd_mult_vec_blocks, tests_run); - mu_run_test(test_pd_operator_block_left_mult_vec, tests_run); - mu_run_test(test_permuted_dense_to_csr_basic, tests_run); - mu_run_test(test_permuted_dense_to_csr_empty, tests_run); - mu_run_test(test_permuted_dense_to_csr_full, tests_run); - mu_run_test(test_permuted_dense_to_csr_single_row, tests_run); - mu_run_test(test_permuted_dense_to_csr_single_col, tests_run); - mu_run_test(test_DA_pd_fill_values, tests_run); - mu_run_test(test_ATA_pd_alloc, tests_run); - mu_run_test(test_ATDA_pd_fill_values, tests_run); - mu_run_test(test_permuted_dense_times_csc, tests_run); - mu_run_test(test_permuted_dense_times_csc_no_active, tests_run); - mu_run_test(test_permuted_dense_to_csr_lazy, tests_run); - mu_run_test(test_permuted_dense_col_inv, tests_run); - mu_run_test(test_permuted_dense_row_gather, tests_run); - mu_run_test(test_row_gather_sparse, tests_run); - mu_run_test(test_row_gather_pd_vs_sparse_twin, tests_run); - mu_run_test(test_row_gather_spd_vs_sparse_twin, tests_run); -#ifdef SP_TRACK_MEMORY - mu_run_test(test_row_gather_spd_fill_no_transient_alloc, tests_run); -#endif - mu_run_test(test_row_reduce_sparse, tests_run); - mu_run_test(test_row_reduce_pd, tests_run); - mu_run_test(test_row_reduce_spd_cross_block_accumulate, tests_run); - mu_run_test(test_row_reduce_spd_within_block, tests_run); - mu_run_test(test_row_reduce_spd_all_to_one, tests_run); -#ifdef SP_TRACK_MEMORY - mu_run_test(test_row_reduce_spd_fill_no_transient_alloc, tests_run); -#endif - mu_run_test(test_permuted_dense_diag_vec, tests_run); - mu_run_test(test_permuted_dense_BTA_matching_row_perm, tests_run); - mu_run_test(test_permuted_dense_BTA_empty_overlap, tests_run); - mu_run_test(test_permuted_dense_BTA_partial_overlap, tests_run); - mu_run_test(test_permuted_dense_BTDA_decomposition, tests_run); - mu_run_test(test_permuted_dense_BTDA_matching_row_perm, tests_run); - mu_run_test(test_permuted_dense_BTDA_partial_overlap, tests_run); - mu_run_test(test_BA_pd_matrices_pd_pd_full_block_B, tests_run); - mu_run_test(test_BA_pd_matrices_pd_pd_general_B, tests_run); - mu_run_test(test_BA_pd_matrices_pd_csc, tests_run); - mu_run_test(test_BA_pd_matrices_spd_A, tests_run); - mu_run_test(test_BA_pd_matrices_fast_path, tests_run); - mu_run_test(test_BA_spd_csc_two_blocks_both_kept, tests_run); - mu_run_test(test_BA_spd_csc_one_block_dropped, tests_run); - mu_run_test(test_BA_spd_csc_all_blocks_dropped, tests_run); - mu_run_test(test_BA_spd_pd_two_blocks_both_kept, tests_run); - mu_run_test(test_BA_spd_pd_one_block_dropped, tests_run); - mu_run_test(test_BA_spd_spd_two_blocks_both_kept, tests_run); - mu_run_test(test_BA_spd_spd_one_block_dropped, tests_run); - mu_run_test(test_BA_spd_spd_all_blocks_dropped, tests_run); - mu_run_test(test_BA_spd_spd_empty_A, tests_run); - mu_run_test(test_BA_spd_spd_empty_B, tests_run); - mu_run_test(test_BA_spd_spd_alloc_then_fill_values, tests_run); - mu_run_test(test_BTDA_matrices_pd_pd, tests_run); - mu_run_test(test_BTDA_matrices_pd_csr, tests_run); - mu_run_test(test_BTA_csc_pd_basic, tests_run); - mu_run_test(test_BTA_csc_pd_partial_and_excluded_cols, tests_run); - mu_run_test(test_BTA_csc_pd_empty, tests_run); - mu_run_test(test_BTDA_matrices_spd_pd, tests_run); - mu_run_test(test_BTDA_matrices_pd_spd, tests_run); - mu_run_test(test_BTA_pd_csc_basic, tests_run); - mu_run_test(test_BTA_pd_csc_partial_and_excluded_cols, tests_run); - mu_run_test(test_BTA_pd_csc_empty, tests_run); - mu_run_test(test_BTDA_matrices_spd_spd, tests_run); - mu_run_test(test_BTA_pd_spd_two_blocks_both_kept, tests_run); - mu_run_test(test_BTDA_pd_spd_two_blocks_both_kept, tests_run); - mu_run_test(test_BTDA_spd_pd_overlapping_cp, tests_run); -#ifdef SP_TRACK_MEMORY - mu_run_test(test_BTDA_fill_no_transient_alloc, tests_run); -#endif - mu_run_test(test_BTA_spd_pd_overlapping_cp, tests_run); - mu_run_test(test_BTDA_spd_csc_overlapping_cp, tests_run); - mu_run_test(test_BTA_spd_csc_overlapping, tests_run); - mu_run_test(test_BTA_spd_csc_block_no_overlap, tests_run); - mu_run_test(test_BTDA_spd_spd_overlapping, tests_run); - mu_run_test(test_BTA_spd_spd_overlapping, tests_run); - mu_run_test(test_BTA_spd_spd_multi_A_per_block, tests_run); - mu_run_test(test_BTA_spd_spd_nonoverlapping_block, tests_run); - mu_run_test(test_BTA_spd_matrices_pd_A, tests_run); - mu_run_test(test_BTA_spd_matrices_csc_A, tests_run); - mu_run_test(test_BTA_spd_matrices_spd_A, tests_run); - mu_run_test(test_BTA_pd_matrices_pd_A, tests_run); - mu_run_test(test_BTA_pd_matrices_csc_A, tests_run); - mu_run_test(test_BTA_pd_matrices_spd_A, tests_run); - mu_run_test(test_BTDA_csc_spd_overlapping, tests_run); - mu_run_test(test_BTA_csc_spd_overlapping, tests_run); - mu_run_test(test_BTA_csc_spd_block_no_overlap, tests_run); - mu_run_test(test_BTA_sparse_matrices_pd_A, tests_run); - mu_run_test(test_BTA_sparse_matrices_csc_A, tests_run); - mu_run_test(test_BTA_sparse_matrices_spd_A, tests_run); - mu_run_test(test_BTA_matrices_fill_pd_spd, tests_run); - mu_run_test(test_BTA_matrices_fill_spd_csc, tests_run); - mu_run_test(test_BTA_matrices_fill_csc_pd, tests_run); - mu_run_test(test_BTA_matrices_fill_csc_csc, tests_run); - mu_run_test(test_BA_pd_kron_spd_no_cache_staleness, tests_run); - mu_run_test(test_BA_pd_spd_transpose_cache_refresh, tests_run); - mu_run_test(test_stacked_pd_construct_and_free, tests_run); - mu_run_test(test_coalesce_no_overlap, tests_run); - mu_run_test(test_coalesce_three_signatures, tests_run); - mu_run_test(test_coalesce_shared_signature_merges_rows, tests_run); - mu_run_test(test_coalesce_alloc_then_fill_values, tests_run); - mu_run_test(test_coalesce_empty_input, tests_run); - mu_run_test(test_transpose_spd_no_overlap, tests_run); - mu_run_test(test_transpose_spd_overlap_coalesces, tests_run); - mu_run_test(test_transpose_spd_alloc_then_fill_values, tests_run); - mu_run_test(test_transpose_spd_empty, tests_run); - mu_run_test(test_transpose_spd_single_block_full, tests_run); - mu_run_test(test_copy_sparsity_spd_alloc, tests_run); - mu_run_test(test_DA_spd_two_blocks, tests_run); - mu_run_test(test_DA_spd_empty, tests_run); - mu_run_test(test_DA_spd_single_block_full, tests_run); - mu_run_test(test_ATA_spd_alloc_disjoint_cols, tests_run); - mu_run_test(test_ATA_spd_alloc_overlapping_cols, tests_run); - mu_run_test(test_ATDA_spd_disjoint_cols, tests_run); - mu_run_test(test_ATDA_spd_overlapping_cols, tests_run); - mu_run_test(test_ATDA_spd_alloc_then_fill_values, tests_run); - mu_run_test(test_ATDA_spd_empty, tests_run); - mu_run_test(test_BA_pd_spd_two_blocks_disjoint_cols, tests_run); - mu_run_test(test_BA_pd_spd_only_one_block_contributes, tests_run); - mu_run_test(test_BA_pd_spd_no_blocks_contribute, tests_run); - mu_run_test(test_BA_pd_spd_empty_A, tests_run); - mu_run_test(test_BA_pd_spd_overlapping_col_perms, tests_run); - mu_run_test(test_BA_pd_spd_alloc_then_fill_values, tests_run); - mu_run_test(test_spd_vtable_copy_sparsity, tests_run); - mu_run_test(test_spd_vtable_DA_fill_values, tests_run); - mu_run_test(test_spd_vtable_ATA_alloc, tests_run); - mu_run_test(test_spd_vtable_ATDA_fill_values, tests_run); - mu_run_test(test_spd_vtable_transpose, tests_run); - mu_run_test(test_spd_vtable_refresh_csc_values_noop, tests_run); - mu_run_test(test_spd_vtable_row_gather, tests_run); - mu_run_test(test_spd_vtable_diag_vec, tests_run); - mu_run_test(test_YT_kron_I, tests_run); - mu_run_test(test_YT_kron_I_larger, tests_run); - mu_run_test(test_I_kron_X, tests_run); - mu_run_test(test_I_kron_X_larger, tests_run); - - printf("\n=== All %d sparsediff_linalg tests passed ===\n", tests_run); - return 0; -} diff --git a/sparsediff_linalg/tests/test_COO_matrix.h b/sparsediff_linalg/tests/test_COO_matrix.h deleted file mode 100644 index 58d3ce1a..00000000 --- a/sparsediff_linalg/tests/test_COO_matrix.h +++ /dev/null @@ -1,104 +0,0 @@ -#include -#include -#include - -#include "minunit.h" -#include "sparsediff_linalg/COO_matrix.h" -#include "test_helpers.h" - -const char *test_csr_to_coo(void) -{ - /* Create a 3x3 CSR_matrix matrix A: - * [1.0 2.0 0.0] - * [0.0 3.0 4.0] - * [5.0 0.0 6.0] - */ - CSR_matrix *A = new_CSR_matrix(3, 3, 6); - double Ax[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - int Ai[6] = {0, 1, 1, 2, 0, 2}; - int Ap[4] = {0, 2, 4, 6}; - memcpy(A->x, Ax, 6 * sizeof(double)); - memcpy(A->i, Ai, 6 * sizeof(int)); - memcpy(A->p, Ap, 4 * sizeof(int)); - - COO_matrix *coo = new_COO_matrix(A); - - mu_assert("m incorrect", coo->m == 3); - mu_assert("n incorrect", coo->n == 3); - mu_assert("nnz incorrect", coo->nnz == 6); - - int expected_rows[6] = {0, 0, 1, 1, 2, 2}; - int expected_cols[6] = {0, 1, 1, 2, 0, 2}; - double expected_x[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - - mu_assert("rows incorrect", cmp_int_array(coo->rows, expected_rows, 6)); - mu_assert("cols incorrect", cmp_int_array(coo->cols, expected_cols, 6)); - mu_assert("vals incorrect", cmp_double_array(coo->x, expected_x, 6)); - - free_COO_matrix(coo); - free_CSR_matrix(A); - - return 0; -} - -const char *test_csr_to_coo_lower_triangular(void) -{ - /* Symmetric 3x3 matrix: - * [1 2 3] - * [2 5 6] - * [3 6 9] - */ - CSR_matrix *A = new_CSR_matrix(3, 3, 9); - int Ap[4] = {0, 3, 6, 9}; - int Ai[9] = {0, 1, 2, 0, 1, 2, 0, 1, 2}; - double Ax[9] = {1, 2, 3, 2, 5, 6, 3, 6, 9}; - memcpy(A->p, Ap, 4 * sizeof(int)); - memcpy(A->i, Ai, 9 * sizeof(int)); - memcpy(A->x, Ax, 9 * sizeof(double)); - - COO_matrix *coo = new_COO_matrix_lower_triangular(A); - - mu_assert("ltri m incorrect", coo->m == 3); - mu_assert("ltri n incorrect", coo->n == 3); - mu_assert("ltri nnz incorrect", coo->nnz == 6); - - int expected_rows[6] = {0, 1, 1, 2, 2, 2}; - int expected_cols[6] = {0, 0, 1, 0, 1, 2}; - double expected_x[6] = {1, 2, 5, 3, 6, 9}; - int expected_map[6] = {0, 3, 4, 6, 7, 8}; - - mu_assert("ltri rows incorrect", cmp_int_array(coo->rows, expected_rows, 6)); - mu_assert("ltri cols incorrect", cmp_int_array(coo->cols, expected_cols, 6)); - mu_assert("ltri vals incorrect", cmp_double_array(coo->x, expected_x, 6)); - mu_assert("ltri value_map incorrect", - cmp_int_array(coo->value_map, expected_map, 6)); - - free_COO_matrix(coo); - free_CSR_matrix(A); - - return 0; -} - -const char *test_refresh_lower_triangular_coo(void) -{ - CSR_matrix *A = new_CSR_matrix(3, 3, 9); - int Ap[4] = {0, 3, 6, 9}; - int Ai[9] = {0, 1, 2, 0, 1, 2, 0, 1, 2}; - double Ax[9] = {1, 2, 3, 2, 5, 6, 3, 6, 9}; - memcpy(A->p, Ap, 4 * sizeof(int)); - memcpy(A->i, Ai, 9 * sizeof(int)); - memcpy(A->x, Ax, 9 * sizeof(double)); - - COO_matrix *coo = new_COO_matrix_lower_triangular(A); - - double vals2[9] = {10, 20, 30, 20, 50, 60, 30, 60, 90}; - refresh_lower_triangular_coo(coo, vals2); - - double expected_x[6] = {10, 20, 50, 30, 60, 90}; - mu_assert("refresh vals incorrect", cmp_double_array(coo->x, expected_x, 6)); - - free_COO_matrix(coo); - free_CSR_matrix(A); - - return 0; -} diff --git a/sparsediff_linalg/tests/test_alloc_overflow.h b/sparsediff_linalg/tests/test_alloc_overflow.h deleted file mode 100644 index f2567606..00000000 --- a/sparsediff_linalg/tests/test_alloc_overflow.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2026 Daniel Cederberg and William Zhang - * - * This file is part of the SparseDiffEngine project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef TEST_ALLOC_OVERFLOW_H -#define TEST_ALLOC_OVERFLOW_H - -#include "minunit.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/matrix.h" -#include "sparsediff_linalg/sparse_matrix.h" -#include "sparsediff_linalg/utils.h" -#include -#include -#include - -/* sat_mul_int returns the exact product when it fits and clamps to INT_MAX on - overflow (never wraps to a negative or small-positive value). */ -const char *test_sat_mul_int_clamps_on_overflow(void) -{ - mu_assert("exact product", sat_mul_int(40000, 40000) == 1600000000); - mu_assert("zero operand", sat_mul_int(0, 12345) == 0); - mu_assert("negative operand", sat_mul_int(-1, 12345) == 0); - /* 250000 * 250000 = 6.25e10 overflows int32; must clamp, not wrap negative. */ - mu_assert("overflow clamps to INT_MAX", sat_mul_int(250000, 250000) == INT_MAX); - mu_assert("boundary still positive", sat_mul_int(46341, 46341) == INT_MAX); - return 0; -} - -/* The sparse row gather (then sparse_index_alloc) once sized its allocation as - MIN(Jx->nnz, n_idxs * self->n). For a large jacobian the dense product - n_idxs * self->n overflows int (e.g. a transpose of a 250000-variable matrix), - wrapping negative so MIN selected it -> calloc overflow -> SIGSEGV. This builds - a gather whose product overflows and checks the result is a valid CSR with the - true (subset) nnz. */ -const char *test_row_gather_alloc_no_int_overflow(void) -{ - /* n_idxs * self->n = 25000 * 100000 = 2.5e9 overflows int32. */ - const int n_idxs = 25000; - const int n_cols = 100000; - - /* identity-like CSR: n_idxs rows, one nonzero per row */ - CSR_matrix *Jx = new_CSR_matrix(n_idxs, n_cols, n_idxs); - for (int i = 0; i < n_idxs; i++) - { - Jx->p[i] = i; - Jx->i[i] = i; - Jx->x[i] = 1.0; - } - Jx->p[n_idxs] = n_idxs; - - matrix *mat = new_sparse_matrix(Jx); - - int *indices = (int *) malloc(n_idxs * sizeof(int)); - for (int i = 0; i < n_idxs; i++) indices[i] = i; - - /* pre-fix: crashes here in new_CSR_matrix(... negative nnz) */ - matrix *out = mat->row_gather_alloc(mat, indices, n_idxs); - CSR_matrix *out_csr = out->to_csr(out); - - mu_assert("nnz must equal selected-row total", out_csr->nnz == n_idxs); - mu_assert("nnz must be non-negative", out_csr->nnz >= 0); - - free(indices); - free_matrix(out); - free_matrix(mat); - return 0; -} - -#endif /* TEST_ALLOC_OVERFLOW_H */ diff --git a/sparsediff_linalg/tests/test_cblas.h b/sparsediff_linalg/tests/test_cblas.h deleted file mode 100644 index e7cac203..00000000 --- a/sparsediff_linalg/tests/test_cblas.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef TEST_CBLAS_H -#define TEST_CBLAS_H - -#include "minunit.h" -#include "sparsediff_linalg/cblas_wrapper.h" -#include - -static char *test_cblas_ddot(void) -{ - double x[] = {1.0, 2.0, 3.0, 4.0}; - double y[] = {5.0, 6.0, 7.0, 8.0}; - double result = cblas_ddot(4, x, 1, y, 1); - double expected = 1.0 * 5.0 + 2.0 * 6.0 + 3.0 * 7.0 + 4.0 * 8.0; - mu_assert("test_cblas_ddot: wrong dot product", fabs(result - expected) < 1e-12); - return 0; -} - -#endif /* TEST_CBLAS_H */ diff --git a/sparsediff_linalg/tests/test_csc_matrix.h b/sparsediff_linalg/tests/test_csc_matrix.h deleted file mode 100644 index 6a59e59c..00000000 --- a/sparsediff_linalg/tests/test_csc_matrix.h +++ /dev/null @@ -1,211 +0,0 @@ -#include -#include -#include -#include - -#include "minunit.h" -#include "sparsediff_linalg/CSC_matrix.h" -#include "test_helpers.h" - -/* Test ATA_alloc with a simple 3x3 example - * A is 4x3 (4 rows, 3 columns): - * [x 0 x] - * [0 x 0] - * [0 0 x] - * [0 x 0] - * - * A^T A is 3x3: - * [x 0 x] - * [0 x 0] - * [x 0 x] - */ -const char *test_ATA_alloc_simple(void) -{ - CSC_matrix *A = new_CSC_matrix(4, 3, 6); - int Ap[4] = {0, 2, 3, 6}; - int Ai[5] = {0, 2, 1, 2, 1}; - memcpy(A->p, Ap, 4 * sizeof(int)); - memcpy(A->i, Ai, 5 * sizeof(int)); - - /* Compute C = A^T A */ - CSR_matrix *C = ATA_alloc(A); - int expected_p[4] = {0, 2, 3, 5}; - int expected_i[5] = {0, 2, 1, 0, 2}; - - mu_assert("p incorrect", cmp_int_array(C->p, expected_p, 4)); - mu_assert("i incorrect", cmp_int_array(C->i, expected_i, C->nnz)); - mu_assert("nnz incorrect", C->nnz == 5); - - free_CSR_matrix(C); - free_CSC_matrix(A); - - return 0; -} - -/* Test ATA_alloc with a sparse 3x4 matrix with no overlaps on some pairs - * A is 3x4: - * [1 0 0 2] - * [0 1 0 0] - * [0 0 1 0] - * - * A^T A is 4x4: - * [1 0 0 2] - * [0 1 0 0] - * [0 0 1 0] - * [2 0 0 4] - * - */ -const char *test_ATA_alloc_diagonal_like(void) -{ - /* Create A in CSC_matrix format (3 rows, 4 cols, 4 nonzeros) */ - CSC_matrix *A = new_CSC_matrix(3, 4, 4); - int Ap[5] = {0, 1, 2, 3, 4}; - int Ai[4] = {0, 1, 2, 0}; - memcpy(A->p, Ap, 5 * sizeof(int)); - memcpy(A->i, Ai, 4 * sizeof(int)); - CSR_matrix *C = ATA_alloc(A); - - int expected_p[5] = {0, 2, 3, 4, 6}; - int expected_i[6] = {0, 3, 1, 2, 0, 3}; - - mu_assert("p incorrect", cmp_int_array(C->p, expected_p, 5)); - mu_assert("i incorrect", cmp_int_array(C->i, expected_i, C->nnz)); - mu_assert("nnz incorrect", C->nnz == 6); - - free_CSR_matrix(C); - free_CSC_matrix(A); - - return 0; -} - -const char *test_ATA_alloc_random(void) -{ - /* Create A in CSC_matrix format */ - CSC_matrix *A = new_CSC_matrix(10, 15, 15); - int Ap[16] = {0, 1, 1, 1, 1, 4, 5, 6, 7, 8, 9, 11, 11, 11, 13, 15}; - int Ai[15] = {5, 0, 6, 9, 0, 5, 1, 3, 6, 0, 6, 3, 6, 6, 8}; - double Ax[15] = {7, 4, 8, 5, 7, 3, 7, 8, 5, 4, 8, 8, 3, 6, 5}; - memcpy(A->p, Ap, 16 * sizeof(int)); - memcpy(A->i, Ai, 15 * sizeof(int)); - memcpy(A->x, Ax, 15 * sizeof(double)); - CSR_matrix *C = ATA_alloc(A); - - int expected_p[16] = {0, 2, 2, 2, 2, 8, 11, 13, 14, 16, 21, 27, 27, 27, 33, 38}; - int expected_i[38] = {0, 6, 4, 5, 9, 10, 13, 14, 4, 5, 10, 0, 6, - 7, 8, 13, 4, 9, 10, 13, 14, 4, 5, 9, 10, 13, - 14, 4, 8, 9, 10, 13, 14, 4, 9, 10, 13, 14}; - - mu_assert("p incorrect", cmp_int_array(C->p, expected_p, 16)); - mu_assert("i incorrect", cmp_int_array(C->i, expected_i, C->nnz)); - mu_assert("nnz incorrect", C->nnz == 38); - - double d[10] = {2, 8, 6, 2, 5, 1, 6, 9, 1, 3}; - - ATDA_fill_values(A, d, C); - - double Cx_correct[38] = { - 49., 21., 491., 56., 240., 416., 144., 288., 56., 98., 56., 21., 9., - 392., 128., 128., 240., 150., 240., 90., 180., 416., 56., 240., 416., 144., - 288., 144., 128., 90., 144., 182., 108., 288., 180., 288., 108., 241.}; - mu_assert("x incorrect", cmp_double_array(C->x, Cx_correct, C->nnz)); - - free_CSR_matrix(C); - free_CSC_matrix(A); - - return 0; -} - -const char *test_ATA_alloc_random2(void) -{ - /* Create A in CSC_matrix format */ - int m = 15; - int n = 10; - CSC_matrix *A = new_CSC_matrix(m, n, 15); - int Ap[11] = {0, 2, 4, 6, 6, 9, 12, 12, 14, 14, 15}; - int Ai[15] = {9, 12, 3, 4, 1, 6, 4, 8, 13, 1, 3, 7, 5, 13, 6}; - double Ax[15] = {0.99, 0.9, 0.51, 0.64, 0.39, 0.29, 0.26, 0.91, - 0.35, 0.18, 0.33, 0.73, 0.97, 0.86, 1.03}; - memcpy(A->p, Ap, 11 * sizeof(int)); - memcpy(A->i, Ai, 15 * sizeof(int)); - memcpy(A->x, Ax, 15 * sizeof(double)); - CSR_matrix *C = ATA_alloc(A); - - int expected_p[11] = {0, 1, 4, 7, 7, 10, 13, 13, 15, 15, 17}; - int expected_i[17] = {0, 1, 4, 5, 2, 5, 9, 1, 4, 7, 1, 2, 5, 4, 7, 2, 9}; - - mu_assert("p incorrect", cmp_int_array(C->p, expected_p, 11)); - mu_assert("i incorrect", cmp_int_array(C->i, expected_i, C->nnz)); - mu_assert("nnz incorrect", C->nnz == 17); - double d[15] = {-0.6, -0.23, -0.29, -1.36, 0.4, 0.36, 0.11, -0.13, - -1.32, -0.32, -0.24, -0.7, -0.06, 0.5, 1.99}; - - ATDA_fill_values(A, d, C); - - double Cx_correct[17] = {-0.362232, -0.189896, 0.06656, -0.228888, -0.025732, - -0.016146, 0.032857, 0.06656, -1.004802, 0.1505, - -0.228888, -0.016146, -0.224833, 0.1505, 0.708524, - 0.032857, 0.116699}; - mu_assert("x incorrect", cmp_double_array(C->x, Cx_correct, C->nnz)); - - free_CSR_matrix(C); - free_CSC_matrix(A); - - return 0; -} -const char *test_BTA_alloc_and_BTDA_fill(void) -{ - /* Create A: 4x3 CSC_matrix matrix - * [1.0 0.0 2.0] - * [0.0 3.0 0.0] - * [4.0 0.0 5.0] - * [0.0 6.0 0.0] - */ - int m = 4; - int n = 3; - CSC_matrix *A = new_CSC_matrix(m, n, 6); - int Ap_A[4] = {0, 2, 4, 6}; - int Ai_A[6] = {0, 2, 1, 3, 0, 2}; - double Ax_A[6] = {1.0, 4.0, 3.0, 6.0, 2.0, 5.0}; - memcpy(A->p, Ap_A, 4 * sizeof(int)); - memcpy(A->i, Ai_A, 6 * sizeof(int)); - memcpy(A->x, Ax_A, 6 * sizeof(double)); - - /* Create B: 4x2 CSC_matrix matrix - * [1.0 0.0] - * [0.0 2.0] - * [3.0 0.0] - * [0.0 4.0] - */ - int p = 2; - CSC_matrix *B = new_CSC_matrix(m, p, 4); - int Bp[3] = {0, 2, 4}; - int Bi[4] = {0, 2, 1, 3}; - double Bx[4] = {1.0, 3.0, 2.0, 4.0}; - memcpy(B->p, Bp, 3 * sizeof(int)); - memcpy(B->i, Bi, 4 * sizeof(int)); - memcpy(B->x, Bx, 4 * sizeof(double)); - - /* Allocate C = B^T A (should be 2x3) */ - CSR_matrix *C = BTA_alloc(A, B); - - /* Sparsity pattern check before filling values */ - int expected_p[3] = {0, 2, 3}; - int expected_i[3] = {0, 2, 1}; - mu_assert("C dimensions incorrect", C->m == 2 && C->n == 3); - mu_assert("C nnz incorrect", C->nnz == 3); - mu_assert("C->p incorrect", cmp_int_array(C->p, expected_p, 3)); - mu_assert("C->i incorrect", cmp_int_array(C->i, expected_i, 3)); - - /* Fill values with diagonal weights d */ - double d[4] = {1.0, 2.0, 3.0, 4.0}; - BTDA_fill_values(A, B, d, C); - - double expected_x[3] = {37.0, 47.0, 108.0}; - mu_assert("C values incorrect", cmp_double_array(C->x, expected_x, 3)); - - free_CSR_matrix(C); - free_CSC_matrix(A); - free_CSC_matrix(B); - - return 0; -} diff --git a/sparsediff_linalg/tests/test_csr_csc_conversion.h b/sparsediff_linalg/tests/test_csr_csc_conversion.h deleted file mode 100644 index 66b14d43..00000000 --- a/sparsediff_linalg/tests/test_csr_csc_conversion.h +++ /dev/null @@ -1,171 +0,0 @@ -#include -#include -#include -#include - -#include "minunit.h" -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "test_helpers.h" - -/* Test CSR_matrix to CSC_matrix conversion with fill_sparsity and fill_values */ -const char *test_csr_to_csc_split(void) -{ - /* Create a 4x5 CSR_matrix matrix A: - * [1.0 0.0 0.0 0.0 1.0] - * [0.0 0.0 3.0 0.0 0.0] - * [0.0 2.0 0.0 0.0 0.0] - * [0.0 0.0 0.0 4.0 0.0] - */ - CSR_matrix *A = new_CSR_matrix(4, 5, 5); - double Ax[5] = {1.0, 1.0, 3.0, 2.0, 4.0}; - int Ai[5] = {0, 4, 2, 1, 3}; - int Ap[5] = {0, 2, 3, 4, 5}; - memcpy(A->x, Ax, 5 * sizeof(double)); - memcpy(A->i, Ai, 5 * sizeof(int)); - memcpy(A->p, Ap, 5 * sizeof(int)); - - /* Allocate workspace */ - int *iwork = (int *) malloc(A->n * sizeof(int)); - - /* First, fill sparsity pattern */ - CSC_matrix *C = csr_to_csc_alloc(A, iwork); - - /* Check sparsity pattern */ - int Cp_correct[6] = {0, 1, 2, 3, 4, 5}; - int Ci_correct[5] = {0, 2, 1, 3, 0}; - - mu_assert("C col pointers incorrect", cmp_int_array(C->p, Cp_correct, 6)); - mu_assert("C row indices incorrect", cmp_int_array(C->i, Ci_correct, 5)); - - /* Now fill values */ - csr_to_csc_fill_values(A, C, iwork); - - /* Check values */ - double Cx_correct[5] = {1.0, 2.0, 3.0, 4.0, 1.0}; - - mu_assert("C vals incorrect", cmp_double_array(C->x, Cx_correct, 5)); - - free(iwork); - free_CSR_matrix(A); - free_CSC_matrix(C); - - return 0; -} - -/* Test CSC_matrix to CSR_matrix conversion with fill_sparsity */ -const char *test_csc_to_csr_sparsity(void) -{ - /* Create a 4x5 CSC_matrix matrix A: - * [1.0 0.0 0.0 0.0 2.0] - * [0.0 0.0 3.0 0.0 0.0] - * [0.0 4.0 0.0 0.0 0.0] - * [0.0 0.0 0.0 5.0 0.0] - */ - CSC_matrix *A = new_CSC_matrix(4, 5, 5); - double Ax[5] = {1.0, 4.0, 3.0, 5.0, 2.0}; - int Ai[5] = {0, 2, 1, 3, 0}; - int Ap[6] = {0, 1, 2, 3, 4, 5}; - memcpy(A->x, Ax, 5 * sizeof(double)); - memcpy(A->i, Ai, 5 * sizeof(int)); - memcpy(A->p, Ap, 6 * sizeof(int)); - - /* Allocate workspace */ - int *iwork = (int *) malloc(A->m * sizeof(int)); - - /* Fill sparsity pattern */ - CSR_matrix *C = csc_to_csr_alloc(A, iwork); - - /* Expected CSR_matrix format: - * Row 0: [1.0 at col 0, 2.0 at col 4] - * Row 1: [3.0 at col 2] - * Row 2: [4.0 at col 1] - * Row 3: [5.0 at col 3] - */ - int Cp_correct[5] = {0, 2, 3, 4, 5}; - int Ci_correct[5] = {0, 4, 2, 1, 3}; - - mu_assert("C row pointers incorrect", cmp_int_array(C->p, Cp_correct, 5)); - mu_assert("C col indices incorrect", cmp_int_array(C->i, Ci_correct, 5)); - mu_assert("C dimensions incorrect", C->m == 4 && C->n == 5); - mu_assert("C nnz incorrect", C->nnz == 5); - - free(iwork); - free_CSC_matrix(A); - free_CSR_matrix(C); - - return 0; -} - -/* Test CSC_matrix to CSR_matrix conversion with fill_values */ -const char *test_csc_to_csr_values(void) -{ - /* Create a 4x5 CSC_matrix matrix A */ - CSC_matrix *A = new_CSC_matrix(4, 5, 5); - double Ax[5] = {1.0, 4.0, 3.0, 5.0, 2.0}; - int Ai[5] = {0, 2, 1, 3, 0}; - int Ap[6] = {0, 1, 2, 3, 4, 5}; - memcpy(A->x, Ax, 5 * sizeof(double)); - memcpy(A->i, Ai, 5 * sizeof(int)); - memcpy(A->p, Ap, 6 * sizeof(int)); - - /* Allocate workspace */ - int *iwork = (int *) malloc(A->m * sizeof(int)); - - /* Fill sparsity pattern */ - CSR_matrix *C = csc_to_csr_alloc(A, iwork); - - /* Fill values */ - csc_to_csr_fill_values(A, C, iwork); - - /* Check values */ - double Cx_correct[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; - - mu_assert("C vals incorrect", cmp_double_array(C->x, Cx_correct, 5)); - - free(iwork); - free_CSC_matrix(A); - free_CSR_matrix(C); - - return 0; -} - -/* Test round-trip conversion: CSR_matrix -> CSC_matrix -> CSR_matrix */ -const char *test_csr_csc_csr_roundtrip(void) -{ - /* Create a 3x4 CSR_matrix matrix A: - * [1.0 2.0 0.0 3.0] - * [0.0 4.0 5.0 0.0] - * [6.0 0.0 7.0 8.0] - */ - CSR_matrix *A = new_CSR_matrix(3, 4, 8); - double Ax[8] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}; - int Ai[8] = {0, 1, 3, 1, 2, 0, 2, 3}; - int Ap[4] = {0, 3, 5, 8}; - memcpy(A->x, Ax, 8 * sizeof(double)); - memcpy(A->i, Ai, 8 * sizeof(int)); - memcpy(A->p, Ap, 4 * sizeof(int)); - - /* Convert CSR_matrix to CSC_matrix */ - int *iwork_csc = (int *) malloc(A->n * sizeof(int)); - CSC_matrix *B = csr_to_csc_alloc(A, iwork_csc); - csr_to_csc_fill_values(A, B, iwork_csc); - - /* Convert CSC_matrix back to CSR_matrix */ - int *iwork_csr = (int *) malloc(B->m * sizeof(int)); - CSR_matrix *C = csc_to_csr_alloc(B, iwork_csr); - csc_to_csr_fill_values(B, C, iwork_csr); - - /* C should match A */ - mu_assert("Round-trip: vals incorrect", cmp_double_array(C->x, Ax, 8)); - mu_assert("Round-trip: col indices incorrect", cmp_int_array(C->i, Ai, 8)); - mu_assert("Round-trip: row pointers incorrect", cmp_int_array(C->p, Ap, 4)); - - free(iwork_csc); - free(iwork_csr); - free_CSR_matrix(A); - free_CSC_matrix(B); - free_CSR_matrix(C); - - return 0; -} diff --git a/sparsediff_linalg/tests/test_csr_matrix.h b/sparsediff_linalg/tests/test_csr_matrix.h deleted file mode 100644 index 4dbdd3c2..00000000 --- a/sparsediff_linalg/tests/test_csr_matrix.h +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include - -#include "minunit.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "test_helpers.h" - -const char *test_transpose(void) -{ - CSR_matrix *A = new_CSR_matrix(4, 5, 5); - double Ax[5] = {1.0, 1.0, 3.0, 2.0, 4.0}; - int Ai[5] = {0, 4, 1, 0, 1}; - int Ap[5] = {0, 2, 3, 4, 5}; - memcpy(A->x, Ax, 5 * sizeof(double)); - memcpy(A->i, Ai, 5 * sizeof(int)); - memcpy(A->p, Ap, 5 * sizeof(int)); - - int iwork[5]; - CSR_matrix *AT = transpose(A, iwork); - double ATx_correct[5] = {1.0, 2.0, 3.0, 4.0, 1.0}; - int ATi_correct[5] = {0, 2, 1, 3, 0}; - int ATp_correct[6] = {0, 2, 4, 4, 4, 5}; - mu_assert("AT vals incorrect", cmp_double_array(AT->x, ATx_correct, 5)); - mu_assert("AT cols incorrect", cmp_int_array(AT->i, ATi_correct, 5)); - mu_assert("AT rows incorrect", cmp_int_array(AT->p, ATp_correct, 6)); - - free_CSR_matrix(A); - free_CSR_matrix(AT); - - return 0; -} - -const char *test_AT_alloc_and_fill(void) -{ - /* Create a 3x4 CSR_matrix matrix A: - * [1.0 0.0 2.0 0.0] - * [0.0 3.0 0.0 4.0] - * [5.0 0.0 6.0 0.0] - */ - CSR_matrix *A = new_CSR_matrix(3, 4, 6); - double Ax[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - int Ai[6] = {0, 2, 1, 3, 0, 2}; - int Ap[4] = {0, 2, 4, 6}; - memcpy(A->x, Ax, 6 * sizeof(double)); - memcpy(A->i, Ai, 6 * sizeof(int)); - memcpy(A->p, Ap, 4 * sizeof(int)); - - /* Allocate A^T (should be 4x3) */ - int *iwork = (int *) malloc(A->n * sizeof(int)); - CSR_matrix *AT = AT_alloc(A, iwork); - - /* Fill values of A^T */ - AT_fill_values(A, AT, iwork); - - /* Expected A^T: - * [1.0 0.0 5.0] - * [0.0 3.0 0.0] - * [2.0 0.0 6.0] - * [0.0 4.0 0.0] - */ - double ATx_correct[6] = {1.0, 5.0, 3.0, 2.0, 6.0, 4.0}; - int ATi_correct[6] = {0, 2, 1, 0, 2, 1}; - int ATp_correct[5] = {0, 2, 3, 5, 6}; - - mu_assert("AT dimensions incorrect", AT->m == 4 && AT->n == 3); - mu_assert("AT nnz incorrect", AT->nnz == 6); - mu_assert("AT vals incorrect", cmp_double_array(AT->x, ATx_correct, 6)); - mu_assert("AT cols incorrect", cmp_int_array(AT->i, ATi_correct, 6)); - mu_assert("AT rows incorrect", cmp_int_array(AT->p, ATp_correct, 5)); - - free_CSR_matrix(A); - free_CSR_matrix(AT); - free(iwork); - - return 0; -} diff --git a/sparsediff_linalg/tests/test_helpers.c b/sparsediff_linalg/tests/test_helpers.c deleted file mode 100644 index 01c55bd0..00000000 --- a/sparsediff_linalg/tests/test_helpers.c +++ /dev/null @@ -1,158 +0,0 @@ -#include -#include -#include -#include - -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/matrix.h" - -#define EPSILON 1e-7 - -#define ABS_TOL 1e-6 -#define REL_TOL 1e-6 - -int is_equal_double(double a, double b) -{ - return fabs(a - b) <= fmax(ABS_TOL, REL_TOL * fmax(fabs(a), fabs(b))); -} - -int cmp_double_array(const double *actual, const double *expected, int size) -{ - for (int i = 0; i < size; i++) - { - if (!is_equal_double(actual[i], expected[i])) - { - printf(" FAILED: actual[%d] = %f, expected %f\n", i, actual[i], - expected[i]); - return 0; - } - } - return 1; -} - -int cmp_int_array(const int *actual, const int *expected, int size) -{ - for (int i = 0; i < size; i++) - { - if (actual[i] != expected[i]) - { - printf(" FAILED: actual[%d] = %d, expected %d\n", i, actual[i], - expected[i]); - return 0; - } - } - return 1; -} - -int cmp_sparsity(matrix *M, const int *exp_p, const int *exp_i, int m, int nnz) -{ - if (M->m != m) - { - printf(" FAILED: M->m = %d, expected %d\n", M->m, m); - return 0; - } - if (M->nnz != nnz) - { - printf(" FAILED: M->nnz = %d, expected %d\n", M->nnz, nnz); - return 0; - } - CSR_matrix *csr = M->to_csr(M); - return cmp_int_array(csr->p, exp_p, m + 1) && cmp_int_array(csr->i, exp_i, nnz); -} - -int cmp_values(const matrix *M, const double *exp_x, int nnz) -{ - if (M->nnz != nnz) - { - printf(" FAILED: M->nnz = %d, expected %d\n", M->nnz, nnz); - return 0; - } - return cmp_double_array(M->x, exp_x, nnz); -} - -int csr_is_valid(const CSR_matrix *A) -{ - if (A->p[0] != 0) - { - printf(" FAILED: p[0] = %d, expected 0\n", A->p[0]); - return 0; - } - for (int i = 0; i < A->m; i++) - { - if (A->p[i] > A->p[i + 1]) - { - printf(" FAILED: p[%d] = %d > p[%d] = %d\n", i, A->p[i], i + 1, - A->p[i + 1]); - return 0; - } - } - if (A->p[A->m] != A->nnz) - { - printf(" FAILED: p[m] = %d, but nnz = %d\n", A->p[A->m], A->nnz); - return 0; - } - for (int jj = 0; jj < A->nnz; jj++) - { - if (A->i[jj] < 0 || A->i[jj] >= A->n) - { - printf(" FAILED: i[%d] = %d out of range [0, %d)\n", jj, A->i[jj], - A->n); - return 0; - } - } - return 1; -} - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -/* Standard normal via Box-Muller transform */ -static double randn(void) -{ - double u1 = ((double) rand() + 1.0) / ((double) RAND_MAX + 1.0); - double u2 = ((double) rand() + 1.0) / ((double) RAND_MAX + 1.0); - return sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2); -} - -CSR_matrix *new_csr_random(int m, int n, double density) -{ - /* Single pass: over-allocate, fill, then copy to exact size */ - int cap = (int) ((double) m * (double) n * density * 1.5) + m; - int *tmp_p = (int *) malloc(((size_t) m + 1) * sizeof(int)); - int *tmp_i = (int *) malloc((size_t) cap * sizeof(int)); - double *tmp_x = (double *) malloc((size_t) cap * sizeof(double)); - - int nnz = 0; - for (int r = 0; r < m; r++) - { - tmp_p[r] = nnz; - for (int c = 0; c < n; c++) - { - double u = (double) rand() / (double) RAND_MAX; - if (u < density) - { - if (nnz >= cap) - { - cap *= 2; - tmp_i = (int *) realloc(tmp_i, (size_t) cap * sizeof(int)); - tmp_x = (double *) realloc(tmp_x, (size_t) cap * sizeof(double)); - } - tmp_i[nnz] = c; - tmp_x[nnz] = randn(); - nnz++; - } - } - } - tmp_p[m] = nnz; - - CSR_matrix *A = new_CSR_matrix(m, n, nnz); - memcpy(A->p, tmp_p, ((size_t) m + 1) * sizeof(int)); - memcpy(A->i, tmp_i, (size_t) nnz * sizeof(int)); - memcpy(A->x, tmp_x, (size_t) nnz * sizeof(double)); - - free(tmp_p); - free(tmp_i); - free(tmp_x); - return A; -} diff --git a/sparsediff_linalg/tests/test_helpers.h b/sparsediff_linalg/tests/test_helpers.h deleted file mode 100644 index 01bcba6c..00000000 --- a/sparsediff_linalg/tests/test_helpers.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef TEST_HELPERS_H -#define TEST_HELPERS_H - -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/matrix.h" - -/* Compare two double arrays directly - * Returns 1 if all values match, 0 otherwise */ -int cmp_double_array(const double *actual, const double *expected, int size); - -/* Compare two int arrays directly - * Returns 1 if all values match, 0 otherwise */ -int cmp_int_array(const int *actual, const int *expected, int size); - -/* Verify M has shape (m, *) with exactly nnz entries, and that M's CSR_matrix - * row pointers and column indices match exp_p (length m+1) and exp_i - * (length nnz). Returns 1 on full match, 0 otherwise. */ -int cmp_sparsity(matrix *M, const int *exp_p, const int *exp_i, int m, int nnz); - -/* Verify M has nnz entries and that its value array matches exp_x of - * length nnz. Returns 1 on full match, 0 otherwise. */ -int cmp_values(const matrix *M, const double *exp_x, int nnz); - -/* Check the CSR invariants: p[0] == 0, p nondecreasing, p[m] == nnz, and - * all column indices in [0, n). Returns 1 if valid, 0 otherwise. Use on - * hand-built fixtures before handing them to the code under test. */ -int csr_is_valid(const CSR_matrix *A); - -/* Create a random m x n CSR_matrix matrix with approximate nonzero density - * in [0, 1]. Nonzero values are standard Gaussian (Box-Muller). */ -CSR_matrix *new_csr_random(int m, int n, double density); - -/* Only available with -DSP_TRACK_MEMORY=ON: reads the tracked allocator - * counters, which do not exist in a default build. */ -#ifdef SP_TRACK_MEMORY -#include "sparsediff_linalg/tracked_alloc.h" - -/* No-alloc-in-fill contract: after alloc and one warm-up fill, a second fill - * must not touch the tracked allocator at all. Any transient sp_malloc inside - * the fill raises g_peak_bytes above the baseline even if freed before - * returning; a permanent one raises g_allocated_bytes. */ -static inline int fill_is_alloc_free(void (*fill)(const void *ctx), const void *ctx) -{ - size_t base = g_allocated_bytes; - g_peak_bytes = base; - fill(ctx); - return g_allocated_bytes == base && g_peak_bytes == base; -} -#endif - -#endif /* TEST_HELPERS_H */ diff --git a/sparsediff_linalg/tests/test_linalg_sparse_matmuls.h b/sparsediff_linalg/tests/test_linalg_sparse_matmuls.h deleted file mode 100644 index 527c51f3..00000000 --- a/sparsediff_linalg/tests/test_linalg_sparse_matmuls.h +++ /dev/null @@ -1,649 +0,0 @@ -#include -#include -#include -#include - -#include "minunit.h" -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/iVec.h" -#include "sparsediff_linalg/linalg_sparse_matmuls.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include "test_helpers.h" - -/* Test block_left_multiply_fill_sparsity with simple case: single block */ -const char *test_block_left_multiply_single_block(void) -{ - /* A is 2x3 CSR_matrix: - * [1.0 0.0 0.0] - * [0.0 1.0 1.0] - */ - CSR_matrix *A = new_CSR_matrix(2, 3, 3); - double Ax[3] = {1.0, 1.0, 1.0}; - int Ai[3] = {0, 1, 2}; - int Ap[3] = {0, 1, 3}; - memcpy(A->x, Ax, 3 * sizeof(double)); - memcpy(A->i, Ai, 3 * sizeof(int)); - memcpy(A->p, Ap, 3 * sizeof(int)); - - /* J is 3x2 CSC_matrix (single block, so p=1): - * [1.0 0.0] - * [1.0 0.0] - * [0.0 1.0] - */ - CSC_matrix *J = new_CSC_matrix(3, 2, 3); - double Jx[3] = {1.0, 1.0, 1.0}; - int Ji[3] = {0, 1, 2}; - int Jp[3] = {0, 2, 3}; - memcpy(J->x, Jx, 3 * sizeof(double)); - memcpy(J->i, Ji, 3 * sizeof(int)); - memcpy(J->p, Jp, 3 * sizeof(int)); - - /* Compute C = A @ J1 (p=1 means just one block) */ - CSC_matrix *C = block_left_multiply_fill_sparsity(A, J, 1); - - /* Expected C is 2x2: - * C[0,0] = A[0,:] @ J[:,0] = 1.0 * 1.0 = 1.0 (row 0 has column 0, J col 0 has - * row 0) C[1,0] = A[1,:] @ J[:,0] = 1.0*1.0 + 1.0*0.0 = 1.0 (row 1 has columns - * 1,2, J col 0 has row 1) C[0,1] = A[0,:] @ J[:,1] = 0.0 (row 0 has column 0, J - * col 1 has row 2) C[1,1] = A[1,:] @ J[:,1] = 1.0*1.0 = 1.0 (row 1 has columns - * 1,2, J col 1 has row 2) - */ - int expected_p[3] = {0, 2, 3}; - int expected_i[3] = {0, 1, 1}; - - mu_assert("C dims incorrect", C->m == 2 && C->n == 2 && C->nnz == 3); - mu_assert("C col pointers incorrect", cmp_int_array(C->p, expected_p, 3)); - mu_assert("C row indices incorrect", cmp_int_array(C->i, expected_i, 3)); - - free_CSC_matrix(C); - free_CSR_matrix(A); - free_CSC_matrix(J); - return NULL; -} - -/* Test block_left_multiply_fill_sparsity with two blocks */ -const char *test_block_left_multiply_two_blocks(void) -{ - /* A is 2x2 CSR_matrix: - * [1.0 0.0] - * [0.0 1.0] - */ - CSR_matrix *A = new_CSR_matrix(2, 2, 2); - double Ax[2] = {1.0, 1.0}; - int Ai[2] = {0, 1}; - int Ap[3] = {0, 1, 2}; - memcpy(A->x, Ax, 2 * sizeof(double)); - memcpy(A->i, Ai, 2 * sizeof(int)); - memcpy(A->p, Ap, 3 * sizeof(int)); - - /* J is 4x3 CSC_matrix (two blocks of 2 rows each): - * Block 1 rows [0,1]: - * [1.0 0.0 0.0] - * [0.0 0.0 0.0] - * Block 2 rows [2,3]: - * [0.0 1.0 0.0] - * [0.0 0.0 1.0] - * So J is: - * [1.0 0.0 0.0] - * [0.0 0.0 0.0] - * [0.0 1.0 0.0] - * [0.0 0.0 1.0] - */ - CSC_matrix *J = new_CSC_matrix(4, 3, 3); - double Jx[3] = {1.0, 1.0, 1.0}; - int Ji[3] = {0, 2, 3}; - int Jp[4] = {0, 1, 2, 3}; - memcpy(J->x, Jx, 3 * sizeof(double)); - memcpy(J->i, Ji, 3 * sizeof(int)); - memcpy(J->p, Jp, 4 * sizeof(int)); - - /* Compute C = [A @ J1; A @ J2] where: - * J1 = [[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]] - * J2 = [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] - * - * C = [A @ J1; A @ J2] is 4x3: - * A @ J1 = [[1, 0, 0], [0, 0, 0]] (row 0 of A matches col 0 of J1) - * A @ J2 = [[0, 0, 0], [0, 1, 1]] (row 1 of A matches cols 1,2 of J2) - * So C is: - * [1.0 0.0 0.0] - * [0.0 0.0 0.0] - * [0.0 0.0 0.0] - * [0.0 1.0 1.0] - */ - CSC_matrix *C = block_left_multiply_fill_sparsity(A, J, 2); - block_left_multiply_fill_values(A, J, C); - - int expected_p2[4] = {0, 1, 2, 3}; - int expected_i2[3] = {0, 2, 3}; - double expected_x2[3] = {1.0, 1.0, 1.0}; - - mu_assert("C dims incorrect", C->m == 4 && C->n == 3 && C->nnz == 3); - mu_assert("C col pointers incorrect", cmp_int_array(C->p, expected_p2, 4)); - mu_assert("C row indices incorrect", cmp_int_array(C->i, expected_i2, 3)); - mu_assert("C values incorrect", cmp_double_array(C->x, expected_x2, 3)); - - free_CSC_matrix(C); - free_CSR_matrix(A); - free_CSC_matrix(J); - return NULL; -} - -/* Test block_left_multiply_fill_sparsity with all zero column in J */ -const char *test_block_left_multiply_zero_column(void) -{ - /* A is 2x2 CSR_matrix (identity) */ - CSR_matrix *A = new_CSR_matrix(2, 2, 2); - double Ax[2] = {1.0, 1.0}; - int Ai[2] = {0, 1}; - int Ap[3] = {0, 1, 2}; - memcpy(A->x, Ax, 2 * sizeof(double)); - memcpy(A->i, Ai, 2 * sizeof(int)); - memcpy(A->p, Ap, 3 * sizeof(int)); - - /* J is 2x2 with an empty column: - * [1.0 0.0] - * [0.0 0.0] - */ - CSC_matrix *J = new_CSC_matrix(2, 2, 1); - double Jx[1] = {1.0}; - int Ji[1] = {0}; - int Jp[3] = {0, 1, 1}; /* Column 0 has one nonzero, column 1 is empty */ - memcpy(J->x, Jx, 1 * sizeof(double)); - memcpy(J->i, Ji, 1 * sizeof(int)); - memcpy(J->p, Jp, 3 * sizeof(int)); - - CSC_matrix *C = block_left_multiply_fill_sparsity(A, J, 1); - - int expected_p3[3] = {0, 1, 1}; - int expected_i3[1] = {0}; - - mu_assert("C dims incorrect", C->m == 2 && C->n == 2 && C->nnz == 1); - mu_assert("C col pointers incorrect", cmp_int_array(C->p, expected_p3, 3)); - mu_assert("C row indices incorrect", cmp_int_array(C->i, expected_i3, 1)); - - free_CSC_matrix(C); - free_CSR_matrix(A); - free_CSC_matrix(J); - return NULL; -} - -/* Test csr_csc_matmul_alloc: C = A @ B where A is CSR_matrix and B is CSC_matrix */ -const char *test_csr_csc_matmul_alloc_basic(void) -{ - /* A is 3x2 CSR_matrix: - * [1.0 0.0] - * [0.0 1.0] - * [1.0 1.0] - */ - CSR_matrix *A = new_CSR_matrix(3, 2, 4); - double Ax[4] = {1.0, 1.0, 1.0, 1.0}; - int Ai[4] = {0, 1, 0, 1}; - int Ap[4] = {0, 1, 2, 4}; - memcpy(A->x, Ax, 4 * sizeof(double)); - memcpy(A->i, Ai, 4 * sizeof(int)); - memcpy(A->p, Ap, 4 * sizeof(int)); - - /* B is 2x3 CSC_matrix: - * [1.0 0.0 1.0] - * [0.0 1.0 1.0] - */ - CSC_matrix *B = new_CSC_matrix(2, 3, 4); - double Bx[4] = {1.0, 1.0, 1.0, 1.0}; - int Bi[4] = {0, 1, 0, 1}; - int Bp[4] = {0, 1, 2, 4}; - memcpy(B->x, Bx, 4 * sizeof(double)); - memcpy(B->i, Bi, 4 * sizeof(int)); - memcpy(B->p, Bp, 4 * sizeof(int)); - - /* C = A @ B is 3x3: - * C = [[1, 0, 1], - * [0, 1, 1], - * [1, 1, 2]] - */ - CSR_matrix *C = csr_csc_matmul_alloc(A, B); - - int expected_p4[4] = {0, 2, 4, 7}; - int expected_i4[7] = {0, 2, 1, 2, 0, 1, 2}; - - mu_assert("C dims incorrect", C->m == 3 && C->n == 3 && C->nnz == 7); - mu_assert("C row pointers incorrect", cmp_int_array(C->p, expected_p4, 4)); - mu_assert("C col indices incorrect", cmp_int_array(C->i, expected_i4, 7)); - - free_CSR_matrix(C); - free_CSR_matrix(A); - free_CSC_matrix(B); - return NULL; -} - -/* Test csr_csc_matmul_alloc with sparse result */ -const char *test_csr_csc_matmul_alloc_sparse(void) -{ - /* A is 2x3 CSR_matrix: - * [1.0 0.0 0.0] - * [0.0 0.0 1.0] - */ - CSR_matrix *A = new_CSR_matrix(2, 3, 2); - double Ax[2] = {1.0, 1.0}; - int Ai[2] = {0, 2}; - int Ap[3] = {0, 1, 2}; - memcpy(A->x, Ax, 2 * sizeof(double)); - memcpy(A->i, Ai, 2 * sizeof(int)); - memcpy(A->p, Ap, 3 * sizeof(int)); - - /* B is 3x2 CSC_matrix: - * [1.0 0.0] - * [0.0 0.0] - * [0.0 1.0] - */ - CSC_matrix *B = new_CSC_matrix(3, 2, 2); - double Bx[2] = {1.0, 1.0}; - int Bi[2] = {0, 2}; - int Bp[3] = {0, 1, 2}; - memcpy(B->x, Bx, 2 * sizeof(double)); - memcpy(B->i, Bi, 2 * sizeof(int)); - memcpy(B->p, Bp, 3 * sizeof(int)); - - /* C = A @ B is 2x2: - * C = [[1, 0], - * [0, 1]] - */ - CSR_matrix *C = csr_csc_matmul_alloc(A, B); - - int expected_p5[3] = {0, 1, 2}; - int expected_i5[2] = {0, 1}; - - mu_assert("C dims incorrect", C->m == 2 && C->n == 2 && C->nnz == 2); - mu_assert("C row pointers incorrect", cmp_int_array(C->p, expected_p5, 3)); - mu_assert("C col indices incorrect", cmp_int_array(C->i, expected_i5, 2)); - - free_CSR_matrix(C); - free_CSR_matrix(A); - free_CSC_matrix(B); - return NULL; -} - -/* Dedup + ordering: one block whose child entries hit the same A row through - * several columns (dedup), with CSC column lists that interleave so a missing - * sort would emit rows out of ascending order. */ -const char *test_block_left_multiply_dedup_order(void) -{ - /* A is 4x3 CSR_matrix: - * [1.0 0.0 2.0] CSC col 0: rows {0, 2} - * [0.0 3.0 0.0] CSC col 1: rows {1, 3} - * [4.0 0.0 0.0] CSC col 2: rows {0, 3} - * [0.0 5.0 6.0] - */ - CSR_matrix *A = new_CSR_matrix(4, 3, 6); - double Ax[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - int Ai[6] = {0, 2, 1, 0, 1, 2}; - int Ap[5] = {0, 2, 3, 4, 6}; - memcpy(A->x, Ax, 6 * sizeof(double)); - memcpy(A->i, Ai, 6 * sizeof(int)); - memcpy(A->p, Ap, 5 * sizeof(int)); - - /* J is 3x1 dense column (p=1): child entries 0, 1, 2. Gathering CSC columns - * in child order visits rows 0,2 then 1,3 then 0,3 — rows 0 and 3 twice - * (dedup) and out of order (sort). Expected: all four rows, ascending. */ - CSC_matrix *J = new_CSC_matrix(3, 1, 3); - double Jx[3] = {1.0, 1.0, 1.0}; - int Ji[3] = {0, 1, 2}; - int Jp[2] = {0, 3}; - memcpy(J->x, Jx, 3 * sizeof(double)); - memcpy(J->i, Ji, 3 * sizeof(int)); - memcpy(J->p, Jp, 2 * sizeof(int)); - - CSC_matrix *C = block_left_multiply_fill_sparsity(A, J, 1); - - int expected_p[2] = {0, 4}; - int expected_i[4] = {0, 1, 2, 3}; - - mu_assert("C dims incorrect", C->m == 4 && C->n == 1 && C->nnz == 4); - mu_assert("C col pointers incorrect", cmp_int_array(C->p, expected_p, 2)); - mu_assert("C row indices incorrect", cmp_int_array(C->i, expected_i, 4)); - - free_CSC_matrix(C); - free_CSR_matrix(A); - free_CSC_matrix(J); - return NULL; -} - -/* Reference implementation of the block sparsity (the original per-row - * has_overlap scan) used to cross-check the gather rewrite on random input. */ -static CSC_matrix *block_left_multiply_fill_sparsity_ref(const CSR_matrix *A, - const CSC_matrix *J, int p) -{ - int m = A->m; - int n = A->n; - - int *Cp = (int *) sp_malloc((J->n + 1) * sizeof(int)); - iVec *Ci = iVec_new(J->nnz > 0 ? J->nnz : 1); - Cp[0] = 0; - - for (int j = 0; j < J->n; j++) - { - if (J->p[j] == J->p[j + 1]) - { - Cp[j + 1] = Cp[j]; - continue; - } - int jj = J->p[j]; - for (int block = 0; block < p; block++) - { - int block_start = block * n; - int block_end = block_start + n; - while (jj < J->p[j + 1] && J->i[jj] < block_start) - { - jj++; - } - int block_jj_start = jj; - while (jj < J->p[j + 1] && J->i[jj] < block_end) - { - jj++; - } - int nnz_in_block = jj - block_jj_start; - if (nnz_in_block == 0) - { - continue; - } - for (int i = 0; i < m; i++) - { - int a_len = A->p[i + 1] - A->p[i]; - if (has_overlap(A->i + A->p[i], a_len, J->i + block_jj_start, - nnz_in_block, block_start)) - { - iVec_append(Ci, block * m + i); - } - } - } - Cp[j + 1] = Ci->len; - } - - CSC_matrix *C = new_CSC_matrix(m * p, J->n, Ci->len); - memcpy(C->p, Cp, (J->n + 1) * sizeof(int)); - memcpy(C->i, Ci->data, Ci->len * sizeof(int)); - sp_free(Cp); - iVec_free(Ci); - return C; -} - -/* Random cross-check: the gather rewrite must be byte-identical to the - * original has_overlap scan. */ -const char *test_block_left_multiply_matches_reference_random(void) -{ - srand(42); - int n = 15, p = 3, k = 8; - - for (int trial = 0; trial < 5; trial++) - { - CSR_matrix *A = new_csr_random(20, n, 0.2); - CSR_matrix *G = new_csr_random(n * p, k, 0.15); - - int *iwork = (int *) sp_malloc(G->n * sizeof(int)); - CSC_matrix *J = csr_to_csc_alloc(G, iwork); - sp_free(iwork); - - CSC_matrix *C = block_left_multiply_fill_sparsity(A, J, p); - CSC_matrix *C_ref = block_left_multiply_fill_sparsity_ref(A, J, p); - - mu_assert("random block sparsity nnz mismatch", C->nnz == C_ref->nnz); - mu_assert("random block sparsity col pointers mismatch", - cmp_int_array(C->p, C_ref->p, J->n + 1)); - mu_assert("random block sparsity row indices mismatch", - cmp_int_array(C->i, C_ref->i, C_ref->nnz)); - - free_CSC_matrix(C); - free_CSC_matrix(C_ref); - free_CSC_matrix(J); - free_CSR_matrix(G); - free_CSR_matrix(A); - } - return NULL; -} - -/* Dedup + ordering for csr_csc_matmul_alloc: A's row hits the same B column - * through several B rows (dedup), with B's CSR row lists interleaved so a - * missing sort would emit columns out of ascending order. */ -const char *test_csr_csc_matmul_alloc_dedup_order(void) -{ - /* A is 1x2 CSR_matrix: [1.0 1.0] */ - CSR_matrix *A = new_CSR_matrix(1, 2, 2); - double Ax[2] = {1.0, 1.0}; - int Ai[2] = {0, 1}; - int Ap[2] = {0, 2}; - memcpy(A->x, Ax, 2 * sizeof(double)); - memcpy(A->i, Ai, 2 * sizeof(int)); - memcpy(A->p, Ap, 2 * sizeof(int)); - - /* B is 2x3 CSC_matrix: - * [1.0 0.0 1.0] CSR row 0: cols {0, 2} - * [0.0 1.0 1.0] CSR row 1: cols {1, 2} - * Gathering rows 0 then 1 visits cols 0,2 then 1,2 — col 2 twice (dedup) - * and out of order (sort). Expected: cols {0, 1, 2} ascending. */ - CSC_matrix *B = new_CSC_matrix(2, 3, 4); - double Bx[4] = {1.0, 1.0, 1.0, 1.0}; - int Bi[4] = {0, 1, 0, 1}; - int Bp[4] = {0, 1, 2, 4}; - memcpy(B->x, Bx, 4 * sizeof(double)); - memcpy(B->i, Bi, 4 * sizeof(int)); - memcpy(B->p, Bp, 4 * sizeof(int)); - - CSR_matrix *C = csr_csc_matmul_alloc(A, B); - - int expected_p[2] = {0, 3}; - int expected_i[3] = {0, 1, 2}; - - mu_assert("C dims incorrect", C->m == 1 && C->n == 3 && C->nnz == 3); - mu_assert("C row pointers incorrect", cmp_int_array(C->p, expected_p, 2)); - mu_assert("C col indices incorrect", cmp_int_array(C->i, expected_i, 3)); - - free_CSR_matrix(C); - free_CSR_matrix(A); - free_CSC_matrix(B); - return NULL; -} - -/* Reference implementation of csr_csc_matmul_alloc (the original full - * row-times-column has_overlap loop) for the random cross-check. */ -static CSR_matrix *csr_csc_matmul_alloc_ref(const CSR_matrix *A, const CSC_matrix *B) -{ - int m = A->m; - int p = B->n; - - int *Cp = (int *) sp_malloc((m + 1) * sizeof(int)); - iVec *Ci = iVec_new(m); - Cp[0] = 0; - - int nnz = 0; - for (int i = 0; i < m; i++) - { - int len_a = A->p[i + 1] - A->p[i]; - for (int j = 0; j < p; j++) - { - int len_b = B->p[j + 1] - B->p[j]; - if (has_overlap(A->i + A->p[i], len_a, B->i + B->p[j], len_b, 0)) - { - iVec_append(Ci, j); - nnz++; - } - } - Cp[i + 1] = nnz; - } - - CSR_matrix *C = new_CSR_matrix(m, p, nnz); - memcpy(C->p, Cp, (m + 1) * sizeof(int)); - memcpy(C->i, Ci->data, nnz * sizeof(int)); - sp_free(Cp); - iVec_free(Ci); - return C; -} - -/* Random cross-check: the gather rewrite must be byte-identical to the - * original has_overlap pair loop. */ -const char *test_csr_csc_matmul_alloc_matches_reference_random(void) -{ - srand(7); - - for (int trial = 0; trial < 5; trial++) - { - CSR_matrix *A = new_csr_random(20, 15, 0.2); - CSR_matrix *G = new_csr_random(15, 12, 0.2); - - int *iwork = (int *) sp_malloc(G->n * sizeof(int)); - CSC_matrix *B = csr_to_csc_alloc(G, iwork); - sp_free(iwork); - - CSR_matrix *C = csr_csc_matmul_alloc(A, B); - CSR_matrix *C_ref = csr_csc_matmul_alloc_ref(A, B); - - mu_assert("random matmul sparsity nnz mismatch", C->nnz == C_ref->nnz); - mu_assert("random matmul sparsity row pointers mismatch", - cmp_int_array(C->p, C_ref->p, A->m + 1)); - mu_assert("random matmul sparsity col indices mismatch", - cmp_int_array(C->i, C_ref->i, C_ref->nnz)); - - free_CSR_matrix(C); - free_CSR_matrix(C_ref); - free_CSC_matrix(B); - free_CSR_matrix(G); - free_CSR_matrix(A); - } - return NULL; -} - -/* Test block_left_multiply_vec with single block: y = A @ x */ -const char *test_block_left_multiply_vec_single_block(void) -{ - /* A is 2x3 CSR_matrix: - * [1.0 0.0 2.0] - * [0.0 3.0 0.0] - */ - CSR_matrix *A = new_CSR_matrix(2, 3, 3); - double Ax[3] = {1.0, 3.0, 2.0}; - int Ai[3] = {0, 1, 2}; - int Ap[3] = {0, 2, 3}; - memcpy(A->x, Ax, 3 * sizeof(double)); - memcpy(A->i, Ai, 3 * sizeof(int)); - memcpy(A->p, Ap, 3 * sizeof(int)); - - /* x is (3*1)-length vector = [1.0, 2.0, 3.0] */ - double x[3] = {1.0, 2.0, 3.0}; - double y[2] = {0.0, 0.0}; - - block_left_multiply_vec(A, x, y, 1); - - /* Expected: y = [1.0*1.0 + 0.0*2.0 + 2.0*3.0, 0.0*1.0 + 3.0*2.0 + 0.0*3.0] - * = [1.0 + 6.0, 6.0] - * = [7.0, 6.0] - */ - double expected_y[2] = {7.0, 6.0}; - mu_assert("y values incorrect", cmp_double_array(y, expected_y, 2)); - - free_CSR_matrix(A); - return NULL; -} - -/* Test block_left_multiply_vec with two blocks: y = [A @ x1; A @ x2] */ -const char *test_block_left_multiply_vec_two_blocks(void) -{ - /* A is 2x3 CSR_matrix: - * [1.0 2.0 0.0] - * [0.0 3.0 4.0] - */ - CSR_matrix *A = new_CSR_matrix(2, 3, 4); - double Ax[4] = {1.0, 2.0, 3.0, 4.0}; - int Ai[4] = {0, 1, 1, 2}; - int Ap[3] = {0, 2, 4}; - memcpy(A->x, Ax, 4 * sizeof(double)); - memcpy(A->i, Ai, 4 * sizeof(int)); - memcpy(A->p, Ap, 3 * sizeof(int)); - - /* x is (3*2)-length vector = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - * x1 = [1.0, 2.0, 3.0], x2 = [4.0, 5.0, 6.0] - */ - double x[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - double y[4] = {0.0, 0.0, 0.0, 0.0}; - - block_left_multiply_vec(A, x, y, 2); - - /* Expected block 1: y[0:2] = A @ x1 = [1.0*1.0 + 2.0*2.0, 3.0*2.0 + 4.0*3.0] = - * [5.0, 18.0] Expected block 2: y[2:4] = A @ x2 = [1.0*4.0 + 2.0*5.0, 3.0*5.0 - * + 4.0*6.0] = [14.0, 39.0] - */ - double expected_y[4] = {5.0, 18.0, 14.0, 39.0}; - mu_assert("y values incorrect", cmp_double_array(y, expected_y, 4)); - - free_CSR_matrix(A); - return NULL; -} - -/* Test block_left_multiply_vec with sparse matrix and multiple blocks */ -const char *test_block_left_multiply_vec_sparse(void) -{ - /* A is 3x4 CSR_matrix (very sparse): - * [2.0 0.0 0.0 0.0] - * [0.0 0.0 3.0 0.0] - * [0.0 0.0 0.0 4.0] - */ - CSR_matrix *A = new_CSR_matrix(3, 4, 3); - double Ax[3] = {2.0, 3.0, 4.0}; - int Ai[3] = {0, 2, 3}; - int Ap[4] = {0, 1, 2, 3}; - memcpy(A->x, Ax, 3 * sizeof(double)); - memcpy(A->i, Ai, 3 * sizeof(int)); - memcpy(A->p, Ap, 4 * sizeof(int)); - - /* x is (4*2)-length = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] - * x1 = [1.0, 2.0, 3.0, 4.0], x2 = [5.0, 6.0, 7.0, 8.0] - */ - double x[8] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}; - double y[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; - - block_left_multiply_vec(A, x, y, 2); - - /* Expected block 1: y[0:3] = A @ x1 = [2.0*1.0, 3.0*3.0, 4.0*4.0] = - * [2.0, 9.0, 16.0] Expected block 2: y[3:6] = A @ x2 = - * [2.0*5.0, 3.0*7.0, 4.0*8.0] = [10.0, 21.0, 32.0] - */ - double expected_y[6] = {2.0, 9.0, 16.0, 10.0, 21.0, 32.0}; - mu_assert("y values incorrect", cmp_double_array(y, expected_y, 6)); - - free_CSR_matrix(A); - return NULL; -} - -/* Test block_left_multiply_vec with three blocks */ -const char *test_block_left_multiply_vec_three_blocks(void) -{ - /* A is 2x2 CSR_matrix: - * [1.0 2.0] - * [3.0 4.0] - */ - CSR_matrix *A = new_CSR_matrix(2, 2, 4); - double Ax[4] = {1.0, 2.0, 3.0, 4.0}; - int Ai[4] = {0, 1, 0, 1}; - int Ap[3] = {0, 2, 4}; - memcpy(A->x, Ax, 4 * sizeof(double)); - memcpy(A->i, Ai, 4 * sizeof(int)); - memcpy(A->p, Ap, 3 * sizeof(int)); - - /* x is (2*3)-length = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - * x1 = [1.0, 2.0], x2 = [3.0, 4.0], x3 = [5.0, 6.0] - */ - double x[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - double y[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; - - block_left_multiply_vec(A, x, y, 3); - - /* Expected block 1: y[0:2] = A @ x1 = [1.0*1.0 + 2.0*2.0, 3.0*1.0 + 4.0*2.0] = - * [5.0, 11.0] Expected block 2: y[2:4] = A @ x2 = [1.0*3.0 + 2.0*4.0, 3.0*3.0 - * + 4.0*4.0] = [11.0, 25.0] Expected block 3: y[4:6] = A @ x3 = [1.0*5.0 - * + 2.0*6.0, 3.0*5.0 + 4.0*6.0] = [17.0, 39.0] - */ - double expected_y[6] = {5.0, 11.0, 11.0, 25.0, 17.0, 39.0}; - mu_assert("y values incorrect", cmp_double_array(y, expected_y, 6)); - - free_CSR_matrix(A); - return NULL; -} diff --git a/sparsediff_linalg/tests/test_linalg_utils_matmul_chain_rule.h b/sparsediff_linalg/tests/test_linalg_utils_matmul_chain_rule.h deleted file mode 100644 index cdeb2d55..00000000 --- a/sparsediff_linalg/tests/test_linalg_utils_matmul_chain_rule.h +++ /dev/null @@ -1,228 +0,0 @@ -#include -#include -#include - -#include "minunit.h" -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/linalg_dense_sparse_matmuls.h" -#include "test_helpers.h" - -/* Test YT_kron_I_alloc and YT_kron_I_fill_values - * - * C = (Y^T kron I_m) @ J - * m=2, k=2, n=2, p=3 - * - * Y (k x n, col-major [1,2,3,4]): - * [1 3] - * [2 4] - * - * J (mk=4 x p=3, CSC_matrix): - * [1 0 2] - * [0 1 0] - * [3 0 0] - * [0 0 1] - * - * C = (Y^T kron I_2) @ J: - * [ 7 0 2] - * [ 0 1 2] - * [15 0 6] - * [ 0 3 4] - */ -const char *test_YT_kron_I(void) -{ - int m = 2, k = 2, n = 2; - - /* J is 4x3 CSC_matrix */ - CSC_matrix *J = new_CSC_matrix(4, 3, 5); - int Jp[4] = {0, 2, 3, 5}; - int Ji[5] = {0, 2, 1, 0, 3}; - double Jx[5] = {1.0, 3.0, 1.0, 2.0, 1.0}; - memcpy(J->p, Jp, 4 * sizeof(int)); - memcpy(J->i, Ji, 5 * sizeof(int)); - memcpy(J->x, Jx, 5 * sizeof(double)); - - /* Y col-major: Y[0,0]=1, Y[1,0]=2, Y[0,1]=3, Y[1,1]=4 */ - double Y[4] = {1.0, 2.0, 3.0, 4.0}; - - CSR_matrix *C = YT_kron_I_alloc(m, k, n, J); - - /* Expected CSR_matrix (from scipy) */ - int exp_p[5] = {0, 2, 4, 6, 8}; - int exp_i[8] = {0, 2, 1, 2, 0, 2, 1, 2}; - double exp_x[8] = {7.0, 2.0, 1.0, 2.0, 15.0, 6.0, 3.0, 4.0}; - - mu_assert("C dims", C->m == 4 && C->n == 3); - mu_assert("C nnz", C->nnz == 8); - mu_assert("C row ptrs", cmp_int_array(C->p, exp_p, 5)); - mu_assert("C col indices", cmp_int_array(C->i, exp_i, 8)); - - YT_kron_I_fill_values(m, k, n, Y, J, C); - mu_assert("C values", cmp_double_array(C->x, exp_x, 8)); - - free_CSR_matrix(C); - free_CSC_matrix(J); - return NULL; -} - -/* Test YT_kron_I with larger dimensions: m=3, k=2, n=3, p=4 - * - * Y (k=2 x n=3, col-major [1,3,0.5,1,2,0.5]): - * [1.0 0.5 2.0] - * [3.0 1.0 0.5] - * - * J (mk=6 x p=4, CSC_matrix): - * [1 0 0 2] - * [0 0 1 0] - * [0 3 0 0] - * [2 0 0 1] - * [0 1 0 0] - * [0 0 4 0] - * - * C = (Y^T kron I_3) @ J is 9 x 4 - */ -const char *test_YT_kron_I_larger(void) -{ - int m = 3, k = 2, n = 3; - - /* J is 6x4 CSC_matrix */ - CSC_matrix *J = new_CSC_matrix(6, 4, 8); - int Jp[5] = {0, 2, 4, 6, 8}; - int Ji[8] = {0, 3, 2, 4, 1, 5, 0, 3}; - double Jx[8] = {1.0, 2.0, 3.0, 1.0, 1.0, 4.0, 2.0, 1.0}; - memcpy(J->p, Jp, 5 * sizeof(int)); - memcpy(J->i, Ji, 8 * sizeof(int)); - memcpy(J->x, Jx, 8 * sizeof(double)); - - /* Y col-major */ - double Y[6] = {1.0, 3.0, 0.5, 1.0, 2.0, 0.5}; - - CSR_matrix *C = YT_kron_I_alloc(m, k, n, J); - - /* Expected CSR_matrix (from scipy) */ - int exp_p[10] = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18}; - int exp_i[18] = {0, 3, 1, 2, 1, 2, 0, 3, 1, 2, 1, 2, 0, 3, 1, 2, 1, 2}; - double exp_x[18] = {7.0, 5.0, 3.0, 1.0, 3.0, 12.0, 2.5, 2.0, 1.0, - 0.5, 1.5, 4.0, 3.0, 4.5, 0.5, 2.0, 6.0, 2.0}; - - mu_assert("C2 dims", C->m == 9 && C->n == 4); - mu_assert("C2 nnz", C->nnz == 18); - mu_assert("C2 row ptrs", cmp_int_array(C->p, exp_p, 10)); - mu_assert("C2 col indices", cmp_int_array(C->i, exp_i, 18)); - - YT_kron_I_fill_values(m, k, n, Y, J, C); - mu_assert("C2 values", cmp_double_array(C->x, exp_x, 18)); - - free_CSR_matrix(C); - free_CSC_matrix(J); - return NULL; -} - -/* Test I_kron_X_alloc and I_kron_X_fill_values - * - * C = (I_n kron X) @ J - * m=2, k=2, n=2, p=3 - * - * X (m x k, col-major [1,2,3,4]): - * [1 3] - * [2 4] - * - * J (kn=4 x p=3, CSC_matrix): - * [1 0 2] - * [0 1 0] - * [3 0 0] - * [0 0 1] - * - * C = (I_2 kron X) @ J: - * [1 3 2] - * [2 4 4] - * [3 0 3] - * [6 0 4] - */ -const char *test_I_kron_X(void) -{ - int m = 2, k = 2, n = 2; - - /* J is 4x3 CSC_matrix */ - CSC_matrix *J = new_CSC_matrix(4, 3, 5); - int Jp[4] = {0, 2, 3, 5}; - int Ji[5] = {0, 2, 1, 0, 3}; - double Jx[5] = {1.0, 3.0, 1.0, 2.0, 1.0}; - memcpy(J->p, Jp, 4 * sizeof(int)); - memcpy(J->i, Ji, 5 * sizeof(int)); - memcpy(J->x, Jx, 5 * sizeof(double)); - - /* X col-major */ - double X[4] = {1.0, 2.0, 3.0, 4.0}; - - CSR_matrix *C = I_kron_X_alloc(m, k, n, J); - - /* Expected CSR_matrix */ - int exp_p[5] = {0, 3, 6, 8, 10}; - int exp_i[10] = {0, 1, 2, 0, 1, 2, 0, 2, 0, 2}; - double exp_x[10] = {1.0, 3.0, 2.0, 2.0, 4.0, 4.0, 3.0, 3.0, 6.0, 4.0}; - - mu_assert("C dims", C->m == 4 && C->n == 3); - mu_assert("C nnz", C->nnz == 10); - mu_assert("C row ptrs", cmp_int_array(C->p, exp_p, 5)); - mu_assert("C col indices", cmp_int_array(C->i, exp_i, 10)); - - I_kron_X_fill_values(m, k, n, X, J, C); - mu_assert("C values", cmp_double_array(C->x, exp_x, 10)); - - free_CSR_matrix(C); - free_CSC_matrix(J); - return NULL; -} - -/* Test I_kron_X with larger dimensions: m=3, k=2, n=2, p=4 - * - * X (m=3 x k=2, col-major [1,2,3,0.5,1,0.5]): - * [1.0 0.5] - * [2.0 1.0] - * [3.0 0.5] - * - * J (kn=4 x p=4, CSC_matrix): - * [1 0 0 2] - * [0 3 1 0] - * [0 0 4 0] - * [2 0 0 1] - * - * C = (I_2 kron X) @ J is 6 x 4 - */ -const char *test_I_kron_X_larger(void) -{ - int m = 3, k = 2, n = 2; - - /* J is 4x4 CSC_matrix */ - CSC_matrix *J = new_CSC_matrix(4, 4, 7); - int Jp[5] = {0, 2, 3, 5, 7}; - int Ji[7] = {0, 3, 1, 1, 2, 0, 3}; - double Jx[7] = {1.0, 2.0, 3.0, 1.0, 4.0, 2.0, 1.0}; - memcpy(J->p, Jp, 5 * sizeof(int)); - memcpy(J->i, Ji, 7 * sizeof(int)); - memcpy(J->x, Jx, 7 * sizeof(double)); - - /* X col-major */ - double X[6] = {1.0, 2.0, 3.0, 0.5, 1.0, 0.5}; - - CSR_matrix *C = I_kron_X_alloc(m, k, n, J); - - /* Expected CSR_matrix */ - int exp_p[7] = {0, 4, 8, 12, 15, 18, 21}; - int exp_i[21] = {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 2, 3, 0, 2, 3, 0, 2, 3}; - double exp_x[21] = {1.0, 1.5, 0.5, 2.0, 2.0, 3.0, 1.0, 4.0, 3.0, 1.5, 0.5, - 6.0, 1.0, 4.0, 0.5, 2.0, 8.0, 1.0, 1.0, 12.0, 0.5}; - - mu_assert("C2 dims", C->m == 6 && C->n == 4); - mu_assert("C2 nnz", C->nnz == 21); - mu_assert("C2 row ptrs", cmp_int_array(C->p, exp_p, 7)); - mu_assert("C2 col indices", cmp_int_array(C->i, exp_i, 21)); - - I_kron_X_fill_values(m, k, n, X, J, C); - mu_assert("C2 values", cmp_double_array(C->x, exp_x, 21)); - - free_CSR_matrix(C); - free_CSC_matrix(J); - return NULL; -} diff --git a/sparsediff_linalg/tests/test_matmul_dispatchers.h b/sparsediff_linalg/tests/test_matmul_dispatchers.h deleted file mode 100644 index 23ac8d14..00000000 --- a/sparsediff_linalg/tests/test_matmul_dispatchers.h +++ /dev/null @@ -1,2592 +0,0 @@ -#ifndef TEST_MATMUL_DISPATCHERS_H -#define TEST_MATMUL_DISPATCHERS_H - -#include "internal/permuted_dense_linalg.h" -#include "internal/stacked_pd_linalg.h" -#include "minunit.h" -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/matmul_dispatchers.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/sparse_matrix.h" -#include "sparsediff_linalg/stacked_pd.h" -#include "sparsediff_linalg/tracked_alloc.h" -#include "sparsediff_linalg/utils.h" -#include "test_helpers.h" -#include -#include - -/* Wrapper dispatch sanity: (PD, PD). Compare against direct - BTDA_pd_pd_fill_values. */ -const char *test_BTDA_matrices_pd_pd(void) -{ - int row_perm[2] = {0, 1}; - int col_perm_A[2] = {0, 2}; - int col_perm_B[2] = {1, 3}; - double XA[4] = {1.0, 2.0, 3.0, 4.0}; - double XB[4] = {5.0, 6.0, 7.0, 8.0}; - double d[2] = {2.0, -1.5}; - - matrix *A_m = new_permuted_dense(2, 4, 2, 2, row_perm, col_perm_A, XA); - matrix *B_m = new_permuted_dense(2, 4, 2, 2, row_perm, col_perm_B, XB); - - /* Wrapper path. */ - matrix *C_m = BTA_matrices_alloc(A_m, B_m); - BTDA_matrices_fill_values(A_m, d, B_m, C_m); - - /* Direct primitive path on independent operands. */ - matrix *A2 = new_permuted_dense(2, 4, 2, 2, row_perm, col_perm_A, XA); - matrix *B2 = new_permuted_dense(2, 4, 2, 2, row_perm, col_perm_B, XB); - matrix *C2 = BTA_pd_pd_alloc((permuted_dense *) B2, (permuted_dense *) A2); - BTDA_pd_pd_fill_values((permuted_dense *) B2, d, (permuted_dense *) A2, - (permuted_dense *) C2); - - mu_assert("values", cmp_double_array(C_m->x, C2->x, C_m->nnz)); - - free_matrix(C_m); - free_matrix(B_m); - free_matrix(A_m); - free_matrix(C2); - free_matrix(B2); - free_matrix(A2); - return 0; -} - -/* Wrapper dispatch sanity: (PD, CSR_matrix). Compare against direct - BTDA_csc_pd_fill_values. */ -const char *test_BTDA_matrices_pd_csr(void) -{ - /* A: 4x5 PD, row_perm = [1, 3], col_perm = [0, 2]. */ - int row_perm_A[2] = {1, 3}; - int col_perm_A[2] = {0, 2}; - double XA[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *A_m = new_permuted_dense(4, 5, 2, 2, row_perm_A, col_perm_A, XA); - - /* B: 4x4 CSR_matrix. */ - CSR_matrix *B = new_CSR_matrix(4, 4, 5); - B->p[0] = 0; - B->p[1] = 2; - B->p[2] = 3; - B->p[3] = 4; - B->p[4] = 5; - int Bi[5] = {0, 2, 1, 0, 3}; - double Bx[5] = {10.0, 20.0, 30.0, 40.0, 50.0}; - memcpy(B->i, Bi, sizeof Bi); - memcpy(B->x, Bx, sizeof Bx); - matrix *B_m = new_sparse_matrix(B); - - double d[4] = {1.0, -2.0, 0.5, 3.0}; - - /* Wrapper path. Dispatchers don't touch sparse_matrix internals — caller - owns csc_cache structure and values. */ - sparse_matrix_ensure_csc_cache((sparse_matrix *) B_m); - matrix *C_m = BTA_matrices_alloc(A_m, B_m); - B_m->refresh_csc_values(B_m); - BTDA_matrices_fill_values(A_m, d, B_m, C_m); - - /* Direct primitive path: production now dispatches the (PD, Sparse) - branch through CSC-pd kernels. Build a CSC view of B and call - BTA_csc_pd_alloc + BTDA_csc_pd_fill_values to match. */ - matrix *A2_m = new_permuted_dense(4, 5, 2, 2, row_perm_A, col_perm_A, XA); - permuted_dense *A2 = (permuted_dense *) A2_m; - CSR_matrix *B2_csr = new_CSR_matrix(4, 4, 5); - B2_csr->p[0] = 0; - B2_csr->p[1] = 2; - B2_csr->p[2] = 3; - B2_csr->p[3] = 4; - B2_csr->p[4] = 5; - memcpy(B2_csr->i, Bi, sizeof Bi); - memcpy(B2_csr->x, Bx, sizeof Bx); - int *iwork = (int *) malloc(MAX(B2_csr->m, B2_csr->n) * sizeof(int)); - CSC_matrix *B2_csc = csr_to_csc_alloc(B2_csr, iwork); - csr_to_csc_fill_values(B2_csr, B2_csc, iwork); - matrix *C2 = BTA_csc_pd_alloc(B2_csc, A2); - BTDA_csc_pd_fill_values(B2_csc, d, A2, (permuted_dense *) C2); - - mu_assert("values", cmp_double_array(C_m->x, C2->x, C_m->nnz)); - - free_matrix(C_m); - free_matrix(B_m); - free_matrix(A_m); - free_matrix(C2); - free_CSC_matrix(B2_csc); - free_CSR_matrix(B2_csr); - free(iwork); - free_matrix(A2_m); - return 0; -} - -/* ---------------------------------------------------------------- */ -/* spd to_csr fallback in the BTA/BTDA dispatcher: when an operand is */ -/* spd, the dispatcher must materialize it as a temp CSC via to_csr */ -/* and produce results identical to dispatching with the same matrix */ -/* expressed directly as a sparse_matrix. */ -/* ---------------------------------------------------------------- */ - -/* Deep-copy an spd's csr_cache into a fresh sparse_matrix (taking the - csr by value so we don't share buffers). Used as the "reference" - route in the equivalence tests below. */ -static matrix *spd_to_sparse_matrix_copy(matrix *spd_m) -{ - CSR_matrix *src = spd_m->to_csr(spd_m); - CSR_matrix *dst = new_CSR_matrix(src->m, src->n, src->nnz); - memcpy(dst->p, src->p, (size_t) (src->m + 1) * sizeof(int)); - memcpy(dst->i, src->i, (size_t) src->nnz * sizeof(int)); - memcpy(dst->x, src->x, (size_t) src->nnz * sizeof(double)); - dst->nnz = src->nnz; - return new_sparse_matrix(dst); -} - -/* Primitive BTA_csc_pd kernel (no diagonal): C = B^T @ A, B is CSC, A is PD. - Route 1 calls BTA_csc_pd_alloc + BTA_csc_pd_fill_values directly. The oracle - flattens BOTH operands to sparse_matrix and runs the dispatcher with d = ones, - which routes through the csc/csc sparse_dot path — sharing no code with the - csc_pd kernel. Both compared via to_csr. */ -const char *test_BTA_csc_pd_basic(void) -{ - /* A: 4x5 PD, row_perm = [1,3], col_perm = [0,2] (both permuted, so row_inv - genuinely permutes and the transpose matters). */ - int row_perm_A[2] = {1, 3}; - int col_perm_A[2] = {0, 2}; - double XA[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *A_m = new_permuted_dense(4, 5, 2, 2, row_perm_A, col_perm_A, XA); - - /* B: 4x4 CSR -> CSC. Rows: 0:{0,2} 1:{1} 2:{0} 3:{3}. */ - int Bp[5] = {0, 2, 3, 4, 5}; - int Bi[5] = {0, 2, 1, 0, 3}; - double Bx[5] = {10.0, 20.0, 30.0, 40.0, 50.0}; - - CSR_matrix *B_csr = new_CSR_matrix(4, 4, 5); - memcpy(B_csr->p, Bp, sizeof Bp); - memcpy(B_csr->i, Bi, sizeof Bi); - memcpy(B_csr->x, Bx, sizeof Bx); - int *iwork = (int *) malloc(MAX(B_csr->m, B_csr->n) * sizeof(int)); - CSC_matrix *B_csc = csr_to_csc_alloc(B_csr, iwork); - csr_to_csc_fill_values(B_csr, B_csc, iwork); - - /* Route 1: our kernel. */ - matrix *C_ours = BTA_csc_pd_alloc(B_csc, (permuted_dense *) A_m); - BTA_csc_pd_fill_values(B_csc, (permuted_dense *) A_m, (permuted_dense *) C_ours); - - /* Oracle: both operands sparse, d = ones, csc/csc dispatch. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_m); - CSR_matrix *B_csr2 = new_CSR_matrix(4, 4, 5); - memcpy(B_csr2->p, Bp, sizeof Bp); - memcpy(B_csr2->i, Bi, sizeof Bi); - memcpy(B_csr2->x, Bx, sizeof Bx); - matrix *B_sparse = new_sparse_matrix(B_csr2); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(A_sparse); - free_matrix(C_ours); - free_CSC_matrix(B_csc); - free_CSR_matrix(B_csr); - free(iwork); - free_matrix(A_m); - return 0; -} - -/* BTA_csc_pd corner case: B columns that (a) hit no A-row -> excluded from C's - row_perm by BTA_csc_pd_alloc; (b) partially overlap A's rows (some B->i map to - row_inv == -1) -> exercises sparse_dot_dense's -1 filtering; (c) fully land in - A's rows. A->row_perm = {1,3}. B columns by source rows: col0:{0,2} (excluded), - col1:{1} (clean), col2:{0,1,3} (partial), col3:{3} (clean). */ -const char *test_BTA_csc_pd_partial_and_excluded_cols(void) -{ - int row_perm_A[2] = {1, 3}; - int col_perm_A[2] = {0, 2}; - double XA[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *A_m = new_permuted_dense(4, 5, 2, 2, row_perm_A, col_perm_A, XA); - - /* B: 4x4. Rows: 0:{0,2} 1:{1,2} 2:{0} 3:{2,3}. */ - int Bp[5] = {0, 2, 4, 5, 7}; - int Bi[7] = {0, 2, 1, 2, 0, 2, 3}; - double Bx[7] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; - - CSR_matrix *B_csr = new_CSR_matrix(4, 4, 7); - memcpy(B_csr->p, Bp, sizeof Bp); - memcpy(B_csr->i, Bi, sizeof Bi); - memcpy(B_csr->x, Bx, sizeof Bx); - int *iwork = (int *) malloc(MAX(B_csr->m, B_csr->n) * sizeof(int)); - CSC_matrix *B_csc = csr_to_csc_alloc(B_csr, iwork); - csr_to_csc_fill_values(B_csr, B_csc, iwork); - - matrix *C_ours = BTA_csc_pd_alloc(B_csc, (permuted_dense *) A_m); - BTA_csc_pd_fill_values(B_csc, (permuted_dense *) A_m, (permuted_dense *) C_ours); - - matrix *A_sparse = spd_to_sparse_matrix_copy(A_m); - CSR_matrix *B_csr2 = new_CSR_matrix(4, 4, 7); - memcpy(B_csr2->p, Bp, sizeof Bp); - memcpy(B_csr2->i, Bi, sizeof Bi); - memcpy(B_csr2->x, Bx, sizeof Bx); - matrix *B_sparse = new_sparse_matrix(B_csr2); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(A_sparse); - free_matrix(C_ours); - free_CSC_matrix(B_csc); - free_CSR_matrix(B_csr); - free(iwork); - free_matrix(A_m); - return 0; -} - -/* BTA_csc_pd corner case: every B column misses A's rows -> C->base.nnz == 0 - early-out. A->row_perm = {1,3}; B has nonzeros only in rows {0,2}. */ -const char *test_BTA_csc_pd_empty(void) -{ - int row_perm_A[2] = {1, 3}; - int col_perm_A[2] = {0, 2}; - double XA[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *A_m = new_permuted_dense(4, 5, 2, 2, row_perm_A, col_perm_A, XA); - - /* B: 4x4, nonzeros only in rows 0 and 2. Rows: 0:{0,1} 2:{2,3}. */ - int Bp[5] = {0, 2, 2, 4, 4}; - int Bi[4] = {0, 1, 2, 3}; - double Bx[4] = {1.0, 2.0, 3.0, 4.0}; - - CSR_matrix *B_csr = new_CSR_matrix(4, 4, 4); - memcpy(B_csr->p, Bp, sizeof Bp); - memcpy(B_csr->i, Bi, sizeof Bi); - memcpy(B_csr->x, Bx, sizeof Bx); - int *iwork = (int *) malloc(MAX(B_csr->m, B_csr->n) * sizeof(int)); - CSC_matrix *B_csc = csr_to_csc_alloc(B_csr, iwork); - csr_to_csc_fill_values(B_csr, B_csc, iwork); - - matrix *C_ours = BTA_csc_pd_alloc(B_csc, (permuted_dense *) A_m); - BTA_csc_pd_fill_values(B_csc, (permuted_dense *) A_m, (permuted_dense *) C_ours); - - matrix *A_sparse = spd_to_sparse_matrix_copy(A_m); - CSR_matrix *B_csr2 = new_CSR_matrix(4, 4, 4); - memcpy(B_csr2->p, Bp, sizeof Bp); - memcpy(B_csr2->i, Bi, sizeof Bi); - memcpy(B_csr2->x, Bx, sizeof Bx); - matrix *B_sparse = new_sparse_matrix(B_csr2); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("nnz zero", csr_ours->nnz == 0); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(A_sparse); - free_matrix(C_ours); - free_CSC_matrix(B_csc); - free_CSR_matrix(B_csr); - free(iwork); - free_matrix(A_m); - return 0; -} - -/* Primitive BTA_pd_csc kernel (no diagonal): C = B^T @ A, B is PD, A is CSC. - Mirror of the csc_pd tests with roles swapped. Route 1 calls BTA_pd_csc_alloc - + BTA_pd_csc_fill_values. Oracle flattens BOTH operands to sparse_matrix and - runs the dispatcher with d = ones (csc/csc sparse_dot path), sharing no code - with the pd_csc kernel. Both compared via to_csr. */ -const char *test_BTA_pd_csc_basic(void) -{ - /* B: 4x5 PD, row_perm = [1,3], col_perm = [0,2]. */ - int row_perm_B[2] = {1, 3}; - int col_perm_B[2] = {0, 2}; - double XB[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *B_m = new_permuted_dense(4, 5, 2, 2, row_perm_B, col_perm_B, XB); - - /* A: 4x4 CSR -> CSC. Rows: 0:{1} 1:{0,2} 2:{3} 3:{1,3}. All A-columns - overlap B's rows {1,3} (some partially). */ - int Ap[5] = {0, 1, 3, 4, 6}; - int Ai[6] = {1, 0, 2, 3, 1, 3}; - double Ax[6] = {10.0, 20.0, 30.0, 40.0, 50.0, 60.0}; - - CSR_matrix *A_csr = new_CSR_matrix(4, 4, 6); - memcpy(A_csr->p, Ap, sizeof Ap); - memcpy(A_csr->i, Ai, sizeof Ai); - memcpy(A_csr->x, Ax, sizeof Ax); - int *iwork = (int *) malloc(MAX(A_csr->m, A_csr->n) * sizeof(int)); - CSC_matrix *A_csc = csr_to_csc_alloc(A_csr, iwork); - csr_to_csc_fill_values(A_csr, A_csc, iwork); - - /* Route 1: our kernel. */ - matrix *C_ours = BTA_pd_csc_alloc((permuted_dense *) B_m, A_csc); - BTA_pd_csc_fill_values((permuted_dense *) B_m, A_csc, (permuted_dense *) C_ours); - - /* Oracle: both operands sparse, d = ones, csc/csc dispatch. Dispatcher arg - order is (rightA, leftB) and computes B^T @ A, so A goes first. */ - matrix *B_sparse = spd_to_sparse_matrix_copy(B_m); - CSR_matrix *A_csr2 = new_CSR_matrix(4, 4, 6); - memcpy(A_csr2->p, Ap, sizeof Ap); - memcpy(A_csr2->i, Ai, sizeof Ai); - memcpy(A_csr2->x, Ax, sizeof Ax); - matrix *A_sparse = new_sparse_matrix(A_csr2); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(B_sparse); - free_matrix(C_ours); - free_CSC_matrix(A_csc); - free_CSR_matrix(A_csr); - free(iwork); - free_matrix(B_m); - return 0; -} - -/* BTA_pd_csc corner case: A columns that (a) hit no B-row -> excluded from C's - col_perm by BTA_pd_csc_alloc; (b) partially overlap B's rows (some A->i map to - row_inv == -1) -> exercises sparse_dot_dense's -1 filtering; (c) fully land in - B's rows. B->row_perm = {1,3}. A columns by source rows: col0:{0,2} (excluded), - col1:{1} (clean), col2:{0,1,3} (partial), col3:{3} (clean). */ -const char *test_BTA_pd_csc_partial_and_excluded_cols(void) -{ - int row_perm_B[2] = {1, 3}; - int col_perm_B[2] = {0, 2}; - double XB[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *B_m = new_permuted_dense(4, 5, 2, 2, row_perm_B, col_perm_B, XB); - - /* A: 4x4. Rows: 0:{0,2} 1:{1,2} 2:{0} 3:{2,3}. */ - int Ap[5] = {0, 2, 4, 5, 7}; - int Ai[7] = {0, 2, 1, 2, 0, 2, 3}; - double Ax[7] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; - - CSR_matrix *A_csr = new_CSR_matrix(4, 4, 7); - memcpy(A_csr->p, Ap, sizeof Ap); - memcpy(A_csr->i, Ai, sizeof Ai); - memcpy(A_csr->x, Ax, sizeof Ax); - int *iwork = (int *) malloc(MAX(A_csr->m, A_csr->n) * sizeof(int)); - CSC_matrix *A_csc = csr_to_csc_alloc(A_csr, iwork); - csr_to_csc_fill_values(A_csr, A_csc, iwork); - - matrix *C_ours = BTA_pd_csc_alloc((permuted_dense *) B_m, A_csc); - BTA_pd_csc_fill_values((permuted_dense *) B_m, A_csc, (permuted_dense *) C_ours); - - matrix *B_sparse = spd_to_sparse_matrix_copy(B_m); - CSR_matrix *A_csr2 = new_CSR_matrix(4, 4, 7); - memcpy(A_csr2->p, Ap, sizeof Ap); - memcpy(A_csr2->i, Ai, sizeof Ai); - memcpy(A_csr2->x, Ax, sizeof Ax); - matrix *A_sparse = new_sparse_matrix(A_csr2); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(B_sparse); - free_matrix(C_ours); - free_CSC_matrix(A_csc); - free_CSR_matrix(A_csr); - free(iwork); - free_matrix(B_m); - return 0; -} - -/* BTA_pd_csc corner case: every A column misses B's rows -> C->base.nnz == 0 - early-out. B->row_perm = {1,3}; A has nonzeros only in rows {0,2}. */ -const char *test_BTA_pd_csc_empty(void) -{ - int row_perm_B[2] = {1, 3}; - int col_perm_B[2] = {0, 2}; - double XB[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *B_m = new_permuted_dense(4, 5, 2, 2, row_perm_B, col_perm_B, XB); - - /* A: 4x4, nonzeros only in rows 0 and 2. Rows: 0:{0,1} 2:{2,3}. */ - int Ap[5] = {0, 2, 2, 4, 4}; - int Ai[4] = {0, 1, 2, 3}; - double Ax[4] = {1.0, 2.0, 3.0, 4.0}; - - CSR_matrix *A_csr = new_CSR_matrix(4, 4, 4); - memcpy(A_csr->p, Ap, sizeof Ap); - memcpy(A_csr->i, Ai, sizeof Ai); - memcpy(A_csr->x, Ax, sizeof Ax); - int *iwork = (int *) malloc(MAX(A_csr->m, A_csr->n) * sizeof(int)); - CSC_matrix *A_csc = csr_to_csc_alloc(A_csr, iwork); - csr_to_csc_fill_values(A_csr, A_csc, iwork); - - matrix *C_ours = BTA_pd_csc_alloc((permuted_dense *) B_m, A_csc); - BTA_pd_csc_fill_values((permuted_dense *) B_m, A_csc, (permuted_dense *) C_ours); - - matrix *B_sparse = spd_to_sparse_matrix_copy(B_m); - CSR_matrix *A_csr2 = new_CSR_matrix(4, 4, 4); - memcpy(A_csr2->p, Ap, sizeof Ap); - memcpy(A_csr2->i, Ai, sizeof Ai); - memcpy(A_csr2->x, Ax, sizeof Ax); - matrix *A_sparse = new_sparse_matrix(A_csr2); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("nnz zero", csr_ours->nnz == 0); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(B_sparse); - free_matrix(C_ours); - free_CSC_matrix(A_csc); - free_CSR_matrix(A_csr); - free(iwork); - free_matrix(B_m); - return 0; -} - -/* Wrapper dispatch: (A=spd, B=PD). The spd-fallback path goes through - BTA_csc_pd_alloc / BTDA_csc_pd_fill_values; result must match the - equivalent (A=sparse, B=PD) dispatch on the same matrix. */ -const char *test_BTDA_matrices_spd_pd(void) -{ - /* A: 4x3 spd, two blocks. - blk0: rows {0,1}, cols {0,2}, X = [[1,2],[3,4]] - blk1: rows {2,3}, cols {1,2}, X = [[5,6],[7,8]] */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - /* B: 4x5 PD. */ - int B_rp[3] = {0, 1, 2}; - int B_cp[3] = {1, 3, 4}; - double BX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *B = new_permuted_dense(4, 5, 3, 3, B_rp, B_cp, BX); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: dispatcher with spd operand. */ - matrix *C_spd = BTA_matrices_alloc(A_spd, B); - BTDA_matrices_fill_values(A_spd, d, B, C_spd); - - /* Route 2: dispatcher with the same matrix expressed as sparse. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B); - A_sparse->refresh_csc_values(A_sparse); - BTDA_matrices_fill_values(A_sparse, d, B, C_ref); - - /* Both routes produce a PD (B is PD). Compare X buffers. */ - permuted_dense *C_spd_pd = (permuted_dense *) C_spd; - permuted_dense *C_ref_pd = (permuted_dense *) C_ref; - mu_assert("m", C_spd->m == C_ref->m); - mu_assert("n", C_spd->n == C_ref->n); - mu_assert("m0", C_spd_pd->m0 == C_ref_pd->m0); - mu_assert("n0", C_spd_pd->n0 == C_ref_pd->n0); - mu_assert("row_perm", - cmp_int_array(C_spd_pd->row_perm, C_ref_pd->row_perm, C_spd_pd->m0)); - mu_assert("col_perm", - cmp_int_array(C_spd_pd->col_perm, C_ref_pd->col_perm, C_spd_pd->n0)); - mu_assert("X", cmp_double_array(C_spd_pd->X, C_ref_pd->X, - (size_t) C_spd_pd->m0 * C_spd_pd->n0)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_spd); - free_matrix(B); - free_matrix(A_spd); - return 0; -} - -/* Wrapper dispatch: (A=PD, B=spd). Goes through BTA_pd_csc_alloc / - BTDA_pd_csc_fill_values; equivalence with sparse-B. */ -const char *test_BTDA_matrices_pd_spd(void) -{ - /* A: 4x5 PD. */ - int A_rp[3] = {0, 1, 2}; - int A_cp[3] = {1, 3, 4}; - double AX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *A = new_permuted_dense(4, 5, 3, 3, A_rp, A_cp, AX); - - /* B: 4x3 spd, same as test_BTDA_matrices_spd_pd. */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - matrix *C_spd = BTA_matrices_alloc(A, B_spd); - BTDA_matrices_fill_values(A, d, B_spd, C_spd); - - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A, B_sparse); - B_sparse->refresh_csc_values(B_sparse); - BTDA_matrices_fill_values(A, d, B_sparse, C_ref); - - /* Output types now differ: the spd-B route returns stacked_pd (via - BTA_spd_matrices), the sparse-B route returns permuted_dense (via - BTA_sparse_matrices' PD branch). Compare via to_csr. */ - CSR_matrix *csr_spd = C_spd->to_csr(C_spd); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_spd->m == csr_ref->m); - mu_assert("n", csr_spd->n == csr_ref->n); - mu_assert("nnz", csr_spd->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_spd->p, csr_ref->p, csr_spd->m + 1)); - mu_assert("i", cmp_int_array(csr_spd->i, csr_ref->i, csr_spd->nnz)); - mu_assert("x", cmp_double_array(csr_spd->x, csr_ref->x, csr_spd->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(C_spd); - free_matrix(B_spd); - free_matrix(A); - return 0; -} - -/* Wrapper dispatch: (A=spd, B=spd). The spd-spd route returns stacked_pd - (via BTA_spd_matrices); the sparse-sparse fallback returns sparse_matrix. - Compare via to_csr. */ -const char *test_BTDA_matrices_spd_spd(void) -{ - /* A: 4x3 spd. */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *Ablk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *Ablk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) Ablk0, - (permuted_dense *) Ablk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - /* B: 4x4 spd. */ - int B0_rp[2] = {0, 2}; - int B0_cp[2] = {0, 3}; - double B0X[4] = {9, 8, 7, 6}; - matrix *Bblk0 = new_permuted_dense(4, 4, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {1, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 4, 3, 2}; - matrix *Bblk1 = new_permuted_dense(4, 4, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) Bblk0, - (permuted_dense *) Bblk1}; - matrix *B_spd = new_stacked_pd(4, 4, 2, B_blocks, NULL, NULL); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - matrix *C_spd = BTA_matrices_alloc(A_spd, B_spd); - BTDA_matrices_fill_values(A_spd, d, B_spd, C_spd); - - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - BTDA_matrices_fill_values(A_sparse, d, B_sparse, C_ref); - - /* Output types differ: spd-spd route returns stacked_pd, sparse-sparse - returns sparse_matrix. Compare via to_csr. */ - CSR_matrix *csr_spd = C_spd->to_csr(C_spd); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_spd->m == csr_ref->m); - mu_assert("n", csr_spd->n == csr_ref->n); - mu_assert("nnz", csr_spd->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_spd->p, csr_ref->p, csr_spd->m + 1)); - mu_assert("i", cmp_int_array(csr_spd->i, csr_ref->i, csr_spd->nnz)); - mu_assert("x", cmp_double_array(csr_spd->x, csr_ref->x, csr_spd->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(A_sparse); - free_matrix(C_spd); - free_matrix(B_spd); - free_matrix(A_spd); - return 0; -} - -/* Primitive BTA_pd_spd kernel (transpose + BA_pd_spd implementation): - C = B^T @ A with B a permuted_dense and A a 2-block stacked_pd whose - col_perms overlap (share col 2), exercising the scatter-accumulate - path inside BA_pd_spd. Reference is the production dispatcher path - with A flattened to a sparse_matrix and d = ones (BTDA with all-ones - d is equivalent to BTA). */ -const char *test_BTA_pd_spd_two_blocks_both_kept(void) -{ - /* A: 4x3 spd, two blocks. - blk0: rows {0,1}, cols {0,2}, X = [[1,2],[3,4]] - blk1: rows {2,3}, cols {1,2}, X = [[5,6],[7,8]] */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - /* B: 4x5 PD with row_perm = {0,1,2}, col_perm = {1,3,4}. */ - int B_rp[3] = {0, 1, 2}; - int B_cp[3] = {1, 3, 4}; - double BX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *B = new_permuted_dense(4, 5, 3, 3, B_rp, B_cp, BX); - - /* Route 1: our new BTA_pd_spd_* primitives. */ - matrix *C_ours = BTA_pd_spd_alloc((permuted_dense *) B, (stacked_pd *) A_spd); - BTA_pd_spd_fill_values((permuted_dense *) B, (stacked_pd *) A_spd, - (permuted_dense *) C_ours); - - /* Route 2: dispatcher with A flattened to sparse_matrix, d = ones. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B); - A_sparse->refresh_csc_values(A_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B, C_ref); - - /* Both routes produce a PD with row_perm = B->col_perm and col_perm - = union of contributing A_k->col_perms. Compare shape, perms, X. */ - permuted_dense *C_ours_pd = (permuted_dense *) C_ours; - permuted_dense *C_ref_pd = (permuted_dense *) C_ref; - mu_assert("m", C_ours->m == C_ref->m); - mu_assert("n", C_ours->n == C_ref->n); - mu_assert("m0", C_ours_pd->m0 == C_ref_pd->m0); - mu_assert("n0", C_ours_pd->n0 == C_ref_pd->n0); - mu_assert("row_perm", - cmp_int_array(C_ours_pd->row_perm, C_ref_pd->row_perm, C_ours_pd->m0)); - mu_assert("col_perm", - cmp_int_array(C_ours_pd->col_perm, C_ref_pd->col_perm, C_ours_pd->n0)); - mu_assert("X", cmp_double_array(C_ours_pd->X, C_ref_pd->X, - (size_t) C_ours_pd->m0 * C_ours_pd->n0)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(B); - free_matrix(A_spd); - return 0; -} - -/* Primitive BTDA_pd_spd kernel (temp-DA composition): - C = B^T @ diag(d) @ A. Same B and A layout as - test_BTA_pd_spd_two_blocks_both_kept, but with a non-trivial d so - BTDA differs from BTA. Reference is the production dispatcher path - with A flattened to a sparse_matrix. */ -const char *test_BTDA_pd_spd_two_blocks_both_kept(void) -{ - /* A: 4x3 spd, two blocks. - blk0: rows {0,1}, cols {0,2}, X = [[1,2],[3,4]] - blk1: rows {2,3}, cols {1,2}, X = [[5,6],[7,8]] */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - /* B: 4x5 PD with row_perm = {0,1,2}, col_perm = {1,3,4}. */ - int B_rp[3] = {0, 1, 2}; - int B_cp[3] = {1, 3, 4}; - double BX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *B = new_permuted_dense(4, 5, 3, 3, B_rp, B_cp, BX); - - /* Non-trivial d so BTDA != BTA. */ - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: our new BTDA_pd_spd_fill_values. C structure comes from - BTA_pd_spd_alloc (BTDA shares sparsity with BTA). */ - matrix *C_ours = BTA_pd_spd_alloc((permuted_dense *) B, (stacked_pd *) A_spd); - BTDA_pd_spd_fill_values((permuted_dense *) B, d, (stacked_pd *) A_spd, - (permuted_dense *) C_ours); - - /* Route 2: dispatcher with A flattened to sparse_matrix. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B); - A_sparse->refresh_csc_values(A_sparse); - BTDA_matrices_fill_values(A_sparse, d, B, C_ref); - - /* Both routes produce a PD with row_perm = B->col_perm and col_perm - = union of contributing A_k->col_perms. Compare shape, perms, X. */ - permuted_dense *C_ours_pd = (permuted_dense *) C_ours; - permuted_dense *C_ref_pd = (permuted_dense *) C_ref; - mu_assert("m", C_ours->m == C_ref->m); - mu_assert("n", C_ours->n == C_ref->n); - mu_assert("m0", C_ours_pd->m0 == C_ref_pd->m0); - mu_assert("n0", C_ours_pd->n0 == C_ref_pd->n0); - mu_assert("row_perm", - cmp_int_array(C_ours_pd->row_perm, C_ref_pd->row_perm, C_ours_pd->m0)); - mu_assert("col_perm", - cmp_int_array(C_ours_pd->col_perm, C_ref_pd->col_perm, C_ours_pd->n0)); - mu_assert("X", cmp_double_array(C_ours_pd->X, C_ref_pd->X, - (size_t) C_ours_pd->m0 * C_ours_pd->n0)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(B); - free_matrix(A_spd); - return 0; -} - -/* Primitive BTDA_spd_pd kernel (ATA-style direct: per-block BTDA_pd_pd - + accumulating coalesce). C = B^T @ diag(d) @ A with B a 2-block - stacked_pd whose col_perms share column 2 — exercises the - accumulating coalesce path (multiple source blocks contribute to the - same output row). Reference is the production dispatcher with B - flattened to a sparse_matrix; outputs differ in storage (stacked_pd - vs permuted_dense), so compare via to_csr. */ -const char *test_BTDA_spd_pd_overlapping_cp(void) -{ - /* B: 4x3 spd, two blocks with overlapping col_perms (share col 2). - blk0: rows {0,1}, cols {0,2}, X = [[1,2],[3,4]] - blk1: rows {2,3}, cols {1,2}, X = [[5,6],[7,8]] */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - /* A: 4x3 PD, full row_perm, full col_perm. */ - int A_rp[4] = {0, 1, 2, 3}; - int A_cp[3] = {0, 1, 2}; - double AX[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - matrix *A = new_permuted_dense(4, 3, 4, 3, A_rp, A_cp, AX); - - /* Non-trivial d so BTDA != BTA. */ - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: our new BTDA_spd_pd_fill_values. */ - matrix *C_ours = BTA_spd_pd_alloc((stacked_pd *) B_spd, (permuted_dense *) A); - BTDA_spd_pd_fill_values((stacked_pd *) B_spd, d, (permuted_dense *) A, - (stacked_pd *) C_ours); - - /* Route 2: dispatcher with B flattened to sparse_matrix. Note - BTA_matrices_alloc(A, B) computes B^T @ A, so A goes first. */ - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A, B_sparse); - B_sparse->refresh_csc_values(B_sparse); - BTDA_matrices_fill_values(A, d, B_sparse, C_ref); - - /* C_ours is stacked_pd, C_ref is permuted_dense — compare via to_csr. - Both represent the same global matrix; rows are emitted in sorted - order, and the spd's per-signature blocks share col_perm = A->col_perm, - so the CSR layouts match exactly. */ - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(C_ours); - free_matrix(A); - free_matrix(B_spd); - return 0; -} - -/* Primitive BTA_spd_pd kernel (no diagonal). Same matrices as the BTDA - variant above. Reference is the production dispatcher with B flattened to a - sparse_matrix and d = ones, which equals plain B^T @ A. */ -const char *test_BTA_spd_pd_overlapping_cp(void) -{ - /* B: 4x3 spd, two blocks with overlapping col_perms (share col 2). - blk0: rows {0,1}, cols {0,2}, X = [[1,2],[3,4]] - blk1: rows {2,3}, cols {1,2}, X = [[5,6],[7,8]] */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - /* A: 4x3 PD, full row_perm, full col_perm. */ - int A_rp[4] = {0, 1, 2, 3}; - int A_cp[3] = {0, 1, 2}; - double AX[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - matrix *A = new_permuted_dense(4, 3, 4, 3, A_rp, A_cp, AX); - - /* Route 1: our new BTA_spd_pd_fill_values. */ - matrix *C_ours = BTA_spd_pd_alloc((stacked_pd *) B_spd, (permuted_dense *) A); - BTA_spd_pd_fill_values((stacked_pd *) B_spd, (permuted_dense *) A, - (stacked_pd *) C_ours); - - /* Route 2: dispatcher with B flattened to sparse_matrix and d = ones, so - BTDA == BTA. Note BTA_matrices_alloc(A, B) computes B^T @ A, so A goes - first. */ - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A, B_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A, d_ones, B_sparse, C_ref); - - /* C_ours is stacked_pd, C_ref is permuted_dense — compare via to_csr. */ - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(C_ours); - free_matrix(A); - free_matrix(B_spd); - return 0; -} - -/* Primitive BTDA_spd_csc kernel (ATA-style direct: per-block BTDA_pd_csc - + accumulating coalesce). C = B^T @ diag(d) @ A with B a 2-block - stacked_pd whose col_perms share column 2, and A a sparse_matrix - wrapping a small CSR. Reference is the production dispatcher with B - flattened to a sparse_matrix; outputs differ in storage (stacked_pd - vs sparse_matrix), so compare via to_csr. */ -const char *test_BTDA_spd_csc_overlapping_cp(void) -{ - /* B: 4x3 spd, two blocks with overlapping col_perms (share col 2). - Same layout as test_BTDA_spd_pd_overlapping_cp. - blk0: rows {0,1}, cols {0,2}, X = [[1,2],[3,4]] - blk1: rows {2,3}, cols {1,2}, X = [[5,6],[7,8]] */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - /* A: 4x3 sparse_matrix wrapping a CSR with arbitrary nonzeros. - Row 0: col 0 = 1, col 2 = 2 - Row 1: col 1 = 3 - Row 2: col 0 = 4, col 1 = 5, col 2 = 6 - Row 3: col 2 = 7 - p = [0, 2, 3, 6, 7], i = [0,2, 1, 0,1,2, 2], x = [1,2, 3, 4,5,6, 7]. */ - CSR_matrix *A_csr = new_CSR_matrix(4, 3, 7); - int Ap[5] = {0, 2, 3, 6, 7}; - int Ai[7] = {0, 2, 1, 0, 1, 2, 2}; - double Ax[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(A_csr->p, Ap, sizeof(Ap)); - memcpy(A_csr->i, Ai, sizeof(Ai)); - memcpy(A_csr->x, Ax, sizeof(Ax)); - matrix *A_sm = new_sparse_matrix(A_csr); - - /* Non-trivial d so BTDA != BTA. */ - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: our new BTDA_spd_csc_fill_values. Need A's csc_cache. */ - sparse_matrix_ensure_csc_cache((sparse_matrix *) A_sm); - A_sm->refresh_csc_values(A_sm); - matrix *C_ours = - BTA_spd_csc_alloc((stacked_pd *) B_spd, ((sparse_matrix *) A_sm)->csc_cache); - BTDA_spd_csc_fill_values((stacked_pd *) B_spd, d, - ((sparse_matrix *) A_sm)->csc_cache, - (stacked_pd *) C_ours); - - /* Route 2: dispatcher with B flattened to sparse_matrix. Note - BTA_matrices_alloc(A, B) computes B^T @ A, so A goes first. */ - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sm, B_sparse); - B_sparse->refresh_csc_values(B_sparse); - BTDA_matrices_fill_values(A_sm, d, B_sparse, C_ref); - - /* C_ours is stacked_pd, C_ref is sparse_matrix — compare via to_csr. */ - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(C_ours); - free_matrix(A_sm); - free_matrix(B_spd); - return 0; -} - -/* Primitive BTA_spd_csc kernel (no diagonal): C = B^T @ A, B is a 2-block - stacked_pd whose col_perms share a column (accumulating coalesce fires), A is - CSC. Same matrices as the BTDA variant above. Oracle is the dispatcher with B - flattened to sparse and d = ones (csc/csc sparse_dot path). */ -const char *test_BTA_spd_csc_overlapping(void) -{ - /* B: 4x3 spd, two blocks with overlapping col_perms (share col 2). */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - /* A: 4x3 sparse_matrix wrapping a CSR. Rows: 0:{0,2} 1:{1} 2:{0,1,2} 3:{2}. */ - CSR_matrix *A_csr = new_CSR_matrix(4, 3, 7); - int Ap[5] = {0, 2, 3, 6, 7}; - int Ai[7] = {0, 2, 1, 0, 1, 2, 2}; - double Ax[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(A_csr->p, Ap, sizeof(Ap)); - memcpy(A_csr->i, Ai, sizeof(Ai)); - memcpy(A_csr->x, Ax, sizeof(Ax)); - matrix *A_sm = new_sparse_matrix(A_csr); - - /* Route 1: new kernel. Need A's csc_cache. */ - sparse_matrix_ensure_csc_cache((sparse_matrix *) A_sm); - A_sm->refresh_csc_values(A_sm); - matrix *C_ours = - BTA_spd_csc_alloc((stacked_pd *) B_spd, ((sparse_matrix *) A_sm)->csc_cache); - BTA_spd_csc_fill_values((stacked_pd *) B_spd, - ((sparse_matrix *) A_sm)->csc_cache, - (stacked_pd *) C_ours); - - /* Route 2: dispatcher with B flattened to sparse and d = ones, so BTDA == BTA. - BTA_matrices_alloc(A, B) computes B^T @ A, so A goes first. */ - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sm, B_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sm, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(C_ours); - free_matrix(A_sm); - free_matrix(B_spd); - return 0; -} - -/* BTA_spd_csc corner case: one B-block whose rows match no nonzero row of A -> - that block's per-block partial is empty (m0 = 0), while the other block - contributes. Exercises the empty-source-block path through BTA_pd_csc_alloc - and the accumulating coalesce. */ -const char *test_BTA_spd_csc_block_no_overlap(void) -{ - /* B: 4x3 spd, two blocks. blk0 rows {0,1}; blk1 rows {2,3}. */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 1}; - double B0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - /* A: 4x3, nonzeros only in rows {0,1} -> overlaps blk0 only; blk1's rows - {2,3} hit no A nonzero -> empty partial. Rows: 0:{0,2} 1:{1}. */ - CSR_matrix *A_csr = new_CSR_matrix(4, 3, 3); - int Ap[5] = {0, 2, 3, 3, 3}; - int Ai[3] = {0, 2, 1}; - double Ax[3] = {1, 2, 3}; - memcpy(A_csr->p, Ap, sizeof(Ap)); - memcpy(A_csr->i, Ai, sizeof(Ai)); - memcpy(A_csr->x, Ax, sizeof(Ax)); - matrix *A_sm = new_sparse_matrix(A_csr); - - sparse_matrix_ensure_csc_cache((sparse_matrix *) A_sm); - A_sm->refresh_csc_values(A_sm); - matrix *C_ours = - BTA_spd_csc_alloc((stacked_pd *) B_spd, ((sparse_matrix *) A_sm)->csc_cache); - BTA_spd_csc_fill_values((stacked_pd *) B_spd, - ((sparse_matrix *) A_sm)->csc_cache, - (stacked_pd *) C_ours); - - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sm, B_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sm, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(C_ours); - free_matrix(A_sm); - free_matrix(B_spd); - return 0; -} - -/* Primitive BTDA_spd_spd kernel (per-block BTDA_pd_spd + accumulating coalesce). - C = B^T @ diag(d) @ A with both B and A 2-block stacked_pds. B's c_k's - share col 2 (outer coalesce-accumulate path), and A's col_perms also - share at least one column (inner BTA_pd_spd accumulate path). Reference - is the production dispatcher with both operands flattened to sparse_matrix. - Output types differ (stacked_pd vs sparse_matrix); compare via to_csr. */ -const char *test_BTDA_spd_spd_overlapping(void) -{ - /* B: 4x3 spd, two blocks with overlapping c_k(B) (share col 2). */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *B_blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *B_blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) B_blk0, - (permuted_dense *) B_blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - /* A: 4x3 spd, two blocks. A_0's rows overlap B_0's, A_1's rows overlap - B_1's. A_0 and A_1 share col 1 → inner BTA_pd_spd accumulate fires. */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 1}; - double A0X[4] = {10, 11, 12, 13}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {20, 21, 22, 23}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: new kernel. */ - matrix *C_ours = BTA_spd_spd_alloc((stacked_pd *) B_spd, (stacked_pd *) A_spd); - BTDA_spd_spd_fill_values((stacked_pd *) B_spd, d, (stacked_pd *) A_spd, - (stacked_pd *) C_ours); - - /* Route 2: dispatcher with both flattened to sparse_matrix. - BTA_matrices_alloc(A, B) computes B^T @ A, so A goes first. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - BTDA_matrices_fill_values(A_sparse, d, B_sparse, C_ref); - - /* C_ours is stacked_pd, C_ref is sparse_matrix — compare via to_csr. */ - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(A_spd); - free_matrix(B_spd); - return 0; -} - -/* Primitive BTA_spd_spd kernel (no diagonal). Same matrices as the BTDA - variant above. Reference is the production dispatcher with both operands - flattened to sparse_matrix and d = ones, which equals plain B^T @ A. */ -const char *test_BTA_spd_spd_overlapping(void) -{ - /* B: 4x3 spd, two blocks with overlapping c_k(B) (share col 2). */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *B_blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *B_blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) B_blk0, - (permuted_dense *) B_blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - /* A: 4x3 spd, two blocks. A_0's rows overlap B_0's, A_1's rows overlap - B_1's. A_0 and A_1 share col 1. */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 1}; - double A0X[4] = {10, 11, 12, 13}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {20, 21, 22, 23}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - /* Route 1: new kernel. */ - matrix *C_ours = BTA_spd_spd_alloc((stacked_pd *) B_spd, (stacked_pd *) A_spd); - BTA_spd_spd_fill_values((stacked_pd *) B_spd, (stacked_pd *) A_spd, - (stacked_pd *) C_ours); - - /* Route 2: dispatcher with both flattened to sparse_matrix and d = ones, so - BTDA == BTA. BTA_matrices_alloc(A, B) computes B^T @ A, so A goes first. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sparse, C_ref); - - /* C_ours is stacked_pd, C_ref is sparse_matrix — compare via to_csr. */ - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(A_spd); - free_matrix(B_spd); - return 0; -} - -/* BTA_spd_spd corner case: a single B-block whose rows span MULTIPLE A-blocks, - forcing BTA_pd_spd_fill_values' inner += accumulation over A-blocks into one - partial, with overlapping output columns (A_0 and A_1 share col 1). Not - exercised by test_BTA_spd_spd_overlapping (there each B-block overlaps exactly - one A-block). */ -const char *test_BTA_spd_spd_multi_A_per_block(void) -{ - /* B: 4x2, one block spanning all four contraction rows. */ - int B0_rp[4] = {0, 1, 2, 3}; - int B0_cp[2] = {0, 1}; - double B0X[8] = {1, 2, 3, 4, 5, 6, 7, 8}; - matrix *B_blk0 = new_permuted_dense(4, 2, 4, 2, B0_rp, B0_cp, B0X); - permuted_dense *B_blocks[1] = {(permuted_dense *) B_blk0}; - matrix *B_spd = new_stacked_pd(4, 2, 1, B_blocks, NULL, NULL); - - /* A: 4x3, two blocks with disjoint rows but overlapping cols (share col 1). - Both overlap B_0's rows, so both contribute to B_0's single partial. */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 1}; - double A0X[4] = {10, 11, 12, 13}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {20, 21, 22, 23}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - matrix *C_ours = BTA_spd_spd_alloc((stacked_pd *) B_spd, (stacked_pd *) A_spd); - BTA_spd_spd_fill_values((stacked_pd *) B_spd, (stacked_pd *) A_spd, - (stacked_pd *) C_ours); - - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(A_spd); - free_matrix(B_spd); - return 0; -} - -/* BTA_spd_spd corner case: a B-block that overlaps NO A-block, producing an - empty partial (n0 = 0) that coalesces to a zero-column output block (output - row 1, all zeros). Exercises the partial-level nnz == 0 early-out in - BTA_pd_spd_fill_values and the empty-block path through coalesce/to_csr. */ -const char *test_BTA_spd_spd_nonoverlapping_block(void) -{ - /* B: 4x2, two blocks. B_0 rows {0,1}; B_1 rows {2,3}. */ - int B0_rp[2] = {0, 1}; - int B0_cp[1] = {0}; - double B0X[2] = {1, 2}; - matrix *B_blk0 = new_permuted_dense(4, 2, 2, 1, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[1] = {1}; - double B1X[2] = {3, 4}; - matrix *B_blk1 = new_permuted_dense(4, 2, 2, 1, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) B_blk0, - (permuted_dense *) B_blk1}; - matrix *B_spd = new_stacked_pd(4, 2, 2, B_blocks, NULL, NULL); - - /* A: 4x2, one block on rows {0,1}. B_0 overlaps it; B_1 does not. */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 1}; - double A0X[4] = {10, 11, 12, 13}; - matrix *A_blk0 = new_permuted_dense(4, 2, 2, 2, A0_rp, A0_cp, A0X); - permuted_dense *A_blocks[1] = {(permuted_dense *) A_blk0}; - matrix *A_spd = new_stacked_pd(4, 2, 1, A_blocks, NULL, NULL); - - matrix *C_ours = BTA_spd_spd_alloc((stacked_pd *) B_spd, (stacked_pd *) A_spd); - BTA_spd_spd_fill_values((stacked_pd *) B_spd, (stacked_pd *) A_spd, - (stacked_pd *) C_ours); - - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(A_spd); - free_matrix(B_spd); - return 0; -} - -/* BTA_spd_matrices dispatcher: A is permuted_dense. Verifies the - PD-branch routes to BTA_spd_pd / BTDA_spd_pd. */ -const char *test_BTA_spd_matrices_pd_A(void) -{ - /* Same inputs as test_BTDA_spd_pd_overlapping_cp. */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *B_blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *B_blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) B_blk0, - (permuted_dense *) B_blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - int A_rp[4] = {0, 1, 2, 3}; - int A_cp[3] = {0, 1, 2}; - double AX[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - matrix *A = new_permuted_dense(4, 3, 4, 3, A_rp, A_cp, AX); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: public dispatcher with B in its native spd type — routes - internally through the spd-B branch. */ - matrix *C_ours = BTA_matrices_alloc(A, B_spd); - BTDA_matrices_fill_values(A, d, B_spd, C_ours); - - /* Route 2: same public dispatcher with B flattened to sparse — routes - through a different internal branch (csc-PD path). */ - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A, B_sparse); - B_sparse->refresh_csc_values(B_sparse); - BTDA_matrices_fill_values(A, d, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(C_ours); - free_matrix(A); - free_matrix(B_spd); - return 0; -} - -/* Public dispatcher equivalence: B is spd and A is sparse_matrix. The - spd-B internal branch is exercised in Route 1; Route 2 flattens B to - sparse and exercises the csc-csc branch. */ -const char *test_BTA_spd_matrices_csc_A(void) -{ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *B_blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *B_blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) B_blk0, - (permuted_dense *) B_blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - /* A as sparse_matrix wrapping a CSR (same shape/values as - test_BTDA_spd_csc_overlapping_cp). */ - CSR_matrix *A_csr = new_CSR_matrix(4, 3, 7); - int Ap[5] = {0, 2, 3, 6, 7}; - int Ai[7] = {0, 2, 1, 0, 1, 2, 2}; - double Ax[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(A_csr->p, Ap, sizeof(Ap)); - memcpy(A_csr->i, Ai, sizeof(Ai)); - memcpy(A_csr->x, Ax, sizeof(Ax)); - matrix *A_sm = new_sparse_matrix(A_csr); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: public dispatcher with B native spd — routes through - the spd-B branch (BTA_spd_csc internally). */ - matrix *C_ours = BTA_matrices_alloc(A_sm, B_spd); - A_sm->refresh_csc_values(A_sm); - BTDA_matrices_fill_values(A_sm, d, B_spd, C_ours); - - /* Route 2: same public dispatcher with B flattened to sparse — - routes through the csc-csc branch. */ - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sm, B_sparse); - A_sm->refresh_csc_values(A_sm); - B_sparse->refresh_csc_values(B_sparse); - BTDA_matrices_fill_values(A_sm, d, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(C_ours); - free_matrix(A_sm); - free_matrix(B_spd); - return 0; -} - -/* BTA_spd_matrices dispatcher: A is stacked_pd. Verifies the - spd-branch routes to BTA_spd_spd / BTDA_spd_spd. */ -const char *test_BTA_spd_matrices_spd_A(void) -{ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *B_blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *B_blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) B_blk0, - (permuted_dense *) B_blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 1}; - double A0X[4] = {10, 11, 12, 13}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {20, 21, 22, 23}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: public dispatcher with both operands as native spd — - routes through the spd-spd branch (BTA_spd_spd internally). */ - matrix *C_ours = BTA_matrices_alloc(A_spd, B_spd); - BTDA_matrices_fill_values(A_spd, d, B_spd, C_ours); - - /* Route 2: same public dispatcher with both flattened to sparse — - routes through the csc-csc branch. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *B_sparse = spd_to_sparse_matrix_copy(B_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sparse); - A_sparse->refresh_csc_values(A_sparse); - B_sparse->refresh_csc_values(B_sparse); - BTDA_matrices_fill_values(A_sparse, d, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(A_spd); - free_matrix(B_spd); - return 0; -} - -/* BA_spd_matrices dispatcher tests previously lived here. The dispatcher - is now static inside matmul_dispatchers.c (no production caller, no public - API). The underlying primitives BA_spd_csc_*, BA_spd_pd_*, BA_spd_spd_* - are tested directly in tests/utils/test_stacked_pd.h. */ - -/* BTA_pd_matrices dispatcher: A is permuted_dense. Verifies the PD-branch - routes to BTA_pd_pd / BTDA_pd_pd. Both routes produce a PD; compare X - buffers directly. */ -const char *test_BTA_pd_matrices_pd_A(void) -{ - /* B: 2x4 PD with row_perm = {0,1}, col_perm = {1,3}. */ - int B_rp[2] = {0, 1}; - int B_cp[2] = {1, 3}; - double BX[4] = {1, 2, 3, 4}; - matrix *B = new_permuted_dense(2, 4, 2, 2, B_rp, B_cp, BX); - - /* A: 2x4 PD with row_perm = {0,1}, col_perm = {0,2}. */ - int A_rp[2] = {0, 1}; - int A_cp[2] = {0, 2}; - double AX[4] = {5, 6, 7, 8}; - matrix *A = new_permuted_dense(2, 4, 2, 2, A_rp, A_cp, AX); - - double d[2] = {2.0, -1.5}; - - /* Route 1: public dispatcher with both operands native PD — - routes through BTA_pd_matrices -> BTA_pd_pd internally. */ - matrix *C_ours = BTA_matrices_alloc(A, B); - BTDA_matrices_fill_values(A, d, B, C_ours); - - /* Route 2: same dispatcher with A flattened to sparse — routes - through BTA_pd_matrices -> BTA_pd_csc, a different kernel. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B); - A_sparse->refresh_csc_values(A_sparse); - BTDA_matrices_fill_values(A_sparse, d, B, C_ref); - - /* Both routes produce PD (B is PD). Compare X/perms directly. */ - permuted_dense *C_ours_pd = (permuted_dense *) C_ours; - permuted_dense *C_ref_pd = (permuted_dense *) C_ref; - mu_assert("m", C_ours->m == C_ref->m); - mu_assert("n", C_ours->n == C_ref->n); - mu_assert("m0", C_ours_pd->m0 == C_ref_pd->m0); - mu_assert("n0", C_ours_pd->n0 == C_ref_pd->n0); - mu_assert("row_perm", - cmp_int_array(C_ours_pd->row_perm, C_ref_pd->row_perm, C_ours_pd->m0)); - mu_assert("col_perm", - cmp_int_array(C_ours_pd->col_perm, C_ref_pd->col_perm, C_ours_pd->n0)); - mu_assert("X", cmp_double_array(C_ours_pd->X, C_ref_pd->X, - (size_t) C_ours_pd->m0 * C_ours_pd->n0)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(A); - free_matrix(B); - return 0; -} - -/* BTA_pd_matrices dispatcher: A is sparse_matrix. Verifies the sparse-branch - ensures csc_cache and routes to BTA_pd_csc / BTDA_pd_csc. */ -const char *test_BTA_pd_matrices_csc_A(void) -{ - /* B: 4x5 PD. */ - int B_rp[3] = {0, 1, 2}; - int B_cp[3] = {1, 3, 4}; - double BX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *B = new_permuted_dense(4, 5, 3, 3, B_rp, B_cp, BX); - - /* A: 4x3 sparse_matrix wrapping a CSR. */ - CSR_matrix *A_csr = new_CSR_matrix(4, 3, 7); - int Ap[5] = {0, 2, 3, 6, 7}; - int Ai[7] = {0, 2, 1, 0, 1, 2, 2}; - double Ax[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(A_csr->p, Ap, sizeof(Ap)); - memcpy(A_csr->i, Ai, sizeof(Ai)); - memcpy(A_csr->x, Ax, sizeof(Ax)); - matrix *A_sm = new_sparse_matrix(A_csr); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: public dispatcher with B native PD, A native sparse — - routes through BTA_pd_matrices -> BTA_pd_csc internally. */ - matrix *C_ours = BTA_matrices_alloc(A_sm, B); - A_sm->refresh_csc_values(A_sm); - BTDA_matrices_fill_values(A_sm, d, B, C_ours); - - /* Route 2: same dispatcher with B flattened to sparse — routes - through BTA_sparse_matrices -> BTA_alloc (csc-csc), a different - internal kernel. Output type differs (sparse_matrix vs PD), so - compare via to_csr. */ - matrix *B_sparse = spd_to_sparse_matrix_copy(B); - matrix *C_ref = BTA_matrices_alloc(A_sm, B_sparse); - A_sm->refresh_csc_values(A_sm); - B_sparse->refresh_csc_values(B_sparse); - BTDA_matrices_fill_values(A_sm, d, B_sparse, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B_sparse); - free_matrix(C_ours); - free_matrix(A_sm); - free_matrix(B); - return 0; -} - -/* BTA_pd_matrices dispatcher: A is stacked_pd. Verifies the spd-branch - routes to BTA_pd_spd / BTDA_pd_spd. */ -const char *test_BTA_pd_matrices_spd_A(void) -{ - /* B: 4x5 PD. */ - int B_rp[3] = {0, 1, 2}; - int B_cp[3] = {1, 3, 4}; - double BX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *B = new_permuted_dense(4, 5, 3, 3, B_rp, B_cp, BX); - - /* A: 4x3 spd, two blocks with shared col 2 (exercises BTA_pd_spd - scatter-accumulate). */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: public dispatcher with A native spd, B native PD — - routes through BTA_pd_matrices -> BTA_pd_spd internally. */ - matrix *C_ours = BTA_matrices_alloc(A_spd, B); - BTDA_matrices_fill_values(A_spd, d, B, C_ours); - - /* Route 2: same public dispatcher with A flattened to sparse — - routes through BTA_pd_matrices -> BTA_pd_csc, a different - internal kernel. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B); - A_sparse->refresh_csc_values(A_sparse); - BTDA_matrices_fill_values(A_sparse, d, B, C_ref); - - /* Both routes produce PD (B is PD). Compare X/perms directly. */ - permuted_dense *C_ours_pd = (permuted_dense *) C_ours; - permuted_dense *C_ref_pd = (permuted_dense *) C_ref; - mu_assert("m", C_ours->m == C_ref->m); - mu_assert("n", C_ours->n == C_ref->n); - mu_assert("m0", C_ours_pd->m0 == C_ref_pd->m0); - mu_assert("n0", C_ours_pd->n0 == C_ref_pd->n0); - mu_assert("row_perm", - cmp_int_array(C_ours_pd->row_perm, C_ref_pd->row_perm, C_ours_pd->m0)); - mu_assert("col_perm", - cmp_int_array(C_ours_pd->col_perm, C_ref_pd->col_perm, C_ours_pd->n0)); - mu_assert("X", cmp_double_array(C_ours_pd->X, C_ref_pd->X, - (size_t) C_ours_pd->m0 * C_ours_pd->n0)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(A_spd); - free_matrix(B); - return 0; -} - -/* Primitive BTDA_csc_spd kernel: C = B^T @ diag(d) @ A with B a CSC and A a - 2-block stacked_pd whose col_perms share at least one column (so the - accumulating coalesce path fires). Reference is the production - dispatcher with A flattened to a sparse_matrix; outputs differ in - storage (stacked_pd vs sparse_matrix), so compare via to_csr. */ -const char *test_BTDA_csc_spd_overlapping(void) -{ - /* A: 4x3 spd, two blocks with overlapping col_perms (share col 2). */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - /* B: 4x3 sparse_matrix wrapping a CSR. */ - CSR_matrix *B_csr = new_CSR_matrix(4, 3, 7); - int Bp[5] = {0, 2, 3, 6, 7}; - int Bi[7] = {0, 2, 1, 0, 1, 2, 2}; - double Bx[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(B_csr->p, Bp, sizeof(Bp)); - memcpy(B_csr->i, Bi, sizeof(Bi)); - memcpy(B_csr->x, Bx, sizeof(Bx)); - matrix *B_sm = new_sparse_matrix(B_csr); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: new kernel. Ensure B's csc_cache and refresh values. */ - sparse_matrix_ensure_csc_cache((sparse_matrix *) B_sm); - B_sm->refresh_csc_values(B_sm); - matrix *C_ours = - BTA_csc_spd_alloc(((sparse_matrix *) B_sm)->csc_cache, (stacked_pd *) A_spd); - BTDA_csc_spd_fill_values(((sparse_matrix *) B_sm)->csc_cache, d, - (stacked_pd *) A_spd, (stacked_pd *) C_ours); - - /* Route 2: production dispatcher with A flattened to sparse_matrix. - BTA_matrices_alloc(A, B) computes B^T @ A, so A goes first. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sm); - A_sparse->refresh_csc_values(A_sparse); - BTDA_matrices_fill_values(A_sparse, d, B_sm, C_ref); - - /* C_ours is stacked_pd, C_ref is sparse_matrix — compare via to_csr. */ - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(B_sm); - free_matrix(A_spd); - return 0; -} - -/* Primitive BTA_csc_spd kernel (no diagonal): C = B^T @ A, B is CSC, A is a - 2-block stacked_pd whose col_perms share a column (so the accumulating coalesce - path fires). Same matrices as the BTDA variant above. Oracle is the dispatcher - with A flattened to sparse and d = ones (csc/csc sparse_dot path). */ -const char *test_BTA_csc_spd_overlapping(void) -{ - /* A: 4x3 spd, two blocks with overlapping col_perms (share col 2). */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - /* B: 4x3 sparse_matrix wrapping a CSR. */ - CSR_matrix *B_csr = new_CSR_matrix(4, 3, 7); - int Bp[5] = {0, 2, 3, 6, 7}; - int Bi[7] = {0, 2, 1, 0, 1, 2, 2}; - double Bx[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(B_csr->p, Bp, sizeof(Bp)); - memcpy(B_csr->i, Bi, sizeof(Bi)); - memcpy(B_csr->x, Bx, sizeof(Bx)); - matrix *B_sm = new_sparse_matrix(B_csr); - - /* Route 1: new kernel. Ensure B's csc_cache and refresh values. */ - sparse_matrix_ensure_csc_cache((sparse_matrix *) B_sm); - B_sm->refresh_csc_values(B_sm); - matrix *C_ours = - BTA_csc_spd_alloc(((sparse_matrix *) B_sm)->csc_cache, (stacked_pd *) A_spd); - BTA_csc_spd_fill_values(((sparse_matrix *) B_sm)->csc_cache, - (stacked_pd *) A_spd, (stacked_pd *) C_ours); - - /* Route 2: dispatcher with A flattened to sparse_matrix and d = ones, so - BTDA == BTA. BTA_matrices_alloc(A, B) computes B^T @ A, so A goes first. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sm); - A_sparse->refresh_csc_values(A_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sm, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(B_sm); - free_matrix(A_spd); - return 0; -} - -/* BTA_csc_spd corner case: one A-block whose row_perm matches no nonzero row of - any B column -> that block's per-block partial is empty (m0 = 0), while the - other block contributes. Exercises the empty-source-block path through - BTA_csc_pd_alloc and the accumulating coalesce. */ -const char *test_BTA_csc_spd_block_no_overlap(void) -{ - /* A: 4x3 spd, two blocks. blk0 rows {0,1}; blk1 rows {2,3}. */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 1}; - double A0X[4] = {1, 2, 3, 4}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - /* B: 4x3, nonzeros only in rows {0,1} -> overlaps blk0 only; blk1's rows - {2,3} hit no B column -> empty partial for blk1. Rows: 0:{0,2} 1:{1}. */ - CSR_matrix *B_csr = new_CSR_matrix(4, 3, 3); - int Bp[5] = {0, 2, 3, 3, 3}; - int Bi[3] = {0, 2, 1}; - double Bx[3] = {1, 2, 3}; - memcpy(B_csr->p, Bp, sizeof(Bp)); - memcpy(B_csr->i, Bi, sizeof(Bi)); - memcpy(B_csr->x, Bx, sizeof(Bx)); - matrix *B_sm = new_sparse_matrix(B_csr); - - sparse_matrix_ensure_csc_cache((sparse_matrix *) B_sm); - B_sm->refresh_csc_values(B_sm); - matrix *C_ours = - BTA_csc_spd_alloc(((sparse_matrix *) B_sm)->csc_cache, (stacked_pd *) A_spd); - BTA_csc_spd_fill_values(((sparse_matrix *) B_sm)->csc_cache, - (stacked_pd *) A_spd, (stacked_pd *) C_ours); - - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sm); - A_sparse->refresh_csc_values(A_sparse); - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_matrices_fill_values(A_sparse, d_ones, B_sm, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(B_sm); - free_matrix(A_spd); - return 0; -} - -/* BTA_sparse_matrices dispatcher: A is permuted_dense. Both routes produce - PD; compare X / perms directly. */ -const char *test_BTA_sparse_matrices_pd_A(void) -{ - /* B: 4x3 sparse_matrix. */ - CSR_matrix *B_csr = new_CSR_matrix(4, 3, 7); - int Bp[5] = {0, 2, 3, 6, 7}; - int Bi[7] = {0, 2, 1, 0, 1, 2, 2}; - double Bx[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(B_csr->p, Bp, sizeof(Bp)); - memcpy(B_csr->i, Bi, sizeof(Bi)); - memcpy(B_csr->x, Bx, sizeof(Bx)); - matrix *B_sm = new_sparse_matrix(B_csr); - - /* A: 4x3 PD. */ - int A_rp[4] = {0, 1, 2, 3}; - int A_cp[3] = {0, 1, 2}; - double AX[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - matrix *A = new_permuted_dense(4, 3, 4, 3, A_rp, A_cp, AX); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: public dispatcher with B native sparse, A native PD — - routes through BTA_sparse_matrices -> BTA_csc_pd internally. */ - matrix *C_ours = BTA_matrices_alloc(A, B_sm); - B_sm->refresh_csc_values(B_sm); - BTDA_matrices_fill_values(A, d, B_sm, C_ours); - - /* Route 2: same dispatcher with A flattened to sparse — routes - through BTA_sparse_matrices -> BTA_alloc (csc-csc), a different - kernel. Output type differs (sparse_matrix vs PD), so compare - via to_csr. */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sm); - A_sparse->refresh_csc_values(A_sparse); - B_sm->refresh_csc_values(B_sm); - BTDA_matrices_fill_values(A_sparse, d, B_sm, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(A); - free_matrix(B_sm); - return 0; -} - -/* Public dispatcher: B and A both sparse_matrix. Both routes go - through the same csc-csc kernel, so the reference is a hand-computed - dense matrix product. */ -const char *test_BTA_sparse_matrices_csc_A(void) -{ - /* B: 4x3 sparse_matrix. */ - CSR_matrix *B_csr = new_CSR_matrix(4, 3, 7); - int Bp[5] = {0, 2, 3, 6, 7}; - int Bi[7] = {0, 2, 1, 0, 1, 2, 2}; - double Bx[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(B_csr->p, Bp, sizeof(Bp)); - memcpy(B_csr->i, Bi, sizeof(Bi)); - memcpy(B_csr->x, Bx, sizeof(Bx)); - matrix *B_sm = new_sparse_matrix(B_csr); - - /* A: 4x3 sparse_matrix. */ - CSR_matrix *A_csr = new_CSR_matrix(4, 3, 6); - int Ap[5] = {0, 1, 3, 5, 6}; - int Ai[6] = {1, 0, 2, 1, 2, 0}; - double Ax[6] = {10, 20, 30, 40, 50, 60}; - memcpy(A_csr->p, Ap, sizeof(Ap)); - memcpy(A_csr->i, Ai, sizeof(Ai)); - memcpy(A_csr->x, Ax, sizeof(Ax)); - matrix *A_sm = new_sparse_matrix(A_csr); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1 (only route): public dispatcher with both operands native - sparse — routes through BTA_sparse_matrices -> BTA_alloc (csc-csc). */ - matrix *C_ours = BTA_matrices_alloc(A_sm, B_sm); - A_sm->refresh_csc_values(A_sm); - B_sm->refresh_csc_values(B_sm); - BTDA_matrices_fill_values(A_sm, d, B_sm, C_ours); - - /* Reference: hand-compute C = B^T @ diag(d) @ A as a dense 3x3 buffer. - Both operands are 4x3 sparse — too small to be worth a second - dispatcher route (nothing to flatten). */ - double B_dense[4 * 3] = { - 1, 0, 2, /* row 0 */ - 0, 3, 0, /* row 1 */ - 4, 5, 6, /* row 2 */ - 0, 0, 7 /* row 3 */ - }; - double A_dense[4 * 3] = { - 0, 10, 0, /* row 0 */ - 20, 0, 30, /* row 1 */ - 0, 40, 50, /* row 2 */ - 60, 0, 0 /* row 3 */ - }; - double C_dense_ref[3 * 3] = {0}; - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 3; j++) - { - double s = 0.0; - for (int l = 0; l < 4; l++) - { - s += B_dense[l * 3 + i] * d[l] * A_dense[l * 3 + j]; - } - C_dense_ref[i * 3 + j] = s; - } - } - - /* Scatter dispatcher's CSR output into a dense buffer for comparison. */ - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - mu_assert("m", csr_ours->m == 3); - mu_assert("n", csr_ours->n == 3); - double C_dense_ours[3 * 3] = {0}; - for (int i = 0; i < csr_ours->m; i++) - { - for (int jj = csr_ours->p[i]; jj < csr_ours->p[i + 1]; jj++) - { - C_dense_ours[i * 3 + csr_ours->i[jj]] = csr_ours->x[jj]; - } - } - mu_assert("C dense", cmp_double_array(C_dense_ours, C_dense_ref, 9)); - - free_matrix(C_ours); - free_matrix(A_sm); - free_matrix(B_sm); - return 0; -} - -/* BTA_sparse_matrices dispatcher: A is stacked_pd. Our route returns - stacked_pd (via BTA_csc_spd_alloc); the production route returns - sparse_matrix (it materializes A->csc inside resolve_operand, then - takes the csc-csc branch). Compare via to_csr. */ -const char *test_BTA_sparse_matrices_spd_A(void) -{ - /* B: 4x3 sparse_matrix. */ - CSR_matrix *B_csr = new_CSR_matrix(4, 3, 7); - int Bp[5] = {0, 2, 3, 6, 7}; - int Bi[7] = {0, 2, 1, 0, 1, 2, 2}; - double Bx[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(B_csr->p, Bp, sizeof(Bp)); - memcpy(B_csr->i, Bi, sizeof(Bi)); - memcpy(B_csr->x, Bx, sizeof(Bx)); - matrix *B_sm = new_sparse_matrix(B_csr); - - /* A: 4x3 spd, two blocks with overlapping col_perm (share col 2). */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - double d[4] = {2.0, -1.5, 0.5, 1.25}; - - /* Route 1: public dispatcher with B native sparse, A native spd — - routes through BTA_sparse_matrices -> BTA_csc_spd internally. */ - matrix *C_ours = BTA_matrices_alloc(A_spd, B_sm); - B_sm->refresh_csc_values(B_sm); - BTDA_matrices_fill_values(A_spd, d, B_sm, C_ours); - - /* Route 2: same dispatcher with A flattened to sparse — routes - through BTA_sparse_matrices -> BTA_alloc (csc-csc). */ - matrix *A_sparse = spd_to_sparse_matrix_copy(A_spd); - matrix *C_ref = BTA_matrices_alloc(A_sparse, B_sm); - A_sparse->refresh_csc_values(A_sparse); - B_sm->refresh_csc_values(B_sm); - BTDA_matrices_fill_values(A_sparse, d, B_sm, C_ref); - - /* Routes produce different output types — compare via to_csr. */ - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(A_sparse); - free_matrix(C_ours); - free_matrix(A_spd); - free_matrix(B_sm); - return 0; -} - -/* Top-level BTA_matrices_fill_values dispatcher. Each test runs Route 1 = - BTA_matrices_alloc + BTA_matrices_fill_values and Route 2 = the same alloc + - BTDA_matrices_fill_values with d = ones (B^T diag(1) A = B^T A). The two - share structure, so we compare values via to_csr. Together the four cover all - three B-branches and the special csc/csc (d == NULL) path. Recall - BTA_matrices_alloc(A, B) computes B^T @ A, so the transposed operand goes - second. */ -const char *test_BTA_matrices_fill_pd_spd(void) -{ - /* B: 4x5 PD (transposed operand). */ - int B_rp[3] = {0, 1, 2}; - int B_cp[3] = {1, 3, 4}; - double BX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *B = new_permuted_dense(4, 5, 3, 3, B_rp, B_cp, BX); - - /* A: 4x3 spd, two blocks (share col 2). */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *A_blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *A_blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) A_blk0, - (permuted_dense *) A_blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - - matrix *C_ours = BTA_matrices_alloc(A_spd, B); - BTA_matrices_fill_values(A_spd, B, C_ours); - - matrix *C_ref = BTA_matrices_alloc(A_spd, B); - BTDA_matrices_fill_values(A_spd, d_ones, B, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(C_ours); - free_matrix(A_spd); - free_matrix(B); - return 0; -} - -const char *test_BTA_matrices_fill_spd_csc(void) -{ - /* B: 4x3 spd, two blocks (transposed operand). */ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *B_blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *B_blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) B_blk0, - (permuted_dense *) B_blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - - /* A: 4x3 sparse_matrix. */ - CSR_matrix *A_csr = new_CSR_matrix(4, 3, 7); - int Ap[5] = {0, 2, 3, 6, 7}; - int Ai[7] = {0, 2, 1, 0, 1, 2, 2}; - double Ax[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(A_csr->p, Ap, sizeof(Ap)); - memcpy(A_csr->i, Ai, sizeof(Ai)); - memcpy(A_csr->x, Ax, sizeof(Ax)); - matrix *A_sm = new_sparse_matrix(A_csr); - - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - - matrix *C_ours = BTA_matrices_alloc(A_sm, B_spd); - A_sm->refresh_csc_values(A_sm); - BTA_matrices_fill_values(A_sm, B_spd, C_ours); - - matrix *C_ref = BTA_matrices_alloc(A_sm, B_spd); - A_sm->refresh_csc_values(A_sm); - BTDA_matrices_fill_values(A_sm, d_ones, B_spd, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(C_ours); - free_matrix(A_sm); - free_matrix(B_spd); - return 0; -} - -const char *test_BTA_matrices_fill_csc_pd(void) -{ - /* B: 4x4 sparse_matrix (transposed operand). */ - CSR_matrix *B_csr = new_CSR_matrix(4, 4, 5); - int Bp[5] = {0, 2, 3, 4, 5}; - int Bi[5] = {0, 2, 1, 0, 3}; - double Bx[5] = {10, 20, 30, 40, 50}; - memcpy(B_csr->p, Bp, sizeof(Bp)); - memcpy(B_csr->i, Bi, sizeof(Bi)); - memcpy(B_csr->x, Bx, sizeof(Bx)); - matrix *B_sm = new_sparse_matrix(B_csr); - - /* A: 4x3 PD. */ - int A_rp[4] = {0, 1, 2, 3}; - int A_cp[3] = {0, 1, 2}; - double AX[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - matrix *A = new_permuted_dense(4, 3, 4, 3, A_rp, A_cp, AX); - - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - - matrix *C_ours = BTA_matrices_alloc(A, B_sm); - B_sm->refresh_csc_values(B_sm); - BTA_matrices_fill_values(A, B_sm, C_ours); - - matrix *C_ref = BTA_matrices_alloc(A, B_sm); - B_sm->refresh_csc_values(B_sm); - BTDA_matrices_fill_values(A, d_ones, B_sm, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(C_ours); - free_matrix(A); - free_matrix(B_sm); - return 0; -} - -const char *test_BTA_matrices_fill_csc_csc(void) -{ - /* B: 4x3 sparse_matrix (transposed operand). */ - CSR_matrix *B_csr = new_CSR_matrix(4, 3, 7); - int Bp[5] = {0, 2, 3, 6, 7}; - int Bi[7] = {0, 2, 1, 0, 1, 2, 2}; - double Bx[7] = {1, 2, 3, 4, 5, 6, 7}; - memcpy(B_csr->p, Bp, sizeof(Bp)); - memcpy(B_csr->i, Bi, sizeof(Bi)); - memcpy(B_csr->x, Bx, sizeof(Bx)); - matrix *B_sm = new_sparse_matrix(B_csr); - - /* A: 4x4 sparse_matrix. */ - CSR_matrix *A_csr = new_CSR_matrix(4, 4, 5); - int Ap[5] = {0, 2, 3, 4, 5}; - int Ai[5] = {0, 2, 1, 0, 3}; - double Ax[5] = {10, 20, 30, 40, 50}; - memcpy(A_csr->p, Ap, sizeof(Ap)); - memcpy(A_csr->i, Ai, sizeof(Ai)); - memcpy(A_csr->x, Ax, sizeof(Ax)); - matrix *A_sm = new_sparse_matrix(A_csr); - - double d_ones[4] = {1.0, 1.0, 1.0, 1.0}; - - matrix *C_ours = BTA_matrices_alloc(A_sm, B_sm); - A_sm->refresh_csc_values(A_sm); - B_sm->refresh_csc_values(B_sm); - BTA_matrices_fill_values(A_sm, B_sm, C_ours); - - matrix *C_ref = BTA_matrices_alloc(A_sm, B_sm); - A_sm->refresh_csc_values(A_sm); - B_sm->refresh_csc_values(B_sm); - BTDA_matrices_fill_values(A_sm, d_ones, B_sm, C_ref); - - CSR_matrix *csr_ours = C_ours->to_csr(C_ours); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(C_ours); - free_matrix(A_sm); - free_matrix(B_sm); - return 0; -} - -/* Regression test for the kron-scratch / transpose-cache crash. Exercises - BA_dense_kron_spd_fill_values with p > 1: the underlying kron_for_each_ - output_block reuses a single scratch PD across iterations, mutating - its row_perm / col_perm in place. A prior implementation of - BA_pd_spd_fill_values cached B's transpose on B->transpose_cache, - which (a) was never populated for the fill-path scratch, and (b) - would have held stale perms even if populated. BA_pd_spd still uses - that cache, but the kron path stays correct because - BA_dense_kron_spd bypasses the wrapper and calls BTA_pd_spd_* - directly on its mutating scratch (see stacked_pd_linalg.c). The - reference path runs the same dispatch with J flattened to - sparse_matrix (which routes via BA_dense_kron_csc, no transpose-cache - involvement); we compare via to_csr. */ -const char *test_BA_pd_kron_spd_no_cache_staleness(void) -{ - /* A: 2x2 PD (kron-block size). */ - int A_rp[2] = {0, 1}; - int A_cp[2] = {0, 1}; - double AX[4] = {1, 2, 3, 4}; - matrix *A = new_permuted_dense(2, 2, 2, 2, A_rp, A_cp, AX); - - /* J: 6x3 spd with three blocks, one block per kron-stride so each - iteration's scratch perms differ. p = 3 -> kron(I_3, A) is 6x6, - J is 6x3, output is 6x3. */ - int J0_rp[2] = {0, 1}; - int J0_cp[2] = {0, 1}; - double J0X[4] = {1, 2, 3, 4}; - matrix *Jblk0 = new_permuted_dense(6, 3, 2, 2, J0_rp, J0_cp, J0X); - int J1_rp[2] = {2, 3}; - int J1_cp[2] = {1, 2}; - double J1X[4] = {5, 6, 7, 8}; - matrix *Jblk1 = new_permuted_dense(6, 3, 2, 2, J1_rp, J1_cp, J1X); - int J2_rp[2] = {4, 5}; - int J2_cp[2] = {0, 2}; - double J2X[4] = {9, 10, 11, 12}; - matrix *Jblk2 = new_permuted_dense(6, 3, 2, 2, J2_rp, J2_cp, J2X); - permuted_dense *J_blocks[3] = {(permuted_dense *) Jblk0, - (permuted_dense *) Jblk1, - (permuted_dense *) Jblk2}; - matrix *J_spd = new_stacked_pd(6, 3, 3, J_blocks, NULL, NULL); - - int p = 3; - - /* Route 1: spd path through BA_pd_kron_spd. */ - matrix *C_spd = BA_dense_kron_matrices_alloc((permuted_dense *) A, p, J_spd); - BA_dense_kron_matrices_fill_values((permuted_dense *) A, p, J_spd, - (stacked_pd *) C_spd); - - /* Route 2: sparse path via spd_to_sparse_matrix_copy + BA_dense_kron_csc. */ - matrix *J_sparse = spd_to_sparse_matrix_copy(J_spd); - matrix *C_ref = BA_dense_kron_matrices_alloc((permuted_dense *) A, p, J_sparse); - J_sparse->refresh_csc_values(J_sparse); - BA_dense_kron_matrices_fill_values((permuted_dense *) A, p, J_sparse, - (stacked_pd *) C_ref); - - /* Compare via to_csr — output structures may differ. */ - CSR_matrix *csr_spd = C_spd->to_csr(C_spd); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_spd->m == csr_ref->m); - mu_assert("n", csr_spd->n == csr_ref->n); - mu_assert("nnz", csr_spd->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_spd->p, csr_ref->p, csr_spd->m + 1)); - mu_assert("i", cmp_int_array(csr_spd->i, csr_ref->i, csr_spd->nnz)); - mu_assert("x", cmp_double_array(csr_spd->x, csr_ref->x, csr_spd->nnz)); - - free_matrix(C_ref); - free_matrix(J_sparse); - free_matrix(C_spd); - free_matrix(J_spd); - free_matrix(A); - return 0; -} - -/* Only compiled with -DSP_TRACK_MEMORY=ON: the test reads the tracked - allocator counters, which do not exist in a default build. */ -#ifdef SP_TRACK_MEMORY - -/* No-alloc-in-fill contract for the BTDA kernels via fill_is_alloc_free - (tests/test_helpers.h). Covers BTDA_pd_pd (both the matching-row_perm and - gather paths), BTDA_pd_spd, and the blockwise BTDA_spd_pd. */ -typedef struct -{ - const permuted_dense *B; - const double *d; - const void *A; - matrix *C; -} btda_fill_args; - -static void run_BTDA_pd_pd(const void *ctx) -{ - const btda_fill_args *a = (const btda_fill_args *) ctx; - BTDA_pd_pd_fill_values(a->B, a->d, (const permuted_dense *) a->A, - (permuted_dense *) a->C); -} - -static void run_BTDA_pd_spd(const void *ctx) -{ - const btda_fill_args *a = (const btda_fill_args *) ctx; - BTDA_pd_spd_fill_values(a->B, a->d, (const stacked_pd *) a->A, - (permuted_dense *) a->C); -} - -typedef struct -{ - const stacked_pd *B; - const double *d; - const permuted_dense *A; - stacked_pd *C; -} btda_spd_fill_args; - -static void run_BTDA_spd_pd(const void *ctx) -{ - const btda_spd_fill_args *a = (const btda_spd_fill_args *) ctx; - BTDA_spd_pd_fill_values(a->B, a->d, a->A, a->C); -} - -const char *test_BTDA_fill_no_transient_alloc(void) -{ - double d[8] = {2.0, -1.5, 0.5, 1.25, 3.0, -0.5, 1.0, 2.5}; - - /* --- pd_pd, matching row_perms (fast path) --- */ - { - int row_perm[2] = {1, 3}; - int cp_A[2] = {0, 2}; - int cp_B[2] = {1, 3}; - double XA[4] = {1.0, 2.0, 3.0, 4.0}; - double XB[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *A = new_permuted_dense(4, 4, 2, 2, row_perm, cp_A, XA); - matrix *B = new_permuted_dense(4, 4, 2, 2, row_perm, cp_B, XB); - matrix *C = BTA_pd_pd_alloc((permuted_dense *) B, (permuted_dense *) A); - btda_fill_args args = {(permuted_dense *) B, d, A, C}; - run_BTDA_pd_pd(&args); /* warm-up */ - mu_assert("pd_pd fast path allocates in fill", - fill_is_alloc_free(run_BTDA_pd_pd, &args)); - free_matrix(C); - free_matrix(B); - free_matrix(A); - } - - /* --- pd_pd, partial overlap (gather path) --- */ - { - int rp_A[3] = {1, 3, 5}; - int rp_B[3] = {3, 5, 7}; - int cp_A[2] = {0, 2}; - int cp_B[2] = {1, 3}; - double XA[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - double XB[6] = {10.0, 20.0, 30.0, 40.0, 50.0, 60.0}; - matrix *A = new_permuted_dense(8, 4, 3, 2, rp_A, cp_A, XA); - matrix *B = new_permuted_dense(8, 4, 3, 2, rp_B, cp_B, XB); - matrix *C = BTA_pd_pd_alloc((permuted_dense *) B, (permuted_dense *) A); - btda_fill_args args = {(permuted_dense *) B, d, A, C}; - run_BTDA_pd_pd(&args); - mu_assert("pd_pd gather path allocates in fill", - fill_is_alloc_free(run_BTDA_pd_pd, &args)); - free_matrix(C); - free_matrix(B); - free_matrix(A); - } - - /* --- pd_spd (two-block A, both kept) --- */ - { - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 2}; - double A1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) blk0, - (permuted_dense *) blk1}; - matrix *A_spd = new_stacked_pd(4, 3, 2, A_blocks, NULL, NULL); - int B_rp[3] = {0, 1, 2}; - int B_cp[3] = {1, 3, 4}; - double BX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *B = new_permuted_dense(4, 5, 3, 3, B_rp, B_cp, BX); - matrix *C = BTA_pd_spd_alloc((permuted_dense *) B, (stacked_pd *) A_spd); - btda_fill_args args = {(permuted_dense *) B, d, A_spd, C}; - run_BTDA_pd_spd(&args); - mu_assert("pd_spd allocates in fill", - fill_is_alloc_free(run_BTDA_pd_spd, &args)); - free_matrix(C); - free_matrix(B); - free_matrix(A_spd); - } - - /* --- spd_pd (blockwise, overlapping col_perms) --- */ - { - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {1, 2, 3, 4}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {5, 6, 7, 8}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - permuted_dense *B_blocks[2] = {(permuted_dense *) blk0, - (permuted_dense *) blk1}; - matrix *B_spd = new_stacked_pd(4, 3, 2, B_blocks, NULL, NULL); - int A_rp[4] = {0, 1, 2, 3}; - int A_cp[3] = {0, 1, 2}; - double AX[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - matrix *A = new_permuted_dense(4, 3, 4, 3, A_rp, A_cp, AX); - matrix *C = BTA_spd_pd_alloc((stacked_pd *) B_spd, (permuted_dense *) A); - btda_spd_fill_args args = {(stacked_pd *) B_spd, d, (permuted_dense *) A, - (stacked_pd *) C}; - run_BTDA_spd_pd(&args); - mu_assert("spd_pd blockwise allocates in fill", - fill_is_alloc_free(run_BTDA_spd_pd, &args)); - free_matrix(C); - free_matrix(B_spd); - free_matrix(A); - } - - return 0; -} - -#endif /* SP_TRACK_MEMORY */ - -/* BA_pd_spd transpose cache: the fill refreshes B's cached transpose iff - B's values_version moved since the last fill. Fill once, mutate B's - values + bump, refill, and compare against a fresh computation with the - mutated values. */ -const char *test_BA_pd_spd_transpose_cache_refresh(void) -{ - /* B: 3x4 pd with a non-square 2x3 block. */ - int B_rp[2] = {0, 2}; - int B_cp[3] = {0, 1, 3}; - double BX[6] = {1, 2, 3, 4, 5, 6}; - matrix *B_m = new_permuted_dense(3, 4, 2, 3, B_rp, B_cp, BX); - permuted_dense *B = (permuted_dense *) B_m; - - /* A: 4x5 spd with two disjoint-row blocks. */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {1, 2, 3, 4}; - matrix *Ablk0 = new_permuted_dense(4, 5, 2, 2, A0_rp, A0_cp, A0X); - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 4}; - double A1X[4] = {5, 6, 7, 8}; - matrix *Ablk1 = new_permuted_dense(4, 5, 2, 2, A1_rp, A1_cp, A1X); - permuted_dense *A_blocks[2] = {(permuted_dense *) Ablk0, - (permuted_dense *) Ablk1}; - matrix *A_spd = new_stacked_pd(4, 5, 2, A_blocks, NULL, NULL); - stacked_pd *A = (stacked_pd *) A_spd; - - matrix *C = BA_pd_spd_alloc(B, A); - BA_pd_spd_fill_values(B, A, (permuted_dense *) C); - mu_assert("seen must match after fill", - B->transpose_cache->transpose_seen == B->base.values_version); - - /* Mutate B's values and bump; the guarded refill must refresh the - cached transpose. */ - double BX2[6] = {-1, 7, 0.5, 2, -3, 6}; - memcpy(B->X, BX2, 6 * sizeof(double)); - matrix_values_changed(B_m); - BA_pd_spd_fill_values(B, A, (permuted_dense *) C); - mu_assert("seen must catch up after bump + refill", - B->transpose_cache->transpose_seen == B->base.values_version); - - /* Reference: fresh B with the mutated values, fresh cache. */ - matrix *B2_m = new_permuted_dense(3, 4, 2, 3, B_rp, B_cp, BX2); - permuted_dense *B2 = (permuted_dense *) B2_m; - matrix *C_ref = BA_pd_spd_alloc(B2, A); - BA_pd_spd_fill_values(B2, A, (permuted_dense *) C_ref); - - CSR_matrix *csr_ours = C->to_csr(C); - CSR_matrix *csr_ref = C_ref->to_csr(C_ref); - mu_assert("m", csr_ours->m == csr_ref->m); - mu_assert("n", csr_ours->n == csr_ref->n); - mu_assert("nnz", csr_ours->nnz == csr_ref->nnz); - mu_assert("p", cmp_int_array(csr_ours->p, csr_ref->p, csr_ours->m + 1)); - mu_assert("i", cmp_int_array(csr_ours->i, csr_ref->i, csr_ours->nnz)); - mu_assert("x", cmp_double_array(csr_ours->x, csr_ref->x, csr_ours->nnz)); - - free_matrix(C_ref); - free_matrix(B2_m); - free_matrix(C); - free_matrix(A_spd); - free_matrix(B_m); - return 0; -} - -#endif /* TEST_MATMUL_DISPATCHERS_H */ diff --git a/sparsediff_linalg/tests/test_matrix.h b/sparsediff_linalg/tests/test_matrix.h deleted file mode 100644 index 0227e5e5..00000000 --- a/sparsediff_linalg/tests/test_matrix.h +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef TEST_MATRIX_H -#define TEST_MATRIX_H - -#include "minunit.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/sparse_matrix.h" -#include "test_helpers.h" -#include -#include - -/* Test full-block PD block_left_mult_vec against known result. - A = [1 2; 3 4] (2x2), x = [1; 2], p = 1 - y = A * x = [1*1+2*2; 3*1+4*2] = [5; 11] */ -const char *test_pd_mult_vec_basic(void) -{ - double data[] = {1.0, 2.0, 3.0, 4.0}; - matrix *A = new_permuted_dense_full(2, 2, data); - - double x[] = {1.0, 2.0}; - double y[2] = {0.0, 0.0}; - - A->block_left_mult_vec(A, x, y, 1); - - double y_expected[2] = {5.0, 11.0}; - mu_assert("y incorrect", cmp_double_array(y, y_expected, 2)); - - free_matrix(A); - return 0; -} - -/* Test full-block PD block_left_mult_vec with multiple blocks. - A = [1 2; 3 4] (2x2), x = [1; 2; 3; 4], p = 2 - y = [A*[1;2]; A*[3;4]] = [5; 11; 11; 25] */ -const char *test_pd_mult_vec_blocks(void) -{ - double data[] = {1.0, 2.0, 3.0, 4.0}; - matrix *A = new_permuted_dense_full(2, 2, data); - - double x[] = {1.0, 2.0, 3.0, 4.0}; - double y[4] = {0}; - - A->block_left_mult_vec(A, x, y, 2); - - double y_expected[4] = {5.0, 11.0, 11.0, 25.0}; - mu_assert("y incorrect", cmp_double_array(y, y_expected, 4)); - - free_matrix(A); - return 0; -} - -/* Compare sparse vs PD block_left_mult_vec for a non-square matrix. - A = [1 2 3; 4 5 6] (2x3), x = [1; 2; 3], p = 1 */ -const char *test_sparse_vs_pd_mult_vec(void) -{ - /* Build CSR_matrix for A = [1 2 3; 4 5 6] */ - CSR_matrix *csr = new_CSR_matrix(2, 3, 6); - int Ap[3] = {0, 3, 6}; - int Ai[6] = {0, 1, 2, 0, 1, 2}; - double Ax[6] = {1, 2, 3, 4, 5, 6}; - memcpy(csr->p, Ap, 3 * sizeof(int)); - memcpy(csr->i, Ai, 6 * sizeof(int)); - memcpy(csr->x, Ax, 6 * sizeof(double)); - - double pd_data[] = {1, 2, 3, 4, 5, 6}; - - matrix *sparse = new_sparse_matrix(csr); - matrix *pd = new_permuted_dense_full(2, 3, pd_data); - - double x[] = {1.0, 2.0, 3.0}; - double y_sparse[2] = {0}; - double y_pd[2] = {0}; - - sparse->block_left_mult_vec(sparse, x, y_sparse, 1); - pd->block_left_mult_vec(pd, x, y_pd, 1); - - mu_assert("sparse vs pd mismatch", cmp_double_array(y_sparse, y_pd, 2)); - - free_matrix(sparse); - free_matrix(pd); - return 0; -} - -/* Test full-block PD transpose via vtable. */ -const char *test_pd_trans_full_block(void) -{ - double data[] = {1, 2, 3, 4, 5, 6}; /* 2x3 */ - matrix *A = new_permuted_dense_full(2, 3, data); - matrix *AT = A->transpose_alloc(A); - A->transpose_fill_values(A, AT); - - mu_assert("transpose m", AT->m == 3); - mu_assert("transpose n", AT->n == 2); - - /* AT should be [1 4; 2 5; 3 6] stored row-major */ - double AT_expected[6] = {1.0, 4.0, 2.0, 5.0, 3.0, 6.0}; - mu_assert("AT vals incorrect", cmp_double_array(AT->x, AT_expected, 6)); - - free_matrix(A); - free_matrix(AT); - return 0; -} - -/* Compare sparse vs PD block_left_mult_vec with p=2 blocks. - A = [1 2; 3 4], x = [1; 2; 3; 4], p = 2 */ -const char *test_sparse_vs_pd_mult_vec_blocks(void) -{ - CSR_matrix *csr = new_CSR_matrix(2, 2, 4); - int Ap[3] = {0, 2, 4}; - int Ai[4] = {0, 1, 0, 1}; - double Ax[4] = {1, 2, 3, 4}; - memcpy(csr->p, Ap, 3 * sizeof(int)); - memcpy(csr->i, Ai, 4 * sizeof(int)); - memcpy(csr->x, Ax, 4 * sizeof(double)); - - double pd_data[] = {1, 2, 3, 4}; - - matrix *sparse = new_sparse_matrix(csr); - matrix *pd = new_permuted_dense_full(2, 2, pd_data); - - double x[] = {1.0, 2.0, 3.0, 4.0}; - double y_sparse[4] = {0}; - double y_pd[4] = {0}; - - sparse->block_left_mult_vec(sparse, x, y_sparse, 2); - pd->block_left_mult_vec(pd, x, y_pd, 2); - - mu_assert("sparse vs pd blocks mismatch", cmp_double_array(y_sparse, y_pd, 4)); - - free_matrix(sparse); - free_matrix(pd); - return 0; -} - -/* Full-block permuted_dense acting as operator: smoke test mirroring - test_pd_mult_vec_basic with an explicit row_perm/col_perm. */ -const char *test_pd_operator_block_left_mult_vec(void) -{ - double data[] = {1.0, 2.0, 3.0, 4.0}; - int row_perm[2] = {0, 1}; - int col_perm[2] = {0, 1}; - matrix *A = new_permuted_dense(2, 2, 2, 2, row_perm, col_perm, data); - - double x[] = {1.0, 2.0}; - double y[2] = {0.0, 0.0}; - - A->block_left_mult_vec(A, x, y, 1); - - double y_expected[2] = {5.0, 11.0}; - mu_assert("y incorrect", cmp_double_array(y, y_expected, 2)); - - free_matrix(A); - return 0; -} - -#endif /* TEST_MATRIX_H */ diff --git a/sparsediff_linalg/tests/test_permuted_dense.h b/sparsediff_linalg/tests/test_permuted_dense.h deleted file mode 100644 index 4fea0fe0..00000000 --- a/sparsediff_linalg/tests/test_permuted_dense.h +++ /dev/null @@ -1,932 +0,0 @@ -#ifndef TEST_PERMUTED_DENSE_H -#define TEST_PERMUTED_DENSE_H - -#include "internal/permuted_dense_linalg.h" -#include "minunit.h" -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/matmul_dispatchers.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/sparse_matrix.h" -#include "sparsediff_linalg/stacked_pd.h" -#include "sparsediff_linalg/utils.h" -#include "test_helpers.h" -#include -#include - -/* 5x6 matrix with a 3x2 dense block at rows {1, 2, 4}, cols {0, 3}: - - global view: - [0 0 0 0 0 0] - [1 0 0 2 0 0] - [3 0 0 4 0 0] - [0 0 0 0 0 0] - [5 0 0 6 0 0] */ -const char *test_permuted_dense_to_csr_basic(void) -{ - int row_perm[3] = {1, 2, 4}; - int col_perm[2] = {0, 3}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - - matrix *M = new_permuted_dense(5, 6, 3, 2, row_perm, col_perm, X); - - CSR_matrix *C = M->to_csr(M); - int Cp_expected[6] = {0, 0, 2, 4, 4, 6}; - int Ci_expected[6] = {0, 3, 0, 3, 0, 3}; - double Cx_expected[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - - mu_assert("dim m", C->m == 5); - mu_assert("dim n", C->n == 6); - mu_assert("nnz", C->nnz == 6); - mu_assert("p", cmp_int_array(C->p, Cp_expected, 6)); - mu_assert("i", cmp_int_array(C->i, Ci_expected, 6)); - mu_assert("x", cmp_double_array(C->x, Cx_expected, 6)); - - free_matrix(M); - return 0; -} - -/* Empty dense block (m0 = n0 = 0): result is an m x n CSR_matrix with - no nonzeros. */ -const char *test_permuted_dense_to_csr_empty(void) -{ - matrix *M = new_permuted_dense(4, 5, 0, 0, NULL, NULL, NULL); - - CSR_matrix *C = M->to_csr(M); - int Cp_expected[5] = {0, 0, 0, 0, 0}; - mu_assert("nnz", C->nnz == 0); - mu_assert("p", cmp_int_array(C->p, Cp_expected, 5)); - - free_matrix(M); - return 0; -} - -/* Full dense (row_perm = [0..m), col_perm = [0..n)): result is the dense - matrix in CSR_matrix. */ -const char *test_permuted_dense_to_csr_full(void) -{ - int row_perm[2] = {0, 1}; - int col_perm[3] = {0, 1, 2}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - - matrix *M = new_permuted_dense(2, 3, 2, 3, row_perm, col_perm, X); - - CSR_matrix *C = M->to_csr(M); - int Cp_expected[3] = {0, 3, 6}; - int Ci_expected[6] = {0, 1, 2, 0, 1, 2}; - double Cx_expected[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - - mu_assert("p", cmp_int_array(C->p, Cp_expected, 3)); - mu_assert("i", cmp_int_array(C->i, Ci_expected, 6)); - mu_assert("x", cmp_double_array(C->x, Cx_expected, 6)); - - free_matrix(M); - return 0; -} - -/* Single dense row, two dense cols. Tests rows with no entries before - and after the active row. */ -const char *test_permuted_dense_to_csr_single_row(void) -{ - int row_perm[1] = {2}; - int col_perm[2] = {1, 4}; - double X[2] = {7.0, 9.0}; - - matrix *M = new_permuted_dense(4, 5, 1, 2, row_perm, col_perm, X); - - CSR_matrix *C = M->to_csr(M); - int Cp_expected[5] = {0, 0, 0, 2, 2}; - int Ci_expected[2] = {1, 4}; - double Cx_expected[2] = {7.0, 9.0}; - - mu_assert("p", cmp_int_array(C->p, Cp_expected, 5)); - mu_assert("i", cmp_int_array(C->i, Ci_expected, 2)); - mu_assert("x", cmp_double_array(C->x, Cx_expected, 2)); - - free_matrix(M); - return 0; -} - -/* Single dense col across multiple rows. */ -const char *test_permuted_dense_to_csr_single_col(void) -{ - int row_perm[3] = {0, 2, 3}; - int col_perm[1] = {2}; - double X[3] = {1.0, 2.0, 3.0}; - - matrix *M = new_permuted_dense(4, 4, 3, 1, row_perm, col_perm, X); - - CSR_matrix *C = M->to_csr(M); - int Cp_expected[5] = {0, 1, 1, 2, 3}; - int Ci_expected[3] = {2, 2, 2}; - double Cx_expected[3] = {1.0, 2.0, 3.0}; - - mu_assert("p", cmp_int_array(C->p, Cp_expected, 5)); - mu_assert("i", cmp_int_array(C->i, Ci_expected, 3)); - mu_assert("x", cmp_double_array(C->x, Cx_expected, 3)); - - free_matrix(M); - return 0; -} - -/* DA_fill_values: compare against CSR_matrix DA_fill_values on the equivalent - CSR_matrix. - - PD is the 5x6 matrix from the basic to_csr test, with d a length-5 - global-row diagonal including a negative and zero entry. */ -const char *test_DA_pd_fill_values(void) -{ - int row_perm[3] = {1, 2, 4}; - int col_perm[2] = {0, 3}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - double d[5] = {7.0, -1.5, 0.0, 9.0, 2.5}; - - matrix *M = new_permuted_dense(5, 6, 3, 2, row_perm, col_perm, X); - matrix *M_out = new_permuted_dense(5, 6, 3, 2, row_perm, col_perm, NULL); - permuted_dense *pd = (permuted_dense *) M; - permuted_dense *pd_out = (permuted_dense *) M_out; - - DA_pd_fill_values(d, pd, pd_out); - - /* Ground truth: build CSR_matrix of self, run DA_fill_values, compare. */ - CSR_matrix *csr = M->to_csr(M); - CSR_matrix *csr_expected = new_csr_copy_sparsity(csr); - DA_fill_values(d, csr, csr_expected); - - CSR_matrix *csr_out = M_out->to_csr(M_out); - mu_assert("x", cmp_double_array(csr_out->x, csr_expected->x, csr->nnz)); - - free_CSR_matrix(csr_expected); - free_matrix(M); - free_matrix(M_out); - return 0; -} - -/* ATA_alloc: structure-only check. Output is 6x6 with a 2x2 dense block at - perms {0, 3} (= self.col_perm on both sides). Values are uninitialized - here; ATDA_fill_values is the value-producing op. */ -const char *test_ATA_pd_alloc(void) -{ - int row_perm[3] = {1, 2, 4}; - int col_perm[2] = {0, 3}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - - matrix *M = new_permuted_dense(5, 6, 3, 2, row_perm, col_perm, X); - permuted_dense *pd = (permuted_dense *) M; - - matrix *M_ata = ATA_pd_alloc(pd); - permuted_dense *pd_ata = (permuted_dense *) M_ata; - - int perm_expected[2] = {0, 3}; - mu_assert("m", M_ata->m == 6); - mu_assert("n", M_ata->n == 6); - mu_assert("m0", pd_ata->m0 == 2); - mu_assert("n0", pd_ata->n0 == 2); - mu_assert("row_perm", cmp_int_array(pd_ata->row_perm, perm_expected, 2)); - mu_assert("col_perm", cmp_int_array(pd_ata->col_perm, perm_expected, 2)); - - free_matrix(M); - free_matrix(M_ata); - return 0; -} - -/* ATDA: same 5x6 PD, d with negative + zero entries to catch sign bugs. - Hand-computed: d_perm = [-1.5, 0, 2.5], Y = diag(d_perm) X gives - [[-1.5,-3],[0,0],[12.5,15]], and X^T Y = [[61,72],[72,84]]. */ -const char *test_ATDA_pd_fill_values(void) -{ - int row_perm[3] = {1, 2, 4}; - int col_perm[2] = {0, 3}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - double d[5] = {7.0, -1.5, 0.0, 9.0, 2.5}; - - matrix *M = new_permuted_dense(5, 6, 3, 2, row_perm, col_perm, X); - permuted_dense *pd = (permuted_dense *) M; - - matrix *M_out = ATA_pd_alloc(pd); - permuted_dense *pd_out = (permuted_dense *) M_out; - ATDA_pd_fill_values(pd, d, pd_out); - - double X_expected[4] = {61.0, 72.0, 72.0, 84.0}; - mu_assert("X", cmp_double_array(pd_out->X, X_expected, 4)); - - free_matrix(M); - free_matrix(M_out); - return 0; -} - -/* PD x CSC_matrix: J is 6x4. col 0 empty; col 1 has rows {0,3} (vals 10, 20); - col 2 has row {2} (val 30, but row 2 not in col_perm_self = {0,3} so col 2 - is INACTIVE); col 3 has row {3} (val 40). Active cols: {1, 3}. - - Expected: m0=3, n0=2, row_perm={1,2,4}, col_perm={1,3}. - Values: out.X[:,0] = 10*[1,3,5] + 20*[2,4,6] = [50,110,170], - out.X[:,1] = 40*[2,4,6] = [80,160,240]. */ -const char *test_permuted_dense_times_csc(void) -{ - int row_perm[3] = {1, 2, 4}; - int col_perm[2] = {0, 3}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - matrix *M = new_permuted_dense(5, 6, 3, 2, row_perm, col_perm, X); - permuted_dense *pd = (permuted_dense *) M; - - CSC_matrix *J = new_CSC_matrix(6, 4, 4); - int Jp[5] = {0, 0, 2, 3, 4}; - int Ji[4] = {0, 3, 2, 3}; - double Jx[4] = {10.0, 20.0, 30.0, 40.0}; - memcpy(J->p, Jp, 5 * sizeof(int)); - memcpy(J->i, Ji, 4 * sizeof(int)); - memcpy(J->x, Jx, 4 * sizeof(double)); - - matrix *M_out = BA_pd_csc_alloc(pd, J); - permuted_dense *pd_out = (permuted_dense *) M_out; - BA_pd_csc_fill_values(pd->X, pd->n0, pd->col_inv, J, pd_out); - - int row_perm_expected[3] = {1, 2, 4}; - int col_perm_expected[2] = {1, 3}; - double X_expected[6] = {50.0, 80.0, 110.0, 160.0, 170.0, 240.0}; - - mu_assert("m", M_out->m == 5); - mu_assert("n", M_out->n == 4); - mu_assert("m0", pd_out->m0 == 3); - mu_assert("n0", pd_out->n0 == 2); - mu_assert("row_perm", cmp_int_array(pd_out->row_perm, row_perm_expected, 3)); - mu_assert("col_perm", cmp_int_array(pd_out->col_perm, col_perm_expected, 2)); - mu_assert("X", cmp_double_array(pd_out->X, X_expected, 6)); - - free_matrix(M); - free_matrix(M_out); - free_CSC_matrix(J); - return 0; -} - -/* PD x CSC_matrix edge case: every column of J has its only nonzero outside - col_perm_self, so col_perm_out is empty (n0 = 0). */ -const char *test_permuted_dense_times_csc_no_active(void) -{ - int row_perm[3] = {1, 2, 4}; - int col_perm[2] = {0, 3}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - matrix *M = new_permuted_dense(5, 6, 3, 2, row_perm, col_perm, X); - permuted_dense *pd = (permuted_dense *) M; - - /* J: col 0 has row {1}, col 1 has row {5}. Neither in col_perm_self. */ - CSC_matrix *J = new_CSC_matrix(6, 2, 2); - int Jp[3] = {0, 1, 2}; - int Ji[2] = {1, 5}; - double Jx[2] = {100.0, 200.0}; - memcpy(J->p, Jp, 3 * sizeof(int)); - memcpy(J->i, Ji, 2 * sizeof(int)); - memcpy(J->x, Jx, 2 * sizeof(double)); - - matrix *M_out = BA_pd_csc_alloc(pd, J); - permuted_dense *pd_out = (permuted_dense *) M_out; - BA_pd_csc_fill_values(pd->X, pd->n0, pd->col_inv, J, pd_out); - - mu_assert("m", M_out->m == 5); - mu_assert("n", M_out->n == 2); - mu_assert("m0", pd_out->m0 == 3); - mu_assert("n0", pd_out->n0 == 0); - - free_matrix(M); - free_matrix(M_out); - free_CSC_matrix(J); - return 0; -} - -/* to_csr vtable method: lazy CSR_matrix view. First call allocates pd->csr_cache; - subsequent calls refresh values to reflect the current pd->X. */ -const char *test_permuted_dense_to_csr_lazy(void) -{ - int row_perm[3] = {1, 2, 4}; - int col_perm[2] = {0, 3}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - - matrix *M = new_permuted_dense(5, 6, 3, 2, row_perm, col_perm, X); - permuted_dense *pd = (permuted_dense *) M; - - mu_assert("csr_cache initially NULL", pd->csr_cache == NULL); - - CSR_matrix *csr = M->to_csr(M); - mu_assert("csr_cache populated", pd->csr_cache != NULL); - mu_assert("returns the cache", csr == pd->csr_cache); - - double expected[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - mu_assert("values match X", cmp_double_array(csr->x, expected, 6)); - - /* Mutate X and re-call to_csr: values must reflect the change. */ - pd->X[0] = 99.0; - csr = M->to_csr(M); - mu_assert("refresh picks up new value", csr->x[0] == 99.0); - - free_matrix(M); - return 0; -} - -/* Sanity check: col_inv is built correctly. col_perm = {0, 3} on n = 6 - should give col_inv = {0, -1, -1, 1, -1, -1}. */ -const char *test_permuted_dense_col_inv(void) -{ - int row_perm[1] = {0}; - int col_perm[2] = {0, 3}; - double X[2] = {0.0, 0.0}; - - matrix *M = new_permuted_dense(1, 6, 1, 2, row_perm, col_perm, X); - permuted_dense *pd = (permuted_dense *) M; - - int expected[6] = {0, -1, -1, 1, -1, -1}; - mu_assert("col_inv", cmp_int_array(pd->col_inv, expected, 6)); - - free_matrix(M); - return 0; -} - -/* PD row_gather_alloc / row_gather_fill_values: output must be another PD whose - row_perm is the set of output positions where map[i] hits the source - row_perm, with repeats handled. */ -const char *test_permuted_dense_row_gather(void) -{ - /* Source PD, shape (6, 4), dense block at rows {1, 3, 4} x cols {0, 2}. */ - int row_perm[3] = {1, 3, 4}; - int col_perm[2] = {0, 2}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - matrix *M = new_permuted_dense(6, 4, 3, 2, row_perm, col_perm, X); - - /* map = [0, 3, 1, 4, 3, 5]: - - position 0 -> source row 0 (not in row_perm, zero) - - position 1 -> source row 3 (ii=1, dense) - - position 2 -> source row 1 (ii=0, dense) - - position 3 -> source row 4 (ii=2, dense) - - position 4 -> source row 3 again (ii=1, dense) - - position 5 -> source row 5 (not in row_perm, zero) */ - int map[6] = {0, 3, 1, 4, 3, 5}; - matrix *out = M->row_gather_alloc(M, map, 6); - permuted_dense *out_pd = (permuted_dense *) out; - - mu_assert("out m", out->m == 6); - mu_assert("out n", out->n == 4); - mu_assert("out nnz", out->nnz == 8); /* m0=4 * n0=2 */ - mu_assert("m0", out_pd->m0 == 4); - mu_assert("n0", out_pd->n0 == 2); - - int expected_row_perm[4] = {1, 2, 3, 4}; - mu_assert("row_perm", cmp_int_array(out_pd->row_perm, expected_row_perm, 4)); - int expected_col_perm[2] = {0, 2}; - mu_assert("col_perm", cmp_int_array(out_pd->col_perm, expected_col_perm, 2)); - int expected_src[4] = {1, 0, 2, 1}; - mu_assert("bound_iwork", cmp_int_array(out_pd->bound_iwork, expected_src, 4)); - - M->row_gather_fill_values(M, out); - - /* dense rows of out = X[1, :], X[0, :], X[2, :], X[1, :] */ - double expected_X[8] = {3.0, 4.0, 1.0, 2.0, 5.0, 6.0, 3.0, 4.0}; - mu_assert("values", cmp_double_array(out_pd->X, expected_X, 8)); - - free_matrix(out); - free_matrix(M); - return 0; -} - -/* PD diag_vec_alloc / diag_vec_fill_values. - Source PD shape (3, 6) with m0=2 (rows 0 and 2) -> output PD shape - (9, 6) with the same 2 dense rows mapped to positions {0, 8} = {0*4, 2*4}. */ -const char *test_permuted_dense_diag_vec(void) -{ - int row_perm[2] = {0, 2}; - int col_perm[2] = {1, 4}; - double X[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *M = new_permuted_dense(3, 6, 2, 2, row_perm, col_perm, X); - - matrix *out = M->diag_vec_alloc(M); - permuted_dense *out_pd = (permuted_dense *) out; - - mu_assert("out m", out->m == 9); - mu_assert("out n", out->n == 6); - mu_assert("m0", out_pd->m0 == 2); - mu_assert("n0", out_pd->n0 == 2); - /* row_perm = {0*(n+1), 2*(n+1)} = {0, 8} */ - int expected_rp[2] = {0, 8}; - mu_assert("row_perm", cmp_int_array(out_pd->row_perm, expected_rp, 2)); - int expected_cp[2] = {1, 4}; - mu_assert("col_perm", cmp_int_array(out_pd->col_perm, expected_cp, 2)); - - M->diag_vec_fill_values(M, out); - /* X is identical to the source X */ - double expected_X[4] = {1.0, 2.0, 3.0, 4.0}; - mu_assert("values", cmp_double_array(out_pd->X, expected_X, 4)); - - free_matrix(out); - free_matrix(M); - return 0; -} - -/* ---- Helpers for BTA / BTDA tests ---- */ - -/* Scatter a PD into a dense m x n_global buffer (row-major), zero-filled. - Buffer is allocated by the caller. */ -static void scatter_pd_to_dense(const permuted_dense *pd, int n_global, - double *dense) -{ - int m = pd->base.m; - memset(dense, 0, (size_t) m * (size_t) n_global * sizeof(double)); - for (int ii = 0; ii < pd->m0; ii++) - { - int row = pd->row_perm[ii]; - for (int jj = 0; jj < pd->n0; jj++) - { - int col = pd->col_perm[jj]; - dense[row * n_global + col] = pd->X[ii * pd->n0 + jj]; - } - } -} - -/* BTA: A and B share row_perm = [1, 3]; both have m=4, distinct col_perms. - C = B^T A is computed via the primitive and compared against a hand - reference X_B^T X_A. */ -const char *test_permuted_dense_BTA_matching_row_perm(void) -{ - int row_perm[2] = {1, 3}; - int col_perm_A[2] = {0, 2}; - int col_perm_B[2] = {1, 3}; - /* X_A is (2, 2), X_B is (2, 2), both row-major. */ - double XA[4] = {1.0, 2.0, 3.0, 4.0}; /* rows: [1,2], [3,4] */ - double XB[4] = {5.0, 6.0, 7.0, 8.0}; /* rows: [5,6], [7,8] */ - matrix *A_m = new_permuted_dense(4, 4, 2, 2, row_perm, col_perm_A, XA); - matrix *B_m = new_permuted_dense(4, 4, 2, 2, row_perm, col_perm_B, XB); - permuted_dense *A = (permuted_dense *) A_m; - permuted_dense *B = (permuted_dense *) B_m; - - matrix *C_m = BTA_pd_pd_alloc(B, A); - permuted_dense *C = (permuted_dense *) C_m; - - mu_assert("out m", C_m->m == 4); /* B.n */ - mu_assert("out n", C_m->n == 4); /* A.n */ - mu_assert("m0", C->m0 == 2); - mu_assert("n0", C->n0 == 2); - mu_assert("row_perm", cmp_int_array(C->row_perm, col_perm_B, 2)); - mu_assert("col_perm", cmp_int_array(C->col_perm, col_perm_A, 2)); - - BTA_pd_pd_fill_values(B, A, C); - - /* Reference: X_B^T X_A. With X_B = [[5,6],[7,8]], X_A = [[1,2],[3,4]]: - X_B^T = [[5,7],[6,8]]. X_B^T X_A = [[5*1+7*3, 5*2+7*4], [6*1+8*3, 6*2+8*4]] - = [[26, 38], [30, 44]]. */ - double expected[4] = {26.0, 38.0, 30.0, 44.0}; - mu_assert("values", cmp_double_array(C->X, expected, 4)); - - free_matrix(C_m); - free_matrix(B_m); - free_matrix(A_m); - return 0; -} - -/* BTA with empty row intersection: row_perm_A = [0, 2], row_perm_B = [1, 3]. - BTA_pd_pd_alloc should return an empty C (nnz = 0); the fill - kernels should short-circuit without crashing. */ -const char *test_permuted_dense_BTA_empty_overlap(void) -{ - int row_perm_A[2] = {0, 2}; - int row_perm_B[2] = {1, 3}; - int col_perm_A[2] = {0, 2}; - int col_perm_B[2] = {1, 3}; - double XA[4] = {1.0, 2.0, 3.0, 4.0}; - double XB[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *A_m = new_permuted_dense(4, 4, 2, 2, row_perm_A, col_perm_A, XA); - matrix *B_m = new_permuted_dense(4, 4, 2, 2, row_perm_B, col_perm_B, XB); - permuted_dense *A = (permuted_dense *) A_m; - permuted_dense *B = (permuted_dense *) B_m; - - matrix *C_m = BTA_pd_pd_alloc(B, A); - permuted_dense *C = (permuted_dense *) C_m; - - mu_assert("out m", C_m->m == 4); /* B.n */ - mu_assert("out n", C_m->n == 4); /* A.n */ - mu_assert("m0", C->m0 == 0); - mu_assert("n0", C->n0 == 0); - mu_assert("nnz", C_m->nnz == 0); - - /* fill kernels should be safe no-ops on empty C. */ - BTA_pd_pd_fill_values(B, A, C); - double d[4] = {1.0, 1.0, 1.0, 1.0}; - BTDA_pd_pd_fill_values(B, d, A, C); - - free_matrix(C_m); - free_matrix(B_m); - free_matrix(A_m); - return 0; -} - -/* BTA with partial overlap: row_perm_A = [1, 3, 5], row_perm_B = [3, 5, 7]. - Intersection = {3, 5}. */ -const char *test_permuted_dense_BTA_partial_overlap(void) -{ - int row_perm_A[3] = {1, 3, 5}; - int row_perm_B[3] = {3, 5, 7}; - int col_perm_A[2] = {0, 2}; - int col_perm_B[2] = {1, 3}; - /* X_A rows correspond to A row_perm order: row 0 -> source row 1, row 1 -> 3, - * row 2 -> 5. */ - double XA[6] = {1.0, 2.0, /* row 1 (NOT in B) */ - 3.0, 4.0, /* row 3 (in B at pos 0) */ - 5.0, 6.0}; /* row 5 (in B at pos 1) */ - /* X_B rows: row 0 -> source row 3, row 1 -> 5, row 2 -> 7. */ - double XB[6] = {10.0, 20.0, /* row 3 (in A at pos 1) */ - 30.0, 40.0, /* row 5 (in A at pos 2) */ - 50.0, 60.0}; /* row 7 (NOT in A) */ - matrix *A_m = new_permuted_dense(8, 4, 3, 2, row_perm_A, col_perm_A, XA); - matrix *B_m = new_permuted_dense(8, 4, 3, 2, row_perm_B, col_perm_B, XB); - permuted_dense *A = (permuted_dense *) A_m; - permuted_dense *B = (permuted_dense *) B_m; - - matrix *C_m = BTA_pd_pd_alloc(B, A); - permuted_dense *C = (permuted_dense *) C_m; - BTA_pd_pd_fill_values(B, A, C); - - /* Reference: scatter A, B to dense 8x4, compute B^T A, compare block at - (col_perm_B, col_perm_A). */ - double *A_d = (double *) calloc((size_t) 8 * 4, sizeof(double)); - double *B_d = (double *) calloc((size_t) 8 * 4, sizeof(double)); - scatter_pd_to_dense(A, 4, A_d); - scatter_pd_to_dense(B, 4, B_d); - - /* Reference C_ref is 4x4 = B_d^T (4x8) * A_d (8x4). */ - double C_ref[16]; - memset(C_ref, 0, sizeof C_ref); - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - double s = 0.0; - for (int k = 0; k < 8; k++) - { - s += B_d[k * 4 + i] * A_d[k * 4 + j]; - } - C_ref[i * 4 + j] = s; - } - } - - /* Extract reference block at (col_perm_B, col_perm_A) and compare to C->X. */ - double expected[4]; - for (int ii = 0; ii < 2; ii++) - { - for (int jj = 0; jj < 2; jj++) - { - expected[ii * 2 + jj] = C_ref[col_perm_B[ii] * 4 + col_perm_A[jj]]; - } - } - mu_assert("values", cmp_double_array(C->X, expected, 4)); - - free(A_d); - free(B_d); - free_matrix(C_m); - free_matrix(B_m); - free_matrix(A_m); - return 0; -} - -/* Full BTDA decomposition: tmp = diag(w) A; C = B^T tmp. Compare against a - dense triple product B_d^T diag(w) A_d. */ -const char *test_permuted_dense_BTDA_decomposition(void) -{ - int row_perm[3] = {0, 1, 2}; - int col_perm_A[2] = {0, 2}; - int col_perm_B[2] = {1, 3}; - double XA[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - double XB[6] = {7.0, 8.0, 9.0, 10.0, 11.0, 12.0}; - double w[3] = {2.0, -1.0, 3.0}; - - matrix *A_m = new_permuted_dense(3, 4, 3, 2, row_perm, col_perm_A, XA); - matrix *B_m = new_permuted_dense(3, 4, 3, 2, row_perm, col_perm_B, XB); - permuted_dense *A = (permuted_dense *) A_m; - permuted_dense *B = (permuted_dense *) B_m; - - /* tmp has the same sparsity as A. */ - matrix *tmp_m = A_m->copy_sparsity(A_m); - permuted_dense *tmp = (permuted_dense *) tmp_m; - DA_pd_fill_values(w, A, tmp); - - matrix *C_m = BTA_pd_pd_alloc(B, tmp); - permuted_dense *C = (permuted_dense *) C_m; - BTA_pd_pd_fill_values(B, tmp, C); - - /* Reference: dense B_d^T diag(w) A_d, extract (col_perm_B, col_perm_A) block. */ - double *A_d = (double *) calloc((size_t) 3 * 4, sizeof(double)); - double *B_d = (double *) calloc((size_t) 3 * 4, sizeof(double)); - scatter_pd_to_dense(A, 4, A_d); - scatter_pd_to_dense(B, 4, B_d); - - double C_ref[16]; - memset(C_ref, 0, sizeof C_ref); - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - double s = 0.0; - for (int k = 0; k < 3; k++) - { - s += B_d[k * 4 + i] * w[k] * A_d[k * 4 + j]; - } - C_ref[i * 4 + j] = s; - } - } - double expected[4]; - for (int ii = 0; ii < 2; ii++) - { - for (int jj = 0; jj < 2; jj++) - { - expected[ii * 2 + jj] = C_ref[col_perm_B[ii] * 4 + col_perm_A[jj]]; - } - } - mu_assert("values", cmp_double_array(C->X, expected, 4)); - - free(A_d); - free(B_d); - free_matrix(C_m); - free_matrix(tmp_m); - free_matrix(B_m); - free_matrix(A_m); - return 0; -} - -/* Direct BTDA_pd_pd on the matching-row_perm (fast) path: A and B share - row_perm, so the kernel takes the single-dgemm route with diag(d) folded - in. Oracle is the decomposition tmp = diag(d) A; C_ref = B^T tmp. */ -const char *test_permuted_dense_BTDA_matching_row_perm(void) -{ - int row_perm[2] = {1, 3}; - int col_perm_A[2] = {0, 2}; - int col_perm_B[2] = {1, 3}; - double XA[4] = {1.0, 2.0, 3.0, 4.0}; - double XB[4] = {5.0, 6.0, 7.0, 8.0}; - /* d indexed by global row; only d[1], d[3] are read. */ - double d[4] = {-2.0, 0.5, 100.0, 3.0}; - matrix *A_m = new_permuted_dense(4, 4, 2, 2, row_perm, col_perm_A, XA); - matrix *B_m = new_permuted_dense(4, 4, 2, 2, row_perm, col_perm_B, XB); - permuted_dense *A = (permuted_dense *) A_m; - permuted_dense *B = (permuted_dense *) B_m; - - matrix *C_m = BTA_pd_pd_alloc(B, A); - permuted_dense *C = (permuted_dense *) C_m; - BTDA_pd_pd_fill_values(B, d, A, C); - - matrix *tmp_m = A_m->copy_sparsity(A_m); - permuted_dense *tmp = (permuted_dense *) tmp_m; - DA_pd_fill_values(d, A, tmp); - matrix *C_ref_m = BTA_pd_pd_alloc(B, tmp); - permuted_dense *C_ref = (permuted_dense *) C_ref_m; - BTA_pd_pd_fill_values(B, tmp, C_ref); - - mu_assert("values", cmp_double_array(C->X, C_ref->X, 4)); - - free_matrix(C_ref_m); - free_matrix(tmp_m); - free_matrix(C_m); - free_matrix(B_m); - free_matrix(A_m); - return 0; -} - -/* Direct BTDA_pd_pd on the partial-overlap (gather) path: row_perm_A = - [1, 3, 5], row_perm_B = [3, 5, 7], intersection {3, 5}. Same - decomposition oracle as above. */ -const char *test_permuted_dense_BTDA_partial_overlap(void) -{ - int row_perm_A[3] = {1, 3, 5}; - int row_perm_B[3] = {3, 5, 7}; - int col_perm_A[2] = {0, 2}; - int col_perm_B[2] = {1, 3}; - double XA[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - double XB[6] = {10.0, 20.0, 30.0, 40.0, 50.0, 60.0}; - /* d indexed by global row; only d[3], d[5] are read. */ - double d[8] = {9.0, 9.0, 9.0, 2.0, 9.0, -1.5, 9.0, 9.0}; - matrix *A_m = new_permuted_dense(8, 4, 3, 2, row_perm_A, col_perm_A, XA); - matrix *B_m = new_permuted_dense(8, 4, 3, 2, row_perm_B, col_perm_B, XB); - permuted_dense *A = (permuted_dense *) A_m; - permuted_dense *B = (permuted_dense *) B_m; - - matrix *C_m = BTA_pd_pd_alloc(B, A); - permuted_dense *C = (permuted_dense *) C_m; - BTDA_pd_pd_fill_values(B, d, A, C); - - matrix *tmp_m = A_m->copy_sparsity(A_m); - permuted_dense *tmp = (permuted_dense *) tmp_m; - DA_pd_fill_values(d, A, tmp); - matrix *C_ref_m = BTA_pd_pd_alloc(B, tmp); - permuted_dense *C_ref = (permuted_dense *) C_ref_m; - BTA_pd_pd_fill_values(B, tmp, C_ref); - - mu_assert("values", cmp_double_array(C->X, C_ref->X, 4)); - - free_matrix(C_ref_m); - free_matrix(tmp_m); - free_matrix(C_m); - free_matrix(B_m); - free_matrix(A_m); - return 0; -} - -/* BA_pd_matrices: C = B @ A where B is full-block PD (the production - shape gated by left_matmul.c) and A is PD with non-trivial perms. - B (2x3) row_perm=[0,1], col_perm=[0,1,2], X_B=[[1,2,3],[4,5,6]]. - A (3x5) row_perm=[0,2], col_perm=[1,4], X_A=[[7,8],[9,10]]. - Hand-computed C (2x5) nonzero at cols {1,4}: X_C=[[34,38],[82,92]]. */ -const char *test_BA_pd_matrices_pd_pd_full_block_B(void) -{ - int row_perm_B[2] = {0, 1}; - int col_perm_B[3] = {0, 1, 2}; - double XB[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - matrix *B_m = new_permuted_dense(2, 3, 2, 3, row_perm_B, col_perm_B, XB); - - int row_perm_A[2] = {0, 2}; - int col_perm_A[2] = {1, 4}; - double XA[4] = {7.0, 8.0, 9.0, 10.0}; - matrix *A_m = new_permuted_dense(3, 5, 2, 2, row_perm_A, col_perm_A, XA); - - matrix *C_m = BA_pd_matrices_alloc((permuted_dense *) B_m, A_m); - BA_pd_matrices_fill_values((permuted_dense *) B_m, A_m, (permuted_dense *) C_m); - - permuted_dense *C = (permuted_dense *) C_m; - mu_assert("dim m", C_m->m == 2); - mu_assert("dim n", C_m->n == 5); - mu_assert("m0", C->m0 == 2); - mu_assert("n0", C->n0 == 2); - int expected_row_perm[2] = {0, 1}; - int expected_col_perm[2] = {1, 4}; - mu_assert("row_perm", cmp_int_array(C->row_perm, expected_row_perm, 2)); - mu_assert("col_perm", cmp_int_array(C->col_perm, expected_col_perm, 2)); - double expected_X[4] = {34.0, 38.0, 82.0, 92.0}; - mu_assert("X", cmp_double_array(C->X, expected_X, 4)); - - free_matrix(C_m); - free_matrix(A_m); - free_matrix(B_m); - return 0; -} - -/* BA_pd_matrices with general (non-full-block) B. B->col_perm and - A->row_perm only partially overlap, exercising the - sorted_intersect_indices gather path. - B (2x5) row_perm=[0,1], col_perm=[1,3], X_B=[[1,2],[3,4]]. - A (5x4) row_perm=[1,2], col_perm=[0,3], X_A=[[5,6],[7,8]]. - Intersection K = {1,3} ∩ {1,2} = {1}, s=1. - Hand-computed C (2x4) nonzero at cols {0,3}: X_C=[[5,6],[15,18]]. */ -const char *test_BA_pd_matrices_pd_pd_general_B(void) -{ - int row_perm_B[2] = {0, 1}; - int col_perm_B[2] = {1, 3}; - double XB[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *B_m = new_permuted_dense(2, 5, 2, 2, row_perm_B, col_perm_B, XB); - - int row_perm_A[2] = {1, 2}; - int col_perm_A[2] = {0, 3}; - double XA[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *A_m = new_permuted_dense(5, 4, 2, 2, row_perm_A, col_perm_A, XA); - - matrix *C_m = BA_pd_matrices_alloc((permuted_dense *) B_m, A_m); - BA_pd_matrices_fill_values((permuted_dense *) B_m, A_m, (permuted_dense *) C_m); - - permuted_dense *C = (permuted_dense *) C_m; - mu_assert("dim m", C_m->m == 2); - mu_assert("dim n", C_m->n == 4); - mu_assert("m0", C->m0 == 2); - mu_assert("n0", C->n0 == 2); - int expected_row_perm[2] = {0, 1}; - int expected_col_perm[2] = {0, 3}; - mu_assert("row_perm", cmp_int_array(C->row_perm, expected_row_perm, 2)); - mu_assert("col_perm", cmp_int_array(C->col_perm, expected_col_perm, 2)); - double expected_X[4] = {5.0, 6.0, 15.0, 18.0}; - mu_assert("X", cmp_double_array(C->X, expected_X, 4)); - - free_matrix(C_m); - free_matrix(A_m); - free_matrix(B_m); - return 0; -} - -/* BA_pd_matrices with sparse A. Same B and same global A content as the - pd_pd_general_B test — the dispatcher routes through BA_pd_csc_* - and should yield byte-identical output. */ -const char *test_BA_pd_matrices_pd_csc(void) -{ - int row_perm_B[2] = {0, 1}; - int col_perm_B[2] = {1, 3}; - double XB[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *B_m = new_permuted_dense(2, 5, 2, 2, row_perm_B, col_perm_B, XB); - - /* A as 5x4 sparse_matrix, same nonzero values as the PD case: - (1,0)=5, (1,3)=6, (2,0)=7, (2,3)=8. */ - CSR_matrix *csr = new_CSR_matrix(5, 4, 4); - int Ap[6] = {0, 0, 2, 4, 4, 4}; - int Ai[4] = {0, 3, 0, 3}; - double Ax[4] = {5.0, 6.0, 7.0, 8.0}; - memcpy(csr->p, Ap, 6 * sizeof(int)); - memcpy(csr->i, Ai, 4 * sizeof(int)); - memcpy(csr->x, Ax, 4 * sizeof(double)); - matrix *A_m = new_sparse_matrix(csr); - - matrix *C_m = BA_pd_matrices_alloc((permuted_dense *) B_m, A_m); - A_m->refresh_csc_values(A_m); /* values must be fresh before fill */ - BA_pd_matrices_fill_values((permuted_dense *) B_m, A_m, (permuted_dense *) C_m); - - permuted_dense *C = (permuted_dense *) C_m; - mu_assert("dim m", C_m->m == 2); - mu_assert("dim n", C_m->n == 4); - mu_assert("m0", C->m0 == 2); - mu_assert("n0", C->n0 == 2); - int expected_row_perm[2] = {0, 1}; - int expected_col_perm[2] = {0, 3}; - mu_assert("row_perm", cmp_int_array(C->row_perm, expected_row_perm, 2)); - mu_assert("col_perm", cmp_int_array(C->col_perm, expected_col_perm, 2)); - double expected_X[4] = {5.0, 6.0, 15.0, 18.0}; - mu_assert("X", cmp_double_array(C->X, expected_X, 4)); - - free_matrix(C_m); - free_matrix(A_m); - free_matrix(B_m); - return 0; -} - -/* BA_pd_matrices dispatcher routes B(PD) @ A(spd) to BA_pd_spd_*. Fixture - mirrors test_BA_pd_spd_two_blocks_disjoint_cols; the assertion is that - the dispatcher and the direct kernel produce identical output. */ -const char *test_BA_pd_matrices_spd_A(void) -{ - int B_rp[3] = {0, 1, 2}; - int B_cp[3] = {0, 1, 3}; - double BX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *B = new_permuted_dense(3, 4, 3, 3, B_rp, B_cp, BX); - - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 4}; - double A0X[4] = {10, 11, 12, 13}; - matrix *A0 = new_permuted_dense(4, 6, 2, 2, A0_rp, A0_cp, A0X); - - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 4}; - double A1X[4] = {20, 21, 22, 23}; - matrix *A1 = new_permuted_dense(4, 6, 2, 2, A1_rp, A1_cp, A1X); - - permuted_dense *blocks[2] = {(permuted_dense *) A0, (permuted_dense *) A1}; - matrix *A = new_stacked_pd(4, 6, 2, blocks, NULL, NULL); - - matrix *C_m = BA_pd_matrices_alloc((permuted_dense *) B, A); - BA_pd_matrices_fill_values((permuted_dense *) B, A, (permuted_dense *) C_m); - permuted_dense *C = (permuted_dense *) C_m; - - mu_assert("C m", C_m->m == 3); - mu_assert("C n", C_m->n == 6); - mu_assert("C m0", C->m0 == 3); - mu_assert("C n0", C->n0 == 3); - int rp_exp[3] = {0, 1, 2}; - int cp_exp[3] = {0, 1, 4}; - mu_assert("C row_perm", cmp_int_array(C->row_perm, rp_exp, 3)); - mu_assert("C col_perm", cmp_int_array(C->col_perm, cp_exp, 3)); - double CX_exp[9] = {34, 66, 106, 100, 132, 247, 166, 198, 388}; - mu_assert("C X", cmp_double_array(C->X, CX_exp, 9)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -/* BA_pd_matrices fast path: B->col_perm == A->row_perm exactly, so the - slow-path gather is skipped and one cblas_dgemm runs directly on - B->X and A->X. - B (2x4) row_perm=[0,1], col_perm=[1,3], X_B=[[1,2],[3,4]]. - A (4x3) row_perm=[1,3], col_perm=[0,2], X_A=[[5,6],[7,8]]. - Matching col_perm_B == row_perm_A == [1,3] triggers the fast path. - Hand-computed C (2x3) nonzero at cols {0,2}: X_C=[[19,22],[43,50]]. */ -const char *test_BA_pd_matrices_fast_path(void) -{ - int row_perm_B[2] = {0, 1}; - int col_perm_B[2] = {1, 3}; - double XB[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *B_m = new_permuted_dense(2, 4, 2, 2, row_perm_B, col_perm_B, XB); - - int row_perm_A[2] = {1, 3}; - int col_perm_A[2] = {0, 2}; - double XA[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *A_m = new_permuted_dense(4, 3, 2, 2, row_perm_A, col_perm_A, XA); - - matrix *C_m = BA_pd_matrices_alloc((permuted_dense *) B_m, A_m); - BA_pd_matrices_fill_values((permuted_dense *) B_m, A_m, (permuted_dense *) C_m); - - permuted_dense *C = (permuted_dense *) C_m; - mu_assert("dim m", C_m->m == 2); - mu_assert("dim n", C_m->n == 3); - mu_assert("m0", C->m0 == 2); - mu_assert("n0", C->n0 == 2); - int expected_row_perm[2] = {0, 1}; - int expected_col_perm[2] = {0, 2}; - mu_assert("row_perm", cmp_int_array(C->row_perm, expected_row_perm, 2)); - mu_assert("col_perm", cmp_int_array(C->col_perm, expected_col_perm, 2)); - double expected_X[4] = {19.0, 22.0, 43.0, 50.0}; - mu_assert("X", cmp_double_array(C->X, expected_X, 4)); - - free_matrix(C_m); - free_matrix(A_m); - free_matrix(B_m); - return 0; -} - -#endif /* TEST_PERMUTED_DENSE_H */ diff --git a/sparsediff_linalg/tests/test_row_gather.h b/sparsediff_linalg/tests/test_row_gather.h deleted file mode 100644 index 2e33e8a5..00000000 --- a/sparsediff_linalg/tests/test_row_gather.h +++ /dev/null @@ -1,238 +0,0 @@ -#ifndef TEST_ROW_GATHER_H -#define TEST_ROW_GATHER_H - -#include "minunit.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/sparse_matrix.h" -#include "sparsediff_linalg/stacked_pd.h" -#include "test_helpers.h" -#include -#include -#include - -/* row_gather_alloc / row_gather_fill_values across the three matrix kinds. - The map contract under test: map[i] in [0, A->m) copies source row map[i], - repeats are allowed, and the map is bound to the result at alloc time (the - caller's copy is dead afterwards). */ - -/* Shared 4x5 CSR source with an empty row 2: - row 0: (0: 1.0) (3: 2.0) - row 1: (1: 3.0) (4: 4.0) - row 2: empty - row 3: (0: 5.0) (2: 6.0) (4: 7.0) */ -static matrix *row_gather_sparse_fixture(void) -{ - CSR_matrix *A = new_CSR_matrix(4, 5, 7); - int p[5] = {0, 2, 4, 4, 7}; - int i[7] = {0, 3, 1, 4, 0, 2, 4}; - double x[7] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; - memcpy(A->p, p, 5 * sizeof(int)); - memcpy(A->i, i, 7 * sizeof(int)); - memcpy(A->x, x, 7 * sizeof(double)); - return new_sparse_matrix(A); -} - -/* 7x5 stacked_pd with three blocks; block 2 is never hit by the maps below so - the gather must drop it: - block 0: rows {0, 4}, cols {0, 2}, X = [[1, 2], [3, 4]] - block 1: rows {2, 3}, cols {1, 2}, X = [[5, 6], [7, 8]] - block 2: rows {6}, cols {4}, X = [[9]] - Row 1 and row 5 belong to no block. */ -static matrix *row_gather_spd_fixture(void) -{ - int rp0[2] = {0, 4}, cp0[2] = {0, 2}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - int rp1[2] = {2, 3}, cp1[2] = {1, 2}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - int rp2[1] = {6}, cp2[1] = {4}; - double X2[1] = {9.0}; - permuted_dense *blocks[3] = { - (permuted_dense *) new_permuted_dense(7, 5, 2, 2, rp0, cp0, X0), - (permuted_dense *) new_permuted_dense(7, 5, 2, 2, rp1, cp1, X1), - (permuted_dense *) new_permuted_dense(7, 5, 1, 1, rp2, cp2, X2)}; - return new_stacked_pd(7, 5, 3, blocks, NULL, NULL); -} - -static void row_gather_poison(matrix *M) -{ - for (int k = 0; k < M->nnz; k++) M->x[k] = (double) NAN; -} - -/* Deep-copy any matrix's CSR view into a fresh sparse_matrix. */ -static matrix *row_gather_sparse_twin(matrix *M) -{ - CSR_matrix *src = M->to_csr(M); - CSR_matrix *dst = new_CSR_matrix(src->m, src->n, src->nnz); - memcpy(dst->p, src->p, (size_t) (src->m + 1) * sizeof(int)); - memcpy(dst->i, src->i, (size_t) src->nnz * sizeof(int)); - memcpy(dst->x, src->x, (size_t) src->nnz * sizeof(double)); - dst->nnz = src->nnz; - return new_sparse_matrix(dst); -} - -/* Structure + values equality through the CSR views. */ -static int row_gather_same_csr(matrix *X, matrix *Y) -{ - CSR_matrix *a = X->to_csr(X); - CSR_matrix *b = Y->to_csr(Y); - if (a->m != b->m || a->n != b->n || a->nnz != b->nnz) return 0; - return cmp_int_array(a->p, b->p, a->m + 1) && - cmp_int_array(a->i, b->i, a->nnz) && cmp_double_array(a->x, b->x, a->nnz); -} - -/* map = [3, 0, 3, 2, 1]: a permutation, a repeat (row 3 twice), and the empty - source row 2. Output is 5x5 with nnz 3+2+3+0+2 = 10. */ -const char *test_row_gather_sparse(void) -{ - matrix *A = row_gather_sparse_fixture(); - int map[5] = {3, 0, 3, 2, 1}; - matrix *C = A->row_gather_alloc(A, map, 5); - - int exp_p[6] = {0, 3, 5, 8, 8, 10}; - int exp_i[10] = {0, 2, 4, 0, 3, 0, 2, 4, 1, 4}; - mu_assert("shape", C->m == 5 && C->n == 5); - mu_assert("sparsity", cmp_sparsity(C, exp_p, exp_i, 5, 10)); - - /* the map is bound at alloc time: clobbering the caller's copy is fine */ - for (int k = 0; k < 5; k++) map[k] = -7; - - row_gather_poison(C); - A->row_gather_fill_values(A, C); - double exp_x[10] = {5.0, 6.0, 7.0, 1.0, 2.0, 5.0, 6.0, 7.0, 3.0, 4.0}; - mu_assert("values", cmp_values(C, exp_x, 10)); - - /* fill reads the source's current values, not a snapshot */ - for (int k = 0; k < A->nnz; k++) A->x[k] *= 10.0; - for (int k = 0; k < 10; k++) exp_x[k] *= 10.0; - A->row_gather_fill_values(A, C); - mu_assert("values after refill", cmp_values(C, exp_x, 10)); - - free_matrix(C); - free_matrix(A); - return 0; -} - -/* pd 6x5 with rows {1, 3, 4} x cols {0, 2}. The map hits rows outside - row_perm (0 and 5) and repeats row 3. Result must stay a pd and agree - entrywise with the same gather on a sparse twin. */ -const char *test_row_gather_pd_vs_sparse_twin(void) -{ - int row_perm[3] = {1, 3, 4}; - int col_perm[2] = {0, 2}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - matrix *A = new_permuted_dense(6, 5, 3, 2, row_perm, col_perm, X); - matrix *A_tw = row_gather_sparse_twin(A); - - int map[6] = {0, 3, 1, 3, 5, 4}; - matrix *C = A->row_gather_alloc(A, map, 6); - matrix *C_tw = A_tw->row_gather_alloc(A_tw, map, 6); - mu_assert("kind preserved", C->kind == MATRIX_PERMUTED_DENSE); - mu_assert("m0", ((permuted_dense *) C)->m0 == 4); - mu_assert("nnz", C->nnz == 8); - - row_gather_poison(C); - A->row_gather_fill_values(A, C); - A_tw->row_gather_fill_values(A_tw, C_tw); - matrix_values_changed(C); - mu_assert("twin match", row_gather_same_csr(C, C_tw)); - - /* order-independent mutation of both sources, then refill */ - for (int k = 0; k < A->nnz; k++) A->x[k] *= 2.0; - for (int k = 0; k < A_tw->nnz; k++) A_tw->x[k] *= 2.0; - matrix_values_changed(A); - matrix_values_changed(A_tw); - A->row_gather_fill_values(A, C); - A_tw->row_gather_fill_values(A_tw, C_tw); - matrix_values_changed(C); - mu_assert("twin match after refill", row_gather_same_csr(C, C_tw)); - - free_matrix(C_tw); - free_matrix(C); - free_matrix(A_tw); - free_matrix(A); - return 0; -} - -/* spd fixture above; map = [4, 2, 4, 0, 3, 1, 2]: - pos 0, 2 -> row 4 (block 0, twice) - pos 3 -> row 0 (block 0) - pos 1, 6 -> row 2 (block 1, twice) - pos 4 -> row 3 (block 1) - pos 5 -> row 1 (no block) - Block 2 gets no hits and must be dropped; output blocks stay row-disjoint. */ -const char *test_row_gather_spd_vs_sparse_twin(void) -{ - matrix *A = row_gather_spd_fixture(); - matrix *A_tw = row_gather_sparse_twin(A); - - int map[7] = {4, 2, 4, 0, 3, 1, 2}; - matrix *C = A->row_gather_alloc(A, map, 7); - matrix *C_tw = A_tw->row_gather_alloc(A_tw, map, 7); - mu_assert("kind preserved", C->kind == MATRIX_STACKED_PD); - stacked_pd *C_spd = (stacked_pd *) C; - mu_assert("empty block dropped", C_spd->n_blocks == 2); - mu_assert("src_block_idx", - C_spd->src_block_idx[0] == 0 && C_spd->src_block_idx[1] == 1); - int exp_rp0[3] = {0, 2, 3}; - int exp_rp1[3] = {1, 4, 6}; - mu_assert("block 0 row_perm", - C_spd->blocks[0]->m0 == 3 && - cmp_int_array(C_spd->blocks[0]->row_perm, exp_rp0, 3)); - mu_assert("block 1 row_perm", - C_spd->blocks[1]->m0 == 3 && - cmp_int_array(C_spd->blocks[1]->row_perm, exp_rp1, 3)); - - row_gather_poison(C); - A->row_gather_fill_values(A, C); - A_tw->row_gather_fill_values(A_tw, C_tw); - matrix_values_changed(C); - mu_assert("twin match", row_gather_same_csr(C, C_tw)); - - for (int k = 0; k < A->nnz; k++) A->x[k] *= 2.0; - for (int k = 0; k < A_tw->nnz; k++) A_tw->x[k] *= 2.0; - matrix_values_changed(A); - matrix_values_changed(A_tw); - A->row_gather_fill_values(A, C); - A_tw->row_gather_fill_values(A_tw, C_tw); - matrix_values_changed(C); - mu_assert("twin match after refill", row_gather_same_csr(C, C_tw)); - - free_matrix(C_tw); - free_matrix(C); - free_matrix(A_tw); - free_matrix(A); - return 0; -} - -#ifdef SP_TRACK_MEMORY -typedef struct -{ - matrix *A; - matrix *C; -} row_gather_fill_args; - -static void run_row_gather_fill(const void *ctx) -{ - const row_gather_fill_args *a = (const row_gather_fill_args *) ctx; - a->A->row_gather_fill_values(a->A, a->C); -} - -/* The spd fill dispatches through src_block_idx into per-block pd fills; none - of it may allocate once the alloc phase is done. */ -const char *test_row_gather_spd_fill_no_transient_alloc(void) -{ - matrix *A = row_gather_spd_fixture(); - int map[7] = {4, 2, 4, 0, 3, 1, 2}; - matrix *C = A->row_gather_alloc(A, map, 7); - row_gather_fill_args args = {A, C}; - run_row_gather_fill(&args); /* warm-up */ - mu_assert("spd row_gather fill must not allocate", - fill_is_alloc_free(run_row_gather_fill, &args)); - free_matrix(C); - free_matrix(A); - return 0; -} -#endif - -#endif /* TEST_ROW_GATHER_H */ diff --git a/sparsediff_linalg/tests/test_row_reduce.h b/sparsediff_linalg/tests/test_row_reduce.h deleted file mode 100644 index 49d1c68d..00000000 --- a/sparsediff_linalg/tests/test_row_reduce.h +++ /dev/null @@ -1,286 +0,0 @@ -#ifndef TEST_ROW_REDUCE_H -#define TEST_ROW_REDUCE_H - -#include "minunit.h" -#include "sparsediff_linalg/CSR_matrix.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/sparse_matrix.h" -#include "sparsediff_linalg/stacked_pd.h" -#include "test_helpers.h" -#include -#include -#include - -/* row_reduce_alloc / row_reduce_fill_values across the three matrix kinds: - C[j, :] = sum of rows i of A with group[i] == j. The reduction map is bound to - the result at alloc time; the fill zeroes C and accumulates from A's current - values. */ - -/* 4x5 CSR source with an empty row 2: - row 0: (0: 1.0) (3: 2.0) - row 1: (1: 3.0) (4: 4.0) - row 2: empty - row 3: (0: 5.0) (2: 6.0) (4: 7.0) */ -static matrix *row_reduce_sparse_fixture(void) -{ - CSR_matrix *A = new_CSR_matrix(4, 5, 7); - int p[5] = {0, 2, 4, 4, 7}; - int i[7] = {0, 3, 1, 4, 0, 2, 4}; - double x[7] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; - memcpy(A->p, p, 5 * sizeof(int)); - memcpy(A->i, i, 7 * sizeof(int)); - memcpy(A->x, x, 7 * sizeof(double)); - return new_sparse_matrix(A); -} - -/* 4x4 stacked_pd whose blocks share column 2: - block 0: rows {0, 1}, cols {0, 2}, X = [[1, 2], [3, 4]] - block 1: rows {2, 3}, cols {1, 2}, X = [[5, 6], [7, 8]] */ -static matrix *row_reduce_spd_fixture(void) -{ - int rp0[2] = {0, 1}, cp0[2] = {0, 2}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - int rp1[2] = {2, 3}, cp1[2] = {1, 2}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - permuted_dense *blocks[2] = { - (permuted_dense *) new_permuted_dense(4, 4, 2, 2, rp0, cp0, X0), - (permuted_dense *) new_permuted_dense(4, 4, 2, 2, rp1, cp1, X1)}; - return new_stacked_pd(4, 4, 2, blocks, NULL, NULL); -} - -static void row_reduce_poison(matrix *M) -{ - for (int k = 0; k < M->nnz; k++) M->x[k] = (double) NAN; -} - -/* Deep-copy any matrix's CSR view into a fresh sparse_matrix. */ -static matrix *row_reduce_sparse_twin(matrix *M) -{ - CSR_matrix *src = M->to_csr(M); - CSR_matrix *dst = new_CSR_matrix(src->m, src->n, src->nnz); - memcpy(dst->p, src->p, (size_t) (src->m + 1) * sizeof(int)); - memcpy(dst->i, src->i, (size_t) src->nnz * sizeof(int)); - memcpy(dst->x, src->x, (size_t) src->nnz * sizeof(double)); - dst->nnz = src->nnz; - return new_sparse_matrix(dst); -} - -/* Structure + values equality through the CSR views. */ -static int row_reduce_same_csr(matrix *X, matrix *Y) -{ - CSR_matrix *a = X->to_csr(X); - CSR_matrix *b = Y->to_csr(Y); - if (a->m != b->m || a->n != b->n || a->nnz != b->nnz) return 0; - return cmp_int_array(a->p, b->p, a->m + 1) && - cmp_int_array(a->i, b->i, a->nnz) && cmp_double_array(a->x, b->x, a->nnz); -} - -/* Reduce A and its sparse twin with the same group, compare through to_csr - after a poisoned fill and again after scaling both sources. Returns the - failing assertion message or NULL. */ -static const char *row_reduce_check_against_twin(matrix *A, const int *group, - int m_out, matrix **C_out) -{ - matrix *A_tw = row_reduce_sparse_twin(A); - matrix *C = A->row_reduce_alloc(A, group, m_out); - matrix *C_tw = A_tw->row_reduce_alloc(A_tw, group, m_out); - - row_reduce_poison(C); - A->row_reduce_fill_values(A, C); - A_tw->row_reduce_fill_values(A_tw, C_tw); - matrix_values_changed(C); - int ok = row_reduce_same_csr(C, C_tw); - - for (int k = 0; k < A->nnz; k++) A->x[k] *= 2.0; - for (int k = 0; k < A_tw->nnz; k++) A_tw->x[k] *= 2.0; - matrix_values_changed(A); - A->row_reduce_fill_values(A, C); - A_tw->row_reduce_fill_values(A_tw, C_tw); - matrix_values_changed(C); - int ok_refill = row_reduce_same_csr(C, C_tw); - - free_matrix(C_tw); - free_matrix(A_tw); - *C_out = C; - if (!ok) return "twin match"; - if (!ok_refill) return "twin match after refill"; - return NULL; -} - -/* group = [1, 0, 1, 0], m_out = 3: output row 0 = rows 1 + 3 (columns merge - on 4), output row 1 = rows 0 + 2 (row 2 is empty), output row 2 has no - members. */ -const char *test_row_reduce_sparse(void) -{ - matrix *A = row_reduce_sparse_fixture(); - int group[4] = {1, 0, 1, 0}; - matrix *C = A->row_reduce_alloc(A, group, 3); - - int exp_p[4] = {0, 4, 6, 6}; - int exp_i[6] = {0, 1, 2, 4, 0, 3}; - mu_assert("shape", C->m == 3 && C->n == 5); - mu_assert("sparsity", cmp_sparsity(C, exp_p, exp_i, 3, 6)); - - /* the group is bound at alloc time: clobbering the caller's copy is fine */ - for (int k = 0; k < 4; k++) group[k] = -7; - - /* poisoned output proves the fill zeroes before accumulating */ - row_reduce_poison(C); - A->row_reduce_fill_values(A, C); - double exp_x[6] = {5.0, 3.0, 6.0, 11.0, 1.0, 2.0}; - mu_assert("values", cmp_values(C, exp_x, 6)); - - for (int k = 0; k < A->nnz; k++) A->x[k] *= 10.0; - for (int k = 0; k < 6; k++) exp_x[k] *= 10.0; - A->row_reduce_fill_values(A, C); - mu_assert("values after refill", cmp_values(C, exp_x, 6)); - - free_matrix(C); - free_matrix(A); - return 0; -} - -/* (6, 4) pd with dense rows {0, 3, 4} x cols {1, 3}; group = [0, 0, 1, 0, 1, 0] - sends dense rows 0 and 3 to output 0 and dense row 4 to output 1 - (non-monotone in the row index, two rows collapse). */ -const char *test_row_reduce_pd(void) -{ - int row_perm[3] = {0, 3, 4}; - int col_perm[2] = {1, 3}; - double X[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - matrix *A = new_permuted_dense(6, 4, 3, 2, row_perm, col_perm, X); - - int group[6] = {0, 0, 1, 0, 1, 0}; - matrix *C; - const char *msg = row_reduce_check_against_twin(A, group, 2, &C); - mu_assert(msg ? msg : "", msg == NULL); - - mu_assert("kind preserved", C->kind == MATRIX_PERMUTED_DENSE); - permuted_dense *pd = (permuted_dense *) C; - mu_assert("shape", C->m == 2 && C->n == 4 && pd->m0 == 2 && pd->n0 == 2); - int exp_row_perm[2] = {0, 1}; - int exp_col_perm[2] = {1, 3}; - int exp_bound[3] = {0, 0, 1}; - mu_assert("row_perm", cmp_int_array(pd->row_perm, exp_row_perm, 2)); - mu_assert("col_perm", cmp_int_array(pd->col_perm, exp_col_perm, 2)); - mu_assert("bound_iwork", cmp_int_array(pd->bound_iwork, exp_bound, 3)); - /* sources were scaled by 2 inside the twin check */ - double exp_X[4] = {8.0, 12.0, 10.0, 12.0}; - mu_assert("X", cmp_double_array(pd->X, exp_X, 4)); - - free_matrix(C); - free_matrix(A); - return 0; -} - -/* group = [0, 1, 0, 1]: every output row draws one row from each block, so - the blocks' shared column 2 must be summed across blocks. One output block - with the column union {0, 1, 2}. */ -const char *test_row_reduce_spd_cross_block_accumulate(void) -{ - matrix *A = row_reduce_spd_fixture(); - int group[4] = {0, 1, 0, 1}; - matrix *C; - const char *msg = row_reduce_check_against_twin(A, group, 2, &C); - mu_assert(msg ? msg : "", msg == NULL); - - mu_assert("kind preserved", C->kind == MATRIX_STACKED_PD); - stacked_pd *spd = (stacked_pd *) C; - mu_assert("one output block", spd->n_blocks == 1); - int exp_row_perm[2] = {0, 1}; - int exp_col_perm[3] = {0, 1, 2}; - mu_assert("row_perm", cmp_int_array(spd->blocks[0]->row_perm, exp_row_perm, 2)); - mu_assert("col_perm", cmp_int_array(spd->blocks[0]->col_perm, exp_col_perm, 3)); - /* sources were scaled by 2: row 0 = [1, 5, 2 + 6], row 1 = [3, 7, 4 + 8] */ - double exp_X[6] = {2.0, 10.0, 16.0, 6.0, 14.0, 24.0}; - mu_assert("X", cmp_double_array(spd->blocks[0]->X, exp_X, 6)); - - free_matrix(C); - free_matrix(A); - return 0; -} - -/* group = [0, 0, 1, 1]: each output row is fed by a single block, so the - result keeps two blocks with the source column footprints. */ -const char *test_row_reduce_spd_within_block(void) -{ - matrix *A = row_reduce_spd_fixture(); - int group[4] = {0, 0, 1, 1}; - matrix *C; - const char *msg = row_reduce_check_against_twin(A, group, 2, &C); - mu_assert(msg ? msg : "", msg == NULL); - - mu_assert("kind preserved", C->kind == MATRIX_STACKED_PD); - stacked_pd *spd = (stacked_pd *) C; - mu_assert("two output blocks", spd->n_blocks == 2); - int exp_cp0[2] = {0, 2}; - int exp_cp1[2] = {1, 2}; - mu_assert("block 0", spd->blocks[0]->m0 == 1 && - spd->blocks[0]->row_perm[0] == 0 && - cmp_int_array(spd->blocks[0]->col_perm, exp_cp0, 2)); - mu_assert("block 1", spd->blocks[1]->m0 == 1 && - spd->blocks[1]->row_perm[0] == 1 && - cmp_int_array(spd->blocks[1]->col_perm, exp_cp1, 2)); - double exp_X0[2] = {8.0, 12.0}; /* 2 * ([1, 2] + [3, 4]) */ - double exp_X1[2] = {24.0, 28.0}; /* 2 * ([5, 6] + [7, 8]) */ - mu_assert("X0", cmp_double_array(spd->blocks[0]->X, exp_X0, 2)); - mu_assert("X1", cmp_double_array(spd->blocks[1]->X, exp_X1, 2)); - - free_matrix(C); - free_matrix(A); - return 0; -} - -/* All rows into one: a single 1-row block over the column union. */ -const char *test_row_reduce_spd_all_to_one(void) -{ - matrix *A = row_reduce_spd_fixture(); - int group[4] = {0, 0, 0, 0}; - matrix *C; - const char *msg = row_reduce_check_against_twin(A, group, 1, &C); - mu_assert(msg ? msg : "", msg == NULL); - - mu_assert("kind preserved", C->kind == MATRIX_STACKED_PD); - stacked_pd *spd = (stacked_pd *) C; - mu_assert("one output block", spd->n_blocks == 1 && spd->blocks[0]->m0 == 1); - int exp_col_perm[3] = {0, 1, 2}; - mu_assert("col_perm", cmp_int_array(spd->blocks[0]->col_perm, exp_col_perm, 3)); - double exp_X[3] = {8.0, 24.0, 40.0}; /* 2 * [1 + 3, 5 + 7, 2 + 4 + 6 + 8] */ - mu_assert("X", cmp_double_array(spd->blocks[0]->X, exp_X, 3)); - - free_matrix(C); - free_matrix(A); - return 0; -} - -#ifdef SP_TRACK_MEMORY -typedef struct -{ - matrix *A; - matrix *C; -} row_reduce_fill_args; - -static void run_row_reduce_fill(const void *ctx) -{ - const row_reduce_fill_args *a = (const row_reduce_fill_args *) ctx; - a->A->row_reduce_fill_values(a->A, a->C); -} - -/* The spd fill reduces per block into the raw spd, then coalesce-accumulates; - none of it may allocate once the alloc phase is done. */ -const char *test_row_reduce_spd_fill_no_transient_alloc(void) -{ - matrix *A = row_reduce_spd_fixture(); - int group[4] = {0, 1, 0, 1}; - matrix *C = A->row_reduce_alloc(A, group, 2); - row_reduce_fill_args args = {A, C}; - run_row_reduce_fill(&args); /* warm-up */ - mu_assert("spd row_reduce fill must not allocate", - fill_is_alloc_free(run_row_reduce_fill, &args)); - free_matrix(C); - free_matrix(A); - return 0; -} -#endif - -#endif /* TEST_ROW_REDUCE_H */ diff --git a/sparsediff_linalg/tests/test_stacked_pd.h b/sparsediff_linalg/tests/test_stacked_pd.h deleted file mode 100644 index bd83a049..00000000 --- a/sparsediff_linalg/tests/test_stacked_pd.h +++ /dev/null @@ -1,1966 +0,0 @@ -#ifndef TEST_STACKED_PD_H -#define TEST_STACKED_PD_H - -#include "internal/stacked_pd_linalg.h" -#include "minunit.h" -#include "sparsediff_linalg/CSC_matrix.h" -#include "sparsediff_linalg/permuted_dense.h" -#include "sparsediff_linalg/stacked_pd.h" -#include "test_helpers.h" -#include -#include - -/* Construct a 2-block spd, check identity src_block_idx_*, and free. */ -const char *test_stacked_pd_construct_and_free(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 2}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(5, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[1] = {3}; - int col_perm_1[3] = {0, 1, 3}; - double X1[3] = {5.0, 6.0, 7.0}; - matrix *blk1 = new_permuted_dense(5, 4, 1, 3, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *M = new_stacked_pd(5, 4, 2, blocks, NULL, NULL); - stacked_pd *s = (stacked_pd *) M; - - mu_assert("m", M->m == 5); - mu_assert("n", M->n == 4); - mu_assert("nnz", M->nnz == 4 + 3); - mu_assert("n_blocks", s->n_blocks == 2); - int expected_src_p[3] = {0, 1, 2}; - int expected_src[2] = {0, 1}; - mu_assert("src_block_idx_p", - cmp_int_array(s->src_block_idx_p, expected_src_p, 3)); - mu_assert("src_block_idx", cmp_int_array(s->src_block_idx, expected_src, 2)); - - free_matrix(M); - return 0; -} - -/* Coalesce input with disjoint row perms (and disjoint cols): output - reproduces the input structure (one PD per source block, in min-row - order). */ -const char *test_coalesce_no_overlap(void) -{ - /* block 0: rows {0, 1}, cols {0, 1}, X = [[1, 2], [3, 4]] - block 1: rows {2, 3}, cols {2, 3}, X = [[5, 6], [7, 8]] */ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *src_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - matrix *out_m = coalesce_spd_alloc((stacked_pd *) src_m); - coalesce_spd_fill_values((stacked_pd *) src_m, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - mu_assert("n_blocks", out->n_blocks == 2); - int expected_src_p[3] = {0, 1, 2}; - int expected_src[2] = {0, 1}; - mu_assert("src_block_idx_p", - cmp_int_array(out->src_block_idx_p, expected_src_p, 3)); - mu_assert("src_block_idx", cmp_int_array(out->src_block_idx, expected_src, 2)); - - permuted_dense *O0 = out->blocks[0]; - mu_assert("O0 row_perm", cmp_int_array(O0->row_perm, row_perm_0, 2)); - mu_assert("O0 col_perm", cmp_int_array(O0->col_perm, col_perm_0, 2)); - mu_assert("O0 X", cmp_double_array(O0->X, X0, 4)); - - permuted_dense *O1 = out->blocks[1]; - mu_assert("O1 row_perm", cmp_int_array(O1->row_perm, row_perm_1, 2)); - mu_assert("O1 col_perm", cmp_int_array(O1->col_perm, col_perm_1, 2)); - mu_assert("O1 X", cmp_double_array(O1->X, X1, 4)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Coalesce input with one shared row (the transpose-style example from - the design conversation). Three unique signatures produce three - output PDs, ordered by min row. */ -const char *test_coalesce_three_signatures(void) -{ - /* block 0 (id 0): rows {2, 4}, cols {0, 1}, X = [[1, 2], [3, 4]] - block 1 (id 1): rows {0, 2}, cols {3}, X = [10, 11] - row 2 is shared. col_perms {0,1} and {3} are disjoint -> ok. */ - int row_perm_0[2] = {2, 4}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(5, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {0, 2}; - int col_perm_1[1] = {3}; - double X1[2] = {10.0, 11.0}; - matrix *blk1 = new_permuted_dense(5, 4, 2, 1, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *src_m = new_stacked_pd_unchecked(5, 4, 2, blocks, NULL, NULL); - - matrix *out_m = coalesce_spd_alloc((stacked_pd *) src_m); - coalesce_spd_fill_values((stacked_pd *) src_m, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - /* Expected (ordered by min row): - sig {1}: row {0}, cols {3}, X = [10] - sig {0, 1}: row {2}, cols {0, 1, 3}, X = [1, 2, 11] - sig {0}: row {4}, cols {0, 1}, X = [3, 4] */ - mu_assert("n_blocks", out->n_blocks == 3); - - int expected_src_p[4] = {0, 1, 3, 4}; - int expected_src[4] = {1, 0, 1, 0}; - mu_assert("src_block_idx_p", - cmp_int_array(out->src_block_idx_p, expected_src_p, 4)); - mu_assert("src_block_idx", cmp_int_array(out->src_block_idx, expected_src, 4)); - - permuted_dense *O0 = out->blocks[0]; - int O0_row[1] = {0}; - int O0_col[1] = {3}; - double O0_X[1] = {10.0}; - mu_assert("O0 row", cmp_int_array(O0->row_perm, O0_row, 1)); - mu_assert("O0 col", cmp_int_array(O0->col_perm, O0_col, 1)); - mu_assert("O0 X", cmp_double_array(O0->X, O0_X, 1)); - - permuted_dense *O1 = out->blocks[1]; - int O1_row[1] = {2}; - int O1_col[3] = {0, 1, 3}; - double O1_X[3] = {1.0, 2.0, 11.0}; - mu_assert("O1 row", cmp_int_array(O1->row_perm, O1_row, 1)); - mu_assert("O1 col", cmp_int_array(O1->col_perm, O1_col, 3)); - mu_assert("O1 X", cmp_double_array(O1->X, O1_X, 3)); - - permuted_dense *O2 = out->blocks[2]; - int O2_row[1] = {4}; - int O2_col[2] = {0, 1}; - double O2_X[2] = {3.0, 4.0}; - mu_assert("O2 row", cmp_int_array(O2->row_perm, O2_row, 1)); - mu_assert("O2 col", cmp_int_array(O2->col_perm, O2_col, 2)); - mu_assert("O2 X", cmp_double_array(O2->X, O2_X, 2)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Two rows share the same signature -> they land in the SAME output - PD, demonstrating the group-by-signature reduction. */ -const char *test_coalesce_shared_signature_merges_rows(void) -{ - /* block A (id 0): rows {0, 1, 2}, cols {0}, X = [a0, a1, a2] - block B (id 1): rows {1, 2, 3}, cols {1}, X = [b1, b2, b3] - rows 1 and 2 both have signature {A, B}; cols disjoint -> ok. */ - int row_perm_A[3] = {0, 1, 2}; - int col_perm_A[1] = {0}; - double XA[3] = {1.0, 2.0, 3.0}; - matrix *blkA = new_permuted_dense(4, 2, 3, 1, row_perm_A, col_perm_A, XA); - - int row_perm_B[3] = {1, 2, 3}; - int col_perm_B[1] = {1}; - double XB[3] = {10.0, 20.0, 30.0}; - matrix *blkB = new_permuted_dense(4, 2, 3, 1, row_perm_B, col_perm_B, XB); - - permuted_dense *blocks[2] = {(permuted_dense *) blkA, (permuted_dense *) blkB}; - matrix *src_m = new_stacked_pd_unchecked(4, 2, 2, blocks, NULL, NULL); - - matrix *out_m = coalesce_spd_alloc((stacked_pd *) src_m); - coalesce_spd_fill_values((stacked_pd *) src_m, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - /* Expected (ordered by min row): - sig {0}: row {0}, col {0}, X = [1] - sig {0, 1}: rows {1,2}, cols {0,1}, X = [2, 10, 3, 20] - sig {1}: row {3}, col {1}, X = [30] */ - mu_assert("n_blocks", out->n_blocks == 3); - - int expected_src_p[4] = {0, 1, 3, 4}; - int expected_src[4] = {0, 0, 1, 1}; - mu_assert("src_block_idx_p", - cmp_int_array(out->src_block_idx_p, expected_src_p, 4)); - mu_assert("src_block_idx", cmp_int_array(out->src_block_idx, expected_src, 4)); - - permuted_dense *O0 = out->blocks[0]; - int O0_row[1] = {0}; - int O0_col[1] = {0}; - double O0_X[1] = {1.0}; - mu_assert("O0 row", cmp_int_array(O0->row_perm, O0_row, 1)); - mu_assert("O0 col", cmp_int_array(O0->col_perm, O0_col, 1)); - mu_assert("O0 X", cmp_double_array(O0->X, O0_X, 1)); - - permuted_dense *O1 = out->blocks[1]; - int O1_row[2] = {1, 2}; - int O1_col[2] = {0, 1}; - double O1_X[4] = {2.0, 10.0, 3.0, 20.0}; - mu_assert("O1 m0", O1->m0 == 2); - mu_assert("O1 n0", O1->n0 == 2); - mu_assert("O1 row", cmp_int_array(O1->row_perm, O1_row, 2)); - mu_assert("O1 col", cmp_int_array(O1->col_perm, O1_col, 2)); - mu_assert("O1 X", cmp_double_array(O1->X, O1_X, 4)); - - permuted_dense *O2 = out->blocks[2]; - int O2_row[1] = {3}; - int O2_col[1] = {1}; - double O2_X[1] = {30.0}; - mu_assert("O2 row", cmp_int_array(O2->row_perm, O2_row, 1)); - mu_assert("O2 col", cmp_int_array(O2->col_perm, O2_col, 1)); - mu_assert("O2 X", cmp_double_array(O2->X, O2_X, 1)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Two-phase test: alloc once, mutate source X buffers, refill, re-check. */ -const char *test_coalesce_alloc_then_fill_values(void) -{ - int row_perm_0[2] = {2, 4}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(5, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {0, 2}; - int col_perm_1[1] = {3}; - double X1[2] = {10.0, 11.0}; - matrix *blk1 = new_permuted_dense(5, 4, 2, 1, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *src_m = new_stacked_pd_unchecked(5, 4, 2, blocks, NULL, NULL); - stacked_pd *src = (stacked_pd *) src_m; - - matrix *out_m = coalesce_spd_alloc(src); - coalesce_spd_fill_values(src, (stacked_pd *) out_m); - - /* Mutate source values then refill. New values: - blk0: X = [100, 200, 300, 400] - blk1: X = [50, 60] */ - src->blocks[0]->X[0] = 100.0; - src->blocks[0]->X[1] = 200.0; - src->blocks[0]->X[2] = 300.0; - src->blocks[0]->X[3] = 400.0; - src->blocks[1]->X[0] = 50.0; - src->blocks[1]->X[1] = 60.0; - - coalesce_spd_fill_values(src, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - /* Block order (by min row): {1} -> {0,1} -> {0}. - O0: row {0}, col {3}, X = [50] - O1: row {2}, cols {0, 1, 3}, X = [100, 200, 60] - O2: row {4}, cols {0, 1}, X = [300, 400] */ - double O0_X[1] = {50.0}; - mu_assert("O0 X refilled", cmp_double_array(out->blocks[0]->X, O0_X, 1)); - - double O1_X[3] = {100.0, 200.0, 60.0}; - mu_assert("O1 X refilled", cmp_double_array(out->blocks[1]->X, O1_X, 3)); - - double O2_X[2] = {300.0, 400.0}; - mu_assert("O2 X refilled", cmp_double_array(out->blocks[2]->X, O2_X, 2)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Empty input (n_blocks == 0): output is also empty and safe to free. */ -const char *test_coalesce_empty_input(void) -{ - matrix *src_m = new_stacked_pd(4, 4, 0, NULL, NULL, NULL); - matrix *out_m = coalesce_spd_alloc((stacked_pd *) src_m); - coalesce_spd_fill_values((stacked_pd *) src_m, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - mu_assert("m", out_m->m == 4); - mu_assert("n", out_m->n == 4); - mu_assert("n_blocks", out->n_blocks == 0); - mu_assert("nnz", out_m->nnz == 0); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Transpose where source blocks have disjoint rows AND disjoint cols. - Transposed blocks also have disjoint rows; coalesce leaves them - unchanged. */ -const char *test_transpose_spd_no_overlap(void) -{ - /* block 0: rows {0, 1}, cols {0, 1}, X = [[1, 2], [3, 4]] - block 1: rows {2, 3}, cols {2, 3}, X = [[5, 6], [7, 8]] */ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *src_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - matrix *out_m = transpose_spd_alloc((stacked_pd *) src_m); - transpose_spd_fill_values((stacked_pd *) src_m, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - mu_assert("m", out_m->m == 4); - mu_assert("n", out_m->n == 4); - mu_assert("n_blocks", out->n_blocks == 2); - int expected_src_p[3] = {0, 1, 2}; - int expected_src[2] = {0, 1}; - mu_assert("src_block_idx_p", - cmp_int_array(out->src_block_idx_p, expected_src_p, 3)); - mu_assert("src_block_idx", cmp_int_array(out->src_block_idx, expected_src, 2)); - - /* Transpose of [[1,2],[3,4]] is [[1,3],[2,4]] (row-major flatten: - [1, 3, 2, 4]); similarly for the second block. */ - permuted_dense *O0 = out->blocks[0]; - double O0_X_expected[4] = {1.0, 3.0, 2.0, 4.0}; - mu_assert("O0 row_perm", cmp_int_array(O0->row_perm, row_perm_0, 2)); - mu_assert("O0 col_perm", cmp_int_array(O0->col_perm, col_perm_0, 2)); - mu_assert("O0 X", cmp_double_array(O0->X, O0_X_expected, 4)); - - permuted_dense *O1 = out->blocks[1]; - double O1_X_expected[4] = {5.0, 7.0, 6.0, 8.0}; - mu_assert("O1 row_perm", cmp_int_array(O1->row_perm, row_perm_1, 2)); - mu_assert("O1 col_perm", cmp_int_array(O1->col_perm, col_perm_1, 2)); - mu_assert("O1 X", cmp_double_array(O1->X, O1_X_expected, 4)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Transpose where source blocks share a column. After per-block - transpose two raw blocks share a row, so coalesce merges and produces - three output PDs (the worked example from the design conversation). */ -const char *test_transpose_spd_overlap_coalesces(void) -{ - /* Source (4x5): - block 0: rows {0, 1}, cols {2, 4}, X = [[1, 2], [3, 4]] - block 1: rows {3}, cols {0, 2}, X = [10, 11] - (Disjoint rows; cells {(0,2),(0,4),(1,2),(1,4)} and {(3,0),(3,2)} - are disjoint.) */ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {2, 4}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 5, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[1] = {3}; - int col_perm_1[2] = {0, 2}; - double X1[2] = {10.0, 11.0}; - matrix *blk1 = new_permuted_dense(4, 5, 1, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *src_m = new_stacked_pd(4, 5, 2, blocks, NULL, NULL); - - matrix *out_m = transpose_spd_alloc((stacked_pd *) src_m); - transpose_spd_fill_values((stacked_pd *) src_m, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - /* Transposed (5x4): - raw block 0^T: rows {2, 4}, cols {0, 1}, X = [[1, 3], [2, 4]] - raw block 1^T: rows {0, 2}, cols {3}, X = [10, 11] - Row 2 shared. Coalesce produces (ordered by min row): - sig {1}: row {0}, col {3}, X = [10] - sig {0, 1}: row {2}, cols {0, 1, 3}, X = [1, 3, 11] - sig {0}: row {4}, cols {0, 1}, X = [2, 4] */ - mu_assert("m", out_m->m == 5); - mu_assert("n", out_m->n == 4); - mu_assert("n_blocks", out->n_blocks == 3); - - int expected_src_p[4] = {0, 1, 3, 4}; - int expected_src[4] = {1, 0, 1, 0}; - mu_assert("src_block_idx_p", - cmp_int_array(out->src_block_idx_p, expected_src_p, 4)); - mu_assert("src_block_idx", cmp_int_array(out->src_block_idx, expected_src, 4)); - - permuted_dense *O0 = out->blocks[0]; - int O0_row[1] = {0}; - int O0_col[1] = {3}; - double O0_X[1] = {10.0}; - mu_assert("O0 row", cmp_int_array(O0->row_perm, O0_row, 1)); - mu_assert("O0 col", cmp_int_array(O0->col_perm, O0_col, 1)); - mu_assert("O0 X", cmp_double_array(O0->X, O0_X, 1)); - - permuted_dense *O1 = out->blocks[1]; - int O1_row[1] = {2}; - int O1_col[3] = {0, 1, 3}; - double O1_X[3] = {1.0, 3.0, 11.0}; - mu_assert("O1 row", cmp_int_array(O1->row_perm, O1_row, 1)); - mu_assert("O1 col", cmp_int_array(O1->col_perm, O1_col, 3)); - mu_assert("O1 X", cmp_double_array(O1->X, O1_X, 3)); - - permuted_dense *O2 = out->blocks[2]; - int O2_row[1] = {4}; - int O2_col[2] = {0, 1}; - double O2_X[2] = {2.0, 4.0}; - mu_assert("O2 row", cmp_int_array(O2->row_perm, O2_row, 1)); - mu_assert("O2 col", cmp_int_array(O2->col_perm, O2_col, 2)); - mu_assert("O2 X", cmp_double_array(O2->X, O2_X, 2)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Two-phase: alloc once, mutate src values, refill, re-check. */ -const char *test_transpose_spd_alloc_then_fill_values(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {2, 4}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 5, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[1] = {3}; - int col_perm_1[2] = {0, 2}; - double X1[2] = {10.0, 11.0}; - matrix *blk1 = new_permuted_dense(4, 5, 1, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *src_m = new_stacked_pd(4, 5, 2, blocks, NULL, NULL); - stacked_pd *src = (stacked_pd *) src_m; - - matrix *out_m = transpose_spd_alloc(src); - transpose_spd_fill_values(src, (stacked_pd *) out_m); - - /* Mutate src values then refill: - blk0 X -> [100, 200, 300, 400] - blk1 X -> [50, 60] */ - src->blocks[0]->X[0] = 100.0; - src->blocks[0]->X[1] = 200.0; - src->blocks[0]->X[2] = 300.0; - src->blocks[0]->X[3] = 400.0; - src->blocks[1]->X[0] = 50.0; - src->blocks[1]->X[1] = 60.0; - - transpose_spd_fill_values(src, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - /* Expected after refill: - O0: X = [50] - O1: X = [100, 300, 60] - O2: X = [200, 400] */ - double O0_X[1] = {50.0}; - mu_assert("O0 X refilled", cmp_double_array(out->blocks[0]->X, O0_X, 1)); - double O1_X[3] = {100.0, 300.0, 60.0}; - mu_assert("O1 X refilled", cmp_double_array(out->blocks[1]->X, O1_X, 3)); - double O2_X[2] = {200.0, 400.0}; - mu_assert("O2 X refilled", cmp_double_array(out->blocks[2]->X, O2_X, 2)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Empty source: output also empty; pre_coalesce is an empty spd; free safe. */ -const char *test_transpose_spd_empty(void) -{ - matrix *src_m = new_stacked_pd(4, 5, 0, NULL, NULL, NULL); - matrix *out_m = transpose_spd_alloc((stacked_pd *) src_m); - transpose_spd_fill_values((stacked_pd *) src_m, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - mu_assert("m", out_m->m == 5); - mu_assert("n", out_m->n == 4); - mu_assert("n_blocks", out->n_blocks == 0); - mu_assert("nnz", out_m->nnz == 0); - mu_assert("pre_coalesce not NULL", out->pre_coalesce != NULL); - mu_assert("pre_coalesce n_blocks", out->pre_coalesce->n_blocks == 0); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Single full-dense block: trivial case, just a dense transpose. */ -const char *test_transpose_spd_single_block_full(void) -{ - /* 2x3 dense block, X = [[1, 2, 3], [4, 5, 6]] */ - double data[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - matrix *blk = new_permuted_dense_full(2, 3, data); - - permuted_dense *blocks[1] = {(permuted_dense *) blk}; - matrix *src_m = new_stacked_pd(2, 3, 1, blocks, NULL, NULL); - - matrix *out_m = transpose_spd_alloc((stacked_pd *) src_m); - transpose_spd_fill_values((stacked_pd *) src_m, (stacked_pd *) out_m); - stacked_pd *out = (stacked_pd *) out_m; - - mu_assert("m", out_m->m == 3); - mu_assert("n", out_m->n == 2); - mu_assert("n_blocks", out->n_blocks == 1); - - /* Transposed 3x2 dense: [[1, 4], [2, 5], [3, 6]] -> [1, 4, 2, 5, 3, 6]. */ - permuted_dense *O = out->blocks[0]; - int O_row[3] = {0, 1, 2}; - int O_col[2] = {0, 1}; - double O_X[6] = {1.0, 4.0, 2.0, 5.0, 3.0, 6.0}; - mu_assert("O m0", O->m0 == 3); - mu_assert("O n0", O->n0 == 2); - mu_assert("O row", cmp_int_array(O->row_perm, O_row, 3)); - mu_assert("O col", cmp_int_array(O->col_perm, O_col, 2)); - mu_assert("O X", cmp_double_array(O->X, O_X, 6)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* copy_sparsity_spd_alloc preserves block structure and produces a fresh - spd with identity src_block_idx_*, NULL pre_coalesce, and uninitialized X - buffers. */ -const char *test_copy_sparsity_spd_alloc(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 2}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[1] = {3}; - int col_perm_1[2] = {1, 3}; - double X1[2] = {5.0, 6.0}; - matrix *blk1 = new_permuted_dense(4, 4, 1, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *src_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - stacked_pd *src = (stacked_pd *) src_m; - - matrix *out_m = copy_sparsity_spd_alloc(src); - stacked_pd *out = (stacked_pd *) out_m; - - mu_assert("m", out_m->m == 4); - mu_assert("n", out_m->n == 4); - mu_assert("nnz", out_m->nnz == src_m->nnz); - mu_assert("n_blocks", out->n_blocks == 2); - int expected_src_p[3] = {0, 1, 2}; - int expected_src[2] = {0, 1}; - mu_assert("src_block_idx_p", - cmp_int_array(out->src_block_idx_p, expected_src_p, 3)); - mu_assert("src_block_idx", cmp_int_array(out->src_block_idx, expected_src, 2)); - mu_assert("pre_coalesce NULL", out->pre_coalesce == NULL); - - permuted_dense *O0 = out->blocks[0]; - mu_assert("O0 m0", O0->m0 == 2); - mu_assert("O0 n0", O0->n0 == 2); - mu_assert("O0 row_perm", cmp_int_array(O0->row_perm, row_perm_0, 2)); - mu_assert("O0 col_perm", cmp_int_array(O0->col_perm, col_perm_0, 2)); - - permuted_dense *O1 = out->blocks[1]; - mu_assert("O1 m0", O1->m0 == 1); - mu_assert("O1 n0", O1->n0 == 2); - mu_assert("O1 row_perm", cmp_int_array(O1->row_perm, row_perm_1, 1)); - mu_assert("O1 col_perm", cmp_int_array(O1->col_perm, col_perm_1, 2)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* DA on a 2-block spd: per-row scaling by d[global_row]. */ -const char *test_DA_spd_two_blocks(void) -{ - /* block 0: rows {0, 1}, cols {0, 1}, X = [[1, 2], [3, 4]] - block 1: rows {2, 3}, cols {2, 3}, X = [[5, 6], [7, 8]] */ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - stacked_pd *A = (stacked_pd *) A_m; - - double d[4] = {10.0, 100.0, 1000.0, 10000.0}; - - matrix *C_m = copy_sparsity_spd_alloc(A); - DA_spd_fill_values(d, A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - /* C0 row 0 scaled by d[0]=10; row 1 by d[1]=100. */ - double C0_X_expected[4] = {10.0, 20.0, 300.0, 400.0}; - mu_assert("C0 X", cmp_double_array(C->blocks[0]->X, C0_X_expected, 4)); - - /* C1 row 0 (= global row 2) scaled by d[2]=1000; row 1 (= row 3) by - d[3]=10000. */ - double C1_X_expected[4] = {5000.0, 6000.0, 70000.0, 80000.0}; - mu_assert("C1 X", cmp_double_array(C->blocks[1]->X, C1_X_expected, 4)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* DA on an empty spd: no-op, no crash. */ -const char *test_DA_spd_empty(void) -{ - matrix *A_m = new_stacked_pd(4, 4, 0, NULL, NULL, NULL); - matrix *C_m = copy_sparsity_spd_alloc((stacked_pd *) A_m); - double d[4] = {1.0, 2.0, 3.0, 4.0}; - DA_spd_fill_values(d, (stacked_pd *) A_m, (stacked_pd *) C_m); - - mu_assert("n_blocks", ((stacked_pd *) C_m)->n_blocks == 0); - mu_assert("nnz", C_m->nnz == 0); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* DA on a single full-dense block. */ -const char *test_DA_spd_single_block_full(void) -{ - /* 2x3 dense, X = [[1, 2, 3], [4, 5, 6]] */ - double data[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; - matrix *blk = new_permuted_dense_full(2, 3, data); - - permuted_dense *blocks[1] = {(permuted_dense *) blk}; - matrix *A_m = new_stacked_pd(2, 3, 1, blocks, NULL, NULL); - - double d[2] = {10.0, 100.0}; - - matrix *C_m = copy_sparsity_spd_alloc((stacked_pd *) A_m); - DA_spd_fill_values(d, (stacked_pd *) A_m, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - /* row 0 scaled by 10, row 1 by 100. */ - double C0_X_expected[6] = {10.0, 20.0, 30.0, 400.0, 500.0, 600.0}; - mu_assert("C X", cmp_double_array(C->blocks[0]->X, C0_X_expected, 6)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* ATA_spd_alloc with disjoint source col_perms: no merging; one output - PD per source block. */ -const char *test_ATA_spd_alloc_disjoint_cols(void) -{ - /* block 0: rows {0,1}, cols {0,1}, X = [[1,2],[3,4]] - block 1: rows {2,3}, cols {2,3}, X = [[5,6],[7,8]] */ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - matrix *C_m = ATA_spd_alloc((stacked_pd *) A_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("m", C_m->m == 4); - mu_assert("n", C_m->n == 4); - mu_assert("n_blocks", C->n_blocks == 2); - int expected_src_p[3] = {0, 1, 2}; - int expected_src[2] = {0, 1}; - mu_assert("src_block_idx_p", - cmp_int_array(C->src_block_idx_p, expected_src_p, 3)); - mu_assert("src_block_idx", cmp_int_array(C->src_block_idx, expected_src, 2)); - - permuted_dense *C0 = C->blocks[0]; - mu_assert("C0 row_perm", cmp_int_array(C0->row_perm, col_perm_0, 2)); - mu_assert("C0 col_perm", cmp_int_array(C0->col_perm, col_perm_0, 2)); - - permuted_dense *C1 = C->blocks[1]; - mu_assert("C1 row_perm", cmp_int_array(C1->row_perm, col_perm_1, 2)); - mu_assert("C1 col_perm", cmp_int_array(C1->col_perm, col_perm_1, 2)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* ATA_spd_alloc with overlapping source col_perms: three sigs ({0}, - {0,1}, {1}) produce three output PDs ordered by min row. */ -const char *test_ATA_spd_alloc_overlapping_cols(void) -{ - /* block 0: rows {0,1}, cols {0,1}, X arbitrary - block 1: rows {2,3}, cols {1,2}, X arbitrary */ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {1, 2}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_m = new_stacked_pd(4, 3, 2, blocks, NULL, NULL); - - matrix *C_m = ATA_spd_alloc((stacked_pd *) A_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("m", C_m->m == 3); - mu_assert("n", C_m->n == 3); - mu_assert("n_blocks", C->n_blocks == 3); - - /* Ordering by min row: sig {0} (col 0) -> sig {0,1} (col 1) -> sig {1} (col 2). - */ - int expected_src_p[4] = {0, 1, 3, 4}; - int expected_src[4] = {0, 0, 1, 1}; - mu_assert("src_block_idx_p", - cmp_int_array(C->src_block_idx_p, expected_src_p, 4)); - mu_assert("src_block_idx", cmp_int_array(C->src_block_idx, expected_src, 4)); - - permuted_dense *C0 = C->blocks[0]; - int C0_row[1] = {0}; - int C0_col[2] = {0, 1}; - mu_assert("C0 row_perm", cmp_int_array(C0->row_perm, C0_row, 1)); - mu_assert("C0 col_perm", cmp_int_array(C0->col_perm, C0_col, 2)); - - permuted_dense *C1 = C->blocks[1]; - int C1_row[1] = {1}; - int C1_col[3] = {0, 1, 2}; - mu_assert("C1 row_perm", cmp_int_array(C1->row_perm, C1_row, 1)); - mu_assert("C1 col_perm", cmp_int_array(C1->col_perm, C1_col, 3)); - - permuted_dense *C2 = C->blocks[2]; - int C2_row[1] = {2}; - int C2_col[2] = {1, 2}; - mu_assert("C2 row_perm", cmp_int_array(C2->row_perm, C2_row, 1)); - mu_assert("C2 col_perm", cmp_int_array(C2->col_perm, C2_col, 2)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* ATDA on the disjoint-cols input: values per block match hand-computed - B_k^T diag(d[R_k]) B_k. */ -const char *test_ATDA_spd_disjoint_cols(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - double d[4] = {1.0, 2.0, 3.0, 4.0}; - - matrix *C_m = ATA_spd_alloc((stacked_pd *) A_m); - ATDA_spd_fill_values((stacked_pd *) A_m, d, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - /* B_0^T diag(1, 2) B_0 = [[19, 26], [26, 36]]. */ - double C0_X_expected[4] = {19.0, 26.0, 26.0, 36.0}; - mu_assert("C0 X", cmp_double_array(C->blocks[0]->X, C0_X_expected, 4)); - - /* B_1^T diag(3, 4) B_1 = [[271, 314], [314, 364]]. */ - double C1_X_expected[4] = {271.0, 314.0, 314.0, 364.0}; - mu_assert("C1 X", cmp_double_array(C->blocks[1]->X, C1_X_expected, 4)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* ATDA on overlapping cols: middle PD accumulates from both sources. */ -const char *test_ATDA_spd_overlapping_cols(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 3, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {1, 2}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 3, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_m = new_stacked_pd(4, 3, 2, blocks, NULL, NULL); - - double d[4] = {1.0, 2.0, 3.0, 4.0}; - - matrix *C_m = ATA_spd_alloc((stacked_pd *) A_m); - ATDA_spd_fill_values((stacked_pd *) A_m, d, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - /* C0: row {0}, cols {0, 1}, from B_0 only. - (A^T d A)[0,0] = 1*1 + 2*9 = 19; [0,1] = 1*2 + 2*12 = 26. */ - double C0_X_expected[2] = {19.0, 26.0}; - mu_assert("C0 X", cmp_double_array(C->blocks[0]->X, C0_X_expected, 2)); - - /* C1: row {1}, cols {0, 1, 2}, accumulated from B_0 and B_1. - [1,0] = B_0 contribution = 1*2 + 2*12 = 26. - [1,1] = 1*4 + 2*16 (B_0) + 3*25 + 4*49 (B_1) = 36 + 271 = 307. - [1,2] = B_1 contribution = 3*30 + 4*56 = 90 + 224 = 314. */ - double C1_X_expected[3] = {26.0, 307.0, 314.0}; - mu_assert("C1 X", cmp_double_array(C->blocks[1]->X, C1_X_expected, 3)); - - /* C2: row {2}, cols {1, 2}, from B_1 only. - [2,1] = 3*5*6 + 4*7*8 = 90 + 224 = 314; [2,2] = 3*36 + 4*64 = 364. */ - double C2_X_expected[2] = {314.0, 364.0}; - mu_assert("C2 X", cmp_double_array(C->blocks[2]->X, C2_X_expected, 2)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* Two-phase: alloc once, mutate A values, refill. */ -const char *test_ATDA_spd_alloc_then_fill_values(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - stacked_pd *A = (stacked_pd *) A_m; - - double d[4] = {1.0, 2.0, 3.0, 4.0}; - - matrix *C_m = ATA_spd_alloc(A); - ATDA_spd_fill_values(A, d, (stacked_pd *) C_m); - - /* Mutate A's block 0 X to [2, 0, 0, 2] (= 2 * identity at rows {0,1}, - cols {0,1}). Then A^T d A on block 0 = diag(d[0], d[1]) * 4 = - [[4, 0], [0, 8]]. */ - A->blocks[0]->X[0] = 2.0; - A->blocks[0]->X[1] = 0.0; - A->blocks[0]->X[2] = 0.0; - A->blocks[0]->X[3] = 2.0; - - ATDA_spd_fill_values(A, d, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - double C0_X_expected[4] = {4.0, 0.0, 0.0, 8.0}; - mu_assert("C0 X refilled", cmp_double_array(C->blocks[0]->X, C0_X_expected, 4)); - - /* Block 1 wasn't mutated; matches the disjoint-cols expected value. */ - double C1_X_expected[4] = {271.0, 314.0, 314.0, 364.0}; - mu_assert("C1 X unchanged", cmp_double_array(C->blocks[1]->X, C1_X_expected, 4)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* Empty source: output also empty; free safely. */ -const char *test_ATDA_spd_empty(void) -{ - matrix *A_m = new_stacked_pd(4, 4, 0, NULL, NULL, NULL); - matrix *C_m = ATA_spd_alloc((stacked_pd *) A_m); - double d[4] = {1.0, 2.0, 3.0, 4.0}; - ATDA_spd_fill_values((stacked_pd *) A_m, d, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("n_blocks", C->n_blocks == 0); - mu_assert("nnz", C_m->nnz == 0); - mu_assert("pre_coalesce not NULL", C->pre_coalesce != NULL); - mu_assert("pre_coalesce n_blocks", C->pre_coalesce->n_blocks == 0); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* BA_pd_spd worked example: two contributing A-blocks. */ -const char *test_BA_pd_spd_two_blocks_disjoint_cols(void) -{ - /* B: 3x4 PD. row_perm = {0,1,2}, col_perm = {0,1,3}, - X = [[1,2,3],[4,5,6],[7,8,9]]. */ - int B_rp[3] = {0, 1, 2}; - int B_cp[3] = {0, 1, 3}; - double BX[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - matrix *B = new_permuted_dense(3, 4, 3, 3, B_rp, B_cp, BX); - - /* A: 4x6 spd, two blocks. - A_0: rows {0,1}, cols {0,4}, X = [[10,11],[12,13]]. - A_1: rows {2,3}, cols {1,4}, X = [[20,21],[22,23]]. */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 4}; - double A0X[4] = {10, 11, 12, 13}; - matrix *A0 = new_permuted_dense(4, 6, 2, 2, A0_rp, A0_cp, A0X); - - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {1, 4}; - double A1X[4] = {20, 21, 22, 23}; - matrix *A1 = new_permuted_dense(4, 6, 2, 2, A1_rp, A1_cp, A1X); - - permuted_dense *blocks[2] = {(permuted_dense *) A0, (permuted_dense *) A1}; - matrix *A = new_stacked_pd(4, 6, 2, blocks, NULL, NULL); - - matrix *C_m = BA_pd_spd_alloc((permuted_dense *) B, (stacked_pd *) A); - BA_pd_spd_fill_values((permuted_dense *) B, (stacked_pd *) A, - (permuted_dense *) C_m); - permuted_dense *C = (permuted_dense *) C_m; - - /* C is 3x6 with row_perm {0,1,2}, col_perm {0,1,4}. */ - mu_assert("C m", C_m->m == 3); - mu_assert("C n", C_m->n == 6); - mu_assert("C m0", C->m0 == 3); - mu_assert("C n0", C->n0 == 3); - int rp_exp[3] = {0, 1, 2}; - int cp_exp[3] = {0, 1, 4}; - mu_assert("C row_perm", cmp_int_array(C->row_perm, rp_exp, 3)); - mu_assert("C col_perm", cmp_int_array(C->col_perm, cp_exp, 3)); - - /* C[0,0]= 1*10+2*12 = 34; C[0,1]= 3*22 = 66; - C[0,4]= 1*11+2*13+3*23 = 106; - C[1,0]= 4*10+5*12 = 100; C[1,1]= 6*22 = 132; - C[1,4]= 4*11+5*13+6*23 = 247; - C[2,0]= 7*10+8*12 = 166; C[2,1]= 9*22 = 198; - C[2,4]= 7*11+8*13+9*23 = 388. */ - double CX_exp[9] = {34, 66, 106, 100, 132, 247, 166, 198, 388}; - mu_assert("C X", cmp_double_array(C->X, CX_exp, 9)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -/* Only one A-block intersects B's col_perm. */ -const char *test_BA_pd_spd_only_one_block_contributes(void) -{ - /* B has col_perm {0,1}; A_0 rows {0,1} hits, A_1 rows {3,4} misses. */ - int B_rp[2] = {0, 1}; - int B_cp[2] = {0, 1}; - double BX[4] = {1, 2, 3, 4}; - matrix *B = new_permuted_dense(2, 5, 2, 2, B_rp, B_cp, BX); - - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 2}; - double A0X[4] = {5, 6, 7, 8}; - matrix *A0 = new_permuted_dense(5, 3, 2, 2, A0_rp, A0_cp, A0X); - - int A1_rp[2] = {3, 4}; - int A1_cp[1] = {1}; - double A1X[2] = {99, 100}; - matrix *A1 = new_permuted_dense(5, 3, 2, 1, A1_rp, A1_cp, A1X); - - permuted_dense *blocks[2] = {(permuted_dense *) A0, (permuted_dense *) A1}; - matrix *A = new_stacked_pd(5, 3, 2, blocks, NULL, NULL); - - matrix *C_m = BA_pd_spd_alloc((permuted_dense *) B, (stacked_pd *) A); - BA_pd_spd_fill_values((permuted_dense *) B, (stacked_pd *) A, - (permuted_dense *) C_m); - permuted_dense *C = (permuted_dense *) C_m; - - /* Only A_0 contributes -> col_perm = {0, 2}. - C[0,0]= 1*5+2*7 = 19; C[0,2]= 1*6+2*8 = 22; - C[1,0]= 3*5+4*7 = 43; C[1,2]= 3*6+4*8 = 50. */ - int cp_exp[2] = {0, 2}; - double CX_exp[4] = {19, 22, 43, 50}; - mu_assert("C n0", C->n0 == 2); - mu_assert("C col_perm", cmp_int_array(C->col_perm, cp_exp, 2)); - mu_assert("C X", cmp_double_array(C->X, CX_exp, 4)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -/* B's col_perm disjoint from every A-block's row_perm: empty output. */ -const char *test_BA_pd_spd_no_blocks_contribute(void) -{ - int B_rp[2] = {0, 1}; - int B_cp[1] = {0}; - double BX[2] = {1, 2}; - matrix *B = new_permuted_dense(2, 4, 2, 1, B_rp, B_cp, BX); - - int A0_rp[1] = {1}; - int A0_cp[1] = {0}; - double A0X[1] = {7}; - matrix *A0 = new_permuted_dense(4, 4, 1, 1, A0_rp, A0_cp, A0X); - - int A1_rp[1] = {3}; - int A1_cp[1] = {1}; - double A1X[1] = {9}; - matrix *A1 = new_permuted_dense(4, 4, 1, 1, A1_rp, A1_cp, A1X); - - permuted_dense *blocks[2] = {(permuted_dense *) A0, (permuted_dense *) A1}; - matrix *A = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - matrix *C_m = BA_pd_spd_alloc((permuted_dense *) B, (stacked_pd *) A); - BA_pd_spd_fill_values((permuted_dense *) B, (stacked_pd *) A, - (permuted_dense *) C_m); - permuted_dense *C = (permuted_dense *) C_m; - - mu_assert("C n0", C->n0 == 0); - mu_assert("C nnz", C_m->nnz == 0); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -/* Empty A (n_blocks = 0): output has n0 = 0. */ -const char *test_BA_pd_spd_empty_A(void) -{ - int B_rp[1] = {0}; - int B_cp[1] = {0}; - double BX[1] = {5}; - matrix *B = new_permuted_dense(1, 3, 1, 1, B_rp, B_cp, BX); - - matrix *A = new_stacked_pd(3, 4, 0, NULL, NULL, NULL); - - matrix *C_m = BA_pd_spd_alloc((permuted_dense *) B, (stacked_pd *) A); - BA_pd_spd_fill_values((permuted_dense *) B, (stacked_pd *) A, - (permuted_dense *) C_m); - - mu_assert("C n0", ((permuted_dense *) C_m)->n0 == 0); - mu_assert("C nnz", C_m->nnz == 0); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -/* Both A-blocks contribute AND have overlapping col_perms: cols accumulate. */ -const char *test_BA_pd_spd_overlapping_col_perms(void) -{ - /* B: 2x3, row_perm {0,1}, col_perm {0,1,2}, X = [[1,2,3],[4,5,6]]. */ - int B_rp[2] = {0, 1}; - int B_cp[3] = {0, 1, 2}; - double BX[6] = {1, 2, 3, 4, 5, 6}; - matrix *B = new_permuted_dense(2, 3, 2, 3, B_rp, B_cp, BX); - - /* A_0: rows {0,1}, cols {0,1}, X = [[10,11],[12,13]]. - A_1: rows {2}, cols {1,2}, X = [20,21]. col 1 overlaps A_0. */ - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 1}; - double A0X[4] = {10, 11, 12, 13}; - matrix *A0 = new_permuted_dense(3, 3, 2, 2, A0_rp, A0_cp, A0X); - - int A1_rp[1] = {2}; - int A1_cp[2] = {1, 2}; - double A1X[2] = {20, 21}; - matrix *A1 = new_permuted_dense(3, 3, 1, 2, A1_rp, A1_cp, A1X); - - permuted_dense *blocks[2] = {(permuted_dense *) A0, (permuted_dense *) A1}; - matrix *A = new_stacked_pd(3, 3, 2, blocks, NULL, NULL); - - matrix *C_m = BA_pd_spd_alloc((permuted_dense *) B, (stacked_pd *) A); - BA_pd_spd_fill_values((permuted_dense *) B, (stacked_pd *) A, - (permuted_dense *) C_m); - permuted_dense *C = (permuted_dense *) C_m; - - /* Output col_perm = {0, 1, 2}. - Global A entries: - A[0,0]=10, A[0,1]=11, A[1,0]=12, A[1,1]=13, A[2,1]=20, A[2,2]=21. - C[0,0]= 1*10+2*12 = 34; - C[0,1]= 1*11+2*13+3*20 = 11+26+60 = 97; - C[0,2]= 3*21 = 63; - C[1,0]= 4*10+5*12 = 100; - C[1,1]= 4*11+5*13+6*20 = 44+65+120 = 229; - C[1,2]= 6*21 = 126. */ - int cp_exp[3] = {0, 1, 2}; - double CX_exp[6] = {34, 97, 63, 100, 229, 126}; - mu_assert("C n0", C->n0 == 3); - mu_assert("C col_perm", cmp_int_array(C->col_perm, cp_exp, 3)); - mu_assert("C X", cmp_double_array(C->X, CX_exp, 6)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -/* Two-phase: alloc once, mutate inputs, refill, verify. */ -const char *test_BA_pd_spd_alloc_then_fill_values(void) -{ - int B_rp[2] = {0, 1}; - int B_cp[2] = {0, 1}; - double BX[4] = {1, 2, 3, 4}; - matrix *B = new_permuted_dense(2, 3, 2, 2, B_rp, B_cp, BX); - - int A0_rp[2] = {0, 1}; - int A0_cp[1] = {0}; - double A0X[2] = {10, 20}; - matrix *A0 = new_permuted_dense(3, 2, 2, 1, A0_rp, A0_cp, A0X); - - permuted_dense *blocks[1] = {(permuted_dense *) A0}; - matrix *A = new_stacked_pd(3, 2, 1, blocks, NULL, NULL); - - matrix *C_m = BA_pd_spd_alloc((permuted_dense *) B, (stacked_pd *) A); - BA_pd_spd_fill_values((permuted_dense *) B, (stacked_pd *) A, - (permuted_dense *) C_m); - - /* Mutate B and A_0 values. B is an owner pd, so the write must be - announced (matrix.h contract) — BA_pd_spd_fill_values refreshes its - cached transpose of B iff B's values_version moved. */ - permuted_dense *B_pd = (permuted_dense *) B; - B_pd->X[0] = 5; - B_pd->X[1] = 6; - B_pd->X[2] = 7; - B_pd->X[3] = 8; - matrix_values_changed(B); - permuted_dense *A0_pd = (permuted_dense *) A0; - A0_pd->X[0] = 100; - A0_pd->X[1] = 200; - - BA_pd_spd_fill_values((permuted_dense *) B, (stacked_pd *) A, - (permuted_dense *) C_m); - permuted_dense *C = (permuted_dense *) C_m; - - /* C col_perm = {0}. - C[0,0]= 5*100+6*200 = 1700; - C[1,0]= 7*100+8*200 = 2300. */ - double CX_exp[2] = {1700, 2300}; - mu_assert("C X refilled", cmp_double_array(C->X, CX_exp, 2)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -/* Vtable: copy_sparsity through M->copy_sparsity(M). */ -const char *test_spd_vtable_copy_sparsity(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 2}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[1] = {3}; - int col_perm_1[2] = {1, 3}; - double X1[2] = {5.0, 6.0}; - matrix *blk1 = new_permuted_dense(4, 4, 1, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *M = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - matrix *out_m = M->copy_sparsity(M); - stacked_pd *out = (stacked_pd *) out_m; - - mu_assert("n_blocks", out->n_blocks == 2); - mu_assert("O0 row_perm", cmp_int_array(out->blocks[0]->row_perm, row_perm_0, 2)); - mu_assert("O0 col_perm", cmp_int_array(out->blocks[0]->col_perm, col_perm_0, 2)); - mu_assert("O1 row_perm", cmp_int_array(out->blocks[1]->row_perm, row_perm_1, 1)); - mu_assert("O1 col_perm", cmp_int_array(out->blocks[1]->col_perm, col_perm_1, 2)); - - free_matrix(out_m); - free_matrix(M); - return 0; -} - -/* Vtable: DA_fill_values through A->DA_fill_values(d, A, C). */ -const char *test_spd_vtable_DA_fill_values(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - double d[4] = {10.0, 100.0, 1000.0, 10000.0}; - - matrix *C_m = A_m->copy_sparsity(A_m); - A_m->DA_fill_values(d, A_m, C_m); - stacked_pd *C = (stacked_pd *) C_m; - - double C0_X_exp[4] = {10.0, 20.0, 300.0, 400.0}; - mu_assert("C0 X", cmp_double_array(C->blocks[0]->X, C0_X_exp, 4)); - - double C1_X_exp[4] = {5000.0, 6000.0, 70000.0, 80000.0}; - mu_assert("C1 X", cmp_double_array(C->blocks[1]->X, C1_X_exp, 4)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* Vtable: ATA_alloc through A->ATA_alloc(A). */ -const char *test_spd_vtable_ATA_alloc(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - matrix *C_m = A_m->ATA_alloc(A_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("n_blocks", C->n_blocks == 2); - mu_assert("C0 row_perm", cmp_int_array(C->blocks[0]->row_perm, col_perm_0, 2)); - mu_assert("C0 col_perm", cmp_int_array(C->blocks[0]->col_perm, col_perm_0, 2)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* Vtable: ATDA_fill_values through A->ATDA_fill_values(A, d, C). */ -const char *test_spd_vtable_ATDA_fill_values(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *A_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - double d[4] = {1.0, 2.0, 3.0, 4.0}; - - matrix *C_m = A_m->ATA_alloc(A_m); - A_m->ATDA_fill_values(A_m, d, C_m); - stacked_pd *C = (stacked_pd *) C_m; - - /* Same expected values as test_ATDA_spd_disjoint_cols. */ - double C0_X_exp[4] = {19.0, 26.0, 26.0, 36.0}; - mu_assert("C0 X", cmp_double_array(C->blocks[0]->X, C0_X_exp, 4)); - - double C1_X_exp[4] = {271.0, 314.0, 314.0, 364.0}; - mu_assert("C1 X", cmp_double_array(C->blocks[1]->X, C1_X_exp, 4)); - - free_matrix(C_m); - free_matrix(A_m); - return 0; -} - -/* Vtable: transpose_alloc + transpose_fill_values. */ -const char *test_spd_vtable_transpose(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *src_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - matrix *out_m = src_m->transpose_alloc(src_m); - src_m->transpose_fill_values(src_m, out_m); - stacked_pd *out = (stacked_pd *) out_m; - - /* Same expected values as test_transpose_spd_no_overlap. */ - mu_assert("n_blocks", out->n_blocks == 2); - double O0_X_exp[4] = {1.0, 3.0, 2.0, 4.0}; - mu_assert("O0 X", cmp_double_array(out->blocks[0]->X, O0_X_exp, 4)); - double O1_X_exp[4] = {5.0, 7.0, 6.0, 8.0}; - mu_assert("O1 X", cmp_double_array(out->blocks[1]->X, O1_X_exp, 4)); - - free_matrix(out_m); - free_matrix(src_m); - return 0; -} - -/* Vtable: refresh_csc_values is a no-op; verify no crash. */ -const char *test_spd_vtable_refresh_csc_values_noop(void) -{ - int row_perm[1] = {0}; - int col_perm[1] = {0}; - double X[1] = {1.0}; - matrix *blk = new_permuted_dense(1, 1, 1, 1, row_perm, col_perm, X); - - permuted_dense *blocks[1] = {(permuted_dense *) blk}; - matrix *M = new_stacked_pd(1, 1, 1, blocks, NULL, NULL); - - M->refresh_csc_values(M); - - free_matrix(M); - return 0; -} - -/* row_gather on spd: each output row is routed to the source block (if any) - that carries its source row; per-block row_gather_pd_* is reused; empty - result blocks are dropped; repeats are allowed. */ -const char *test_spd_vtable_row_gather(void) -{ - /* 6x4 spd: - block 0: rows {0,1}, cols {0,2}, X = [[1,2],[3,4]] - block 1: rows {2,3}, cols {1,3}, X = [[5,6],[7,8]] */ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 2}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(6, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {2, 3}; - int col_perm_1[2] = {1, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(6, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *M = new_stacked_pd(6, 4, 2, blocks, NULL, NULL); - - /* map = [3, 0, 5, 1, 3]: row 3 -> blk1 (twice, positions 0 and 4), - rows 0/1 -> blk0 (positions 1 and 3), row 5 -> none. - Expected output (5 rows, position 2 empty): - output row 0 = src row 3 = blk1 row [7, 8] in cols {1,3} - output row 1 = src row 0 = blk0 row [1, 2] in cols {0,2} - output row 3 = src row 1 = blk0 row [3, 4] in cols {0,2} - output row 4 = src row 3 = blk1 row [7, 8] in cols {1,3} */ - int map[5] = {3, 0, 5, 1, 3}; - matrix *C_m = M->row_gather_alloc(M, map, 5); - M->row_gather_fill_values(M, C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("n_blocks", C->n_blocks == 2); - mu_assert("base.m", C_m->m == 5); - mu_assert("base.n", C_m->n == 4); - - /* blk0 is hit at positions 1 and 3: output block 0 carries - row_perm={1,3}, col_perm={0,2}, X=[1,2,3,4]. */ - permuted_dense *out0 = C->blocks[0]; - int expected_row_perm_0[2] = {1, 3}; - int expected_col_perm_0[2] = {0, 2}; - double expected_X0[4] = {1.0, 2.0, 3.0, 4.0}; - mu_assert("out0 m0", out0->m0 == 2); - mu_assert("out0 n0", out0->n0 == 2); - mu_assert("out0 row_perm", - cmp_int_array(out0->row_perm, expected_row_perm_0, 2)); - mu_assert("out0 col_perm", - cmp_int_array(out0->col_perm, expected_col_perm_0, 2)); - mu_assert("out0 X", cmp_double_array(out0->X, expected_X0, 4)); - - /* blk1 is hit at positions 0 and 4, both from its row 1: output block 1 - carries row_perm={0,4}, col_perm={1,3}, X=[7,8,7,8]. */ - permuted_dense *out1 = C->blocks[1]; - int expected_row_perm_1[2] = {0, 4}; - int expected_col_perm_1[2] = {1, 3}; - double expected_X1[4] = {7.0, 8.0, 7.0, 8.0}; - mu_assert("out1 m0", out1->m0 == 2); - mu_assert("out1 n0", out1->n0 == 2); - mu_assert("out1 row_perm", - cmp_int_array(out1->row_perm, expected_row_perm_1, 2)); - mu_assert("out1 col_perm", - cmp_int_array(out1->col_perm, expected_col_perm_1, 2)); - mu_assert("out1 X", cmp_double_array(out1->X, expected_X1, 4)); - - /* src_block_idx: both source blocks survived, identity mapping. */ - mu_assert("src_block_idx[0]", C->src_block_idx[0] == 0); - mu_assert("src_block_idx[1]", C->src_block_idx[1] == 1); - - free_matrix(C_m); - free_matrix(M); - return 0; -} - -/* diag_vec_* on spd: per-block row_perm entries are rescaled r -> r*(n+1); - X buffers are unchanged; structure is preserved (same n_blocks). */ -const char *test_spd_vtable_diag_vec(void) -{ - /* 4x4 spd. Input represents the Jacobian of a length-4 vector w.r.t. - 4 variables. Output represents the Jacobian of diag(v) (16x4) with - only diagonal rows {0, 5, 10, 15} populated. - Block 0: rows {0, 2}, cols {0, 1}, X = [[1, 2], [3, 4]] - Block 1: rows {1, 3}, cols {2, 3}, X = [[5, 6], [7, 8]] */ - int row_perm_0[2] = {0, 2}; - int col_perm_0[2] = {0, 1}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[2] = {1, 3}; - int col_perm_1[2] = {2, 3}; - double X1[4] = {5.0, 6.0, 7.0, 8.0}; - matrix *blk1 = new_permuted_dense(4, 4, 2, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *M = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - matrix *C_m = M->diag_vec_alloc(M); - M->diag_vec_fill_values(M, C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("n_blocks", C->n_blocks == 2); - mu_assert("base.m", C_m->m == 16); - mu_assert("base.n", C_m->n == 4); - - /* Block 0: row_perm = {0*5, 2*5} = {0, 10}; col_perm and X unchanged. */ - permuted_dense *out0 = C->blocks[0]; - int expected_row_perm_0[2] = {0, 10}; - mu_assert("out0 m0", out0->m0 == 2); - mu_assert("out0 n0", out0->n0 == 2); - mu_assert("out0 row_perm", - cmp_int_array(out0->row_perm, expected_row_perm_0, 2)); - mu_assert("out0 col_perm", cmp_int_array(out0->col_perm, col_perm_0, 2)); - mu_assert("out0 X", cmp_double_array(out0->X, X0, 4)); - - /* Block 1: row_perm = {1*5, 3*5} = {5, 15}; col_perm and X unchanged. */ - permuted_dense *out1 = C->blocks[1]; - int expected_row_perm_1[2] = {5, 15}; - mu_assert("out1 m0", out1->m0 == 2); - mu_assert("out1 n0", out1->n0 == 2); - mu_assert("out1 row_perm", - cmp_int_array(out1->row_perm, expected_row_perm_1, 2)); - mu_assert("out1 col_perm", cmp_int_array(out1->col_perm, col_perm_1, 2)); - mu_assert("out1 X", cmp_double_array(out1->X, X1, 4)); - - free_matrix(C_m); - free_matrix(M); - return 0; -} - -/* ---------------------------------------------------------------- */ -/* BA_spd_* primitive tests: C = B @ A where B is stacked_pd and A is */ -/* CSC / PD / spd. Kept as siblings of the BTA_spd_* family in the */ -/* main code (see stacked_pd_linalg.h). Not exercised by any */ -/* production atom today; kept for API symmetry and future use. */ -/* ---------------------------------------------------------------- */ - -/* B is 4x4 spd with two blocks; A is 4x3 CSC chosen so every block of B - produces a nonempty result. Result spd should keep both source blocks. */ -const char *test_BA_spd_csc_two_blocks_both_kept(void) -{ - /* block 0: rows {0, 1}, cols {0, 2}, X = [[1, 2], [3, 4]] - block 1: rows {3}, cols {1, 3}, X = [5, 6] */ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 2}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[1] = {3}; - int col_perm_1[2] = {1, 3}; - double X1[2] = {5.0, 6.0}; - matrix *blk1 = new_permuted_dense(4, 4, 1, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *B_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - /* A: 4x3 CSC. - col 0: (row 0, 1), (row 2, 2) - col 1: (row 1, 3), (row 3, 4) - col 2: (row 2, 5) - For block 0 (col_perm {0, 2}): cols 0 and 2 of A hit -> C0 cols {0, 2}. - For block 1 (col_perm {1, 3}): only col 1 of A hits -> C1 cols {1}. */ - CSC_matrix *A = new_CSC_matrix(4, 3, 5); - int Ap[4] = {0, 2, 4, 5}; - int Ai[5] = {0, 2, 1, 3, 2}; - double Ax[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; - memcpy(A->p, Ap, sizeof(Ap)); - memcpy(A->i, Ai, sizeof(Ai)); - memcpy(A->x, Ax, sizeof(Ax)); - - matrix *C_m = BA_spd_csc_alloc((stacked_pd *) B_m, A); - BA_spd_csc_fill_values((stacked_pd *) B_m, A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("C m", C_m->m == 4); - mu_assert("C n", C_m->n == 3); - mu_assert("n_blocks", C->n_blocks == 2); - int expected_src_p[3] = {0, 1, 2}; - int expected_src[2] = {0, 1}; - mu_assert("src_block_idx_p", - cmp_int_array(C->src_block_idx_p, expected_src_p, 3)); - mu_assert("src_block_idx", cmp_int_array(C->src_block_idx, expected_src, 2)); - - permuted_dense *C0 = C->blocks[0]; - int C0_col_expected[2] = {0, 2}; - double C0_X_expected[4] = {5.0, 10.0, 11.0, 20.0}; - mu_assert("C0 m0", C0->m0 == 2); - mu_assert("C0 n0", C0->n0 == 2); - mu_assert("C0 row_perm", cmp_int_array(C0->row_perm, row_perm_0, 2)); - mu_assert("C0 col_perm", cmp_int_array(C0->col_perm, C0_col_expected, 2)); - mu_assert("C0 X", cmp_double_array(C0->X, C0_X_expected, 4)); - - permuted_dense *C1 = C->blocks[1]; - int C1_col_expected[1] = {1}; - double C1_X_expected[1] = {39.0}; - mu_assert("C1 m0", C1->m0 == 1); - mu_assert("C1 n0", C1->n0 == 1); - mu_assert("C1 row_perm", cmp_int_array(C1->row_perm, row_perm_1, 1)); - mu_assert("C1 col_perm", cmp_int_array(C1->col_perm, C1_col_expected, 1)); - mu_assert("C1 X", cmp_double_array(C1->X, C1_X_expected, 1)); - - free_matrix(C_m); - free_matrix(B_m); - free_CSC_matrix(A); - return 0; -} - -const char *test_BA_spd_csc_one_block_dropped(void) -{ - int row_perm_0[2] = {0, 1}; - int col_perm_0[2] = {0, 2}; - double X0[4] = {1.0, 2.0, 3.0, 4.0}; - matrix *blk0 = new_permuted_dense(4, 4, 2, 2, row_perm_0, col_perm_0, X0); - - int row_perm_1[1] = {3}; - int col_perm_1[2] = {1, 3}; - double X1[2] = {5.0, 6.0}; - matrix *blk1 = new_permuted_dense(4, 4, 1, 2, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *B_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - CSC_matrix *A = new_CSC_matrix(4, 2, 4); - int Ap[3] = {0, 2, 4}; - int Ai[4] = {0, 2, 0, 2}; - double Ax[4] = {1.0, 2.0, 3.0, 4.0}; - memcpy(A->p, Ap, sizeof(Ap)); - memcpy(A->i, Ai, sizeof(Ai)); - memcpy(A->x, Ax, sizeof(Ax)); - - matrix *C_m = BA_spd_csc_alloc((stacked_pd *) B_m, A); - BA_spd_csc_fill_values((stacked_pd *) B_m, A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("n_blocks", C->n_blocks == 1); - int expected_src_p[2] = {0, 1}; - int expected_src[1] = {0}; - mu_assert("src_block_idx_p", - cmp_int_array(C->src_block_idx_p, expected_src_p, 2)); - mu_assert("src_block_idx", cmp_int_array(C->src_block_idx, expected_src, 1)); - - permuted_dense *C0 = C->blocks[0]; - int C0_col_expected[2] = {0, 1}; - double C0_X_expected[4] = {5.0, 11.0, 11.0, 25.0}; - mu_assert("C0 col_perm", cmp_int_array(C0->col_perm, C0_col_expected, 2)); - mu_assert("C0 X", cmp_double_array(C0->X, C0_X_expected, 4)); - - free_matrix(C_m); - free_matrix(B_m); - free_CSC_matrix(A); - return 0; -} - -const char *test_BA_spd_csc_all_blocks_dropped(void) -{ - int row_perm_0[1] = {0}; - int col_perm_0[1] = {0}; - double X0[1] = {1.0}; - matrix *blk0 = new_permuted_dense(4, 4, 1, 1, row_perm_0, col_perm_0, X0); - - int row_perm_1[1] = {2}; - int col_perm_1[1] = {2}; - double X1[1] = {3.0}; - matrix *blk1 = new_permuted_dense(4, 4, 1, 1, row_perm_1, col_perm_1, X1); - - permuted_dense *blocks[2] = {(permuted_dense *) blk0, (permuted_dense *) blk1}; - matrix *B_m = new_stacked_pd(4, 4, 2, blocks, NULL, NULL); - - CSC_matrix *A = new_CSC_matrix(4, 1, 1); - A->p[0] = 0; - A->p[1] = 1; - A->i[0] = 1; - A->x[0] = 7.0; - - matrix *C_m = BA_spd_csc_alloc((stacked_pd *) B_m, A); - BA_spd_csc_fill_values((stacked_pd *) B_m, A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("C m", C_m->m == 4); - mu_assert("C n", C_m->n == 1); - mu_assert("n_blocks", C->n_blocks == 0); - mu_assert("nnz", C_m->nnz == 0); - - free_matrix(C_m); - free_matrix(B_m); - free_CSC_matrix(A); - return 0; -} - -const char *test_BA_spd_pd_two_blocks_both_kept(void) -{ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {7, 8, 9, 10}; - matrix *B0 = new_permuted_dense(4, 3, 2, 2, B0_rp, B0_cp, B0X); - - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {1, 2}; - double B1X[4] = {11, 12, 13, 14}; - matrix *B1 = new_permuted_dense(4, 3, 2, 2, B1_rp, B1_cp, B1X); - - permuted_dense *blocks[2] = {(permuted_dense *) B0, (permuted_dense *) B1}; - matrix *B = new_stacked_pd(4, 3, 2, blocks, NULL, NULL); - - int A_rp[3] = {0, 1, 2}; - int A_cp[2] = {0, 2}; - double AX[6] = {1, 2, 3, 4, 5, 6}; - matrix *A = new_permuted_dense(3, 4, 3, 2, A_rp, A_cp, AX); - - matrix *C_m = BA_spd_pd_alloc((stacked_pd *) B, (permuted_dense *) A); - BA_spd_pd_fill_values((stacked_pd *) B, (permuted_dense *) A, - (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("C m", C_m->m == 4); - mu_assert("C n", C_m->n == 4); - mu_assert("C n_blocks", C->n_blocks == 2); - - permuted_dense *C0 = C->blocks[0]; - int C0_rp_exp[2] = {0, 1}; - int C0_cp_exp[2] = {0, 2}; - double C0_X_exp[4] = {47, 62, 59, 78}; - mu_assert("C0 m0", C0->m0 == 2); - mu_assert("C0 n0", C0->n0 == 2); - mu_assert("C0 row_perm", cmp_int_array(C0->row_perm, C0_rp_exp, 2)); - mu_assert("C0 col_perm", cmp_int_array(C0->col_perm, C0_cp_exp, 2)); - mu_assert("C0 X", cmp_double_array(C0->X, C0_X_exp, 4)); - - permuted_dense *C1 = C->blocks[1]; - int C1_rp_exp[2] = {2, 3}; - int C1_cp_exp[2] = {0, 2}; - double C1_X_exp[4] = {93, 116, 109, 136}; - mu_assert("C1 m0", C1->m0 == 2); - mu_assert("C1 n0", C1->n0 == 2); - mu_assert("C1 row_perm", cmp_int_array(C1->row_perm, C1_rp_exp, 2)); - mu_assert("C1 col_perm", cmp_int_array(C1->col_perm, C1_cp_exp, 2)); - mu_assert("C1 X", cmp_double_array(C1->X, C1_X_exp, 4)); - - int src_exp[2] = {0, 1}; - mu_assert("src_block_idx", cmp_int_array(C->src_block_idx, src_exp, 2)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -const char *test_BA_spd_pd_one_block_dropped(void) -{ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 2}; - double B0X[4] = {7, 8, 9, 10}; - matrix *B0 = new_permuted_dense(4, 6, 2, 2, B0_rp, B0_cp, B0X); - - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {4, 5}; - double B1X[4] = {1, 1, 1, 1}; - matrix *B1 = new_permuted_dense(4, 6, 2, 2, B1_rp, B1_cp, B1X); - - permuted_dense *blocks[2] = {(permuted_dense *) B0, (permuted_dense *) B1}; - matrix *B = new_stacked_pd(4, 6, 2, blocks, NULL, NULL); - - int A_rp[2] = {0, 2}; - int A_cp[2] = {0, 2}; - double AX[4] = {1, 2, 5, 6}; - matrix *A = new_permuted_dense(6, 4, 2, 2, A_rp, A_cp, AX); - - matrix *C_m = BA_spd_pd_alloc((stacked_pd *) B, (permuted_dense *) A); - BA_spd_pd_fill_values((stacked_pd *) B, (permuted_dense *) A, - (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("C n_blocks", C->n_blocks == 1); - mu_assert("C m", C_m->m == 4); - mu_assert("C n", C_m->n == 4); - int src_exp[1] = {0}; - mu_assert("src_block_idx", cmp_int_array(C->src_block_idx, src_exp, 1)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -const char *test_BA_spd_spd_two_blocks_both_kept(void) -{ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 1}; - double B0X[4] = {1, 2, 3, 4}; - matrix *B0 = new_permuted_dense(4, 6, 2, 2, B0_rp, B0_cp, B0X); - - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {3, 4}; - double B1X[4] = {5, 6, 7, 8}; - matrix *B1 = new_permuted_dense(4, 6, 2, 2, B1_rp, B1_cp, B1X); - - permuted_dense *B_blocks[2] = {(permuted_dense *) B0, (permuted_dense *) B1}; - matrix *B = new_stacked_pd(4, 6, 2, B_blocks, NULL, NULL); - - int A0_rp[3] = {0, 1, 2}; - int A0_cp[2] = {0, 1}; - double A0X[6] = {10, 11, 12, 13, 14, 15}; - matrix *A0 = new_permuted_dense(6, 4, 3, 2, A0_rp, A0_cp, A0X); - - int A1_rp[3] = {3, 4, 5}; - int A1_cp[2] = {2, 3}; - double A1X[6] = {20, 21, 22, 23, 24, 25}; - matrix *A1 = new_permuted_dense(6, 4, 3, 2, A1_rp, A1_cp, A1X); - - permuted_dense *A_blocks[2] = {(permuted_dense *) A0, (permuted_dense *) A1}; - matrix *A = new_stacked_pd(6, 4, 2, A_blocks, NULL, NULL); - - matrix *C_m = BA_spd_spd_alloc((stacked_pd *) B, (stacked_pd *) A); - BA_spd_spd_fill_values((stacked_pd *) B, (stacked_pd *) A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("C m", C_m->m == 4); - mu_assert("C n", C_m->n == 4); - mu_assert("n_blocks", C->n_blocks == 2); - int sp_exp[3] = {0, 1, 2}; - int s_exp[2] = {0, 1}; - mu_assert("src_block_idx_p", cmp_int_array(C->src_block_idx_p, sp_exp, 3)); - mu_assert("src_block_idx", cmp_int_array(C->src_block_idx, s_exp, 2)); - - permuted_dense *C0 = C->blocks[0]; - int C0_cp_exp[2] = {0, 1}; - double C0_X_exp[4] = {34, 37, 78, 85}; - mu_assert("C0 row_perm", cmp_int_array(C0->row_perm, B0_rp, 2)); - mu_assert("C0 col_perm", cmp_int_array(C0->col_perm, C0_cp_exp, 2)); - mu_assert("C0 X", cmp_double_array(C0->X, C0_X_exp, 4)); - - permuted_dense *C1 = C->blocks[1]; - int C1_cp_exp[2] = {2, 3}; - double C1_X_exp[4] = {232, 243, 316, 331}; - mu_assert("C1 row_perm", cmp_int_array(C1->row_perm, B1_rp, 2)); - mu_assert("C1 col_perm", cmp_int_array(C1->col_perm, C1_cp_exp, 2)); - mu_assert("C1 X", cmp_double_array(C1->X, C1_X_exp, 4)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -const char *test_BA_spd_spd_one_block_dropped(void) -{ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 1}; - double B0X[4] = {1, 2, 3, 4}; - matrix *B0 = new_permuted_dense(4, 6, 2, 2, B0_rp, B0_cp, B0X); - - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {4, 5}; - double B1X[4] = {5, 6, 7, 8}; - matrix *B1 = new_permuted_dense(4, 6, 2, 2, B1_rp, B1_cp, B1X); - - permuted_dense *B_blocks[2] = {(permuted_dense *) B0, (permuted_dense *) B1}; - matrix *B = new_stacked_pd(4, 6, 2, B_blocks, NULL, NULL); - - int A0_rp[2] = {0, 1}; - int A0_cp[2] = {0, 1}; - double A0X[4] = {10, 11, 12, 13}; - matrix *A0 = new_permuted_dense(6, 4, 2, 2, A0_rp, A0_cp, A0X); - - int A1_rp[2] = {2, 3}; - int A1_cp[2] = {2, 3}; - double A1X[4] = {20, 21, 22, 23}; - matrix *A1 = new_permuted_dense(6, 4, 2, 2, A1_rp, A1_cp, A1X); - - permuted_dense *A_blocks[2] = {(permuted_dense *) A0, (permuted_dense *) A1}; - matrix *A = new_stacked_pd(6, 4, 2, A_blocks, NULL, NULL); - - matrix *C_m = BA_spd_spd_alloc((stacked_pd *) B, (stacked_pd *) A); - BA_spd_spd_fill_values((stacked_pd *) B, (stacked_pd *) A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("n_blocks", C->n_blocks == 1); - int sp_exp[2] = {0, 1}; - int s_exp[1] = {0}; - mu_assert("src_block_idx_p", cmp_int_array(C->src_block_idx_p, sp_exp, 2)); - mu_assert("src_block_idx", cmp_int_array(C->src_block_idx, s_exp, 1)); - - permuted_dense *C0 = C->blocks[0]; - double C0_X_exp[4] = {34, 37, 78, 85}; - mu_assert("C0 X", cmp_double_array(C0->X, C0_X_exp, 4)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -const char *test_BA_spd_spd_all_blocks_dropped(void) -{ - int B0_rp[1] = {0}; - int B0_cp[1] = {0}; - double B0X[1] = {1}; - matrix *B0 = new_permuted_dense(2, 4, 1, 1, B0_rp, B0_cp, B0X); - - int B1_rp[1] = {1}; - int B1_cp[1] = {1}; - double B1X[1] = {2}; - matrix *B1 = new_permuted_dense(2, 4, 1, 1, B1_rp, B1_cp, B1X); - - permuted_dense *B_blocks[2] = {(permuted_dense *) B0, (permuted_dense *) B1}; - matrix *B = new_stacked_pd(2, 4, 2, B_blocks, NULL, NULL); - - int A0_rp[1] = {2}; - int A0_cp[1] = {0}; - double A0X[1] = {10}; - matrix *A0 = new_permuted_dense(4, 2, 1, 1, A0_rp, A0_cp, A0X); - - int A1_rp[1] = {3}; - int A1_cp[1] = {1}; - double A1X[1] = {20}; - matrix *A1 = new_permuted_dense(4, 2, 1, 1, A1_rp, A1_cp, A1X); - - permuted_dense *A_blocks[2] = {(permuted_dense *) A0, (permuted_dense *) A1}; - matrix *A = new_stacked_pd(4, 2, 2, A_blocks, NULL, NULL); - - matrix *C_m = BA_spd_spd_alloc((stacked_pd *) B, (stacked_pd *) A); - BA_spd_spd_fill_values((stacked_pd *) B, (stacked_pd *) A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("C m", C_m->m == 2); - mu_assert("C n", C_m->n == 2); - mu_assert("n_blocks", C->n_blocks == 0); - mu_assert("nnz", C_m->nnz == 0); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -const char *test_BA_spd_spd_empty_A(void) -{ - int B0_rp[1] = {0}; - int B0_cp[1] = {0}; - double B0X[1] = {1}; - matrix *B0 = new_permuted_dense(2, 3, 1, 1, B0_rp, B0_cp, B0X); - - int B1_rp[1] = {1}; - int B1_cp[1] = {1}; - double B1X[1] = {2}; - matrix *B1 = new_permuted_dense(2, 3, 1, 1, B1_rp, B1_cp, B1X); - - permuted_dense *B_blocks[2] = {(permuted_dense *) B0, (permuted_dense *) B1}; - matrix *B = new_stacked_pd(2, 3, 2, B_blocks, NULL, NULL); - - matrix *A = new_stacked_pd(3, 4, 0, NULL, NULL, NULL); - - matrix *C_m = BA_spd_spd_alloc((stacked_pd *) B, (stacked_pd *) A); - BA_spd_spd_fill_values((stacked_pd *) B, (stacked_pd *) A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("n_blocks", C->n_blocks == 0); - mu_assert("nnz", C_m->nnz == 0); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -const char *test_BA_spd_spd_empty_B(void) -{ - matrix *B = new_stacked_pd(2, 3, 0, NULL, NULL, NULL); - - int A0_rp[1] = {0}; - int A0_cp[1] = {0}; - double A0X[1] = {10}; - matrix *A0 = new_permuted_dense(3, 4, 1, 1, A0_rp, A0_cp, A0X); - - permuted_dense *A_blocks[1] = {(permuted_dense *) A0}; - matrix *A = new_stacked_pd(3, 4, 1, A_blocks, NULL, NULL); - - matrix *C_m = BA_spd_spd_alloc((stacked_pd *) B, (stacked_pd *) A); - BA_spd_spd_fill_values((stacked_pd *) B, (stacked_pd *) A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - mu_assert("C m", C_m->m == 2); - mu_assert("C n", C_m->n == 4); - mu_assert("n_blocks", C->n_blocks == 0); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -const char *test_BA_spd_spd_alloc_then_fill_values(void) -{ - int B0_rp[2] = {0, 1}; - int B0_cp[2] = {0, 1}; - double B0X[4] = {1, 2, 3, 4}; - matrix *B0 = new_permuted_dense(4, 6, 2, 2, B0_rp, B0_cp, B0X); - - int B1_rp[2] = {2, 3}; - int B1_cp[2] = {3, 4}; - double B1X[4] = {5, 6, 7, 8}; - matrix *B1 = new_permuted_dense(4, 6, 2, 2, B1_rp, B1_cp, B1X); - - permuted_dense *B_blocks[2] = {(permuted_dense *) B0, (permuted_dense *) B1}; - matrix *B = new_stacked_pd(4, 6, 2, B_blocks, NULL, NULL); - - int A0_rp[3] = {0, 1, 2}; - int A0_cp[2] = {0, 1}; - double A0X[6] = {10, 11, 12, 13, 14, 15}; - matrix *A0 = new_permuted_dense(6, 4, 3, 2, A0_rp, A0_cp, A0X); - - int A1_rp[3] = {3, 4, 5}; - int A1_cp[2] = {2, 3}; - double A1X[6] = {20, 21, 22, 23, 24, 25}; - matrix *A1 = new_permuted_dense(6, 4, 3, 2, A1_rp, A1_cp, A1X); - - permuted_dense *A_blocks[2] = {(permuted_dense *) A0, (permuted_dense *) A1}; - matrix *A = new_stacked_pd(6, 4, 2, A_blocks, NULL, NULL); - - matrix *C_m = BA_spd_spd_alloc((stacked_pd *) B, (stacked_pd *) A); - BA_spd_spd_fill_values((stacked_pd *) B, (stacked_pd *) A, (stacked_pd *) C_m); - - permuted_dense *B0_pd = (permuted_dense *) B0; - B0_pd->X[0] = 10; - B0_pd->X[1] = 20; - B0_pd->X[2] = 30; - B0_pd->X[3] = 40; - - BA_spd_spd_fill_values((stacked_pd *) B, (stacked_pd *) A, (stacked_pd *) C_m); - stacked_pd *C = (stacked_pd *) C_m; - - double C0_X_exp[4] = {340, 370, 780, 850}; - mu_assert("C0 X refilled", cmp_double_array(C->blocks[0]->X, C0_X_exp, 4)); - - double C1_X_exp[4] = {232, 243, 316, 331}; - mu_assert("C1 X unchanged", cmp_double_array(C->blocks[1]->X, C1_X_exp, 4)); - - free_matrix(C_m); - free_matrix(A); - free_matrix(B); - return 0; -} - -#endif /* TEST_STACKED_PD_H */