Skip to content

Commit fdfd6b8

Browse files
committed
std::inclusive_scan support detection, abstraction and basic testing
1 parent 8f2f63b commit fdfd6b8

File tree

9 files changed

+256
-30
lines changed

9 files changed

+256
-30
lines changed

cmake/checkcxx/inclusive_scan.cmake

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# only run this once
2+
include_guard(GLOBAL)
3+
4+
# CMake function to check if std::inclusive_scan is supported or not by the current CXX compiler
5+
function(check_inclusive_scan)
6+
7+
# Ensure c++ is enabled and supported
8+
include(CheckLanguage)
9+
check_language(CXX)
10+
11+
# If this has already been checked for the current CMakeCache, don't rerun.
12+
if(DEFINED EXATEPP_ABM_check_inclusive_scan_RESULT)
13+
return()
14+
endif()
15+
16+
# Try and compile the sample in c++17
17+
try_compile(
18+
HAS_STD_INCLUSIVE_SCAN
19+
"${CMAKE_CURRENT_BINARY_DIR}/try_compile/"
20+
"${CMAKE_CURRENT_LIST_DIR}/inclusive_scan.cpp"
21+
CXX_STANDARD 17
22+
CXX_STANDARD_REQUIRED "ON"
23+
)
24+
25+
# If notsupported, mark store this in the parent cache
26+
if (NOT HAS_STD_INCLUSIVE_SCAN)
27+
set(EXATEPP_ABM_check_inclusive_scan_RESULT "NO" PARENT_SCOPE)
28+
return()
29+
endif()
30+
set(EXATEPP_ABM_check_inclusive_scan_RESULT "YES" PARENT_SCOPE)
31+
endfunction()
32+
33+
check_inclusive_scan()

cmake/checkcxx/inclusive_scan.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Test if std::inclusive_scan is supported or not by the current compiler c++ stdlib.
2+
#include <numeric>
3+
#include <vector>
4+
5+
int main(int argc, char * argv[]) {
6+
std::vector<float> vec = {{0.f, 1.f, 2.f, 3.f}};
7+
std::inclusive_scan(vec.begin(), vec.end(), vec.begin());
8+
return 0;
9+
}

src/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ if(CMAKE_VERSION VERSION_LESS "3.25.0")
7070
target_compile_options(${LIBRARY_NAME} PUBLIC "$<$<COMPILE_LANGUAGE:CUDA>:SHELL:-Xcudafe --diag_suppress=128>")
7171
endif()
7272

73+
# Check for std::inclusive_scan, and set a public definition as required
74+
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/checkcxx/inclusive_scan.cmake)
75+
if (EXATEPP_ABM_check_inclusive_scan_RESULT)
76+
target_compile_definitions(${LIBRARY_NAME} PUBLIC "-DEXATEPP_ABM_USE_STD_INCLUSIVE_SCAN=1")
77+
else()
78+
target_compile_definitions(${LIBRARY_NAME} PUBLIC "-DEXATEPP_ABM_USE_STD_INCLUSIVE_SCAN=0")
79+
endif()
80+
81+
7382
# Enable host device constexpr?
7483
target_compile_options(${LIBRARY_NAME} PUBLIC "$<$<COMPILE_LANGUAGE:CUDA>:--expt-relaxed-constexpr>")
7584

src/exateppabm/population.cu

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "exateppabm/disease.h"
1313
#include "exateppabm/person.h"
1414
#include "exateppabm/input.h"
15+
#include "exateppabm/util.h"
1516

