Skip to content

Commit

Permalink
Switched to a standalone library with the full CMake setup.
Browse files Browse the repository at this point in the history
This avoids having to try to assemble the full libscran library later on.
  • Loading branch information
LTLA committed Jul 11, 2024
1 parent acc789b commit 5cf2e1b
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 154 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/check-install.yaml
Original file line number Diff line number Diff line change
@@ -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_BLOCKS_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_blocks)
target_link_libraries(whee libscran::scran_blocks)
EOF
cd _downstream && cmake -S . -B build
63 changes: 56 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,66 @@
cmake_minimum_required(VERSION 3.14)

project(scran_utils
VERSION 1.0.0
DESCRIPTION "Core utilities for libscran"
project(scran_blocks
VERSION 2.0.0
DESCRIPTION "Utilities for handling blocks"
LANGUAGES CXX)

add_library(scran_core_utils INTERFACE)
target_compile_features(scran_core_utils INTERFACE cxx_std_17)
target_include_directories(scran_core_utils INTERFACE include/scran)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

# Library
add_library(scran_blocks INTERFACE)
add_library(libscran::scran_blocks ALIAS scran_blocks)

target_include_directories(scran_blocks INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/scran_blocks>)
target_compile_features(scran_blocks INTERFACE cxx_std_17)

## Dependencies
#option(SCRAN_MARKERS_FETCH_EXTERN "Automatically fetch scran_blocks's external dependencies." ON)
#if(SCRAN_MARKERS_FETCH_EXTERN)
# add_subdirectory(extern)
#else()
# find_package(tatami_tatami 3.0.0 CONFIG REQUIRED)
# find_package(tatami_tatami_stats 1.1.0 CONFIG REQUIRED)
# find_package(scran_blocks 1.0.0 CONFIG REQUIRED)
#endif()

# Tests
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
option(SCRAN_MARKERS_TESTS "Build scran_blocks's test suite." ON)
else()
option(SCRAN_MARKERS_TESTS "Build scran_blocks's test suite." OFF)
endif()

if(SCRAN_MARKERS_TESTS)
include(CTest)
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
endif()
endif()

# Install
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/scran_blocks)

install(TARGETS scran_blocks
EXPORT scran_blocksTargets)

install(EXPORT scran_blocksTargets
FILE libscran_scran_blocksTargets.cmake
NAMESPACE libscran::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libscran_scran_blocks)

configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/libscran_scran_blocksConfig.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libscran_scran_blocks)

write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/libscran_scran_blocksConfigVersion.cmake"
COMPATIBILITY SameMajorVersion)

install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libscran_scran_blocksConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/libscran_scran_blocksConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libscran_scran_blocks)
106 changes: 65 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
# Utilities for scran
# Blocking utilities for scran

