OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
TestMultiVariateGaussian.cpp
Go to the documentation of this file.
1#include <gtest/gtest.h>
2#include <mpi.h>
3#include <algorithm>
4#include <cmath>
5#include <memory>
6
8#include "Ippl.h"
10#include "Utility/IpplTimings.h"
11
12class MultiVariateGaussianTest : public ::testing::Test {
13protected:
14 static void SetUpTestSuite() {
15 int argc = 0;
16 char** argv = nullptr;
17
18 ippl::initialize(argc, argv);
19 }
20
21 static void TearDownTestSuite() { ippl::finalize(); }
22
23 void SetUp() override {
24 // Minimal 3D grid parameters
25 nr = 32;
26 ippl::Vector<double, 3> rmin = -4.0;
27 ippl::Vector<double, 3> rmax = 4.0;
28 ippl::Vector<double, 3> origin = rmin;
29 ippl::Vector<double, 3> hr = (rmax - rmin) / ippl::Vector<double, 3>(nr);
30 std::array<bool, 3> decomp = {true, true, true};
31
32 ippl::NDIndex<3> domain;
33 for (unsigned i = 0; i < 3; i++) {
34 domain[i] = ippl::Index(this->nr[i]);
35 }
36
37 // Create FieldContainer
38 auto fc = std::make_shared<FieldContainer_t>(
39 hr, rmin, rmax, decomp, domain, origin, this->isAllPeriodic_m);
40
41 // Create mesh and fieldlayout
42 Mesh_t<3> mesh(domain, hr, origin);
43 FieldLayout_t<3> fl(MPI_COMM_WORLD, domain, decomp, this->isAllPeriodic_m);
44
45 // Create ParticleContainer
46 pc = std::make_shared<ParticleContainer<double, 3>>(mesh, fl);
47
48 bunchStateHandler = std::make_shared<BunchStateHandler>();
49 pc->setBunchStateHandler(bunchStateHandler);
50 }
51
52 void TearDown() override {
53 // nothing special
54 }
55
56 std::shared_ptr<ParticleContainer<double, 3>> pc;
57 std::shared_ptr<BunchStateHandler> bunchStateHandler;
58 ippl::Vector<int, 3> nr;
59 bool isAllPeriodic_m = true;
60};
61
62template <typename ViewType>
63void computeMean(ViewType view, size_t nlocal, size_t total_nparticles, double meanR[3]) {
64 double sumR[3] = {0.0, 0.0, 0.0};
65
66 Kokkos::parallel_reduce(
67 nlocal,
68 KOKKOS_LAMBDA(const int k, double& cent0, double& cent1, double& cent2) {
69 cent0 += view(k)[0];
70 cent1 += view(k)[1];
71 cent2 += view(k)[2];
72 },
73 Kokkos::Sum<double>(sumR[0]), Kokkos::Sum<double>(sumR[1]),
74 Kokkos::Sum<double>(sumR[2]));
75 Kokkos::fence();
76
77 MPI_Allreduce(sumR, meanR, 3, MPI_DOUBLE, MPI_SUM, ippl::Comm->getCommunicator());
78 ippl::Comm->barrier();
79
80 for (int i = 0; i < 3; i++)
81 meanR[i] /= total_nparticles;
82}
83
84template <typename ViewType>
86 ViewType view, size_t nlocal, size_t total_nparticles, const double meanR[3],
87 double stddevR[3]) {
88 double sumR2[3] = {0.0, 0.0, 0.0};
89
90 // Compute sum of squares locally
91 Kokkos::parallel_reduce(
92 nlocal,
93 KOKKOS_LAMBDA(const int k, double& s0, double& s1, double& s2) {
94 s0 += view(k)[0] * view(k)[0];
95 s1 += view(k)[1] * view(k)[1];
96 s2 += view(k)[2] * view(k)[2];
97 },
98 Kokkos::Sum<double>(sumR2[0]), Kokkos::Sum<double>(sumR2[1]),
99 Kokkos::Sum<double>(sumR2[2]));
100 Kokkos::fence();
101
102 // MPI reduce to get global sums
103 double global_sumR2[3] = {0.0, 0.0, 0.0};
104 MPI_Allreduce(sumR2, global_sumR2, 3, MPI_DOUBLE, MPI_SUM, ippl::Comm->getCommunicator());
105 ippl::Comm->barrier();
106
107 // Compute variance and stddev
108 for (int i = 0; i < 3; i++) {
109 double mean_square = global_sumR2[i] / static_cast<double>(total_nparticles);
110 double variance = mean_square - meanR[i] * meanR[i];
111 stddevR[i] = std::sqrt(variance);
112 }
113}
114
115// Forward declaration so tests can call this helper before its definition.
116static void preallocateParticleCapacity(
117 const std::shared_ptr<ParticleContainer<double, 3>>& pc, size_t totalParticles);
118
120 const Vector_t<double, 3> sigmaR_ref = 0.5;
121 const Vector_t<double, 3> sigmaP_ref = 1.0;
122 const Vector_t<double, 3> meanR_ref = 0.0;
123 const Vector_t<double, 3> meanP_ref = 0.0;
124 const Vector_t<double, 3> cutoffR_ref = 4.0;
125 const Vector_t<double, 3> cutoffP_ref = 4.0;
126
127 MultiVariateGaussian sampler(
128 pc, meanR_ref, meanP_ref, sigmaR_ref, sigmaP_ref, cutoffR_ref, cutoffP_ref);
129
130 size_t total_nparticles = 100000;
131
132 preallocateParticleCapacity(pc, total_nparticles);
133 sampler.generateParticles(total_nparticles, nr);
134
135 double meanR[3];
136 computeMean(pc->R.getView(), pc->getLocalNum(), total_nparticles, meanR);
137
138 EXPECT_NEAR(meanR[0], meanR_ref[0], 1e-15);
139 EXPECT_NEAR(meanR[1], meanR_ref[1], 1e-15);
140 EXPECT_NEAR(meanR[2], meanR_ref[2], 1e-15);
141
142 // compute stddev of R
143 double stddevR[3];
144 computeStdDev(pc->R.getView(), pc->getLocalNum(), total_nparticles, meanR, stddevR);
145
146 EXPECT_NEAR(stddevR[0], sigmaR_ref[0], 1e-2);
147 EXPECT_NEAR(stddevR[1], sigmaR_ref[1], 1e-2);
148 EXPECT_NEAR(stddevR[2], sigmaR_ref[2], 1e-2);
149}
150
151template <typename ViewType>
153 ViewType view, size_t nlocal, double global_maxAbsR[3], double& global_maxRadius) {
154 // Local maxima initialized
155 double local_maxAbsR[3] = {0.0, 0.0, 0.0};
156 double local_maxRadius = 0.0;
157
158 // Compute local maxima using Kokkos
159 Kokkos::parallel_reduce(
160 nlocal,
161 KOKKOS_LAMBDA(const int k, double& max0, double& max1, double& max2, double& maxr) {
162 double x = view(k)[0];
163 double y = view(k)[1];
164 double z = view(k)[2];
165
166 double ax = Kokkos::fabs(x);
167 double ay = Kokkos::fabs(y);
168 double az = Kokkos::fabs(z);
169 double r = Kokkos::sqrt(x * x + y * y + z * z);
170
171 if (ax > max0) max0 = ax;
172 if (ay > max1) max1 = ay;
173 if (az > max2) max2 = az;
174 if (r > maxr) maxr = r;
175 },
176 Kokkos::Max<double>(local_maxAbsR[0]), Kokkos::Max<double>(local_maxAbsR[1]),
177 Kokkos::Max<double>(local_maxAbsR[2]), Kokkos::Max<double>(local_maxRadius));
178 Kokkos::fence();
179
180 // MPI reduction to get global maxima
181 MPI_Allreduce(
182 local_maxAbsR, global_maxAbsR, 3, MPI_DOUBLE, MPI_MAX, ippl::Comm->getCommunicator());
183 MPI_Allreduce(
184 &local_maxRadius, &global_maxRadius, 1, MPI_DOUBLE, MPI_MAX,
185 ippl::Comm->getCommunicator());
186
187 ippl::Comm->barrier();
188}
189
192static void preallocateParticleCapacity(
193 const std::shared_ptr<ParticleContainer<double, 3>>& pc, size_t totalParticles) {
194 const int nranks = std::max(1, ippl::Comm->size());
195 const size_t nranksU = static_cast<size_t>(nranks);
196
197 const size_t maxLocalNum = totalParticles / nranksU + 2 * nranksU + 1;
198
199 pc->allocateParticles(maxLocalNum);
200}
201
203 const Vector_t<double, 3> sigmaR_ref = 0.5;
204 const Vector_t<double, 3> sigmaP_ref = 1.0;
205 const Vector_t<double, 3> meanR_ref = 0.0;
206 const Vector_t<double, 3> meanP_ref = 0.0;
207 const Vector_t<double, 3> cutoffR_ref = 4.0;
208 const Vector_t<double, 3> cutoffP_ref = 4.0;
209
210 bool fixMeanR = false;
211 bool fixMeanP = false;
212 MultiVariateGaussian sampler(
213 pc, meanR_ref, meanP_ref, sigmaR_ref, sigmaP_ref, cutoffR_ref, cutoffP_ref, fixMeanR,
214 fixMeanP);
215
216 size_t total_nparticles = 100000;
217 preallocateParticleCapacity(pc, total_nparticles);
218 sampler.generateParticles(total_nparticles, nr);
219
220 double global_maxAbsR[3];
221 double global_maxRadius;
222 computeMaxAbsR(pc->R.getView(), pc->getLocalNum(), global_maxAbsR, global_maxRadius);
223
224 // Each coordinate individually should be inside cutoff
225 EXPECT_LE(global_maxAbsR[0], cutoffR_ref[0]);
226 EXPECT_LE(global_maxAbsR[1], cutoffR_ref[1]);
227 EXPECT_LE(global_maxAbsR[2], cutoffR_ref[2]);
228}
229
231 const Vector_t<double, 3> sigmaR_ref = 0.5;
232 const Vector_t<double, 3> sigmaP_ref = 1.0;
233 const Vector_t<double, 3> meanR_ref = 0.0;
234 const Vector_t<double, 3> meanP_ref = 0.0;
235 const Vector_t<double, 3> cutoffR_ref = 4.0;
236 const Vector_t<double, 3> cutoffP_ref = 4.0;
237 bool fixMeanR = true;
238 bool fixMeanP = true;
239
240 MultiVariateGaussian sampler(
241 pc, meanR_ref, meanP_ref, sigmaR_ref, sigmaP_ref, cutoffR_ref, cutoffP_ref, fixMeanR,
242 fixMeanP);
243
244 size_t total_nparticles = 100000;
245 preallocateParticleCapacity(pc, total_nparticles);
246 sampler.generateParticles(total_nparticles, nr);
247
248 double meanP[3] = {0.0, 0.0, 0.0};
249 computeMean(pc->P.getView(), pc->getLocalNum(), total_nparticles, meanP);
250
251 double sigmaP[3] = {0.0, 0.0, 0.0};
252 computeStdDev(pc->P.getView(), pc->getLocalNum(), total_nparticles, meanP, sigmaP);
253
254 EXPECT_NEAR(meanP[0], meanP_ref[0], 1e-2);
255 EXPECT_NEAR(meanP[1], meanP_ref[1], 1e-2);
256 EXPECT_NEAR(meanP[2], meanP_ref[2], 1e-2);
257
258 EXPECT_NEAR(sigmaP[0], sigmaP_ref[0], 1e-2);
259 EXPECT_NEAR(sigmaP[1], sigmaP_ref[1], 1e-2);
260 EXPECT_NEAR(sigmaP[2], sigmaP_ref[2], 1e-2);
261}
262
264 const std::shared_ptr<ParticleContainer<double, 3>>& pc, size_t total_nparticles,
265 double meanSample[6]) {
266 auto Rview = pc->R.getView();
267 auto Pview = pc->P.getView();
268
269 double loc_sum[6] = {0.0};
270
271 // Kokkos parallel_reduce for both R and P
272
273 Kokkos::parallel_reduce(
274 pc->getLocalNum(),
275 KOKKOS_LAMBDA(
276 const int k, double& s0, double& s1, double& s2, double& s3, double& s4,
277 double& s5) {
278 s0 += Rview(k)[0];
279 s1 += Pview(k)[0];
280 s2 += Rview(k)[1];
281 s3 += Pview(k)[1];
282 s4 += Rview(k)[2];
283 s5 += Pview(k)[2];
284 },
285 Kokkos::Sum<double>(loc_sum[0]), Kokkos::Sum<double>(loc_sum[1]),
286 Kokkos::Sum<double>(loc_sum[2]), Kokkos::Sum<double>(loc_sum[3]),
287 Kokkos::Sum<double>(loc_sum[4]), Kokkos::Sum<double>(loc_sum[5]));
288 Kokkos::fence();
289
290 // MPI reduction to global sum
291 double global_sum[6] = {0.0};
292 MPI_Allreduce(loc_sum, global_sum, 6, MPI_DOUBLE, MPI_SUM, ippl::Comm->getCommunicator());
293 ippl::Comm->barrier();
294
295 // Normalize to get mean
296 for (int i = 0; i < 6; i++)
297 meanSample[i] = global_sum[i] / static_cast<double>(total_nparticles);
298}
299
301 const std::shared_ptr<ParticleContainer<double, 3>>& pc, const double meanSample[6],
302 size_t total_nparticles, double moment[6][6]) {
303 auto Rview = pc->R.getView();
304 auto Pview = pc->P.getView();
305
306 Kokkos::Array<double, 6> mean = {meanSample[0], meanSample[1], meanSample[2],
307 meanSample[3], meanSample[4], meanSample[5]};
308
309 double loc_moment[6][6] = {{0.0}};
310
311 for (unsigned i = 0; i < 6; ++i) {
312 const unsigned row = i;
313 Kokkos::parallel_reduce(
314 pc->getLocalNum(),
315 KOKKOS_LAMBDA(
316 const int k, double& s0, double& s1, double& s2, double& s3, double& s4,
317 double& s5) {
318 double part[6] = {Rview(k)[0] - mean[0], Pview(k)[0] - mean[1],
319 Rview(k)[1] - mean[2], Pview(k)[1] - mean[3],
320 Rview(k)[2] - mean[4], Pview(k)[2] - mean[5]};
321 s0 += part[row] * part[0];
322 s1 += part[row] * part[1];
323 s2 += part[row] * part[2];
324 s3 += part[row] * part[3];
325 s4 += part[row] * part[4];
326 s5 += part[row] * part[5];
327 },
328 Kokkos::Sum<double>(loc_moment[row][0]), Kokkos::Sum<double>(loc_moment[row][1]),
329 Kokkos::Sum<double>(loc_moment[row][2]), Kokkos::Sum<double>(loc_moment[row][3]),
330 Kokkos::Sum<double>(loc_moment[row][4]), Kokkos::Sum<double>(loc_moment[row][5]));
331 Kokkos::fence();
332 }
333
334 // MPI reduction across all ranks
335 MPI_Allreduce(loc_moment, moment, 6 * 6, MPI_DOUBLE, MPI_SUM, ippl::Comm->getCommunicator());
336 ippl::Comm->barrier();
337
338 // Normalize to get average covariance/moments
339 for (unsigned i = 0; i < 6; ++i) {
340 for (unsigned j = 0; j < 6; ++j) {
341 moment[i][j] /= static_cast<double>(total_nparticles);
342 }
343 }
344}
345
346TEST_F(MultiVariateGaussianTest, FullCovarianceTest) {
347 // Means
348 Vector_t<double, 3> meanR = {0.2, -0.1, 0.4};
349 Vector_t<double, 3> meanP = {-0.3, 0.5, 0.1};
350
351 // Std dev values
352 Vector_t<double, 3> sigmaR = {0.9, 2.0, 1.0};
353 Vector_t<double, 3> sigmaP = {1.5, 1.0, 0.9};
354
355 // Build L (lower-triangular)
356 using matrix_t = ippl::Vector<ippl::Vector<double, 6>, 6>;
357 matrix_t L;
358 matrix_t covExpected;
359
360 for (int i = 0; i < 6; i++) {
361 for (int j = 0; j < 6; j++) {
362 L[i][j] = 0.0;
363 covExpected[i][j] = 0.0;
364 }
365 }
366
367 // Diagonal entries: sqrt of variances
368 for (int i = 0; i < 3; i++) {
369 L[i * 2][i * 2] = sigmaR[i];
370 L[i * 2 + 1][i * 2 + 1] = sigmaP[i];
371 }
372
373 // Fill below-diagonal elements with some pattern
374 for (int i = 0; i < 6; i++) {
375 for (int j = 0; j < i; j++) {
376 L[i][j] = (i + j + 1) * 0.1; // arbitrary correlation
377 }
378 }
379
380 // Compute covariance matrix Σ = L * L^T
381
382 for (int i = 0; i < 6; i++) {
383 for (int j = 0; j < 6; j++) {
384 double sum = 0.0;
385 for (int k = 0; k < 6; k++)
386 sum += L[i][k] * L[j][k]; // (L * L^T)
387 covExpected[i][j] = sum;
388 }
389 }
390
391 Vector_t<double, 3> cutoffR = 5.0;
392 Vector_t<double, 3> cutoffP = 5.0;
393
394 // Construct sampler
395 MultiVariateGaussian sampler(pc, meanR, meanP, covExpected, cutoffR, cutoffP);
396
397 size_t total_nparticles = 1000000;
398 preallocateParticleCapacity(pc, total_nparticles);
399 sampler.generateParticles(total_nparticles, nr);
400
401 double meanSample[6];
402 computeMeanRAndP(pc, total_nparticles, meanSample);
403
404 // Check means
405 EXPECT_NEAR(meanSample[0], meanR[0], 5e-3);
406 EXPECT_NEAR(meanSample[1], meanP[0], 5e-3);
407 EXPECT_NEAR(meanSample[2], meanR[1], 5e-3);
408 EXPECT_NEAR(meanSample[3], meanP[1], 5e-3);
409 EXPECT_NEAR(meanSample[4], meanR[2], 5e-3);
410 EXPECT_NEAR(meanSample[5], meanP[2], 5e-3);
411
412 // ------------------------------
413 // Compute sample covariance Σ
414 // ------------------------------
415 double moment[6][6];
416 computeCovariance6x6(pc, meanSample, total_nparticles, moment);
417
418 // -----------------------------------------------------
419 // Assertions: covariance comparison
420 // -----------------------------------------------------
421 for (int i = 0; i < 6; i++) {
422 for (int j = 0; j < 6; j++) {
423 EXPECT_NEAR(moment[i][j], covExpected[i][j], 3e-2);
424 }
425 }
426}
ippl::Vector< T, Dim > Vector_t
const int nr
ippl::FieldLayout< Dim > FieldLayout_t
Definition PBunchDefs.h:25
ippl::UniformCartesian< double, 3 > Mesh_t
Definition PBunchDefs.h:18
void computeCovariance6x6(const std::shared_ptr< ParticleContainer< double, 3 > > &pc, const double meanSample[6], size_t total_nparticles, double moment[6][6])
TEST_F(MultiVariateGaussianTest, meanR_varR)
void computeMaxAbsR(ViewType view, size_t nlocal, double global_maxAbsR[3], double &global_maxRadius)
void computeStdDev(ViewType view, size_t nlocal, size_t total_nparticles, const double meanR[3], double stddevR[3])
void computeMean(ViewType view, size_t nlocal, size_t total_nparticles, double meanR[3])
void computeMeanRAndP(const std::shared_ptr< ParticleContainer< double, 3 > > &pc, size_t total_nparticles, double meanSample[6])
std::shared_ptr< BunchStateHandler > bunchStateHandler
std::shared_ptr< ParticleContainer< double, 3 > > pc
A particle generation method following multivariate Gaussian distribution.
void generateParticles(size_t &numberOfParticles, Vector_t< double, 3 > nr) override
Generates particles based on the defined Gaussian distribution.
Container for all per-particle (and per-simulation) fields tracked during OPALX tracking.