Skip to content

Commit f036036

Browse files
authored
Merge pull request #1196 from LLNL/task/rhornung67/device-numeric-limits
Task/rhornung67/device numeric limits
2 parents c284875 + e2602cb commit f036036

37 files changed

+369
-142
lines changed

src/axom/core/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ set(core_headers
6060
Macros.hpp
6161
Map.hpp
6262
FlatMap.hpp
63+
NumericLimits.hpp
6364
Path.hpp
6465
RangeAdapter.hpp
6566
StackArray.hpp

src/axom/core/NumericLimits.hpp

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and
2+
// other Axom Project Developers. See the top-level LICENSE file for details.
3+
//
4+
// SPDX-License-Identifier: (BSD-3-Clause)
5+
6+
/*!
7+
*
8+
* \file NumericLimits.hpp
9+
*
10+
* \brief Header file containing portability layer for std::numeric_limits
11+
* capabilities
12+
*
13+
*/
14+
15+
#ifndef AXOM_NUMERICLIMITS_HPP_
16+
#define AXOM_NUMERICLIMITS_HPP_
17+
18+
#include "axom/config.hpp" // for compile-time definitions
19+
20+
#include <limits>
21+
22+
#if defined(AXOM_USE_CUDA)
23+
#include <cuda/std/limits>
24+
#endif
25+
26+
namespace axom
27+
{
28+
#if defined(AXOM_USE_CUDA)
29+
// Note: cuda::std types work in host and device code as long as Axom is
30+
// configured with CUDA enabled. No need to rely on two different
31+
// header files in that case.
32+
template <typename T>
33+
using numeric_limits = cuda::std::numeric_limits<T>;
34+
#else
35+
template <typename T>
36+
using numeric_limits = std::numeric_limits<T>;
37+
#endif
38+
39+
} // namespace axom
40+
41+
#endif // AXOM_NUMERICLIMITS_HPP_

src/axom/core/tests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set(core_serial_tests
2626
core_map.hpp
2727
core_flatmap.hpp
2828
core_memory_management.hpp
29+
core_numeric_limits.hpp
2930
core_Path.hpp
3031
core_stack_array.hpp
3132
core_static_array.hpp

src/axom/core/tests/core_bit_utilities.hpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "axom/config.hpp"
99
#include "axom/core/Types.hpp"
10+
#include "axom/core/NumericLimits.hpp"
1011
#include "axom/core/utilities/Utilities.hpp"
1112
#include "axom/core/utilities/BitUtilities.hpp"
1213

@@ -22,7 +23,7 @@ T random_int()
2223
{
2324
static_assert(std::is_integral<T>::value, "T must be an integral type");
2425

25-
constexpr T max_int = std::numeric_limits<T>::max();
26+
constexpr T max_int = axom::numeric_limits<T>::max();
2627
constexpr double max_d = static_cast<double>(max_int);
2728

2829
const auto val = axom::utilities::random_real(0., max_d);
+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and
2+
// other Axom Project Developers. See the top-level LICENSE file for details.
3+
//
4+
// SPDX-License-Identifier: (BSD-3-Clause)
5+
6+
#include "axom/config.hpp" // for compile time definitions
7+
8+
#include "axom/core/NumericLimits.hpp"
9+
10+
// for gtest macros
11+
#include "gtest/gtest.h"
12+
13+
//------------------------------------------------------------------------------
14+
// UNIT TESTS
15+
//------------------------------------------------------------------------------
16+
17+
//------------------------------------------------------------------------------
18+
TEST(core_NumericLimits, check_CPU)
19+
{
20+
//
21+
// Tests to compare axom::numeric_limits to std::numeric_limits
22+
// to ensure that Axom type aliasing is correct.
23+
//
24+
EXPECT_TRUE(axom::numeric_limits<int>::lowest() ==
25+
std::numeric_limits<int>::lowest());
26+
EXPECT_TRUE(axom::numeric_limits<int>::min() == std::numeric_limits<int>::min());
27+
EXPECT_TRUE(axom::numeric_limits<int>::max() == std::numeric_limits<int>::max());
28+
EXPECT_TRUE(axom::numeric_limits<int>::is_signed ==
29+
std::numeric_limits<int>::is_signed);
30+
31+
EXPECT_TRUE(axom::numeric_limits<float>::lowest() ==
32+
std::numeric_limits<float>::lowest());
33+
EXPECT_TRUE(axom::numeric_limits<float>::min() ==
34+
std::numeric_limits<float>::min());
35+
EXPECT_TRUE(axom::numeric_limits<float>::max() ==
36+
std::numeric_limits<float>::max());
37+
38+
EXPECT_TRUE(axom::numeric_limits<double>::lowest() ==
39+
std::numeric_limits<double>::lowest());
40+
EXPECT_TRUE(axom::numeric_limits<double>::min() ==
41+
std::numeric_limits<double>::min());
42+
EXPECT_TRUE(axom::numeric_limits<double>::max() ==
43+
std::numeric_limits<double>::max());
44+
}
45+
46+
//------------------------------------------------------------------------------
47+
#if defined(AXOM_USE_CUDA)
48+
//
49+
// Tests to ensure axom::numeric_limits type alias does the correct thing
50+
// in host and CUDA device code.
51+
//
52+
53+
//
54+
// Simple device kernel
55+
//
56+
__global__ void cuda_kernel(int* a, size_t* b, float* c, double* d)
57+
{
58+
a[0] = axom::numeric_limits<int>::min();
59+
b[0] = axom::numeric_limits<size_t>::max();
60+
c[0] = axom::numeric_limits<float>::lowest();
61+
d[0] = axom::numeric_limits<double>::max();
62+
}
63+
64+
TEST(core_NumericLimits, check_CUDA)
65+
{
66+
//
67+
// Device memory allocation and initialiation for a few different types.
68+
//
69+
int* a;
70+
(void)cudaMalloc(&a, sizeof(int));
71+
(void)cudaMemset(a, 0, sizeof(int));
72+
73+
size_t* b;
74+
(void)cudaMalloc(&b, sizeof(size_t));
75+
(void)cudaMemset(b, 0, sizeof(size_t));
76+
77+
float* c;
78+
(void)cudaMalloc(&c, sizeof(float));
79+
(void)cudaMemset(c, 0, sizeof(float));
80+
81+
double* d;
82+
(void)cudaMalloc(&d, sizeof(double));
83+
(void)cudaMemset(d, 0, sizeof(double));
84+
85+
//
86+
// Set values in device code.
87+
//
88+
cuda_kernel<<<1, 1>>>(a, b, c, d);
89+
90+
//
91+
// Copy device values back to host and compare with expectations....
92+
//
93+
int ha;
94+
size_t hb;
95+
float hc;
96+
double hd;
97+
(void)cudaMemcpy(&ha, a, sizeof(int), cudaMemcpyDeviceToHost);
98+
(void)cudaMemcpy(&hb, b, sizeof(size_t), cudaMemcpyDeviceToHost);
99+
(void)cudaMemcpy(&hc, c, sizeof(float), cudaMemcpyDeviceToHost);
100+
(void)cudaMemcpy(&hd, d, sizeof(double), cudaMemcpyDeviceToHost);
101+
102+
EXPECT_TRUE(ha == axom::numeric_limits<int>::min());
103+
EXPECT_TRUE(hb == axom::numeric_limits<size_t>::max());
104+
EXPECT_TRUE(hc == axom::numeric_limits<float>::lowest());
105+
EXPECT_TRUE(hd == axom::numeric_limits<double>::max());
106+
}
107+
#endif
108+
109+
//------------------------------------------------------------------------------
110+
#if defined(AXOM_USE_HIP)
111+
//
112+
// Tests to ensure axom::numeric_limits type alias does the correct thing
113+
// in host and CUDA device code.
114+
//
115+
116+
//
117+
// Simple device kernel
118+
//
119+
__global__ void hip_kernel(int* a, size_t* b, float* c, double* d)
120+
{
121+
a[0] = axom::numeric_limits<int>::min();
122+
b[0] = axom::numeric_limits<size_t>::max();
123+
c[0] = axom::numeric_limits<float>::lowest();
124+
d[0] = axom::numeric_limits<double>::max();
125+
}
126+
127+
TEST(core_NumericLimits, check_HIP)
128+
{
129+
//
130+
// Device memory allocation and initialiation for a few different types.
131+
//
132+
int* a;
133+
(void)hipMalloc(&a, sizeof(int));
134+
(void)hipMemset(a, 0, sizeof(int));
135+
136+
size_t* b;
137+
(void)hipMalloc(&b, sizeof(size_t));
138+
(void)hipMemset(b, 0, sizeof(size_t));
139+
140+
float* c;
141+
(void)hipMalloc(&c, sizeof(float));
142+
(void)hipMemset(c, 0, sizeof(float));
143+
144+
double* d;
145+
(void)hipMalloc(&d, sizeof(double));
146+
(void)hipMemset(d, 0, sizeof(double));
147+
148+
//
149+
// Set values in device code.
150+
//
151+
hip_kernel<<<1, 1>>>(a, b, c, d);
152+
153+
//
154+
// Copy device values back to host and compare with expectations....
155+
//
156+
int ha;
157+
size_t hb;
158+
float hc;
159+
double hd;
160+
(void)hipMemcpy(&ha, a, sizeof(int), hipMemcpyDeviceToHost);
161+
(void)hipMemcpy(&hb, b, sizeof(size_t), hipMemcpyDeviceToHost);
162+
(void)hipMemcpy(&hc, c, sizeof(float), hipMemcpyDeviceToHost);
163+
(void)hipMemcpy(&hd, d, sizeof(double), hipMemcpyDeviceToHost);
164+
165+
EXPECT_TRUE(ha == axom::numeric_limits<int>::min());
166+
EXPECT_TRUE(hb == axom::numeric_limits<size_t>::max());
167+
EXPECT_TRUE(hc == axom::numeric_limits<float>::lowest());
168+
EXPECT_TRUE(hd == axom::numeric_limits<double>::max());
169+
}
170+
#endif

src/axom/core/tests/core_serial_main.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "core_map.hpp"
1919
#include "core_flatmap.hpp"
2020
#include "core_memory_management.hpp"
21+
#include "core_numeric_limits.hpp"
2122
#include "core_Path.hpp"
2223
#include "core_stack_array.hpp"
2324
#include "core_static_array.hpp"

src/axom/core/tests/core_types.hpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
#include "axom/config.hpp"
88
#include "axom/core/Types.hpp"
99
#include "axom/core/Macros.hpp"
10+
#include "axom/core/NumericLimits.hpp"
1011

1112
// gtest includes
1213
#include "gtest/gtest.h"
1314

1415
// C/C++ includes
15-
#include <limits> // for std::numeric_limits
1616
#include <type_traits> // for std::is_same, std::is_integral, etc.
1717

1818
#ifndef AXOM_USE_MPI
@@ -62,7 +62,7 @@ void check_real_type(std::size_t expected_num_bytes,
6262
MPI_Datatype expected_mpi_type)
6363
{
6464
EXPECT_TRUE(std::is_floating_point<RealType>::value);
65-
EXPECT_TRUE(std::numeric_limits<RealType>::is_signed);
65+
EXPECT_TRUE(axom::numeric_limits<RealType>::is_signed);
6666
EXPECT_EQ(sizeof(RealType), expected_num_bytes);
6767

6868
check_mpi_type<RealType>(expected_num_bytes, expected_mpi_type);
@@ -75,9 +75,9 @@ void check_integral_type(std::size_t expected_num_bytes,
7575
int expected_num_digits,
7676
MPI_Datatype expected_mpi_type)
7777
{
78-
EXPECT_TRUE(std::numeric_limits<IntegralType>::is_integer);
79-
EXPECT_EQ(std::numeric_limits<IntegralType>::is_signed, is_signed);
80-
EXPECT_EQ(std::numeric_limits<IntegralType>::digits, expected_num_digits);
78+
EXPECT_TRUE(axom::numeric_limits<IntegralType>::is_integer);
79+
EXPECT_EQ(axom::numeric_limits<IntegralType>::is_signed, is_signed);
80+
EXPECT_EQ(axom::numeric_limits<IntegralType>::digits, expected_num_digits);
8181
EXPECT_EQ(sizeof(IntegralType), expected_num_bytes);
8282

8383
check_mpi_type<IntegralType>(expected_num_bytes, expected_mpi_type);

src/axom/core/tests/numerics_floating_point_limits.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,7 @@ TEST(numerics_floating_point_limits, consistency_with_standard_numeric_limits)
4747
{
4848
check_type_limits<float>("float");
4949
check_type_limits<double>("double");
50+
#if !defined(AXOM_DEVICE_CODE)
5051
check_type_limits<long double>("long double");
52+
#endif
5153
}

src/axom/inlet/examples/documentation_generation.cpp

+9-9
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
// usage : ./inlet_documentation_generation_example --enableDocs --fil lua_file.lua
77

88
#include "axom/inlet.hpp"
9+
#include "axom/core/NumericLimits.hpp"
910
#include "axom/slic/core/SimpleLogger.hpp"
1011

1112
#include "axom/CLI11.hpp"
1213
#include <iostream>
13-
#include <limits>
1414

1515
using axom::inlet::Inlet;
1616
using axom::inlet::LuaReader;
@@ -68,17 +68,17 @@ void defineSchema(Inlet& inlet)
6868
filename_field.required();
6969

7070
inlet.addInt("thermal_solver/mesh/serial", "number of serial refinements")
71-
.range(0, std::numeric_limits<int>::max())
71+
.range(0, axom::numeric_limits<int>::max())
7272
.defaultValue(1);
7373

7474
// The description for thermal_solver/mesh/parallel is left unspecified
7575
inlet.addInt("thermal_solver/mesh/parallel")
76-
.range(1, std::numeric_limits<int>::max())
76+
.range(1, axom::numeric_limits<int>::max())
7777
.defaultValue(1);
7878

7979
inlet.addInt("thermal_solver/order", "polynomial order")
8080
.required()
81-
.range(1, std::numeric_limits<int>::max());
81+
.range(1, axom::numeric_limits<int>::max());
8282

8383
auto& timestep_field =
8484
inlet.addString("thermal_solver/timestepper", "thermal solver timestepper");
@@ -109,13 +109,13 @@ void defineSchema(Inlet& inlet)
109109
solver_schema.addDouble("rel_tol", "solver relative tolerance");
110110
rel_tol_field.required(false);
111111
rel_tol_field.defaultValue(1.e-6);
112-
rel_tol_field.range(0.0, std::numeric_limits<double>::max());
112+
rel_tol_field.range(0.0, axom::numeric_limits<double>::max());
113113

114114
auto& abs_tol_field =
115115
solver_schema.addDouble("abs_tol", "solver absolute tolerance");
116116
abs_tol_field.required(true);
117117
abs_tol_field.defaultValue(1.e-12);
118-
abs_tol_field.range(0.0, std::numeric_limits<double>::max());
118+
abs_tol_field.range(0.0, axom::numeric_limits<double>::max());
119119

120120
auto& print_level_field =
121121
solver_schema.addInt("print_level", "solver print/debug level");
@@ -127,18 +127,18 @@ void defineSchema(Inlet& inlet)
127127
solver_schema.addInt("max_iter", "maximum iteration limit");
128128
max_iter_field.required(false);
129129
max_iter_field.defaultValue(100);
130-
max_iter_field.range(1, std::numeric_limits<int>::max());
130+
max_iter_field.range(1, axom::numeric_limits<int>::max());
131131

132132
auto& dt_field = solver_schema.addDouble("dt", "time step");
133133
dt_field.required(true);
134134
dt_field.defaultValue(1);
135-
dt_field.range(0.0, std::numeric_limits<double>::max());
135+
dt_field.range(0.0, axom::numeric_limits<double>::max());
136136

137137
auto& steps_field =
138138
solver_schema.addInt("steps", "number of steps/cycles to take");
139139
steps_field.required(true);
140140
steps_field.defaultValue(1);
141-
steps_field.range(1, std::numeric_limits<int>::max());
141+
steps_field.range(1, axom::numeric_limits<int>::max());
142142
}
143143

144144
// Checking the contents of the passed inlet

0 commit comments

Comments
 (0)