Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable system installation of OpenRAND #22

Merged
merged 8 commits into from
Mar 11, 2024
Merged
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
run: mkdir build

- name: CMake Configure
run: cmake -B build
run: cmake -B build -DOpenRAND_ENABLE_TESTS=ON

- name: Build project
working-directory: ${{github.workspace}}/build
Expand Down
116 changes: 86 additions & 30 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,61 +1,117 @@
# TODO: Lower the min version required
cmake_minimum_required(VERSION 3.18)

# Project's name
project(OpenRAND)
project(OpenRAND VERSION 1.0)

# Set the C++ standard
# Set the C++ standard and ensure it's strictly required
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#- Wdouble-promotion : implicit float->double promotion is a really bad idea for GPU
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -Wall -Wextra -Wdouble-promotion -Wconversion -O3 -fopenmp")

include_directories(${PROJECT_SOURCE_DIR}/include)
# Define the main library as an interface
add_library(${PROJECT_NAME} INTERFACE)

include(FetchContent)
# Create an alias target for users to include OpenRAND with a namespace
# This isn't explicitly necessary as OpenRAND is header-only but it takes including OpenRAND
# via find_package + target_link_libraries straightforward.
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

# Build TestU01 statistical test suite
set(TESTU01_PATH "" CACHE PATH "Path to TestU01 library")
# Specify the C++ flags using target_compile_options and generator expressions for better control and compatibility
target_compile_options(${PROJECT_NAME} INTERFACE
$<$<CXX_COMPILER_ID:GNU>:
-Wall -Wextra -Wdouble-promotion -Wconversion -O3>
$<$<CXX_COMPILER_ID:Clang>:
-Wall -Wextra -Wconversion -O3>
$<$<CXX_COMPILER_ID:MSVC>:
/W4 /O2>
)

# Only build tests, examples and benchmarks if this isn't compiled
# as a dependecy of another project.
# Thanks to: https://www.foonathan.net/2022/06/cmake-fetchcontent/
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
enable_testing()
add_subdirectory(tests)

add_subdirectory(examples)
add_subdirectory(benchmarks)
# Include directories for the interface target
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)

# Options to control the build process
option(OpenRAND_ENABLE_TESTS "Enable building of tests" OFF)
option(OpenRAND_ENABLE_EXAMPLES "Enable building of examples" OFF)
option(OpenRAND_ENABLE_BENCHMARKS "Enable building of benchmarks" OFF)

message(STATUS "OpenRAND_ENABLE_TESTS: ${OpenRAND_ENABLE_TESTS}")
message(STATUS "OpenRAND_ENABLE_EXAMPLES: ${OpenRAND_ENABLE_EXAMPLES}")
message(STATUS "OpenRAND_ENABLE_BENCHMARKS: ${OpenRAND_ENABLE_BENCHMARKS}")

if(OpenRAND_ENABLE_TESTS)
# Fetch GoogleTest for managing tests
include(FetchContent)

FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) # Prevent GTest from being installed
FetchContent_MakeAvailable(googletest)

enable_testing()
add_subdirectory(tests)
endif()

if(OpenRAND_ENABLE_EXAMPLES)
add_subdirectory(examples)
endif()

# TODO: it's still being built as DEBUG
if(OpenRAND_ENABLE_BENCHMARKS)
# Fetch GoogleBenchmarks for managing benchmarks
include(FetchContent)
FetchContent_Declare(
google_benchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG main
CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release
google_benchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG main
CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release # TODO: it's still being built as DEBUG
)
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) # Prevent GoogleBenchmarks from being installed
FetchContent_MakeAvailable(google_benchmark)

add_subdirectory(benchmarks)
endif()

# Installation rules for the library
include(GNUInstallDirs)

# Installation rules for the library
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

# Exporting and installing the package configuration
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${PROJECT_NAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)

configure_package_config_file(
"${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in"
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)

add_library(cbrng INTERFACE)
target_include_directories(cbrng
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
install(EXPORT ${PROJECT_NAME}Targets
FILE ${PROJECT_NAME}Targets.cmake
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)

install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
)

# Install the include directory
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/openrand
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ OpenRAND is a C++ library designed to foster reproducible scientific research by


## Installation
OpenRAND is header only, so there is no need to install it. Simply copy the header files in `include/` directory into your project and you're good to go!
OpenRAND is header only, so there is no need to install it. You can simply copy the header files in `include/` directory into your project and you're good to go!

