OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
TestPionDecaySpectrum.cpp
Go to the documentation of this file.
1
42#include <gtest/gtest.h>
43#include <mpi.h>
44
45#include <array>
46#include <cmath>
47#include <cstddef>
48#include <iostream>
49#include <memory>
50#include <vector>
51
52#include "Ippl.h"
55#include "Physics/Physics.h"
57#include "SpectrumTestSupport.h"
58#include "Utilities/Options.h"
59
60namespace {
61
63
64 class PionDecaySpectrumTest : public ::testing::Test {
65 protected:
66 static void SetUpTestSuite() {
67 int argc = 0;
68 char** argv = nullptr;
69 ippl::initialize(argc, argv);
70 }
71
72 static void TearDownTestSuite() { ippl::finalize(); }
73
74 void SetUp() override {
75 oldSeed_m = Options::seed;
76 oldUseQMAttributes_m = Options::useQMAttributes;
78 }
79
80 void TearDown() override {
81 Options::seed = oldSeed_m;
82 Options::useQMAttributes = oldUseQMAttributes_m;
83 }
84
85 std::shared_ptr<PC_t> makeContainer(ParticleType species = ParticleType::UNNAMED) {
86 ippl::Vector<int, 3> nr = 8;
87 ippl::Vector<double, 3> rmin = -4.0;
88 ippl::Vector<double, 3> rmax = 4.0;
89 ippl::Vector<double, 3> origin = rmin;
90 ippl::Vector<double, 3> hr = (rmax - rmin) / ippl::Vector<double, 3>(nr);
91 std::array<bool, 3> decomp = {true, true, true};
92
93 ippl::NDIndex<3> domain;
94 for (unsigned i = 0; i < 3; ++i) {
95 domain[i] = ippl::Index(nr[i]);
96 }
97
98 Mesh_t<3> mesh(domain, hr, origin);
99 FieldLayout_t<3> fl(MPI_COMM_WORLD, domain, decomp, true);
100 auto pc = std::make_shared<PC_t>(mesh, fl);
101 pc->Sp = static_cast<short>(species);
102 pc->setBunchStateHandler(std::make_shared<BunchStateHandler>());
103 return pc;
104 }
105
106 void createParticles(std::shared_ptr<PC_t>& pc, std::size_t nPart, double pz) {
107 pc->createParticles(nPart);
108 auto R_host = pc->R.getHostMirror();
109 auto P_host = pc->P.getHostMirror();
110 for (std::size_t i = 0; i < nPart; ++i) {
111 R_host(i)[0] = 0.0;
112 R_host(i)[1] = 0.0;
113 R_host(i)[2] = 0.0;
114 P_host(i)[0] = 0.0;
115 P_host(i)[1] = 0.0;
116 P_host(i)[2] = pz;
117 }
118 Kokkos::deep_copy(pc->R.getView(), R_host);
119 Kokkos::deep_copy(pc->P.getView(), P_host);
120 Kokkos::fence();
121 }
122
123 private:
124 int oldSeed_m = -1;
125 bool oldUseQMAttributes_m = false;
126 };
127
128 TEST_F(PionDecaySpectrumTest, PionDecayRestFrameMomentumIsFixed) {
129 constexpr std::size_t nPions = 100000;
130 constexpr std::size_t nBins = 50;
131
132 auto pions = makeContainer();
133 auto muons = makeContainer(ParticleType::MUON);
134 pions->setM(Physics::m_pi);
135 muons->setM(Physics::m_mu);
136 createParticles(pions, nPions, 0.0);
137
138 Options::seed = 20260516;
139 PionDecay decay(1.0e-12, 0, Physics::m_pi, +1); // pi+
140 decay.setDaughterContainer(muons, Physics::m_mu);
141
142 const std::size_t marked = decay.apply(*pions, 1.0, 0, 0);
143 const std::size_t destroyed = pions->deleteInvalidParticles();
144 ASSERT_EQ(marked, nPions);
145 ASSERT_EQ(destroyed, nPions);
146 ASSERT_EQ(muons->getLocalNum(), nPions);
147
148 auto muP_host = muons->P.getHostMirror();
149 Kokkos::deep_copy(muP_host, muons->P.getView());
150
151 const double pFixed = (Physics::m_pi * Physics::m_pi - Physics::m_mu * Physics::m_mu)
152 / (2.0 * Physics::m_pi);
153
154 std::vector<double> cosThetaSamples;
155 cosThetaSamples.reserve(nPions);
156 double maxRelDev = 0.0;
157 for (std::size_t i = 0; i < nPions; ++i) {
158 const double bgx = muP_host(i)[0];
159 const double bgy = muP_host(i)[1];
160 const double bgz = muP_host(i)[2];
161 const double pmag = Physics::m_mu * std::sqrt(bgx * bgx + bgy * bgy + bgz * bgz);
162 maxRelDev = std::max(maxRelDev, std::abs(pmag - pFixed) / pFixed);
163 cosThetaSamples.push_back(Physics::m_mu * bgz / pmag);
164 }
165 std::cout << "[PionDecayRestFrame] |p|=" << pFixed << " max rel dev=" << maxRelDev << '\n';
166 EXPECT_LT(maxRelDev, 1.0e-12);
167
168 const auto hist = opalx::test::makeHistogram(cosThetaSamples, -1.0, 1.0, nBins);
169 const std::vector<double> uniform(nBins, 0.5);
170
171 const double l1 = opalx::test::l1Distance(hist, uniform);
172 const double mean = opalx::test::sampleMean(cosThetaSamples);
173 const double area = opalx::test::histogramArea(hist);
174 std::cout << "[PionDecayRestFrame] cos(theta) L1=" << l1 << " mean=" << mean
175 << " area=" << area << '\n';
176
177 EXPECT_LT(l1, 0.10);
178 EXPECT_NEAR(mean, 0.0, 0.02);
179 EXPECT_NEAR(area, 1.0, 0.01);
180
181 const std::string csvPath = "pion_rest_costheta.csv";
182 opalx::test::writeSpectrumCsv(csvPath, hist, uniform, "cos(theta) (rest frame)");
183 std::cout << "[PionDecayRestFrame] CSV written: " << csvPath << '\n';
184 }
185
186 TEST_F(PionDecaySpectrumTest, PionDecayBoostedLabEnergyIsFlatBox) {
187 constexpr std::size_t nPions = 100000;
188 constexpr std::size_t nBins = 50;
189 constexpr double parentBetaGam = 5.0; // pz of pion in beta*gamma units
190
191 auto pions = makeContainer();
192 auto muons = makeContainer(ParticleType::MUON);
193 pions->setM(Physics::m_pi);
194 muons->setM(Physics::m_mu);
195 createParticles(pions, nPions, parentBetaGam);
196
197 Options::seed = 20260517;
203 PionDecay decay(1.0e-12, 0, Physics::m_pi, +1); // pi+
204 decay.setDaughterContainer(muons, Physics::m_mu);
205
206 const std::size_t marked = decay.apply(*pions, 1.0, 0, 0);
207 const std::size_t destroyed = pions->deleteInvalidParticles();
208 ASSERT_EQ(marked, nPions);
209 ASSERT_EQ(destroyed, nPions);
210 ASSERT_EQ(muons->getLocalNum(), nPions);
211
212 auto muP_host = muons->P.getHostMirror();
213 Kokkos::deep_copy(muP_host, muons->P.getView());
214
215 // Rest-frame two-body kinematics.
216 const double M = Physics::m_pi;
217 const double md = Physics::m_mu;
218 const double pStar = (M * M - md * md) / (2.0 * M);
219 const double eStar = (M * M + md * md) / (2.0 * M);
220
221 // Parent boost.
222 const double gamma = std::sqrt(1.0 + parentBetaGam * parentBetaGam);
223 const double betaGam = parentBetaGam; // = beta * gamma
224 const double eMinus = gamma * eStar - betaGam * pStar;
225 const double ePlus = gamma * eStar + betaGam * pStar;
226 const double uniform = 1.0 / (ePlus - eMinus);
227
228 std::vector<double> labEnergies;
229 labEnergies.reserve(nPions);
230 for (std::size_t i = 0; i < nPions; ++i) {
231 const double bg2 = muP_host(i)[0] * muP_host(i)[0] + muP_host(i)[1] * muP_host(i)[1]
232 + muP_host(i)[2] * muP_host(i)[2];
233 labEnergies.push_back(std::sqrt(1.0 + bg2) * Physics::m_mu);
234 }
235
236 const auto hist = opalx::test::makeHistogram(labEnergies, eMinus, ePlus, nBins);
237 const std::vector<double> analyticPdf(nBins, uniform);
238
239 const double l1 = opalx::test::l1Distance(hist, analyticPdf);
240 const double mean = opalx::test::sampleMean(labEnergies);
241 const double meanRef = 0.5 * (eMinus + ePlus);
242 const double area = opalx::test::histogramArea(hist);
243 std::cout << "[PionDecayBoosted] E in [" << eMinus << ", " << ePlus << "] GeV, L1=" << l1
244 << " mean=" << mean << " (analytic=" << meanRef << ") area=" << area << '\n';
245
246 EXPECT_LT(l1, 0.10);
247 EXPECT_NEAR(mean, meanRef, 0.005 * (ePlus - eMinus));
248 EXPECT_NEAR(area, 1.0, 0.01);
249
250 const std::string csvPath = "pion_boosted_energy_box.csv";
251 opalx::test::writeSpectrumCsv(csvPath, hist, analyticPdf, "E_mu^lab [GeV]");
252 std::cout << "[PionDecayBoosted] CSV written: " << csvPath << '\n';
253 }
254
255} // namespace
const int nr
ippl::FieldLayout< Dim > FieldLayout_t
Definition PBunchDefs.h:25
ippl::UniformCartesian< double, 3 > Mesh_t
Definition PBunchDefs.h:18
Lightweight histogram + CSV utilities shared by decay spectrum tests.
TEST_F(MonitorTest, GetType)
Container for all per-particle (and per-simulation) fields tracked during OPALX tracking.
Charged pion decay: pi -> mu + nu_mu (two-body).
Definition PionDecay.h:14
bool useQMAttributes
Definition Options.cpp:109
int seed
The current random seed.
Definition Options.cpp:37
constexpr double m_pi
The charged pion rest mass in GeV (PDG)
Definition Physics.h:138
constexpr double m_mu
The muon rest mass in GeV.
Definition Physics.h:129
void writeSpectrumCsv(const std::string &path, const Histogram1D &h, const std::vector< double > &analyticPdf, const std::string &xLabel)
Histogram1D makeHistogram(const std::vector< double > &samples, double low, double high, std::size_t nBins)
double histogramArea(const Histogram1D &h)
Riemann sum of the histogram density (should be ~1 if all samples were in range).
double l1Distance(const Histogram1D &sampled, const std::vector< double > &analyticPdf)
double sampleMean(const std::vector< double > &samples)