42#include <gtest/gtest.h>
64 class PionDecaySpectrumTest :
public ::testing::Test {
66 static void SetUpTestSuite() {
68 char** argv =
nullptr;
69 ippl::initialize(argc, argv);
72 static void TearDownTestSuite() { ippl::finalize(); }
74 void SetUp()
override {
80 void TearDown()
override {
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};
93 ippl::NDIndex<3> domain;
94 for (
unsigned i = 0; i < 3; ++i) {
95 domain[i] = ippl::Index(
nr[i]);
100 auto pc = std::make_shared<PC_t>(mesh, fl);
101 pc->Sp =
static_cast<short>(species);
102 pc->setBunchStateHandler(std::make_shared<BunchStateHandler>());
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) {
118 Kokkos::deep_copy(pc->R.getView(), R_host);
119 Kokkos::deep_copy(pc->P.getView(), P_host);
125 bool oldUseQMAttributes_m =
false;
128 TEST_F(PionDecaySpectrumTest, PionDecayRestFrameMomentumIsFixed) {
129 constexpr std::size_t nPions = 100000;
130 constexpr std::size_t nBins = 50;
132 auto pions = makeContainer();
136 createParticles(pions, nPions, 0.0);
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);
148 auto muP_host = muons->P.getHostMirror();
149 Kokkos::deep_copy(muP_host, muons->P.getView());
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);
165 std::cout <<
"[PionDecayRestFrame] |p|=" << pFixed <<
" max rel dev=" << maxRelDev <<
'\n';
166 EXPECT_LT(maxRelDev, 1.0e-12);
169 const std::vector<double> uniform(nBins, 0.5);
174 std::cout <<
"[PionDecayRestFrame] cos(theta) L1=" << l1 <<
" mean=" << mean
175 <<
" area=" << area <<
'\n';
178 EXPECT_NEAR(mean, 0.0, 0.02);
179 EXPECT_NEAR(area, 1.0, 0.01);
181 const std::string csvPath =
"pion_rest_costheta.csv";
183 std::cout <<
"[PionDecayRestFrame] CSV written: " << csvPath <<
'\n';
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;
191 auto pions = makeContainer();
195 createParticles(pions, nPions, parentBetaGam);
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);
212 auto muP_host = muons->P.getHostMirror();
213 Kokkos::deep_copy(muP_host, muons->P.getView());
218 const double pStar = (M * M - md * md) / (2.0 * M);
219 const double eStar = (M * M + md * md) / (2.0 * M);
222 const double gamma = std::sqrt(1.0 + parentBetaGam * parentBetaGam);
223 const double betaGam = parentBetaGam;
224 const double eMinus = gamma * eStar - betaGam * pStar;
225 const double ePlus = gamma * eStar + betaGam * pStar;
226 const double uniform = 1.0 / (ePlus - eMinus);
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];
237 const std::vector<double> analyticPdf(nBins, uniform);
241 const double meanRef = 0.5 * (eMinus + ePlus);
243 std::cout <<
"[PionDecayBoosted] E in [" << eMinus <<
", " << ePlus <<
"] GeV, L1=" << l1
244 <<
" mean=" << mean <<
" (analytic=" << meanRef <<
") area=" << area <<
'\n';
247 EXPECT_NEAR(mean, meanRef, 0.005 * (ePlus - eMinus));
248 EXPECT_NEAR(area, 1.0, 0.01);
250 const std::string csvPath =
"pion_boosted_energy_box.csv";
252 std::cout <<
"[PionDecayBoosted] CSV written: " << csvPath <<
'\n';
ippl::FieldLayout< Dim > FieldLayout_t
ippl::UniformCartesian< double, 3 > Mesh_t
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).
int seed
The current random seed.
constexpr double m_pi
The charged pion rest mass in GeV (PDG)
constexpr double m_mu
The muon rest mass in GeV.
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)