You can also install OpenRAND using CMake. To integrate OpenRAND into your CMake project, add the following lines to your CMakeLists.txt file:
You can also use CMake. To integrate OpenRAND into your CMake project, add the following lines to your CMakeLists.txt file:

```
include(FetchContent)
Expand All @@ -29,7 +29,11 @@ FetchContent_Declare(

FetchContent_MakeAvailable(crng)
```
If you're building OpenRAND and want to build the TestU01 statistical test suite, set CMake variable `TESTU01_PATH` to locally installed TestU01 library location. For practrand, pipe the output of `pract_rand*` executables (built bu default) to Practrand's `RNG_test` executable.

Alternatively, you can install OpenRAND by running `make install` from a build directory, and linking to it using `target_link_libraries(Executable PRIVATE OpenRAND::OpenRAND)` from an application.


You can optionally turn on tests, examples and benchmarks using `OpenRAND_ENABLE_TESTS`, `OpenRAND_ENABLE_EXAMPLES` and `OpenRAND_ENABLE_BENCHMARKS` flags respectively. If you want to build the TestU01 statistical test suite, set CMake variable `TESTU01_PATH` to locally installed TestU01 library location. For practrand, pipe the output of `pract_rand*` executables (built by default when testing enabled) to Practrand's `RNG_test` executable.