1617
namespace exateppabm {
1718
namespace population {
@@ -22,25 +23,6 @@ namespace {
2223

2324
std::array<std::uint64_t, demographics::AGE_COUNT> infectedPerDemographic = {};
2425

25-
26-
/**
27-
* in-place inclusive scan, for libstdc++ which does not support c++17 (i.e. GCC 8)
28-
*/
29-
template <typename T>
30-
void inplace_inclusive_scan(T& container) {
31-
// @todo - use cmake to detect which path needs taking
32-
// std::inclusive_scan(container.begin(), container.end(), container.begin());
33-
// return;
34-
// @todo - refactor this into a testable method, in a util namespace?
35-
if (container.size() <= 1) {
36-
return;
37-
}
38-
// Naive in-place inclusive scan for libstc++8
39-
for (size_t i = 1; i < container.size(); ++i) {
40-
container[i] = container[i - 1] + container[i];
41-
}
42-
}
43-
4426
} // namespace
4527

4628
std::unique_ptr<flamegpu::AgentVector> generate(flamegpu::ModelDescription& model, const exateppabm::input::config config, const bool verbose, const float env_width, const float interactionRadius) {
@@ -85,7 +67,7 @@ std::unique_ptr<flamegpu::AgentVector> generate(flamegpu::ModelDescription& mode
8567
}};
8668
// Perform an inclusive scan to convert to cumulative probability
8769
// Using a local method which supports inclusive scans in old libstc++
88-
inplace_inclusive_scan(demographicProbabilties);
70+
exateppabm::util::inplace_inclusive_scan(demographicProbabilties);
8971
// std::inclusive_scan(demographicProbabilties.begin(), demographicProbabilties.end(), demographicProbabilties.begin());
9072
std::array<demographics::Age, demographics::AGE_COUNT> allDemographics = {{
9173
demographics::Age::AGE_0_9,

src/exateppabm/util.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <cstdint>
44
#include <string>
5+
#include <numeric>
56

67
namespace exateppabm {
78
namespace util {
@@ -54,5 +55,42 @@ bool getSeatbeltsEnabled();
5455
*/
5556
std::string getCMakeBuildType();
5657

58+
59+
/**
60+
* Perform an inclusive scan on an interable (i.e. std::array<float>), without using std::inclusive_scan which although part of c++17 is not available in some older stdlib implementations (i.e. gcc 8).
61+
*/
62+
63+
/**
64+
* In-place inclusive scan, for libstdc++ which does not support c++17 (i.e. GCC 8)
65+
*
66+
* equivalent to std::inclusive_scan(container.begin(), container.end(), container.begin());
67+
*
68+
* @param container iterable container / something with size() and operator[]
69+
*/
70+
template <typename T>
71+
void naive_inplace_inclusive_scan(T& container) {
72+
if (container.size() <= 1) {
73+
return;
74+
}
75+
// Naive in-place inclusive scan for libstc++8
76+
for (size_t i = 1; i < container.size(); ++i) {
77+
container[i] = container[i - 1] + container[i];
78+
}
79+
}
80+
81+
/**
82+
* In-place inclusive scan, using std::inclusive_scan if possible, else a naive implementation.
83+
*
84+
* equivalent to std::inclusive_scan(container.begin(), container.end(), container.begin());
85+
*/
86+
template <typename T>
87+
void inplace_inclusive_scan(T& container) {
88+
#if defined(EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN) && EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN
89+
std::inclusive_scan(container.begin(), container.end(), container.begin());
90+
#else
91+
naive_inplace_inclusive_scan(container);
92+
#endif // defined(EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN) && EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN
93+
}
94+
5795
} // namespace util
5896
} // namespace exateppabm

tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ include(${FLAMEGPU_ROOT}/cmake/common.cmake)
3232
# List test source files
3333
set(TESTS_SRC
3434
${CMAKE_CURRENT_SOURCE_DIR}/src/main.cu
35-
${CMAKE_CURRENT_SOURCE_DIR}/src/temp.cu
35+
${CMAKE_CURRENT_SOURCE_DIR}/src/exateppabm/test_util.cu
3636
)
3737

3838
# Define output location of binary files

tests/src/exateppabm/test_util.cu

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#include <array>
2+
#include <numeric>
3+
#include <vector>
4+
5+
#include "gtest/gtest.h"
6+
7+
#include "fmt/core.h"
8+
#include "exateppabm/util.h"
9+
10+
/**
11+
* Test the naive inplace inclusive_scan implementation for an array of integers
12+
*/
13+
TEST(TestUtil, naive_inplace_inclusive_scan_array_int) {
14+
constexpr std::uint32_t ELEMENTS = 4;
15+
std::array<int, ELEMENTS> inout = {{1, 2, 3, 4}};
16+
const std::array<int, ELEMENTS> expected = {{1, 3, 6, 10}};
17+
18+
exateppabm::util::naive_inplace_inclusive_scan(inout);
19+
20+
for (std::uint32_t e = 0; e < inout.size(); e++) {
21+
EXPECT_EQ(inout[e], expected[e]);
22+
}
23+
}
24+
25+
/**
26+
* Test the naive inplace inclusive_scan implementation for a vector of unsigned integers
27+
*/
28+
TEST(TestUtil, naive_inplace_inclusive_scan_vec_uint) {
29+
std::vector<std::uint32_t> inout = {{1, 2, 3, 4}};
30+
const std::vector<std::uint32_t> expected = {{1, 3, 6, 10}};
31+
32+
exateppabm::util::naive_inplace_inclusive_scan(inout);
33+
34+
for (std::uint32_t e = 0; e < inout.size(); e++) {
35+
EXPECT_EQ(inout[e], expected[e]);
36+
}
37+
}
38+
39+
/**
40+
* Test the naive inplace inclusive_scan implementation for a vector of floats, using an arbitrary epsilon value which is good enough for the input values
41+
*/
42+
TEST(TestUtil, naive_inplace_inclusive_scan_vec_double) {
43+
std::vector<float> inout = {{1.1, 2.2, 3.3, 4.4}};
44+
const std::vector<float> expected = {{1.1, 3.3, 6.6, 11.0}};
45+
46+
exateppabm::util::naive_inplace_inclusive_scan(inout);
47+
48+
constexpr float EPSILON = 1.0e-6f;
49+
50+
for (float e = 0; e < inout.size(); e++) {
51+
EXPECT_NEAR(inout[e], expected[e], EPSILON);
52+
}
53+
}
54+
55+
/**
56+
* Test the naive or std inplace inclusive_scan implementation for an array of integers
57+
*/
58+
TEST(TestUtil, inplace_inclusive_scan_array_int) {
59+
constexpr std::uint32_t ELEMENTS = 4;
60+
std::array<int, ELEMENTS> inout = {{1, 2, 3, 4}};
61+
const std::array<int, ELEMENTS> expected = {{1, 3, 6, 10}};
62+
63+
exateppabm::util::inplace_inclusive_scan(inout);
64+
65+
for (std::uint32_t e = 0; e < inout.size(); e++) {
66+
EXPECT_EQ(inout[e], expected[e]);
67+
}
68+
}
69+
70+
/**
71+
* Test the naive or std inplace inclusive_scan implementation for a vector of unsigned integers
72+
*/
73+
TEST(TestUtil, inplace_inclusive_scan_vec_uint) {
74+
std::vector<std::uint32_t> inout = {{1, 2, 3, 4}};
75+
const std::vector<std::uint32_t> expected = {{1, 3, 6, 10}};
76+
77+
exateppabm::util::inplace_inclusive_scan(inout);
78+
79+
for (std::uint32_t e = 0; e < inout.size(); e++) {
80+
EXPECT_EQ(inout[e], expected[e]);
81+
}
82+
}
83+
84+
/**
85+
* Test the naive or std inplace inclusive_scan implementation for a vector of floats, using an arbitrary epsilon value which is good enough for the input values
86+
*/
87+
TEST(TestUtil, inplace_inclusive_scan_vec_double) {
88+
std::vector<float> inout = {{1.1, 2.2, 3.3, 4.4}};
89+
const std::vector<float> expected = {{1.1, 3.3, 6.6, 11.0}};
90+
91+
exateppabm::util::inplace_inclusive_scan(inout);
92+
93+
constexpr float EPSILON = 1.0e-6f;
94+
95+
for (float e = 0; e < inout.size(); e++) {
96+
EXPECT_NEAR(inout[e], expected[e], EPSILON);
97+
}
98+
}
99+
100+
101+
/**
102+
* Test the std inplace inclusive_scan implementation for an array of integers
103+
This is just to check the test case behaves too
104+
*/
105+
#if defined(EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN) && EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN
106+
TEST(TestUtil, std_inclusive_scan_array_int) {
107+
constexpr std::uint32_t ELEMENTS = 4;
108+
std::array<int, ELEMENTS> inout = {{1, 2, 3, 4}};
109+
const std::array<int, ELEMENTS> expected = {{1, 3, 6, 10}};
110+
111+
std::inclusive_scan(inout.begin(), inout.end(), inout.begin());
112+
113+
for (std::uint32_t e = 0; e < inout.size(); e++) {
114+
EXPECT_EQ(inout[e], expected[e]);
115+
}
116+
}
117+
#else
118+
TEST(TestUtil, DISABLED_std_inclusive_scan_array_int) {
119+
}
120+
#endif
121+
122+
123+
/**
124+
* Test the std inplace inclusive_scan implementation for a vector of unsigned integers.
125+
* This is just to check the test case behaves too
126+
*/
127+
#if defined(EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN) && EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN
128+
TEST(TestUtil, std_inclusive_scan_vec_uint) {
129+
std::vector<std::uint32_t> inout = {{1, 2, 3, 4}};
130+
const std::vector<std::uint32_t> expected = {{1, 3, 6, 10}};
131+
132+
std::inclusive_scan(inout.begin(), inout.end(), inout.begin());
133+
134+
for (std::uint32_t e = 0; e < inout.size(); e++) {
135+
EXPECT_EQ(inout[e], expected[e]);
136+
}
137+
}
138+
#else
139+
TEST(TestUtil, DISABLED_std_inclusive_scan_vec_uint) {
140+
}
141+
#endif
142+
143+
/**
144+
* Test the std inplace inclusive_scan implementation for a vector of floats, using an arbitrary epsilon value which is good enough for the input values.
145+
* This is just to check the test case behaves too
146+
*/
147+
#if defined(EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN) && EXATEPP_ABM_USE_STD_INCLUSIVE_SCAN
148+
TEST(TestUtil, std_inclusive_scan_vec_double) {
149+
std::vector<float> inout = {{1.1, 2.2, 3.3, 4.4}};
150+
const std::vector<float> expected = {{1.1, 3.3, 6.6, 11.0}};
151+
152+
std::inclusive_scan(inout.begin(), inout.end(), inout.begin());
153+
154+
constexpr float EPSILON = 1.0e-6f;
155+
156+
for (float e = 0; e < inout.size(); e++) {
157+
EXPECT_NEAR(inout[e], expected[e], EPSILON);
158+
}
159+
}
160+
#else
161+
TEST(TestUtil, DISABLED_std_inclusive_scan_vec_double) {
162+
}
163+
#endif

tests/src/main.cu

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#include <cstdio>
2-
// Include google test
2+
// Include google test
33
#include "gtest/gtest.h"
44
// Include flame gpu telemetry header to prevent every test from triggering a telemetry API call.
55
#include "flamegpu/io/Telemetry.h"

tests/src/temp.cu

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)