![Unit tests](https://github.com/libscran/core_utils/actions/workflows/run-tests.yaml/badge.svg)
![Documentation](https://github.com/libscran/core_utils/actions/workflows/doxygenate.yaml/badge.svg)
[![Codecov](https://codecov.io/gh/libscran/core_utils/graph/badge.svg?token=JWV0I4WJX2)](https://codecov.io/gh/libscran/core_utils)
![Unit tests](https://github.com/libscran/scran_blocks/actions/workflows/run-tests.yaml/badge.svg)
![Documentation](https://github.com/libscran/scran_blocks/actions/workflows/doxygenate.yaml/badge.svg)
[![Codecov](https://codecov.io/gh/libscran/scran_blocks/graph/badge.svg?token=JWV0I4WJX2)](https://codecov.io/gh/libscran/scran_blocks)

## Overview

This repository contains utility functions for the other [**libscran**](https://github.com/libscran) repositories.
I don't know what else to say - it's just a mishmash of random functions that are re-used in multiple other libraries or don't warrant their own repository.
Developers should check out the [reference documentation](https://libscran.github.io/core_utils) for the available functions.
This repository contains utilities for blocked analyses for the other [**libscran**](https://github.com/libscran) repositories.
Any uninteresting factor of variation (usually across the cells) can be used as a blocking factor, e.g., experimental batches, sample/patient of origin.
In the presence of blocks, our general strategy is to perform the analysis within each block before combining our conclusions across blocks.
This ensures that our results are not affected by the uninteresting differences between blocks.

## Averaging vectors

The `average_vectors::compute()` function will compute the element-wise averages of any number of equi-length arrays.
This is typically used to average statistics across blocks, where each array contains the statistics for a single block over all genes.

```cpp
#include "scran/average_vectors.hpp"
#include "scran_blocks/scran_blocks.hpp"

std::vector<double> stat1 { 1, 2, 3, 4 };
std::vector<double> stat2 { 5, 6, 7, 8 };

// Contains { 3, 4, 5, 6 }.
auto averaged = scran::average_vectors::compute(
auto averaged = scran_blocks::average_vectors(
stat1.size(),
{ stat1.data(), stat2.data() }
/* skip_nan = */ false
Expand All @@ -31,84 +33,106 @@ auto averaged = scran::average_vectors::compute(
If NaNs are present, they can be ignored:
```cpp
#include "scran/average_vectors.hpp"
#include <limits>
auto nan = std::numeric_limits<double>::quiet_NaN();
std::vector<double> stat1 { 1, nan, 3, nan };
std::vector<double> stat2 { 5, 6, nan, nan };
std::vector<double> stat1n { 1, nan, 3, nan };
std::vector<double> stat2n { 5, 6, nan, nan };
// Contains { 3, 6, 3, nan }.
auto averaged = scran::average_vectors::compute(
stat1.size(),
{ stat1.data(), stat2.data() }
auto averagedn = scran_blocks::average_vectors(
stat1n.size(),
{ stat1n.data(), stat2n.data() }
/* skip_nan = */ true
);
```

We also support per-vector weights:

```cpp
#include "scran/average_vectors.hpp"

std::vector<double> stat1 { 1, 2, 3, 4 };
std::vector<double> stat2 { 5, 6, 7, 8 };
std::vector<double> stat1w { 1, 2, 3, 4 };
std::vector<double> stat2w { 5, 6, 7, 8 };
std::vector<double> weights { 1, 9 };

// Contains { 4.6, 5.6, 6.6, 7.6 }.
auto averaged = scran::average_vectors::compute(
stat1.size(),
{ stat1.data(), stat2.data() }
auto averagedw = scran_blocks::average_vectors_weighted(
stat1w.size(),
{ stat1w.data(), stat2w.data() }
weights.data(),
/* skip_nan = */ false
);
```
## Weighting blocks
See the [reference documentation](https://libscran.github.io/scran_blocks) for more details.
Several **libscran** analysis steps allow users to block on uninteresting factors like the batch or sample of origin.
Statistics are typically computed within each block and then combined across each block (e.g., with `average_vectors::compute()`).
When doing so, it may be desirable to weight each block by its size, favoring larger blocks that can emit more stable statistics.
## Weighting blocks
The `block_weights::compute()` function will compute weights for each block based on its size.
The example below uses a variable block weight that increases linearly with block size from 0 to 200, after which it is capped at 1.
This `VARIABLE` policy penalizes very small blocks to ensure that their unstable statistics do not overly influence the average.
However, blocks are equally weighted once they are "large enough", ensuring that the average is not dominated by a single very large block.
When combining statistics across blocks, it may be desirable to weight each block by its size, favoring larger blocks that can emit more stable statistics.
This is done using the `compute_block_weights()` function that calculates a weight for each block based on its size.
```cpp
#include "scran/block_weights.hpp"
#include "scran_blocks/scran_blocks.hpp"
std::vector<size_t> block_sizes { 10, 100, 1000 };
auto weights = scran::block_weights::compute(
auto weights = scran_blocks::compute_weights(
block_sizes,
/* policy = */ scran::block_weights::VARIABLE,
/* policy = */ scran_blocks::WeightPolicy::VARIABLE,
/* variable = */ { 0, 200 }
);
```

The above code chunk uses a variable block weight that increases linearly with block size from 0 to 200, after which it is capped at 1.
This `VARIABLE` policy penalizes very small blocks to ensure that their unstable statistics do not overly influence the average.
Blocks are equally weighted once they are "large enough", ensuring that the average is not dominated by a single very large block.

Users can also change the policy to `NONE`, where weights are equal to the block size;
or `EQUAL`, where all blocks are equally weighted regardless of size (assuming they are non-empty).
In such cases, the `variable` argument is ignored.
Check out the [reference documentation](https://libscran.github.io/scran_blocks) 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_core_utils
GIT_REPOSITORY https://github.com/libscran/core_utils
scran_blocks
GIT_REPOSITORY https://github.com/libscran/scran_blocks
GIT_TAG master # or any version of interest
)
FetchContent_MakeAvailable(scran_core_utils)
FetchContent_MakeAvailable(scran_blocks)
```

Then you can link to **scran_blocks** to make the headers available during compilation:

```cmake
# For executables:
target_link_libraries(myexe scran_core_utils)
target_link_libraries(myexe libscran::scran_blocks)
# For libaries
target_link_libraries(mylib INTERFACE scran_core_utils)
target_link_libraries(mylib INTERFACE libscran::scran_blocks)
```

### CMake with `find_package()`

```cmake
find_package(libscran_scran_blocks CONFIG REQUIRED)
target_link_libraries(mylib INTERFACE libscran::scran_blocks)
```

To install the library, use:

```sh
mkdir build && cd build
cmake .. -DSCRAN_BLOCKS_TESTS=OFF
cmake --build . --target install
```

### 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`.
3 changes: 3 additions & 0 deletions cmake/Config.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/libscran_scran_blocksTargets.cmake")
8 changes: 4 additions & 4 deletions docs/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.

PROJECT_NAME = "scran_core_utils"
PROJECT_NAME = "scran_blocks"

# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
Expand All @@ -54,7 +54,7 @@ PROJECT_NUMBER =
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.

PROJECT_BRIEF = "Core utilities for libscran"
PROJECT_BRIEF = "Blocking utilities for libscran"

# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
Expand Down Expand Up @@ -917,7 +917,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

INPUT = ../include/scran \
INPUT = ../include/scran_blocks \
../README.md

# This tag can be used to specify the character encoding of the source files
Expand Down Expand Up @@ -2375,7 +2375,7 @@ TAGFILES =
# tag file that is based on the input files it reads. See section "Linking to
# external documentation" for more information about the usage of tag files.

GENERATE_TAGFILE = html/scran_core_utils.tag
GENERATE_TAGFILE = html/scran_blocks.tag

# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
# the class index. If set to NO, only the inherited external classes will be
Expand Down
15 changes: 0 additions & 15 deletions include/scran/scran.hpp

This file was deleted.

Loading

0 comments on commit 5cf2e1b

Please sign in to comment.