## Usage
Expand Down
14 changes: 6 additions & 8 deletions benchmarks/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
add_executable(raw_speed_cpu raw_speed_cpu.cpp)
target_include_directories(raw_speed_cpu PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(raw_speed_cpu benchmark::benchmark)

target_link_libraries(raw_speed_cpu PRIVATE benchmark::benchmark ${PROJECT_NAME})

include(CheckLanguage)
check_language(CUDA)
if (CMAKE_CUDA_COMPILER)
message(STATUS "CUDA FOUND: building CUDA benchmarks")
enable_language(CUDA)
add_executable(raw_speed_cuda raw_speed_cuda.cu)
target_include_directories(raw_speed_cuda PRIVATE ${CMAKE_SOURCE_DIR}/include)
message(STATUS "CUDA FOUND: Building CUDA benchmarks")
enable_language(CUDA)
add_executable(raw_speed_cuda raw_speed_cuda.cu)
target_link_libraries(raw_speed_cuda PRIVATE ${PROJECT_NAME})
else()
message(STATUS "skipping: CUDA benchmarks, CUDA not found")
message(STATUS "CUDA not found: Skipping CUDA benchmarks")
endif()
1 change: 0 additions & 1 deletion benchmarks/raw_speed_cuda.cu
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
#include <cub/cub.cuh>
#include <cuda.h>


#include <openrand/philox.h>
#include <openrand/threefry.h>
#include <openrand/squares.h>
Expand Down
4 changes: 4 additions & 0 deletions cmake/OpenRANDConfig.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
check_required_components("@PROJECT_NAME@")
33 changes: 23 additions & 10 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
# Add the executable
add_executable(basic_usage basic_usage.cpp)
add_executable(pi_openmp pi_openmp.cpp)
add_executable(serial_parallel_equivalence state_forwarding.cpp)
# OpenMP-dependent examples
find_package(OpenMP)
if(OpenMP_CXX_FOUND)
message(STATUS "OpenMP FOUND: Building OpenMP examples")

add_executable(serial_parallel_equivalence state_forwarding.cpp)
target_link_libraries(serial_parallel_equivalence PRIVATE OpenMP::OpenMP_CXX ${PROJECT_NAME})

add_executable(basic_usage basic_usage.cpp)
target_link_libraries(basic_usage PRIVATE OpenMP::OpenMP_CXX ${PROJECT_NAME})

add_executable(pi_openmp pi_openmp.cpp)
target_link_libraries(pi_openmp PRIVATE OpenMP::OpenMP_CXX ${PROJECT_NAME})
else()
message(STATUS "OpenMP not found: Skipping OpenMP examples")
endif()

# MPI examples
include(CheckLanguage)
check_language(CUDA)
if (CMAKE_CUDA_COMPILER)
message(STATUS "CUDA FOUND: building CUDA examples")
enable_language(CUDA)
add_executable(pi_cuda pi_cuda.cu)
target_include_directories(pi_cuda PRIVATE ${CMAKE_SOURCE_DIR}/include)
set_property(TARGET pi_cuda PROPERTY CUDA_STANDARD 17)
message(STATUS "CUDA FOUND: Building CUDA examples")
enable_language(CUDA)

add_executable(pi_cuda pi_cuda.cu)
target_link_libraries(pi_cuda PRIVATE ${PROJECT_NAME})
set_property(TARGET pi_cuda PROPERTY CUDA_STANDARD 17)
else()
message(STATUS "skipping: CUDA examples, CUDA not found")
message(STATUS "CUDA not found: Skipping CUDA examples")
endif()

22 changes: 11 additions & 11 deletions include/openrand/base_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class BaseRNG {
*
* @return uint32_t random number from a uniform distribution
*/
DEVICE result_type operator()() {
OPENRAND_DEVICE result_type operator()() {
return gen().template draw<uint32_t>();
}

Expand All @@ -81,7 +81,7 @@ class BaseRNG {
* @return T random number from a uniform distribution between 0 and 1
*/
template <typename T = float>
DEVICE T rand() {
OPENRAND_DEVICE T rand() {
if constexpr (sizeof(T) <= 4) {
const uint32_t x = gen().template draw<uint32_t>();
if constexpr (std::is_integral_v<T>)
Expand Down Expand Up @@ -109,7 +109,7 @@ class BaseRNG {
* @return T random number from a uniform distribution between a and b
*/
template <typename T = float>
DEVICE T uniform(const T low, const T high) {
OPENRAND_DEVICE T uniform(const T low, const T high) {
// TODO: Allow 64 bit integers
static_assert(!(std::is_integral_v<T> && sizeof(T) > sizeof(int32_t)),
"64 bit int not yet supported");
Expand All @@ -129,7 +129,7 @@ class BaseRNG {
* @Note This array is filled serially. `N` ideally should not be large.
*/
template <typename T = float>
DEVICE void fill_random(T *array, const int N) {
OPENRAND_DEVICE void fill_random(T *array, const int N) {
for (int i = 0; i < N; i++) array[i] = rand<T>();
}

Expand All @@ -145,7 +145,7 @@ class BaseRNG {
* @return T random number from a normal distribution with mean 0 and std 1
*/
template <typename T = float>
DEVICE T randn() {
OPENRAND_DEVICE T randn() {
static_assert(std::is_floating_point_v<T>);
constexpr T M_PI2 = 2 * static_cast<T>(M_PI);

Expand All @@ -163,7 +163,7 @@ class BaseRNG {
* @return T random number from a normal distribution with mean 0 and std 1
*/
template <typename T = float>
DEVICE vec2<T> randn2() {
OPENRAND_DEVICE vec2<T> randn2() {
// Implements box-muller method
static_assert(std::is_floating_point_v<T>);
constexpr T M_PI2 = 2 * static_cast<T>(M_PI);
Expand All @@ -185,7 +185,7 @@ class BaseRNG {
* @return T random number from a normal distribution with mean and std
*/
template <typename T = float>
DEVICE T randn(const T mean, const T std_dev) {
OPENRAND_DEVICE T randn(const T mean, const T std_dev) {
return mean + randn<T>() * std_dev;
}

Expand Down Expand Up @@ -213,7 +213,7 @@ class BaseRNG {
* 1 - (1-p)^32. For N=2^24, that value is 11.8%. For N=2^20, it's 0.8%.
*/
template <bool biased = true, typename T = int>
DEVICE T range(const T N) {
OPENRAND_DEVICE T range(const T N) {
// static_assert(std::is_integral_v<T> && sizeof(T) <= sizeof(int32_t),
// "64 bit int not yet supported");

Expand Down Expand Up @@ -250,7 +250,7 @@ class BaseRNG {
* scale b
*/
template <typename T = float>
DEVICE inline T gamma(T alpha, T b) {
OPENRAND_DEVICE inline T gamma(T alpha, T b) {
T d = alpha - T((1. / 3.));
T c = T(1.) / sqrt(9.f * d);
T v, x;
Expand Down Expand Up @@ -292,7 +292,7 @@ class BaseRNG {
* @brief Converts a random number integer to a floating point number between [0., 1.)
*/
template <typename Ftype, typename Utype>
inline DEVICE Ftype u01(const Utype in) const {
inline OPENRAND_DEVICE Ftype u01(const Utype in) const {
constexpr Ftype factor =
Ftype(1.) / (Ftype(~static_cast<Utype>(0)) + Ftype(1.));
constexpr Ftype halffactor = Ftype(0.5) * factor;
Expand All @@ -303,7 +303,7 @@ class BaseRNG {
/**
* @brief Returns a reference to the random number generator.
*/
DEVICE __inline__ RNG &gen() {
OPENRAND_DEVICE __inline__ RNG &gen() {
return *static_cast<RNG *>(this);
}

Expand Down
Loading