libMesh
Loading...
Searching...
No Matches
kokkos_vector_ops_oracle_fixtures.h
Go to the documentation of this file.
1#ifndef KOKKOS_VECTOR_OPS_ORACLE_FIXTURES_H
2#define KOKKOS_VECTOR_OPS_ORACLE_FIXTURES_H
3
4// Fixtures for the Kokkos vector-ops oracle tests.
5//
6// The oracle strategy: build_host_oracle() computes every result with
7// the established host implementations (the owning types' operators
8// plus free functions like solid_angle()), and the runner's device
9// kernel recomputes the same quantities with the gpu/ operations -
10// mostly through Kokkos storage-backed refs, with some (like the solid
11// angles) using owning values built on the device. Agreement within
12// tolerance validates the gpu/ algebra against the host algebra.
13//
14// The host and device sides communicate only through result *position*:
15// build_host_oracle() pushes results in a fixed order and the kernel in
16// kokkos_vector_ops_oracle_runners.h stores its results in the same
17// order, with the expected counts asserted on both sides. When adding
18// a result, update both sides and the count constants together.
19
20#include "libmesh/libmesh.h"
21#include "libmesh/tensor_value.h"
22#include "libmesh/type_vector.h"
23#include "libmesh/vector_value.h"
25#include "gpu/kokkos_storage.h"
27
29
30#include <type_traits>
31#include <vector>
32
33namespace libMeshTest
34{
35namespace KokkosVectorOracle
36{
37
38using libMesh::Real;
39
40static constexpr double tol = 2.0e-13;
41
42static constexpr double unit_tol = 1.0e-14;
43// For the icosahedron-vertex solid angle case below.
44static constexpr Real golden_ratio = 1.6180339887498948482;
45// Result counts grow with LIBMESH_DIM: higher dimensions unlock more
46// solid angle configurations, and cross products only exist in 3D.
47static constexpr unsigned int solid_angle_results =
48 1 + ((LIBMESH_DIM > 1) ? 2u : 0u) + ((LIBMESH_DIM > 2) ? 4u : 0u);
49static constexpr unsigned int vector_results =
50 11 + ((LIBMESH_DIM > 2) ? 2u : 0u);
51static constexpr unsigned int scalar_results = 12 + solid_angle_results;
52
53// Builds a Vec from up-to-3 components, quietly dropping the ones
54// beyond LIBMESH_DIM so the same fixture data works in 1D/2D/3D builds.
55template <typename Vec>
56LIBMESH_DEVICE_INLINE
57Vec
58make_vector(const Real x, const Real y = 0, const Real z = 0)
59{
60 Vec v;
61 v.zero();
62 v(0) = x;
63#if LIBMESH_DIM > 1
64 v(1) = y;
65#endif
66#if LIBMESH_DIM > 2
67 v(2) = z;
68#endif
69 return v;
70}
71
72// The host reference helpers solid_angle() and cross_norm_sq() take
73// TypeVector arguments, so these adapt whichever Vec the oracle is
74// instantiated with.
77{
78 return v;
79}
80
83{
84 return make_vector<libMesh::TypeVector<Real>>(v(0)
85#if LIBMESH_DIM > 1
86 ,
87 v(1)
88#endif
89#if LIBMESH_DIM > 2
90 ,
91 v(2)
92#endif
93 );
94}
95
96// Host-computed reference results, in the fixed order the device
97// kernel must reproduce.
98template <typename Vec>
100{
101 std::vector<Vec> vectors;
102 std::vector<Real> scalars;
103};
104
105// One input triple (a, b, c) per case. Components beyond LIBMESH_DIM
106// are zero, and each case is gated so e.g. a 1D build only exercises
107// collinear data.
109{
110 const char * name;
111 Real ax, ay, az;
112 Real bx, by, bz;
113 Real cx, cy, cz;
114};
115
116static const vector_case cases[] = {
117#if LIBMESH_DIM >= 1
118 { "line_case_a", 2.0, 0.0, 0.0, -3.0, 0.0, 0.0, 0.5, 0.0, 0.0 },
119 { "line_case_b", -1.25, 0.0, 0.0, 4.5, 0.0, 0.0, -2.0, 0.0, 0.0 },
120#endif
121#if LIBMESH_DIM >= 2
122 { "plane_case_a", 2.0, 3.0, 0.0, 5.0, -6.0, 0.0, 1.25, -0.5, 0.0 },
123 { "plane_case_b", -1.0, 4.0, 0.0, 0.5, 2.5, 0.0, -3.0, 1.5, 0.0 },
124#endif
125#if LIBMESH_DIM >= 3
126 { "space_case_a", 2.0, 3.0, 4.0, 5.0, -6.0, 7.0, 1.25, -0.5, 2.0 },
127 { "space_case_b", -1.0, 4.0, 0.75, 0.5, 2.5, -3.5, -3.0, 1.5, 2.25 },
128#endif
129};
130
131// Computes the reference results with the owning types' own operators.
132// The device kernel in kokkos_vector_ops_oracle_runners.h evaluates the
133// equivalent ref expressions in the
134// same order.
135template <typename Vec>
136inline host_oracle<Vec>
137build_host_oracle(const Vec & a, const Vec & b, const Vec & c)
138{
139 host_oracle<Vec> result;
140 result.vectors.reserve(vector_results);
141 result.scalars.reserve(scalar_results);
142
143 const auto copied = a;
144
145 Vec mix = a + b;
146 mix -= c;
147
148 Vec scaled = 1.25 * a;
149 scaled += (-0.5) * b;
150 scaled += (0.25) * c;
151
152 Vec plus_assign = a;
153 plus_assign += b;
154
155 Vec minus_assign = a;
156 minus_assign -= b;
157
158 Vec accum;
159 accum.zero();
160 accum.add_scaled(a, 1.25);
161 accum.add_scaled(b, -0.5);
162 accum.subtract_scaled(c, -0.25);
163
164 const auto divided = a / 5.0;
165
166 // The device side computes these as scalar products (5.0 * a_ref and
167 // a_ref * 5.0); for real scalars the vector/scalar outer_product() is
168 // the same thing, so it doubles as the host reference.
169 const auto outer_right = libMesh::outer_product(a, 5.0);
170 const auto outer_left = libMesh::outer_product(5.0, a);
171
172 Vec mult_assign = a;
173 mult_assign *= 5.0;
174
175 Vec div_assign = a;
176 div_assign /= 5.0;
177
178 Vec assign_zero = a;
179 assign_zero = 0.0;
180
181 result.vectors.push_back(copied);
182 result.vectors.push_back(mix);
183 result.vectors.push_back(scaled);
184 result.vectors.push_back(accum);
185 result.vectors.push_back(plus_assign);
186 result.vectors.push_back(minus_assign);
187 result.vectors.push_back(divided);
188 result.vectors.push_back(outer_right);
189 result.vectors.push_back(outer_left);
190 result.vectors.push_back(mult_assign);
191 result.vectors.push_back(div_assign);
192
193 // a * b appears twice because the kernel records the dot product both
194 // via operator* and via vector_dot().
195 result.scalars.push_back(a * b);
196 result.scalars.push_back(a * b);
197 result.scalars.push_back(a.contract(b));
198 result.scalars.push_back(mix.norm());
199 result.scalars.push_back(mix.norm_sq());
200 result.scalars.push_back(make_vector<Vec>(0.0, 0.0, 0.0).is_zero() ? 1.0 : 0.0);
201 result.scalars.push_back(mix.is_zero() ? 1.0 : 0.0);
202 result.scalars.push_back((a == a) ? 1.0 : 0.0);
203 result.scalars.push_back((a == b) ? 1.0 : 0.0);
204 result.scalars.push_back((a != a) ? 1.0 : 0.0);
205 result.scalars.push_back((a != b) ? 1.0 : 0.0);
206 result.scalars.push_back(assign_zero.is_zero() ? 1.0 : 0.0);
207
208 // Solid angle cases, from degenerate (all edges collinear) through
209 // planar to genuinely 3D configurations; the icosahedron-vertex case
210 // exercises a nontrivial angle with an analytically clean geometry.
211 const auto xvec = make_vector<Vec>(1.3);
212 result.scalars.push_back(libMesh::solid_angle(as_type_vector(xvec),
213 as_type_vector(xvec),
214 as_type_vector(xvec)));
215
216#if LIBMESH_DIM > 1
217 const auto yvec = make_vector<Vec>(0.0, 2.7);
218 const auto xydiag = make_vector<Vec>(3.1, 3.1);
219 result.scalars.push_back(libMesh::solid_angle(as_type_vector(xvec),
220 as_type_vector(xvec),
221 as_type_vector(yvec)));
222 result.scalars.push_back(libMesh::solid_angle(as_type_vector(xvec),
223 as_type_vector(yvec),
224 as_type_vector(xydiag)));
225#endif
226
227#if LIBMESH_DIM > 2
228 const auto xypdiag = make_vector<Vec>(0.8, -0.8);
229 const auto zvec = make_vector<Vec>(0.0, 0.0, 1.1);
230 const auto xzdiag = make_vector<Vec>(0.0, 0.7, 0.7);
231 const auto icosa1 = make_vector<Vec>(1.0, golden_ratio, 0.0);
232 const auto icosa2 = make_vector<Vec>(-1.0, golden_ratio, 0.0);
233 const auto icosa3 = make_vector<Vec>(0.0, 1.0, golden_ratio);
234 result.scalars.push_back(libMesh::solid_angle(as_type_vector(xydiag),
235 as_type_vector(yvec),
236 as_type_vector(zvec)));
237 result.scalars.push_back(libMesh::solid_angle(as_type_vector(xvec),
238 as_type_vector(yvec),
239 as_type_vector(xzdiag)));
240 result.scalars.push_back(libMesh::solid_angle(as_type_vector(xypdiag),
241 as_type_vector(xydiag),
242 as_type_vector(zvec)));
243 result.scalars.push_back(libMesh::solid_angle(as_type_vector(icosa1),
244 as_type_vector(icosa2),
245 as_type_vector(icosa3)));
246#endif
247
248#if LIBMESH_DIM > 2
249 // unit() asserts on an exactly zero norm and normalizing a tiny one
250 // is numerically unstable, so skip normalizing when a and b are
251 // (nearly) parallel; the device side applies the same guard.
252 const auto cross = a.cross(b);
253 auto unit_cross = cross;
254 if (cross.norm() > unit_tol)
255 unit_cross = cross.unit();
256
257 result.vectors.push_back(cross);
258 result.vectors.push_back(unit_cross);
259#endif
260
261 libmesh_assert_equal_to(result.vectors.size(), vector_results);
262 libmesh_assert_equal_to(result.scalars.size(), scalar_results);
263
264 return result;
265}
266
267} // namespace KokkosVectorOracle
268} // namespace libMeshTest
269
270#endif
This class defines a vector in LIBMESH_DIM dimensional space of type T.
Definition type_vector.h:64
This class defines a vector in LIBMESH_DIM dimensional Real or Complex space.
static const Real b
host_oracle< Vec > build_host_oracle(const Vec &a, const Vec &b, const Vec &c)
static constexpr unsigned int solid_angle_results
LIBMESH_DEVICE_INLINE Vec make_vector(const Real x, const Real y=0, const Real z=0)
libMesh::TypeVector< Real > as_type_vector(const libMesh::TypeVector< Real > &v)
LIBMESH_DEVICE_INLINE T solid_angle(const TypeVector< T > &v01, const TypeVector< T > &v02, const TypeVector< T > &v03)
LIBMESH_DEVICE_INLINE TypeTensor< typename CompareTypes< T, T2 >::supertype > outer_product(const TypeVector< T > &a, const TypeVector< T2 > &b)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real