diff --git a/.github/workflows/check-install.yaml b/.github/workflows/check-install.yaml new file mode 100644 index 0000000..dc6e747 --- /dev/null +++ b/.github/workflows/check-install.yaml @@ -0,0 +1,36 @@ +on: + push: + branches: + - master + pull_request: + +name: Check CMake install + +jobs: + install: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Get latest CMake + uses: lukka/get-cmake@latest + + - name: Configure the build + run: cmake -S . -B build -DSCRAN_AGGREGATE_TESTS=OFF + + - name: Install the library + run: sudo cmake --install build + + - name: Test downstream usage + run: | + mkdir _downstream + touch _downstream/source.cpp + cat << EOF > _downstream/CMakeLists.txt + cmake_minimum_required(VERSION 3.24) + project(test_install) + add_executable(whee source.cpp) + find_package(libscran_scran_aggregate) + target_link_libraries(whee libscran::scran_aggregate) + EOF + cd _downstream && cmake -S . -B build diff --git a/CMakeLists.txt b/CMakeLists.txt index 5810363..bb81dff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,66 @@ cmake_minimum_required(VERSION 3.14) -project(scran_aggregate_across_cells +project(scran_aggregate VERSION 1.0.0 DESCRIPTION "Aggregate expression values across cells" LANGUAGES CXX) -add_library(scran_aggregate_across_cells INTERFACE) -target_compile_features(scran_aggregate_across_cells INTERFACE cxx_std_17) -target_include_directories(scran_aggregate_across_cells INTERFACE include/scran) +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) -add_subdirectory(extern) -target_link_libraries(scran_aggregate_across_cells INTERFACE tatami::tatami) +# Library +add_library(scran_aggregate INTERFACE) +add_library(libscran::scran_aggregate ALIAS scran_aggregate) +target_include_directories(scran_aggregate INTERFACE + $ + $) +target_compile_features(scran_aggregate INTERFACE cxx_std_17) + +# Dependencies +option(SCRAN_AGGREGATE_FETCH_EXTERN "Automatically fetch scran_aggregate's external dependencies." ON) +if(SCRAN_AGGREGATE_FETCH_EXTERN) + add_subdirectory(extern) +else() + find_package(tatami_tatami 3.0.0 CONFIG REQUIRED) +endif() + +target_link_libraries(scran_aggregate INTERFACE tatami::tatami) + +# Tests if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + option(SCRAN_AGGREGATE_TESTS "Build scran_aggregate's test suite." ON) +else() + option(SCRAN_AGGREGATE_TESTS "Build scran_aggregate's test suite." OFF) +endif() + +if(SCRAN_AGGREGATE_TESTS) include(CTest) if(BUILD_TESTING) add_subdirectory(tests) - endif() + endif() endif() + +# Install +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/scran_aggregate) + +install(TARGETS scran_aggregate + EXPORT scran_aggregateTargets) + +install(EXPORT scran_aggregateTargets + FILE libscran_scran_aggregateTargets.cmake + NAMESPACE libscran:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libscran_scran_aggregate) + +configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/libscran_scran_aggregateConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libscran_scran_aggregate) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/libscran_scran_aggregateConfigVersion.cmake" + COMPATIBILITY SameMajorVersion) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libscran_scran_aggregateConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/libscran_scran_aggregateConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libscran_scran_aggregate) diff --git a/README.md b/README.md index 04785d1..c0594ed 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Aggregate expression values across cells -![Unit tests](https://github.com/libscran/aggregate_across_cells/actions/workflows/run-tests.yaml/badge.svg) -![Documentation](https://github.com/libscran/aggregate_across_cells/actions/workflows/doxygenate.yaml/badge.svg) -[![Codecov](https://codecov.io/gh/libscran/aggregate_across_cells/graph/badge.svg?token=JWV0I4WJX2)](https://codecov.io/gh/libscran/aggregate_across_cells) +![Unit tests](https://github.com/libscran/scran_aggregate/actions/workflows/run-tests.yaml/badge.svg) +![Documentation](https://github.com/libscran/scran_aggregate/actions/workflows/doxygenate.yaml/badge.svg) +[![Codecov](https://codecov.io/gh/libscran/scran_aggregate/graph/badge.svg?token=JWV0I4WJX2)](https://codecov.io/gh/libscran/scran_aggregate) ## Overview @@ -15,16 +15,16 @@ factored out into a separate C++ library for easier re-use. ## Quick start Given a [`tatami::Matrix`](https://github.com/tatami-inc/tatami) and an array of group assignments, -the `aggregate_across_cells::compute()` function will compute the aggregate statistics across all genes for each group. +the `aggregate_across_cells()` function will compute the aggregate statistics across all genes for each group. ```cpp -#include "scran/aggregate_across_cells.hpp" +#include "scran_aggregate/scran_aggregate.hpp" -tatami::Matrix* ptr = some_data_source(); +const tatami::Matrix& mat = some_data_source(); std::vector groupings = some_groupings(); -scran::aggregate_across_cells::Options opt; -auto res = scran::aggregate_across_cells::compute(ptr, groupings.data(), opt); +scran_aggregate::AggregateAcrossCellsOptions opt; +auto res = scran_aggregate::aggregate_across_cells(mat, groupings.data(), opt); res.sums; // vector of vectors of per-group sums across genes. res.sums[0]; // vector of sums for the first group across genes. @@ -33,11 +33,9 @@ res.detected; // vector of vectors of the number of detected cells per gene. The array of groupings should contain integer assignments to groups 0, 1, 2, etc. For more complex groupings defined from combinations of multiple factors, -the `combine_factors::compute()` utility will create group assignments from unique combinations of those factors: +the `combine_factors()` utility will create group assignments from unique combinations of those factors: ```cpp -#include "scran/combine_factors.hpp" - std::vector grouping1 { 0, 0, 1, 1, 2, 2 }; std::vector grouping2 { 0, 1, 0, 1, 0, 1 }; @@ -53,28 +51,56 @@ res.factors[0]; // values of grouping1 for each unique combination. res.factors[1]; // values of grouping2 for each unique combination. ``` -Check out the [reference documentation](https://libscran.github.io/aggregate_across_cells) for more details. +Check out the [reference documentation](https://libscran.github.io/scran_aggregate) for more details. ## Building projects -This repository is part of the broader [**libscran**](https://github.com/libscran/libscran) library, -so users are recommended to use the latter in their projects. -**libscran** developers should just use CMake with `FetchContent`: +### CMake with `FetchContent` + +If you're using CMake, you just need to add something like this to your `CMakeLists.txt`: ```cmake include(FetchContent) FetchContent_Declare( - scran_aggregate_across_cells - GIT_REPOSITORY https://github.com/libscran/aggregate_across_cells + scran_aggregate + GIT_REPOSITORY https://github.com/libscran/scran_aggregate GIT_TAG master # or any version of interest ) -FetchContent_MakeAvailable(scran_aggregate_across_cells) +FetchContent_MakeAvailable(scran_aggregate) +``` + +Then you can link to **scran_aggregate** to make the headers available during compilation: +```cmake # For executables: -target_link_libraries(myexe scran_aggregate_across_cells) +target_link_libraries(myexe libscran::scran_aggregate) # For libaries -target_link_libraries(mylib INTERFACE scran_aggregate_across_cells) +target_link_libraries(mylib INTERFACE libscran::scran_aggregate) ``` + +### CMake with `find_package()` + +```cmake +find_package(libscran_scran_aggregate CONFIG REQUIRED) +target_link_libraries(mylib INTERFACE libscran::scran_aggregate) +``` + +To install the library, use: + +```sh +mkdir build && cd build +cmake .. -DSCRAN_AGGREGATE_TESTS=OFF +cmake --build . --target install +``` + +By default, this will use `FetchContent` to fetch all external dependencies. +If you want to install them manually, use `-DSCRAN_AGGREGATE_FETCH_EXTERN=OFF`. +See the tags in [`extern/CMakeLists.txt`](extern/CMakeLists.txt) to find compatible versions of each dependency. + +### Manual + +If you're not using CMake, the simple approach is to just copy the files in `include/` - either directly or with Git submodules - and include their path during compilation with, e.g., GCC's `-I`. +This requires the external dependencies listed in [`extern/CMakeLists.txt`](extern/CMakeLists.txt), which also need to be made available during compilation. diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in new file mode 100644 index 0000000..8397be9 --- /dev/null +++ b/cmake/Config.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(tatami_tatami 3.0.0 CONFIG REQUIRED) + +include("${CMAKE_CURRENT_LIST_DIR}/libscran_scran_aggregateTargets.cmake") diff --git a/include/scran/scran.hpp b/include/scran/scran.hpp deleted file mode 100644 index e71314e..0000000 --- a/include/scran/scran.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef SCRAN_SCRAN_HPP -#define SCRAN_SCRAN_HPP - -/** - * @file scran.hpp - * @brief Methods for single-cell analysis. - */ - -/** - * @namespace scran - * @brief Methods for single-cell analysis. - */ -namespace scran {} - -#endif diff --git a/include/scran/aggregate_across_cells.hpp b/include/scran_aggregate/aggregate_across_cells.hpp similarity index 60% rename from include/scran/aggregate_across_cells.hpp rename to include/scran_aggregate/aggregate_across_cells.hpp index a163289..fbbd24b 100644 --- a/include/scran/aggregate_across_cells.hpp +++ b/include/scran_aggregate/aggregate_across_cells.hpp @@ -1,5 +1,5 @@ -#ifndef SCRAN_AGGREGATE_ACROSS_CELLS_HPP -#define SCRAN_AGGREGATE_ACROSS_CELLS_HPP +#ifndef SCRAN_AGGREGATE_AGGREGATE_ACROSS_CELLS_HPP +#define SCRAN_AGGREGATE_AGGREGATE_ACROSS_CELLS_HPP #include #include @@ -7,31 +7,24 @@ /** * @file aggregate_across_cells.hpp - * * @brief Aggregate expression values across cells. */ -namespace scran { - -/** - * @namespace scran::aggregate_across_cells - * @brief Aggregate expression values across cells. - */ -namespace aggregate_across_cells { +namespace scran_aggregate { /** - * @brief Further options for `aggregate_across_cells::compute()`. + * @brief Options for `aggregate_across_cells()`. */ -struct Options { +struct AggregateAcrossCellsOptions { /** * Whether to compute the sum within each factor level. - * This option only affects the `aggregate_across_cells::compute()` overload where an `aggregate_across_cells::Results` object is returned. + * This option only affects the `aggregate_across_cells()` overload where an `AggregateAcrossCellsResults` object is returned. */ bool compute_sums = true; /** * Whether to compute the number of detected cells within each factor level. - * This option only affects the `aggregate_across_cells::compute()` overload where an `aggregate_across_cells::Results` object is returned. + * This option only affects the `aggregate_across_cells()` overload where an `AggregateAcrossCellsResults` object is returned. */ bool compute_detected = true; @@ -41,24 +34,82 @@ struct Options { int num_threads = 1; }; +/** + * @brief Buffers for `aggregate_across_cells()`. + * @tparam Sum_ Type of the sum, should be numeric. + * @tparam Detected_ Type for the number of detected cells, usually integer. + */ +template +struct AggregateAcrossCellsBuffers { + /** + * Vector of length equal to the number of factor levels. + * Each element is a pointer to an array of length equal to the number of genes, + * to be filled with the summed expression across all cells in the corresponding level for each gene. + * + * If this is empty, the sums for each level are not computed. + */ + std::vector sums; + + /** + * Vector of length equal to the number of factor levels. + * Each element is a pointer to an array of length equal to the number of genes, + * to be filled with the number of cells in the corresponding level with detected expression for each gene. + * + * If this is empty, the number of detected cells for each level is not computed. + */ + std::vector detected; + +}; + +/** + * @brief Results of `aggregate_across_cells()`. + * @tparam Sum_ Type of the sum, should be numeric. + * @tparam Detected_ Type for the number of detected cells, usually integer. + */ +template +struct AggregateAcrossCellsResults { + /** + * Vector of length equal to the number of factor levels. + * Each inner vector is of length equal to the number of genes. + * Each entry contains the summed expression across all cells in the corresponding level for each gene. + * + * If `AggregateAcrossCellsOptions::compute_sums = false`, this vector is empty. + */ + std::vector > sums; + + /** + * Vector of length equal to the number of factor levels. + * Each inner vector is of length equal to the number of genes. + * Each entry contains the number of cells in the corresponding level with detected expression for each gene. + * + * If `AggregateAcrossCellsOptions::compute_detected = false`, this vector is empty. + */ + std::vector > detected; +}; + /** * @cond */ namespace internal { template -void compute_by_row(const tatami::Matrix* p, const Factor_* factor, std::vector& sums, std::vector& detected, const Options& options) { +void compute_aggregate_by_row( + const tatami::Matrix& p, + const Factor_* factor, + const AggregateAcrossCellsBuffers& buffers, + const AggregateAcrossCellsOptions& options) +{ tatami::Options opt; opt.sparse_ordered_index = false; tatami::parallelize([&](size_t, Index_ s, Index_ l) { - auto ext = tatami::consecutive_extractor(p, true, s, l, opt); - size_t nsums = sums.size(); + auto ext = tatami::consecutive_extractor(&p, true, s, l, opt); + size_t nsums = buffers.sums.size(); std::vector tmp_sums(nsums); - size_t ndetected = detected.size(); + size_t ndetected = buffers.detected.size(); std::vector tmp_detected(ndetected); - auto NC = p->ncol(); + auto NC = p.ncol(); std::vector vbuffer(NC); typename std::conditional, Index_>::type ibuffer(NC); @@ -86,7 +137,7 @@ void compute_by_row(const tatami::Matrix* p, const Factor_* facto // Computing before transferring for more cache-friendliness. for (size_t l = 0; l < nsums; ++l) { - sums[l][x] = tmp_sums[l]; + buffers.sums[l][x] = tmp_sums[l]; } } @@ -104,21 +155,26 @@ void compute_by_row(const tatami::Matrix* p, const Factor_* facto } for (size_t l = 0; l < ndetected; ++l) { - detected[l][x] = tmp_detected[l]; + buffers.detected[l][x] = tmp_detected[l]; } } } - }, p->nrow(), options.num_threads); + }, p.nrow(), options.num_threads); } template -void compute_by_column(const tatami::Matrix* p, const Factor_* factor, std::vector& sums, std::vector& detected, const Options& options) { +void compute_aggregate_by_column( + const tatami::Matrix& p, + const Factor_* factor, + const AggregateAcrossCellsBuffers& buffers, + const AggregateAcrossCellsOptions& options) +{ tatami::Options opt; opt.sparse_ordered_index = false; tatami::parallelize([&](size_t, Index_ s, Index_ l) { - auto NC = p->ncol(); - auto ext = tatami::consecutive_extractor(p, false, 0, NC, s, l, opt); + auto NC = p.ncol(); + auto ext = tatami::consecutive_extractor(&p, false, 0, NC, s, l, opt); std::vector vbuffer(l); typename std::conditional, Index_>::type ibuffer(l); @@ -127,15 +183,15 @@ void compute_by_column(const tatami::Matrix* p, const Factor_* fa if constexpr(sparse_) { auto col = ext->fetch(vbuffer.data(), ibuffer.data()); - if (sums.size()) { - auto& cursum = sums[current]; + if (buffers.sums.size()) { + auto& cursum = buffers.sums[current]; for (Index_ i = 0; i < col.number; ++i) { cursum[col.index[i]] += col.value[i]; } } - if (detected.size()) { - auto& curdetected = detected[current]; + if (buffers.detected.size()) { + auto& curdetected = buffers.detected[current]; for (Index_ i = 0; i < col.number; ++i) { curdetected[col.index[i]] += (col.value[i] > 0); } @@ -144,22 +200,22 @@ void compute_by_column(const tatami::Matrix* p, const Factor_* fa } else { auto col = ext->fetch(vbuffer.data()); - if (sums.size()) { - auto cursum = sums[current] + s; + if (buffers.sums.size()) { + auto cursum = buffers.sums[current] + s; for (Index_ i = 0; i < l; ++i) { cursum[i] += col[i]; } } - if (detected.size()) { - auto curdetected = detected[current] + s; + if (buffers.detected.size()) { + auto curdetected = buffers.detected[current] + s; for (Index_ i = 0; i < l; ++i) { curdetected[i] += (col[i] > 0); } } } } - }, p->nrow(), options.num_threads); + }, p.nrow(), options.num_threads); } } @@ -183,59 +239,31 @@ void compute_by_column(const tatami::Matrix* p, const Factor_* fa * @param[in] factor Pointer to an array of length equal to the number of columns of `input`, * containing the factor level for each cell. * All levels should be integers in \f$[0, N)\f$ where \f$N\f$ is the number of unique levels. - * @param[out] sums Vector of length \f$N\f$ (see `factor`), - * containing pointers to arrays of length equal to the number of columns of `input`. - * These will be filled with the summed expression across all cells in the corresponding level for each gene. - * Alternatively, if the vector is of length 0, no sums will be computed. - * @param[out] detected Vector of length \f$N\f$ (see `factor`), - * containing pointers to arrays of length equal to the number of columns of `input`. - * These will be filled with the number of cells with detected expression in the corresponding level for each gene. - * Alternatively, if the vector is of length 0, no numbers will be computed. + * @param[out] buffers Collection of buffers in which to store the aggregate statistics (e.g., sums, number of detected cells) for each level and gene. * @param options Further options. */ template -void compute(const tatami::Matrix* input, const Factor_* factor, std::vector sums, std::vector detected, const Options& options) { - if (input->prefer_rows()) { - if (input->sparse()) { - internal::compute_by_row(input, factor, sums, detected, options); +void aggregate_across_cells( + const tatami::Matrix& input, + const Factor_* factor, + const AggregateAcrossCellsBuffers& buffers, + const AggregateAcrossCellsOptions& options) +{ + if (input.prefer_rows()) { + if (input.sparse()) { + internal::compute_aggregate_by_row(input, factor, buffers, options); } else { - internal::compute_by_row(input, factor, sums, detected, options); + internal::compute_aggregate_by_row(input, factor, buffers, options); } } else { - if (input->sparse()) { - internal::compute_by_column(input, factor, sums, detected, options); + if (input.sparse()) { + internal::compute_aggregate_by_column(input, factor, buffers, options); } else { - internal::compute_by_column(input, factor, sums, detected, options); + internal::compute_aggregate_by_column(input, factor, buffers, options); } } } -/** - * @brief Aggregated results from `aggregate_across_cells::compute()`. - * @tparam Sum_ Type of the sum, should be numeric. - * @tparam Detected_ Type for the number of detected cells, usually integer. - */ -template -struct Results { - /** - * Vector of length equal to the number of factor levels. - * Each inner vector is of length equal to the number of genes. - * Each entry contains the summed expression across all cells in the corresponding level for the corresponding gene. - * - * If `aggregate_across_cells::Options::compute_sums = false`, this vector is empty. - */ - std::vector > sums; - - /** - * Vector of length equal to the number of factor levels. - * Each inner vector is of length equal to the number of genes. - * Each entry contains the number of cells in the corresponding level with detected expression for the corresponding gene. - * - * If `aggregate_across_cells::Options::compute_detected = false`, this vector is empty. - */ - std::vector > detected; -}; - /** * @tparam Sum_ Type of the sum, should be numeric. * @tparam Detected_ Type for the number of detected cells, usually integer. @@ -249,40 +277,41 @@ struct Results { * All levels should be integers in \f$[0, N)\f$ where \f$N\f$ is the number of unique levels. * @param options Further options. * - * @return Results of the aggregation, where the available statistics depend on `aggregate_across_cells::Options`. + * @return Results of the aggregation, where the available statistics depend on `AggregateAcrossCellsOptions`. */ template -Results compute(const tatami::Matrix* input, const Factor_* factor, const Options& options) { - size_t NC = input->ncol(); +AggregateAcrossCellsResults aggregate_across_cells( + const tatami::Matrix& input, + const Factor_* factor, + const AggregateAcrossCellsOptions& options) +{ + size_t NC = input.ncol(); size_t nlevels = (NC ? *std::max_element(factor, factor + NC) + 1 : 0); - size_t ngenes = input->nrow(); + size_t ngenes = input.nrow(); - Results output; - std::vector sumptr; - std::vector detptr; + AggregateAcrossCellsResults output; + AggregateAcrossCellsBuffers buffers; if (options.compute_sums) { output.sums.resize(nlevels, std::vector(ngenes)); - sumptr.resize(nlevels); + buffers.sums.resize(nlevels); for (size_t l = 0; l < nlevels; ++l) { - sumptr[l] = output.sums[l].data(); + buffers.sums[l] = output.sums[l].data(); } } if (options.compute_detected) { output.detected.resize(nlevels, std::vector(ngenes)); - detptr.resize(nlevels); + buffers.detected.resize(nlevels); for (size_t l = 0; l < nlevels; ++l) { - detptr[l] = output.detected[l].data(); + buffers.detected[l] = output.detected[l].data(); } } - compute(input, factor, std::move(sumptr), std::move(detptr), options); + aggregate_across_cells(input, factor, buffers, options); return output; } } -} - #endif diff --git a/include/scran/combine_factors.hpp b/include/scran_aggregate/combine_factors.hpp similarity index 75% rename from include/scran/combine_factors.hpp rename to include/scran_aggregate/combine_factors.hpp index aa2b613..8a98e14 100644 --- a/include/scran/combine_factors.hpp +++ b/include/scran_aggregate/combine_factors.hpp @@ -1,5 +1,5 @@ -#ifndef SCRAN_COMBINE_FACTORS_HPP -#define SCRAN_COMBINE_FACTORS_HPP +#ifndef SCRAN_AGGREGATE_COMBINE_FACTORS_HPP +#define SCRAN_AGGREGATE_COMBINE_FACTORS_HPP #include #include @@ -10,34 +10,30 @@ * @brief Combine categorical factors into a single factor. */ -namespace scran { +namespace scran_aggregate { /** - * @namespace scran::combine_factors - * @brief Combine categorical factors into a single factor. - */ -namespace combine_factors { - -/** - * @brief Unique combinations from `combine_factors::compute()`. + * @brief Unique combinations from `combine_factors()`. * * @tparam Factor_ Factor type, typically an integer. */ template -struct Results { +struct FactorCombinations { /** * @cond */ - Results(size_t n) : factors(n) {} + FactorCombinations(size_t n) : factors(n) {} /** * @endcond */ /** * Unique combinations of factor levels. - * Each inner vector corresponds to a factor. + * Each inner vector corresponds to a factor used as input to `combine_factors()`. * All inner vectors have the same length. - * Corresponding entries of the inner vectors define a particular combination of levels. + * Corresponding entries of the inner vectors define a particular combination of levels, + * i.e., the first combination is defined as `(factors[0][0], factors[1][0], ...)`, + * the second combination is defined as `(factors[0][1], factors[1][1], ...)`, and so on. * Combinations are guaranteed to be sorted. */ std::vector > factors; @@ -61,11 +57,11 @@ struct Results { * @param[out] combined Pointer to an array of length `n`, in which the combined factor is to be stored. * * @return - * A `combine_factors::Results` object is returned containing the unique combinations of levels observed in `factors`. - * A combined factor is saved to `combined`, where each entry is an index into the relevant combination of the output `combine_factors::Results` object. + * Object containing the unique combinations of levels observed in `factors`. + * The combined factor written to `combined`, where each entry is an index into the relevant combination of the output `FactorCombinations` object. */ template -Results compute(size_t n, const std::vector& factors, Combined_* combined) { +FactorCombinations combine_factors(size_t n, const std::vector& factors, Combined_* combined) { auto cmp = [&](size_t left, size_t right) -> bool { for (auto curf : factors) { if (curf[left] < curf[right]) { @@ -96,7 +92,7 @@ Results compute(size_t n, const std::vector& factors, C // Obtaining the sorted set of unique combinations. size_t nfac = factors.size(); - Results output(nfac); + FactorCombinations output(nfac); size_t nuniq = mapping.size(); for (auto& ofac : output.factors) { ofac.reserve(nuniq); @@ -124,6 +120,4 @@ Results compute(size_t n, const std::vector& factors, C } -} - #endif diff --git a/include/scran_aggregate/scran_aggregate.hpp b/include/scran_aggregate/scran_aggregate.hpp new file mode 100644 index 0000000..2c5a563 --- /dev/null +++ b/include/scran_aggregate/scran_aggregate.hpp @@ -0,0 +1,18 @@ +#ifndef SCRAN_AGGREGATE_HPP +#define SCRAN_AGGREGATE_HPP + +#include "aggregate_across_cells.hpp" +#include "combine_factors.hpp" + +/** + * @file scran_aggregate.hpp + * @brief Aggregate single-cell expression values. + */ + +/** + * @namespace scran_aggregate + * @brief Aggregate single-cell expression values. + */ +namespace scran_aggregate {} + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d146795..e676d7b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,24 +1,12 @@ include(FetchContent) FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/1d17ea141d2c11b8917d2c7d029f1c4e2b9769b2.zip -) - -FetchContent_Declare( - scran_test_utils - GIT_REPOSITORY https://github.com/libscran/test_utils + scran_tests + GIT_REPOSITORY https://github.com/libscran/scran_tests GIT_TAG master ) +FetchContent_MakeAvailable(scran_tests) -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -FetchContent_MakeAvailable(scran_test_utils) - -enable_testing() - -set(CODE_COVERAGE OFF CACHE BOOL "Enable coverage testing") +option(CODE_COVERAGE "Enable coverage testing" OFF) set(DO_CODE_COVERAGE OFF) if(CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") set(DO_CODE_COVERAGE ON) @@ -35,9 +23,8 @@ macro(create_test name) target_link_libraries( ${name} - gtest_main - scran_aggregate_across_cells - scran_test_utils + scran_aggregate + scran_tests ) target_compile_options(${name} PRIVATE -Wall -Werror -Wpedantic -Wextra) diff --git a/tests/src/aggregate_across_cells.cpp b/tests/src/aggregate_across_cells.cpp index 64e1bd4..f5a2d88 100644 --- a/tests/src/aggregate_across_cells.cpp +++ b/tests/src/aggregate_across_cells.cpp @@ -1,7 +1,7 @@ #include -#include "aggregate_across_cells.hpp" -#include "simulate_vector.h" +#include "scran_aggregate/aggregate_across_cells.hpp" +#include "scran_tests/scran_tests.hpp" #include #include @@ -21,7 +21,13 @@ class AggregateAcrossCellsTest : public ::testing::TestWithParam(new tatami::DenseRowMatrix(nr, nc, simulate_sparse_vector(nr * nc, 0.1))); + auto vec = scran_tests::simulate_vector(nr * nc, []{ + scran_tests::SimulationParameters sparams; + sparams.density = 0.1; + return sparams; + }()); + + dense_row = std::unique_ptr(new tatami::DenseRowMatrix(nr, nc, std::move(vec))); dense_column = tatami::convert_to_dense(dense_row.get(), false); sparse_row = tatami::convert_to_compressed_sparse(dense_row.get(), true); sparse_column = tatami::convert_to_compressed_sparse(dense_row.get(), false); @@ -35,8 +41,8 @@ TEST_P(AggregateAcrossCellsTest, Basics) { std::vector groupings = create_groupings(dense_row->ncol(), ngroups); - scran::aggregate_across_cells::Options opt; - auto ref = scran::aggregate_across_cells::compute(dense_row.get(), groupings.data(), opt); + scran_aggregate::AggregateAcrossCellsOptions opt; + auto ref = scran_aggregate::aggregate_across_cells(*dense_row, groupings.data(), opt); auto compare = [&](const auto& other) -> void { for (int l = 0; l < ngroups; ++l) { @@ -47,7 +53,7 @@ TEST_P(AggregateAcrossCellsTest, Basics) { opt.num_threads = nthreads; if (nthreads != 1) { - auto res1 = scran::aggregate_across_cells::compute(dense_row.get(), groupings.data(), opt); + auto res1 = scran_aggregate::aggregate_across_cells(*dense_row, groupings.data(), opt); compare(res1); } else { // Doing some cursory checks. @@ -63,13 +69,13 @@ TEST_P(AggregateAcrossCellsTest, Basics) { } } - auto res2 = scran::aggregate_across_cells::compute(sparse_row.get(), groupings.data(), opt); + auto res2 = scran_aggregate::aggregate_across_cells(*sparse_row, groupings.data(), opt); compare(res2); - auto res3 = scran::aggregate_across_cells::compute(dense_column.get(), groupings.data(), opt); + auto res3 = scran_aggregate::aggregate_across_cells(*dense_column, groupings.data(), opt); compare(res3); - auto res4 = scran::aggregate_across_cells::compute(sparse_column.get(), groupings.data(), opt); + auto res4 = scran_aggregate::aggregate_across_cells(*sparse_column, groupings.data(), opt); compare(res4); } @@ -84,22 +90,28 @@ INSTANTIATE_TEST_SUITE_P( TEST(AggregateAcrossCells, Skipping) { int nr = 88, nc = 126; - auto input = std::unique_ptr(new tatami::DenseRowMatrix(nr, nc, simulate_sparse_vector(nr * nc, 0.1))); + auto vec = scran_tests::simulate_vector(nr * nc, []{ + scran_tests::SimulationParameters sparams; + sparams.density = 0.1; + sparams.seed = 69; + return sparams; + }()); + auto input = std::unique_ptr(new tatami::DenseRowMatrix(nr, nc, std::move(vec))); auto grouping = create_groupings(input->ncol(), 2); - scran::aggregate_across_cells::Options opt; - auto ref = scran::aggregate_across_cells::compute(input.get(), grouping.data(), opt); + scran_aggregate::AggregateAcrossCellsOptions opt; + auto ref = scran_aggregate::aggregate_across_cells(*input, grouping.data(), opt); EXPECT_EQ(ref.sums.size(), 2); EXPECT_EQ(ref.detected.size(), 2); // Skipping works correctly when we don't want to compute things. opt.compute_sums = false; - auto partial = scran::aggregate_across_cells::compute(input.get(), grouping.data(), opt); + auto partial = scran_aggregate::aggregate_across_cells(*input, grouping.data(), opt); EXPECT_EQ(partial.sums.size(), 0); EXPECT_EQ(partial.detected.size(), 2); opt.compute_detected = false; - auto skipped = scran::aggregate_across_cells::compute(input.get(), grouping.data(), opt); + auto skipped = scran_aggregate::aggregate_across_cells(*input, grouping.data(), opt); EXPECT_EQ(skipped.sums.size(), 0); EXPECT_EQ(skipped.detected.size(), 0); } diff --git a/tests/src/combine_factors.cpp b/tests/src/combine_factors.cpp index e9a0212..2c14120 100644 --- a/tests/src/combine_factors.cpp +++ b/tests/src/combine_factors.cpp @@ -2,12 +2,12 @@ #include -#include "combine_factors.hpp" +#include "scran_aggregate/combine_factors.hpp" template -std::pair, std::vector > test_combine_factors(size_t n, const std::vector& factors) { +std::pair, std::vector > test_combine_factors(size_t n, const std::vector& factors) { std::vector combined(n); - auto levels = scran::combine_factors::compute(n, factors, combined.data()); + auto levels = scran_aggregate::combine_factors(n, factors, combined.data()); return std::make_pair(std::move(levels), std::move(combined)); }