17#include <gtest/gtest.h>
36 class ParticleContainerTest :
public ::testing::Test {
38 static void SetUpTestSuite() {
40 char** argv =
nullptr;
41 ippl::initialize(argc, argv);
44 static void TearDownTestSuite() { ippl::finalize(); }
48 std::shared_ptr<PC_t> makeContainer() {
49 ippl::Vector<int, 3>
nr = 8;
50 ippl::Vector<double, 3> rmin = -4.0;
51 ippl::Vector<double, 3> rmax = 4.0;
52 ippl::Vector<double, 3> origin = rmin;
53 ippl::Vector<double, 3> hr = (rmax - rmin) / ippl::Vector<double, 3>(
nr);
54 std::array<bool, 3> decomp = {
true,
true,
true};
56 ippl::NDIndex<3> domain;
57 for (
unsigned i = 0; i < 3; i++) {
58 domain[i] = ippl::Index(
nr[i]);
64 std::shared_ptr<PC_t> pc = std::make_shared<PC_t>(mesh, fl);
65 pc->setBunchStateHandler(std::make_shared<BunchStateHandler>());
70 void createParticlesAt(
71 std::shared_ptr<PC_t>& pc,
const std::vector<std::array<double, 3>>& positions,
72 double dtVal = 1e-12,
double pz = 0.1) {
73 const size_t n = positions.size();
76 pc->createParticles(n);
78 auto R_host = pc->R.getHostMirror();
79 auto P_host = pc->P.getHostMirror();
80 auto dt_host = pc->dt.getHostMirror();
82 for (
size_t i = 0; i < n; ++i) {
83 R_host(i)[0] = positions[i][0];
84 R_host(i)[1] = positions[i][1];
85 R_host(i)[2] = positions[i][2];
92 Kokkos::deep_copy(pc->R.getView(), R_host);
93 Kokkos::deep_copy(pc->P.getView(), P_host);
94 Kokkos::deep_copy(pc->dt.getView(), dt_host);
103 TEST_F(ParticleContainerTest, ChargeMass_SingleValueMode_SetAndRead) {
105 auto pc = makeContainer();
107 ASSERT_EQ(pc->getQMStorageMode(), PC_t::QMStorageMode::SingleValue);
109 const double qExpected = 1.6e-19;
110 const double mExpected = 0.938272;
115 auto qView = pc->getQView();
116 auto mView = pc->getMView();
118 ASSERT_EQ(qView.extent(0), 1u);
119 ASSERT_EQ(mView.extent(0), 1u);
121 auto q_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), qView);
122 auto m_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), mView);
124 EXPECT_DOUBLE_EQ(q_host(0), qExpected);
125 EXPECT_DOUBLE_EQ(m_host(0), mExpected);
132 TEST_F(ParticleContainerTest, ChargeMass_AttributeMode_SetAndRead) {
134 auto pc = makeContainer();
136 ASSERT_EQ(pc->getQMStorageMode(), PC_t::QMStorageMode::Attributes);
138 constexpr size_t nPart = 8;
139 pc->createParticles(nPart);
142 const double qExpected = -1.6e-19;
143 const double mExpected = 0.000511;
148 auto q_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->getQView());
149 auto m_host = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->getMView());
151 for (
size_t i = 0; i < nPart; ++i) {
152 EXPECT_DOUBLE_EQ(q_host(i), qExpected) <<
"particle " << i;
153 EXPECT_DOUBLE_EQ(m_host(i), mExpected) <<
"particle " << i;
161 TEST_F(ParticleContainerTest, DtScaling_RoundTrip_SingleValueMode) {
163 auto pc = makeContainer();
165 const double q = 1.6e-19;
168 constexpr size_t nPart = 16;
169 const double dtOrig = 1
e-12;
170 std::vector<std::array<double, 3>> positions(nPart, {0.0, 0.0, 0.0});
171 createParticlesAt(pc, positions, dtOrig);
173 pc->scaleDtByCharge();
175 auto dt_scaled = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->dt.getView());
176 for (
size_t i = 0; i < nPart; ++i) {
177 EXPECT_NEAR(dt_scaled(i), dtOrig * q, 1e-44) <<
"particle " << i;
180 pc->unscaleDtByCharge();
182 auto dt_back = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->dt.getView());
183 for (
size_t i = 0; i < nPart; ++i) {
184 EXPECT_NEAR(dt_back(i), dtOrig, 1e-20) <<
"particle " << i;
192 TEST_F(ParticleContainerTest, DtScaling_RoundTrip_AttributeMode) {
194 auto pc = makeContainer();
196 constexpr size_t nPart = 16;
197 const double dtOrig = 1
e-12;
198 std::vector<std::array<double, 3>> positions(nPart, {0.0, 0.0, 0.0});
199 createParticlesAt(pc, positions, dtOrig);
201 const double q = 1.6e-19;
204 pc->scaleDtByCharge();
206 auto dt_scaled = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->dt.getView());
207 for (
size_t i = 0; i < nPart; ++i) {
208 EXPECT_NEAR(dt_scaled(i), dtOrig * q, 1e-44) <<
"particle " << i;
211 pc->unscaleDtByCharge();
213 auto dt_back = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->dt.getView());
214 for (
size_t i = 0; i < nPart; ++i) {
215 EXPECT_NEAR(dt_back(i), dtOrig, 1e-20) <<
"particle " << i;
223 TEST_F(ParticleContainerTest, MomentsAndMinMax_EmptyContainer_NoNaN) {
225 auto pc = makeContainer();
227 ASSERT_EQ(pc->getLocalNum(), 0u);
230 pc->computeMinMaxR();
232 auto meanR = pc->getMeanR();
233 auto rmsR = pc->getRmsR();
234 auto minR = pc->getMinR();
235 auto maxR = pc->getMaxR();
237 for (
unsigned d = 0; d < 3; ++d) {
238 EXPECT_TRUE(std::isfinite(meanR[d])) <<
"meanR[" << d <<
"]";
239 EXPECT_TRUE(std::isfinite(rmsR[d])) <<
"rmsR[" << d <<
"]";
240 EXPECT_TRUE(std::isfinite(minR[d])) <<
"minR[" << d <<
"]";
241 EXPECT_TRUE(std::isfinite(maxR[d])) <<
"maxR[" << d <<
"]";
242 EXPECT_LE(minR[d], maxR[d]) <<
"min > max at dim " << d;
250 TEST_F(ParticleContainerTest, Moments_KnownSymmetricData) {
252 auto pc = makeContainer();
254 std::vector<std::array<double, 3>> positions;
255 if (ippl::Comm->rank() == 0) {
256 positions = {{1.0, 1.0, 1.0}, {-1.0, -1.0, -1.0}, {1.0, -1.0, 1.0}, {-1.0, 1.0, -1.0}};
258 createParticlesAt(pc, positions);
263 auto meanR = pc->getMeanR();
264 for (
unsigned d = 0; d < 3; ++d) {
265 EXPECT_NEAR(meanR[d], 0.0, 1e-14) <<
"meanR[" << d <<
"]";
268 auto rmsR = pc->getRmsR();
269 for (
unsigned d = 0; d < 3; ++d) {
270 EXPECT_NEAR(rmsR[d], 1.0, 1e-14) <<
"rmsR[" << d <<
"]";
278 TEST_F(ParticleContainerTest, MinMaxR_KnownPositions) {
280 auto pc = makeContainer();
282 std::vector<std::array<double, 3>> positions;
283 if (ippl::Comm->rank() == 0) {
284 positions = {{1.0, 2.0, 3.0}, {-0.5, -1.5, 0.0}, {0.0, 0.0, -2.0}};
286 createParticlesAt(pc, positions);
288 pc->computeMinMaxR();
290 auto minR = pc->getMinR();
291 auto maxR = pc->getMaxR();
293 EXPECT_NEAR(minR[0], -0.5, 1e-14);
294 EXPECT_NEAR(minR[1], -1.5, 1e-14);
295 EXPECT_NEAR(minR[2], -2.0, 1e-14);
297 EXPECT_NEAR(maxR[0], 1.0, 1e-14);
298 EXPECT_NEAR(maxR[1], 2.0, 1e-14);
299 EXPECT_NEAR(maxR[2], 3.0, 1e-14);
306 TEST_F(ParticleContainerTest, MarkParticlesOutside_NegativeSigma_ReturnsZero) {
308 auto pc = makeContainer();
310 std::vector<std::array<double, 3>> positions = {{{0.0, 0.0, 0.0}}};
311 createParticlesAt(pc, positions);
314 size_t localBefore = pc->getLocalNum();
315 auto marked = pc->markParticlesOutside(-1.0);
317 EXPECT_EQ(marked, 0u);
318 EXPECT_EQ(pc->getLocalNum(), localBefore);
321 TEST_F(ParticleContainerTest, MarkParticlesOutside_ZeroSigma_ReturnsZero) {
323 auto pc = makeContainer();
325 std::vector<std::array<double, 3>> positions = {{{0.0, 0.0, 0.0}}};
326 createParticlesAt(pc, positions);
329 size_t localBefore = pc->getLocalNum();
330 auto marked = pc->markParticlesOutside(0.0);
332 EXPECT_EQ(marked, 0u);
333 EXPECT_EQ(pc->getLocalNum(), localBefore);
336 TEST_F(ParticleContainerTest, MarkParticlesOutside_EmptyContainer_ReturnsZero) {
338 auto pc = makeContainer();
340 ASSERT_EQ(pc->getLocalNum(), 0u);
341 auto marked = pc->markParticlesOutside(3.0);
342 EXPECT_EQ(marked, 0u);
349 TEST_F(ParticleContainerTest, MarkAndDeleteInvalidParticles_KnownOutlierRemoval) {
351 auto pc = makeContainer();
356 std::vector<std::array<double, 3>> positions;
357 if (ippl::Comm->rank() == 0) {
359 {0.10, 0.10, 0.10}, {-0.10, -0.10, -0.10}, {0.05, 0.05, 0.05},
360 {-0.05, -0.05, -0.05}, {0.10, -0.10, 0.05}, {-0.10, 0.10, -0.05},
361 {0.08, 0.03, 0.07}, {-0.08, -0.03, -0.07}, {0.02, 0.09, 0.04},
362 {-0.02, -0.09, -0.04}, {3.50, 3.50, 3.50}
365 positions = {{{0.0, 0.0, 0.0}}};
367 createParticlesAt(pc, positions);
370 size_t totalBefore = pc->getTotalNum();
371 auto marked = pc->markParticlesOutside(2.0);
373 EXPECT_EQ(marked, 1u);
375 auto destroyed = pc->deleteInvalidParticles();
376 EXPECT_EQ(destroyed, marked);
378 size_t totalAfter = pc->getTotalNum();
379 EXPECT_EQ(totalAfter + destroyed, totalBefore);
382 TEST_F(ParticleContainerTest, ImageChargeMirrorTransform_AllParticles_RoundTrip) {
384 auto pc = makeContainer();
385 std::vector<std::array<double, 3>> positions = {
386 {0.1, -0.2, 0.0}, {0.0, 0.3, 0.4}, {-0.2, 0.1, -0.5}};
387 const double dtOrig = 2.5e-12;
388 const double qOrig = 1.6e-19;
389 createParticlesAt(pc, positions, dtOrig);
393 ippl::Vector<int, 3>
nr = 8;
394 ippl::Vector<double, 3> rmin = -4.0;
395 ippl::Vector<double, 3> rmax = 4.0;
396 ippl::Vector<double, 3> origin = rmin;
397 ippl::Vector<double, 3> hr = (rmax - rmin) / ippl::Vector<double, 3>(
nr);
398 std::array<bool, 3> decomp = {
true,
true,
true};
399 ippl::NDIndex<3> domain;
400 for (
unsigned i = 0; i < 3; ++i) {
401 domain[i] = ippl::Index(
nr[i]);
407 rhoBaseline.initialize(mesh, fl);
408 rhoImage.initialize(mesh, fl);
412 constexpr double zPlane = 0.125;
417 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->R.getView());
418 auto dt_before_view =
419 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->dt.getView());
421 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->getQView());
422 std::vector<std::array<double, 3>> R_before(pc->getLocalNum());
423 std::vector<double> dt_before(pc->getLocalNum(), 0.0);
424 for (
size_t i = 0; i < pc->getLocalNum(); ++i) {
425 R_before[i][0] = R_before_view(i)[0];
426 R_before[i][1] = R_before_view(i)[1];
427 R_before[i][2] = R_before_view(i)[2];
428 dt_before[i] = dt_before_view(i);
430 const double q_before = q_before_view(0);
433 baselineController.scatterPrimaryAndImage(pc, pc->R, rhoBaseline);
434 imageController.scatterPrimaryAndImage(pc, pc->R, rhoImage);
438 auto R_after = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->R.getView());
439 auto dt_after = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->dt.getView());
440 auto q_after = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->getQView());
441 for (
size_t i = 0; i < pc->getLocalNum(); ++i) {
442 for (
unsigned d = 0; d < 3; ++d) {
443 EXPECT_NEAR(R_after(i)[d], R_before[i][d], 1e-14);
445 EXPECT_NEAR(dt_after(i), dt_before[i], 1e-20);
447 EXPECT_NEAR(q_after(0), q_before, 1e-30);
451 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), rhoBaseline.getView());
453 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), rhoImage.getView());
454 bool rhoDiffers =
false;
455 for (
size_t i = 0; i < rhoBaseHost.extent(0) && !rhoDiffers; ++i) {
456 for (
size_t j = 0; j < rhoBaseHost.extent(1) && !rhoDiffers; ++j) {
457 for (
size_t k = 0; k < rhoBaseHost.extent(2); ++k) {
458 if (std::abs(rhoBaseHost(i, j, k) - rhoImgHost(i, j, k)) > 0.0) {
465 EXPECT_TRUE(rhoDiffers);
472 TEST_F(ParticleContainerTest, AllocateParticles_ReservesCapacityAndKeepsLocalNumZero) {
474 auto pc = makeContainer();
475 ASSERT_EQ(pc->R.size(), 0u);
477 constexpr size_t nReserve = 1024;
478 pc->allocateParticles(nReserve);
481 EXPECT_EQ(pc->getLocalNum(), 0u);
483 EXPECT_GE(pc->R.size(), nReserve);
484 EXPECT_GE(pc->P.size(), nReserve);
485 EXPECT_GE(pc->dt.size(), nReserve);
488 EXPECT_THROW(pc->allocateParticles(nReserve),
OpalException);
495 TEST_F(ParticleContainerTest, CreateParticles_GrowPreservesExistingData) {
497 auto pc = makeContainer();
500 constexpr size_t nFirst = 8;
501 pc->allocateParticles(nFirst);
502 pc->createParticles(nFirst);
503 ASSERT_EQ(pc->getLocalNum(), nFirst);
505 auto P_host = pc->P.getHostMirror();
506 for (
size_t i = 0; i < nFirst; ++i) {
507 P_host(i) = ippl::Vector<double, 3>(
double(i),
double(2 * i),
double(3 * i));
509 Kokkos::deep_copy(pc->P.getView(), P_host);
513 const size_t capBefore = pc->R.size();
514 const size_t nSecond = capBefore + 16;
515 pc->createParticles(nSecond);
517 EXPECT_EQ(pc->getLocalNum(), nFirst + nSecond);
518 EXPECT_GE(pc->R.size(), nFirst + nSecond);
521 auto P_after = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->P.getView());
522 for (
size_t i = 0; i < nFirst; ++i) {
523 EXPECT_DOUBLE_EQ(P_after(i)[0],
double(i)) <<
"particle " << i;
524 EXPECT_DOUBLE_EQ(P_after(i)[1],
double(2 * i)) <<
"particle " << i;
525 EXPECT_DOUBLE_EQ(P_after(i)[2],
double(3 * i)) <<
"particle " << i;
533 TEST_F(ParticleContainerTest, DeleteInvalidParticles_RemovesMarkedAndResetsMask) {
535 auto pc = makeContainer();
537 constexpr size_t nPart = 10;
538 std::vector<std::array<double, 3>> positions(nPart);
539 for (
size_t i = 0; i < nPart; ++i) {
540 positions[i] = {double(i) * 0.1, 0.0, 0.0};
542 createParticlesAt(pc, positions);
543 ASSERT_EQ(pc->getLocalNum(), nPart);
546 auto invalid_host = pc->InvalidMask.getHostMirror();
548 for (
size_t i = 0; i < nPart; ++i) {
549 const bool kill = (i % 2 == 0);
550 invalid_host(i) = kill;
551 if (kill) ++expectedDestroy;
553 Kokkos::deep_copy(pc->InvalidMask.getView(), invalid_host);
555 const size_type expectedGlobalDestroy = pc->getTotalNum() / 2;
556 const size_type destroyed = pc->deleteInvalidParticles();
557 EXPECT_EQ(destroyed, expectedGlobalDestroy);
558 EXPECT_EQ(pc->getLocalNum(), nPart - expectedDestroy);
561 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->InvalidMask.getView());
562 for (
size_t i = 0; i < pc->getLocalNum(); ++i) {
563 EXPECT_FALSE(resetHost(i));
567 TEST_F(ParticleContainerTest, CreateParticles_ResetsInvalidMaskForNewParticles) {
569 auto pc = makeContainer();
571 constexpr size_t nPart = 6;
572 pc->createParticles(nPart);
573 const size_t totalBeforeDelete = pc->getTotalNum();
574 pc->InvalidMask =
true;
576 ASSERT_EQ(pc->deleteInvalidParticles(), totalBeforeDelete);
577 ASSERT_EQ(pc->getLocalNum(), 0u);
579 pc->createParticles(nPart);
581 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->InvalidMask.getView());
582 for (
size_t i = 0; i < pc->getLocalNum(); ++i) {
583 EXPECT_FALSE(invalidHost(i));
591 TEST_F(ParticleContainerTest, CreateParticles_AssignsStridedIDs) {
593 auto pc = makeContainer();
595 constexpr size_t nPart = 32;
596 pc->createParticles(nPart);
597 ASSERT_EQ(pc->getLocalNum(), nPart);
599 auto idHost = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pc->ID.getView());
601 const auto rank =
static_cast<std::int64_t
>(ippl::Comm->rank());
602 const auto nRanks =
static_cast<std::int64_t
>(ippl::Comm->size());
604 for (
size_t i = 0; i < nPart; ++i) {
605 EXPECT_EQ(idHost(i), rank + nRanks *
static_cast<std::int64_t
>(i)) <<
"particle " << i;
ippl::FieldLayout< Dim > FieldLayout_t
ippl::Field< double, Dim, ViewArgs... > Field_t
ippl::UniformCartesian< double, 3 > Mesh_t
ippl::detail::size_type size_type
TEST_F(MonitorTest, GetType)
Orchestrates primary and image-charge scatter deposition.
Container for all per-particle (and per-simulation) fields tracked during OPALX tracking.
constexpr double e
The value of.