LCOV - code coverage report
Current view: top level - test - testInteraction.cpp (source / functions) Coverage Total Hit
Test: coverage.info.cleaned Lines: 95.9 % 1051 1008
Test Date: 2026-07-13 06:03:10 Functions: 98.5 % 68 67

            Line data    Source code
       1              : #include "crpropa/Candidate.h"
       2              : #include "crpropa/Units.h"
       3              : #include "crpropa/ParticleID.h"
       4              : #include "crpropa/PhotonBackground.h"
       5              : #include "crpropa/module/ElectronPairProduction.h"
       6              : #include "crpropa/module/NuclearDecay.h"
       7              : #include "crpropa/module/PhotoDisintegration.h"
       8              : #include "crpropa/module/ElasticScattering.h"
       9              : #include "crpropa/module/PhotoPionProduction.h"
      10              : #include "crpropa/module/Redshift.h"
      11              : #include "crpropa/module/EMPairProduction.h"
      12              : #include "crpropa/module/EMDoublePairProduction.h"
      13              : #include "crpropa/module/EMTripletPairProduction.h"
      14              : #include "crpropa/module/EMInverseComptonScattering.h"
      15              : #include "crpropa/module/SynchrotronRadiation.h"
      16              : #include "gtest/gtest.h"
      17              : #include "kiss/path.h"
      18              : 
      19              : #include <fstream>
      20              : 
      21              : namespace crpropa {
      22              : 
      23              : // ElectronPairProduction -----------------------------------------------------
      24            1 : TEST(ElectronPairProduction, allBackgrounds) {
      25              :         // Test if interaction data files are loaded.
      26            1 :         ref_ptr<PhotonField> cmb = new CMB();
      27            1 :         ElectronPairProduction epp(cmb);
      28            1 :         ref_ptr<PhotonField> irb = new IRB_Kneiske04();
      29            1 :         epp.setPhotonField(irb);
      30            1 :         irb = new IRB_Stecker05();
      31            1 :         epp.setPhotonField(irb);
      32            1 :         irb = new IRB_Franceschini08();
      33            1 :         epp.setPhotonField(irb);
      34            1 :         irb = new IRB_Finke10();
      35            1 :         epp.setPhotonField(irb);
      36            1 :         irb = new IRB_Dominguez11();
      37            1 :         epp.setPhotonField(irb);
      38            1 :         irb = new IRB_Gilmore12();
      39            1 :         epp.setPhotonField(irb);
      40            1 :         irb = new IRB_Stecker16_upper();
      41            1 :         epp.setPhotonField(irb);
      42            1 :         irb = new IRB_Stecker16_lower();
      43            1 :         epp.setPhotonField(irb);
      44            1 :         irb = new IRB_Finke22();
      45            2 :         epp.setPhotonField(irb);
      46            2 : }
      47              : 
      48            1 : TEST(ElectronPairProduction, energyDecreasing) {
      49              :         // Test if energy loss occurs for protons with energies from 1e15 - 1e23 eV.
      50            1 :         Candidate c;
      51            1 :         c.setCurrentStep(2 * Mpc);
      52            1 :         c.current.setId(nucleusId(1, 1)); // proton
      53              : 
      54            1 :         ref_ptr<PhotonField> cmb = new CMB();
      55            1 :         ElectronPairProduction epp1(cmb);
      56           81 :         for (int i = 0; i < 80; i++) {
      57           80 :                 double E = pow(10, 15 + i * 0.1) * eV;
      58           80 :                 c.current.setEnergy(E);
      59           80 :                 epp1.process(&c);
      60           80 :                 EXPECT_LE(c.current.getEnergy(), E);
      61              :         }
      62              : 
      63            1 :         ref_ptr<PhotonField> irb = new IRB_Kneiske04();
      64            2 :         ElectronPairProduction epp2(irb);
      65           81 :         for (int i = 0; i < 80; i++) {
      66           80 :                 double E = pow(10, 15 + i * 0.1) * eV;
      67           80 :                 c.current.setEnergy(E);
      68           80 :                 epp2.process(&c);
      69           80 :                 EXPECT_LE(c.current.getEnergy(), E);
      70              :         }
      71            2 : }
      72              : 
      73            1 : TEST(ElectronPairProduction, belowEnergyTreshold) {
      74              :         // Test if nothing happens below 1e15 eV.
      75            1 :         ref_ptr<PhotonField> cmb = new CMB();
      76            1 :         ElectronPairProduction epp(cmb);
      77            1 :         Candidate c(nucleusId(1, 1), 1E14 * eV);
      78            1 :         epp.process(&c);
      79            1 :         EXPECT_DOUBLE_EQ(1E14 * eV, c.current.getEnergy());
      80            2 : }
      81              : 
      82            1 : TEST(ElectronPairProduction, thisIsNotNucleonic) {
      83              :         // Test if non-nuclei are skipped.
      84            1 :         ref_ptr<PhotonField> cmb = new CMB();
      85            1 :         ElectronPairProduction epp(cmb);
      86            1 :         Candidate c(11, 1E20 * eV);  // electron
      87            1 :         epp.process(&c);
      88            1 :         EXPECT_DOUBLE_EQ(1E20 * eV, c.current.getEnergy());
      89            2 : }
      90              : 
      91            2 : TEST(ElectronPairProduction, valuesCMB) {
      92              :         // Test if energy loss corresponds to the data table.
      93              :         std::vector<double> x;
      94              :         std::vector<double> y;
      95            1 :         std::ifstream infile(getDataPath("pair_CMB.txt").c_str());
      96            1 :         while (infile.good()) {
      97            0 :                 if (infile.peek() != '#') {
      98              :                         double a, b;
      99              :                         infile >> a >> b;
     100            0 :                         if (infile) {
     101            0 :                                 x.push_back(a * eV);
     102            0 :                                 y.push_back(b * eV / Mpc);
     103              :                         }
     104              :                 }
     105            0 :                 infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
     106              :         }
     107            1 :         infile.close();
     108              : 
     109            1 :         Candidate c;
     110            1 :         c.setCurrentStep(1 * Mpc);
     111            1 :         c.current.setId(nucleusId(1, 1)); // proton
     112            1 :         ref_ptr<PhotonField> cmb = new CMB();
     113              : 
     114            1 :         ElectronPairProduction epp(cmb);
     115            1 :         for (int i = 0; i < x.size(); i++) {
     116            0 :                 c.current.setEnergy(x[i]);
     117            0 :                 epp.process(&c);
     118            0 :                 double dE = x[i] - c.current.getEnergy();
     119            0 :                 double dE_table = y[i] * 1 * Mpc;
     120            0 :                 EXPECT_NEAR(dE_table, dE, 1e-12);
     121              :         }
     122            2 : }
     123              : 
     124            1 : TEST(ElectronPairProduction, interactionTag) {
     125              :         
     126            1 :         ref_ptr<PhotonField> cmb = new CMB();
     127            1 :         ElectronPairProduction epp(cmb);
     128              :         
     129              :         // test the default interaction tag
     130            1 :         EXPECT_TRUE(epp.getInteractionTag() == "EPP");
     131              : 
     132              :         // test changing the interaction tag
     133            1 :         epp.setInteractionTag("myTag");
     134            1 :         EXPECT_TRUE(epp.getInteractionTag() == "myTag");
     135              : 
     136              :         // test the tag of produced secondaries
     137            2 :         Candidate c;
     138            1 :         c.setCurrentStep(1 * Gpc);
     139            1 :         c.current.setId(nucleusId(1,1));
     140            1 :         c.current.setEnergy(100 * EeV);
     141            1 :         epp.setHaveElectrons(true);
     142            1 :         epp.process(&c);
     143              :         
     144            1 :         std::string secondaryTag = c.secondaries[0] -> getTagOrigin();
     145            1 :         EXPECT_TRUE(secondaryTag == "myTag");
     146            2 : }
     147              : 
     148            2 : TEST(ElectronPairProduction, valuesIRB) {
     149              :         // Test if energy loss corresponds to the data table.
     150              :         std::vector<double> x;
     151              :         std::vector<double> y;
     152            1 :         std::ifstream infile(getDataPath("pairIRB.txt").c_str());
     153            1 :         while (infile.good()) {
     154            0 :                 if (infile.peek() != '#') {
     155              :                         double a, b;
     156              :                         infile >> a >> b;
     157            0 :                         if (infile) {
     158            0 :                                 x.push_back(a * eV);
     159            0 :                                 y.push_back(b * eV / Mpc);
     160              :                         }
     161              :                 }
     162            0 :                 infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
     163              :         }
     164            1 :         infile.close();
     165              : 
     166            1 :         Candidate c;
     167            1 :         c.setCurrentStep(1 * Mpc);
     168            1 :         c.current.setId(nucleusId(1, 1)); // proton
     169            1 :         ref_ptr<PhotonField> irb = new IRB_Kneiske04();
     170              : 
     171            1 :         ElectronPairProduction epp(irb);
     172            1 :         for (int i = 0; i < x.size(); i++) {
     173            0 :                 c.current.setEnergy(x[i]);
     174            0 :                 epp.process(&c);
     175            0 :                 double dE = x[i] - c.current.getEnergy();
     176            0 :                 double dE_table = y[i] * 1 * Mpc;
     177            0 :                 EXPECT_NEAR(dE, dE_table, 1e-12);
     178              :         }
     179            2 : }
     180              : 
     181              : // NuclearDecay ---------------------------------------------------------------
     182            1 : TEST(NuclearDecay, scandium44) {
     183              :         // Test beta+ decay of 44Sc to 44Ca.
     184              :         // This test can stochastically fail.
     185            1 :         NuclearDecay d(true, true);
     186            1 :         Candidate c(nucleusId(44, 21), 1E18 * eV);
     187            1 :         c.setCurrentStep(100 * Mpc);
     188            1 :         double gamma = c.current.getLorentzFactor();
     189            1 :         d.process(&c);
     190              :         
     191              :         // expected decay product: 44Ca
     192            1 :         EXPECT_EQ(nucleusId(44, 20), c.current.getId());
     193              : 
     194              :         // expect Lorentz factor to be conserved
     195            1 :         EXPECT_DOUBLE_EQ(gamma, c.current.getLorentzFactor());
     196              :         
     197              :         // expect at least two secondaries: positron + electron neutrino
     198            1 :         EXPECT_GE(c.secondaries.size(), 2);
     199            1 : }
     200              : 
     201            1 : TEST(NuclearDecay, lithium4) {
     202              :         // Test proton dripping of Li-4 to He-3
     203              :         // This test can stochastically fail
     204            1 :         NuclearDecay d;
     205            1 :         Candidate c(nucleusId(4, 3), 4 * EeV);
     206            1 :         c.setCurrentStep(100 * Mpc);
     207            1 :         d.process(&c);
     208              :         
     209              :         // expected decay product: He-3
     210            1 :         EXPECT_EQ(nucleusId(3, 2), c.current.getId());
     211              : 
     212              :         // expected secondary: proton
     213            1 :         EXPECT_EQ(1, c.secondaries.size());
     214            2 :         Candidate c1 = *c.secondaries[0];
     215            1 :         EXPECT_EQ(nucleusId(1, 1), c1.current.getId());
     216            1 :         EXPECT_EQ(1 * EeV, c1.current.getEnergy());
     217            1 : }
     218              : 
     219            1 : TEST(NuclearDecay, helium5) {
     220              :         // Test neutron dripping of He-5 to He-4.
     221              :         // This test can stochastically fail.
     222            1 :         NuclearDecay d;
     223            1 :         Candidate c(nucleusId(5, 2), 5 * EeV);
     224            1 :         c.setCurrentStep(100 * Mpc);
     225            1 :         d.process(&c);
     226              : 
     227              :         // expected primary: He-4
     228            1 :         EXPECT_EQ(nucleusId(4, 2), c.current.getId());
     229            1 :         EXPECT_EQ(4, c.current.getEnergy() / EeV);
     230              :         
     231              :         // expected secondary: neutron
     232            2 :         Candidate c2 = *c.secondaries[0];
     233            1 :         EXPECT_EQ(nucleusId(1, 0), c2.current.getId());
     234            1 :         EXPECT_EQ(1, c2.current.getEnergy() / EeV);
     235            1 : }
     236              : 
     237            1 : TEST(NuclearDecay, limitNextStep) {
     238              :         // Test if next step is limited in case of a neutron.
     239            1 :         NuclearDecay decay;
     240            1 :         Candidate c(nucleusId(1, 0), 10 * EeV);
     241            1 :         c.setNextStep(std::numeric_limits<double>::max());
     242            1 :         decay.process(&c);
     243            1 :         EXPECT_LT(c.getNextStep(), std::numeric_limits<double>::max());
     244            1 : }
     245              : 
     246            1 : TEST(NuclearDecay, allChannelsWorking) {
     247              :         // Test if all nuclear decays are working.
     248            1 :         NuclearDecay d;
     249            1 :         Candidate c;
     250              : 
     251            1 :         std::ifstream infile(getDataPath("nuclear_decay.txt").c_str());
     252        11556 :         while (infile.good()) {
     253        11555 :                 if (infile.peek() != '#') {
     254              :                         int Z, N, channel, foo;
     255        11553 :                         infile >> Z >> N >> channel >> foo;
     256        11553 :                         c.current.setId(nucleusId(Z + N, Z));
     257        11553 :                         c.current.setEnergy(80 * EeV);
     258        11553 :                         d.performInteraction(&c, channel);
     259              :                 }
     260        11555 :                 infile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
     261              :         }
     262            1 :         infile.close();
     263            1 : }
     264              : 
     265            1 : TEST(NuclearDecay, secondaries) {
     266              :         // Test if all types of secondaries are produced.
     267            1 :         NuclearDecay d;
     268            1 :         d.setHaveElectrons(true);
     269            1 :         d.setHaveNeutrinos(true);
     270            1 :         d.setHavePhotons(true);
     271            1 :         Candidate c;
     272              : 
     273              :         // He-8 --> Li-8 + e- + neutrino
     274              :         // additional photon emitted with 84% probability
     275              :         // --> expect at least 1 photon out of 10 decays
     276           11 :         for (int i = 0; i < 10; ++i) {
     277           10 :                 c.current.setId(nucleusId(8, 2));
     278           10 :                 c.current.setEnergy(5 * EeV);
     279           10 :                 d.performInteraction(&c, 10000);
     280              :         }
     281              : 
     282              :         // count number of secondaries
     283            1 :         size_t nElectrons = 0;
     284            1 :         size_t nNeutrinos = 0;
     285            1 :         size_t nPhotons = 0;
     286              : 
     287           27 :         for(size_t i = 0; i < c.secondaries.size(); ++i) {
     288           26 :                 int id = (*c.secondaries[i]).current.getId();
     289           26 :                 if (id == 22) nPhotons++;
     290           26 :                 if (id == 11) nElectrons++;
     291           26 :                 if (id == -12) nNeutrinos++;
     292              :         }
     293              : 
     294            1 :         EXPECT_EQ(nElectrons, 10);
     295            1 :         EXPECT_EQ(nNeutrinos, 10);
     296            1 :         EXPECT_GE(nPhotons, 1);
     297            1 : }
     298              : 
     299            1 : TEST(NuclearDecay, thisIsNotNucleonic) {
     300              :         // Test if nothing happens to an electron
     301            1 :         NuclearDecay decay;
     302            1 :         Candidate c(11, 10 * EeV);
     303            1 :         c.setNextStep(std::numeric_limits<double>::max());
     304            1 :         decay.process(&c);
     305            1 :         EXPECT_EQ(11, c.current.getId());
     306            1 :         EXPECT_EQ(10 * EeV, c.current.getEnergy());
     307            1 : }
     308              : 
     309            1 : TEST(NuclearDecay, interactionTag) {
     310              :         // test default interaction tag
     311            1 :         NuclearDecay decay;
     312            1 :         EXPECT_TRUE(decay.getInteractionTag() == "ND");
     313              : 
     314              :         // test secondary tag
     315            1 :         decay.setHaveElectrons(true);
     316            2 :         Candidate c(nucleusId(8,2), 5 * EeV);
     317            1 :         decay.performInteraction(&c, 10000);
     318            1 :         EXPECT_TRUE(c.secondaries[0] -> getTagOrigin() == "ND");
     319              : 
     320              :         // test custom tags
     321            1 :         decay.setInteractionTag("myTag");
     322            1 :         EXPECT_TRUE(decay.getInteractionTag() == "myTag");
     323            1 : }
     324              : 
     325            1 : TEST(NuclearDecay, superheavy_stableHeavyNucleusIsNoOp) {
     326              :         // Zn-68 (Z=30, N=38) is a stable nucleus and has no entry in
     327              :         // nuclear_decay.txt even in the extended (Z<=82) table.
     328              :         // process() must hit the empty-vector guard and return without
     329              :         // touching the candidate.
     330            1 :         NuclearDecay d;
     331            1 :         Candidate c(nucleusId(68, 30), 80 * EeV);  // Zn-68, Z=30
     332            1 :         c.setCurrentStep(100 * Mpc);
     333            1 :         int id_before = c.current.getId();
     334            1 :         double E_before = c.current.getEnergy();
     335            1 :         d.process(&c);
     336            1 :         EXPECT_EQ(id_before, c.current.getId());
     337            1 :         EXPECT_DOUBLE_EQ(E_before, c.current.getEnergy());
     338            1 :         EXPECT_TRUE(c.isActive());
     339            1 : }
     340              : 
     341            1 : TEST(NuclearDecay, superheavy_beyondZmaxSkippedSilently) {
     342              :         // Nuclei with Z > NUCLEAR_ZMAX (82) must be caught by the hard bounds guard
     343              :         // in process() before any array access, leaving the candidate unchanged.
     344            1 :         NuclearDecay d;
     345            1 :         Candidate c(nucleusId(209, 83), 80 * EeV);  // Bi-209, Z=83
     346            1 :         c.setCurrentStep(100 * Mpc);
     347            1 :         int id_before = c.current.getId();
     348            1 :         double E_before = c.current.getEnergy();
     349            1 :         d.process(&c);
     350            1 :         EXPECT_EQ(id_before, c.current.getId());
     351            1 :         EXPECT_DOUBLE_EQ(E_before, c.current.getEnergy());
     352            1 :         EXPECT_TRUE(c.isActive());
     353            1 : }
     354              : 
     355              : 
     356            1 : TEST(NuclearDecay, superheavy_unstableHeavyNucleusDecays) {
     357              :         // Co-56 (Z=27, N=29) beta+ decays to stable Fe-56 (Z=26, N=30).
     358              :         // tau_rest ~ 13.9 Mdays; at 1 EeV the decay length is ~2 Mpc so it
     359              :         // decays well within a 100 Mpc step.
     360              :         // This test is skipped when the extended nuclear_decay.txt (Z<=82) is not
     361              :         // loaded; it can stochastically fail at energies where the decay length
     362              :         // exceeds the step, but the chosen energy makes that vanishingly unlikely.
     363            1 :         NuclearDecay d(true, false, false);  // enable electrons so positron is produced
     364            1 :         int co56 = nucleusId(56, 27);
     365            1 :         if (d.meanFreePath(co56, 1e7) == std::numeric_limits<double>::max())
     366              :                 return;  // extended file not deployed
     367              : 
     368            1 :         Candidate c(co56, 1e18 * eV);
     369            1 :         c.setCurrentStep(100 * Mpc);
     370            1 :         double gamma = c.current.getLorentzFactor();
     371            1 :         d.process(&c);
     372              : 
     373              :         // Fe-56 is the only stable beta+ daughter of Co-56
     374            1 :         EXPECT_EQ(nucleusId(56, 26), c.current.getId());
     375              :         // Lorentz factor conserved across beta+ decay (nuclear recoil negligible)
     376            1 :         EXPECT_DOUBLE_EQ(gamma, c.current.getLorentzFactor());
     377              :         // Positron secondary from beta+ decay
     378            1 :         EXPECT_GE(c.secondaries.size(), 1);
     379            1 : }
     380              : 
     381              : // PhotoDisintegration --------------------------------------------------------
     382            1 : TEST(PhotoDisintegration, allBackgrounds) {
     383              :         // Test if interaction data files are loaded.
     384            1 :         ref_ptr<PhotonField> cmb = new CMB();
     385            1 :         PhotoDisintegration pd(cmb);
     386            1 :         ref_ptr<PhotonField> irb = new IRB_Kneiske04();
     387            1 :         pd.setPhotonField(irb);
     388            1 :         ref_ptr<PhotonField> urb = new URB_Protheroe96();
     389            1 :         pd.setPhotonField(urb);
     390            1 :         irb = new IRB_Stecker05();
     391            1 :         pd.setPhotonField(irb);
     392            1 :         irb = new IRB_Franceschini08();
     393            1 :         pd.setPhotonField(irb);
     394            1 :         irb = new IRB_Finke10();
     395            1 :         pd.setPhotonField(irb);
     396            1 :         irb = new IRB_Dominguez11();
     397            1 :         pd.setPhotonField(irb);
     398            1 :         irb = new IRB_Gilmore12();
     399            1 :         pd.setPhotonField(irb);
     400            1 :         irb = new IRB_Stecker16_upper();
     401            1 :         pd.setPhotonField(irb);
     402            1 :         irb = new IRB_Stecker16_lower();
     403            1 :         pd.setPhotonField(irb);
     404            1 :         irb = new IRB_Finke22();
     405            1 :         pd.setPhotonField(irb);
     406            1 :         urb = new URB_Nitu21();
     407            2 :         pd.setPhotonField(urb);
     408            2 : }
     409              : 
     410            1 : TEST(PhotoDisintegration, carbon) {
     411              :         // Test if a 100 EeV C-12 nucleus photo-disintegrates (at least once) over a distance of 1 Gpc.
     412              :         // This test can stochastically fail.
     413            1 :         ref_ptr<PhotonField> cmb = new CMB();
     414            1 :         PhotoDisintegration pd(cmb);
     415            1 :         Candidate c;
     416            1 :         int id = nucleusId(12, 6);
     417            1 :         c.current.setId(id);
     418            1 :         c.current.setEnergy(100 * EeV);
     419            1 :         c.setCurrentStep(1000 * Mpc);
     420            1 :         pd.process(&c);
     421              : 
     422            1 :         EXPECT_TRUE(c.current.getEnergy() < 100 * EeV);
     423              :         // energy loss
     424            1 :         EXPECT_TRUE(c.secondaries.size() > 0);
     425              :         // secondaries produced
     426              : 
     427            1 :         double E = c.current.getEnergy();
     428            1 :         id = c.current.getId();
     429            1 :         int A = massNumber(id);
     430            1 :         int Z = chargeNumber(id);
     431              : 
     432            4 :         for (int i = 0; i < c.secondaries.size(); i++) {
     433            3 :                 E += (*c.secondaries[i]).current.getEnergy();
     434            3 :                 id = (*c.secondaries[i]).current.getId();
     435            3 :                 A += massNumber(id);
     436            3 :                 Z += chargeNumber(id);
     437              :         }
     438            1 :         EXPECT_EQ(12, A);
     439              :         // nucleon number conserved
     440            1 :         EXPECT_EQ(6, Z);
     441              :         // proton number conserved
     442            1 :         EXPECT_DOUBLE_EQ(100 * EeV, E);
     443              :         // energy conserved
     444            2 : }
     445              : 
     446            1 : TEST(PhotoDisintegration, iron) {
     447              :         // Test if a 200 EeV Fe-56 nucleus photo-disintegrates (at least once) over a distance of 1 Gpc.
     448              :         // This test can stochastically fail.
     449            1 :         ref_ptr<PhotonField> irb = new IRB_Kneiske04();
     450            1 :         PhotoDisintegration pd(irb);
     451            1 :         Candidate c;
     452            1 :         int id = nucleusId(56, 26);
     453            1 :         c.current.setId(id);
     454            1 :         c.current.setEnergy(200 * EeV);
     455            1 :         c.setCurrentStep(1000 * Mpc);
     456            1 :         pd.process(&c);
     457              : 
     458              :         // expect energy loss
     459            1 :         EXPECT_TRUE(c.current.getEnergy() < 200 * EeV);
     460              :         
     461              :         // expect secondaries produced
     462            1 :         EXPECT_TRUE(c.secondaries.size() > 0);
     463              : 
     464            1 :         double E = c.current.getEnergy();
     465            1 :         id = c.current.getId();
     466            1 :         int A = massNumber(id);
     467            1 :         int Z = chargeNumber(id);
     468              : 
     469           43 :         for (int i = 0; i < c.secondaries.size(); i++) {
     470           42 :                 E += (*c.secondaries[i]).current.getEnergy();
     471           42 :                 id = (*c.secondaries[i]).current.getId();
     472           42 :                 A += massNumber(id);
     473           42 :                 Z += chargeNumber(id);
     474              :         }
     475              : 
     476              :         // nucleon number conserved
     477            1 :         EXPECT_EQ(56, A);
     478              :         
     479              :         // proton number conserved (no decay active)
     480            1 :         EXPECT_EQ(26, Z);
     481              :         
     482              :         // energy conserved
     483            1 :         EXPECT_DOUBLE_EQ(200 * EeV, E);
     484            2 : }
     485              : 
     486            1 : TEST(PhotoDisintegration, thisIsNotNucleonic) {
     487              :         // Test that nothing happens to an electron.
     488            1 :         ref_ptr<PhotonField> cmb = new CMB();
     489            1 :         PhotoDisintegration pd(cmb);
     490            1 :         Candidate c;
     491            1 :         c.setCurrentStep(1 * Mpc);
     492            1 :         c.current.setId(11); // electron
     493            1 :         c.current.setEnergy(10 * EeV);
     494            1 :         pd.process(&c);
     495            1 :         EXPECT_EQ(11, c.current.getId());
     496            1 :         EXPECT_EQ(10 * EeV, c.current.getEnergy());
     497            2 : }
     498              : 
     499            1 : TEST(PhotoDisintegration, limitNextStep) {
     500              :         // Test if the interaction limits the next propagation step.
     501            1 :         ref_ptr<PhotonField> cmb = new CMB();
     502            1 :         PhotoDisintegration pd(cmb);
     503            1 :         Candidate c;
     504            1 :         c.setNextStep(std::numeric_limits<double>::max());
     505            1 :         c.current.setId(nucleusId(4, 2));
     506            1 :         c.current.setEnergy(200 * EeV);
     507            1 :         pd.process(&c);
     508            1 :         EXPECT_LT(c.getNextStep(), std::numeric_limits<double>::max());
     509            2 : }
     510              : 
     511            1 : TEST(PhotoDisintegration, allIsotopes) {
     512              :         // Test if all isotopes are handled.
     513            1 :         ref_ptr<PhotonField> cmb = new CMB();
     514            1 :         PhotoDisintegration pd1(cmb);
     515            1 :         ref_ptr<PhotonField> irb = new IRB_Kneiske04();
     516            1 :         PhotoDisintegration pd2(irb);
     517            1 :         Candidate c;
     518            1 :         c.setCurrentStep(10 * Mpc);
     519              : 
     520           27 :         for (int Z = 1; Z <= 26; Z++) {
     521          806 :                 for (int N = 1; N <= 30; N++) {
     522              : 
     523          780 :                         c.current.setId(nucleusId(Z + N, Z));
     524          780 :                         c.current.setEnergy(80 * EeV);
     525          780 :                         pd1.process(&c);
     526              : 
     527          780 :                         c.current.setId(nucleusId(Z + N, Z));
     528          780 :                         c.current.setEnergy(80 * EeV);
     529          780 :                         pd2.process(&c);
     530              :                 }
     531              :         }
     532            3 : }
     533              : 
     534            1 : TEST(Photodisintegration, updateParticleParentProperties) { // Issue: #204
     535            1 :         ref_ptr<PhotonField> cmb = new CMB();
     536            1 :         PhotoDisintegration pd(cmb);
     537              : 
     538            1 :         Candidate c(nucleusId(56,26), 500 * EeV, Vector3d(1 * Mpc, 0, 0));
     539              : 
     540            1 :         pd.performInteraction(&c, 1);
     541              :         // the candidates parent is the original particle
     542            1 :         EXPECT_EQ(c.created.getId(), nucleusId(56,26));
     543              : 
     544            1 :         pd.performInteraction(&c, 1);
     545              :         // now it has to be changed
     546            1 :         EXPECT_NE(c.created.getId(), nucleusId(56,26));
     547            2 : }
     548              : 
     549            1 : TEST(PhotoDisintegration, interactionTag) {
     550            1 :         PhotoDisintegration pd(new CMB());
     551              : 
     552              :         // test default interactionTag
     553            1 :         EXPECT_TRUE(pd.getInteractionTag() == "PD");
     554              : 
     555              :         // test secondary tag
     556            1 :         pd.setHavePhotons(true);
     557            2 :         Candidate c(nucleusId(56,26), 500 * EeV);
     558            1 :         c.setCurrentStep(1 * Gpc);
     559            1 :         pd.process(&c);
     560            1 :         EXPECT_TRUE(c.secondaries[0] -> getTagOrigin() == "PD");
     561              : 
     562              :         // test custom tag
     563            1 :         pd.setInteractionTag("myTag");
     564            1 :         EXPECT_TRUE(pd.getInteractionTag() == "myTag");
     565            1 : }
     566              : 
     567              : // PhotoDisintegration - Superheavy extension ---------------------------------
     568              : 
     569            1 : TEST(PhotoDisintegration, superheavy_standardTablesSkipHeavyNuclei) {
     570              :         // With standard tables (superheavy=false), nuclei with Z > 26 have no
     571              :         // entries in the TALYS 1.8 rate files.  The empty-rate guard in process()
     572              :         // must silently skip them, leaving the candidate unchanged.
     573            1 :         ref_ptr<PhotonField> cmb = new CMB();
     574            1 :         PhotoDisintegration pd(cmb, false, 0.1, false);
     575            1 :         Candidate c;
     576            1 :         c.current.setId(nucleusId(58, 28));  // Ni-58, Z=28
     577            1 :         c.current.setEnergy(200 * EeV);
     578            1 :         c.setCurrentStep(1000 * Mpc);
     579            1 :         int id_before = c.current.getId();
     580            1 :         double E_before = c.current.getEnergy();
     581            1 :         pd.process(&c);
     582            1 :         EXPECT_EQ(id_before, c.current.getId());
     583            1 :         EXPECT_DOUBLE_EQ(E_before, c.current.getEnergy());
     584            2 : }
     585              : 
     586            1 : TEST(PhotoDisintegration, superheavy_boundsGuardZmax) {
     587              :         // Nuclei with Z > NUCLEAR_ZMAX (82) must be caught by the hard bounds
     588              :         // guard in both process() and lossLength() before any array access.
     589              :         // The candidate must be left completely unchanged by process().
     590            1 :         ref_ptr<PhotonField> cmb = new CMB();
     591            1 :         PhotoDisintegration pd(cmb);
     592              :         // Bi-209: Z=83 > NUCLEAR_ZMAX=82
     593            1 :         Candidate c;
     594            1 :         c.current.setId(nucleusId(209, 83));
     595            1 :         c.current.setEnergy(200 * EeV);
     596            1 :         c.setCurrentStep(1000 * Mpc);
     597            1 :         int id_before = c.current.getId();
     598            1 :         double E_before = c.current.getEnergy();
     599            1 :         pd.process(&c);
     600            1 :         EXPECT_EQ(id_before, c.current.getId());
     601            1 :         EXPECT_DOUBLE_EQ(E_before, c.current.getEnergy());
     602            2 : }
     603              : 
     604            1 : TEST(PhotoDisintegration, superheavy_boundsGuardNmax) {
     605              :         // Nuclei with N > NUCLEAR_NMAX (132) must be caught by the hard bounds
     606              :         // guard in process() before any array access.
     607            1 :         ref_ptr<PhotonField> cmb = new CMB();
     608            1 :         PhotoDisintegration pd(cmb);
     609              :         // H-134: Z=1, N=133 > NUCLEAR_NMAX=132
     610            1 :         Candidate c;
     611            1 :         c.current.setId(nucleusId(134, 1));
     612            1 :         c.current.setEnergy(200 * EeV);
     613            1 :         c.setCurrentStep(1000 * Mpc);
     614            1 :         int id_before = c.current.getId();
     615            1 :         double E_before = c.current.getEnergy();
     616            1 :         pd.process(&c);
     617            1 :         EXPECT_EQ(id_before, c.current.getId());
     618            1 :         EXPECT_DOUBLE_EQ(E_before, c.current.getEnergy());
     619            2 : }
     620              : 
     621              : 
     622            1 : TEST(PhotoDisintegration, superheavy_lead) {
     623              :         // Test if a Pb-208 nucleus photo-disintegrates at least once over 1 Gpc
     624              :         // using the superheavy CMB tables.  A, Z, and energy conservation are
     625              :         // verified across all interactions in the step.
     626              :         // This test can stochastically fail.
     627            1 :         ref_ptr<PhotonField> cmb = new CMB();
     628            1 :         PhotoDisintegration pd(cmb, false, 0.1, true);
     629            1 :         Candidate c;
     630            1 :         int id = nucleusId(208, 82);
     631            1 :         c.current.setId(id);
     632              :         // Energy chosen so log10(gamma) ~ 10 for Pb-208, above the CMB threshold.
     633            1 :         c.current.setEnergy(2000 * EeV);
     634            1 :         c.setCurrentStep(1000 * Mpc);
     635            1 :         pd.process(&c);
     636              : 
     637            1 :         EXPECT_LT(c.current.getEnergy(), 2000 * EeV);
     638            1 :         EXPECT_GT(c.secondaries.size(), 0);
     639              : 
     640            1 :         double E = c.current.getEnergy();
     641            1 :         id = c.current.getId();
     642            1 :         int A = massNumber(id);
     643            1 :         int Z = chargeNumber(id);
     644           14 :         for (size_t i = 0; i < c.secondaries.size(); i++) {
     645           13 :                 E  += (*c.secondaries[i]).current.getEnergy();
     646           13 :                 id  = (*c.secondaries[i]).current.getId();
     647           13 :                 A  += massNumber(id);
     648           13 :                 Z  += chargeNumber(id);
     649              :         }
     650              :         // nucleon number conserved
     651            1 :         EXPECT_EQ(208, A);
     652              :         // proton number conserved
     653            1 :         EXPECT_EQ(82, Z);
     654              :         // energy conserved (EXPECT_NEAR: multiple interactions accumulate ~1e-12 J rounding)
     655            1 :         EXPECT_NEAR(2000 * EeV, E, 1e-9);
     656            2 : }
     657              : 
     658            1 : TEST(PhotoDisintegration, superheavy_allHeavyIsotopes) {
     659              :         // With superheavy=true, processing any nucleus in Z=27..NUCLEAR_ZMAX must
     660              :         // not crash, even for isotopes whose slots are empty in the rate table.
     661            1 :         ref_ptr<PhotonField> cmb = new CMB();
     662            1 :         PhotoDisintegration pd(cmb, false, 0.1, true);
     663            1 :         Candidate c;
     664            1 :         c.setCurrentStep(10 * Mpc);
     665           57 :         for (int Z = 27; Z <= NUCLEAR_ZMAX; Z++) {
     666         1736 :                 for (int N = 1; N <= 30; N++) {
     667         1680 :                         c.current.setId(nucleusId(Z + N, Z));
     668         1680 :                         c.current.setEnergy(80 * EeV);
     669         1680 :                         pd.process(&c);
     670              :                 }
     671              :         }
     672            2 : }
     673              : 
     674            1 : TEST(PhotoDisintegration, superheavy_setPhotonFieldReloads) {
     675              :         // setPhotonField(field, true/false) must swap between superheavy and standard
     676              :         // tables.  Pb-208 (only in superheavy tables) is used as the discriminating
     677              :         // nucleus: with standard tables it has no rate so it must not limit the next
     678              :         // step; with superheavy tables it has a rate so it must limit the next step.
     679            1 :         ref_ptr<PhotonField> cmb = new CMB();
     680            1 :         PhotoDisintegration pd(cmb);  // start with standard tables
     681              : 
     682              :         // Standard: Pb-208 has no rate — step must not be limited
     683            1 :         Candidate c_std(nucleusId(208, 82), 2000 * EeV);
     684            1 :         c_std.setNextStep(std::numeric_limits<double>::max());
     685            1 :         pd.process(&c_std);
     686            1 :         EXPECT_DOUBLE_EQ(c_std.getNextStep(), std::numeric_limits<double>::max());
     687              : 
     688              :         // Switch to superheavy: Pb-208 now has a rate — step must be limited
     689            1 :         pd.setPhotonField(cmb, true);
     690            2 :         Candidate c_shv(nucleusId(208, 82), 2000 * EeV);
     691            1 :         c_shv.setNextStep(std::numeric_limits<double>::max());
     692            1 :         pd.process(&c_shv);
     693            1 :         EXPECT_LT(c_shv.getNextStep(), std::numeric_limits<double>::max());
     694              : 
     695              :         // Switch back to standard: step no longer limited for Pb-208
     696            1 :         pd.setPhotonField(cmb, false);
     697            2 :         Candidate c_back(nucleusId(208, 82), 2000 * EeV);
     698            1 :         c_back.setNextStep(std::numeric_limits<double>::max());
     699            1 :         pd.process(&c_back);
     700            1 :         EXPECT_DOUBLE_EQ(c_back.getNextStep(), std::numeric_limits<double>::max());
     701            2 : }
     702              : 
     703            1 : TEST(PhotoDisintegration, superheavy_overlapRatesConsistent) {
     704              :         // Nuclei common to both the standard rate tables and the superheavy 
     705              :         // tables must yield consistent energy loss lengths (within 1%).
     706              :         // The cross sections branches for A<=12 are using the same existing
     707              :         // data since CRPropa 2.0
     708            1 :         ref_ptr<PhotonField> cmb = new CMB();
     709            1 :         ref_ptr<PhotonField> irb = new IRB_Gilmore12();
     710            2 :         PhotoDisintegration pd_cmb_std(cmb);
     711            2 :         PhotoDisintegration pd_cmb_shv(cmb, false, 0.1, true);
     712            2 :         PhotoDisintegration pd_irb_std(irb);
     713            1 :         PhotoDisintegration pd_irb_shv(irb, false, 0.1, true);
     714              : 
     715              :         // Excluded from this comparison:
     716              :         //   A<12  — cross section tables unchanged;
     717              :         //   N-14  — intrinsic ~1.77% cross-section difference reflected in
     718              :         //           the rate tables itself; outlier as all other nuclei agree
     719              :         //           to within <0.025%.
     720            1 :         struct Nucleus { int A, Z; } nuclei[] = {
     721              :                 {12, 6}, 
     722              :                 {16, 8}, 
     723              :                 {20, 10},
     724              :                 {24, 12},
     725              :                 {28, 14},
     726              :                 {40, 20},
     727              :                 {56, 26},
     728              :         };
     729              : 
     730              :         // Sweep log10(gamma) = 6..11 using the sum of CMB + IRB rates.
     731              :         // Above lg=11 both datasets begin to diverge due to extrapolation, so the
     732              :         // range is capped there, suitable for most cosmic ray applications.
     733              :         // Tolerance is 0.1%: actual deviations for the listed nuclei are <0.025%,
     734              :         // giving a 4x margin.
     735              :         const int nSteps = 126;
     736              :         const double lgMin = 6.0, lgMax = 11.0;
     737              :         const double llMax = std::numeric_limits<double>::max();
     738              : 
     739            8 :         for (auto &nuc : nuclei) {
     740            7 :                 int id = nucleusId(nuc.A, nuc.Z);
     741              :                 double sumRatio = 0;
     742              :                 int nValid = 0;
     743          889 :                 for (int i = 0; i < nSteps; i++) {
     744          882 :                         double gamma = pow(10, lgMin + (lgMax - lgMin) * i / (nSteps - 1));
     745          882 :                         double ll_cmb_std = pd_cmb_std.lossLength(id, gamma);
     746          882 :                         double ll_cmb_shv = pd_cmb_shv.lossLength(id, gamma);
     747          882 :                         double ll_irb_std = pd_irb_std.lossLength(id, gamma);
     748          882 :                         double ll_irb_shv = pd_irb_shv.lossLength(id, gamma);
     749          882 :                         double rate_std = (ll_cmb_std < llMax ? 1/ll_cmb_std : 0)
     750          882 :                                         + (ll_irb_std < llMax ? 1/ll_irb_std : 0);
     751          882 :                         double rate_shv = (ll_cmb_shv < llMax ? 1/ll_cmb_shv : 0)
     752          882 :                                         + (ll_irb_shv < llMax ? 1/ll_irb_shv : 0);
     753          882 :                         if (rate_std == 0 || rate_shv == 0) continue;
     754          875 :                         sumRatio += rate_std / rate_shv;
     755          875 :                         nValid++;
     756              :                 }
     757            7 :                 if (nValid == 0) continue;
     758            7 :                 double meanRatio = sumRatio / nValid;
     759            7 :                 EXPECT_GT(meanRatio, 0.999) << "A=" << nuc.A << " Z=" << nuc.Z
     760              :                                             << " mean ratio=" << meanRatio << " over " << nValid << " boosts";
     761            7 :                 EXPECT_LT(meanRatio, 1.001) << "A=" << nuc.A << " Z=" << nuc.Z
     762              :                                             << " mean ratio=" << meanRatio << " over " << nValid << " boosts";
     763              :         }
     764            2 : }
     765              : 
     766              : // ElasticScattering ----------------------------------------------------------
     767            1 : TEST(ElasticScattering, allBackgrounds) {
     768              :         // Test if interaction data files are loaded.
     769            1 :         ref_ptr<PhotonField> cmb = new CMB();
     770            1 :         ElasticScattering scattering(cmb);
     771            1 :         ref_ptr<PhotonField> irb = new IRB_Kneiske04();
     772            1 :         scattering.setPhotonField(irb);
     773            1 :         ref_ptr<PhotonField> urb = new URB_Nitu21();
     774            2 :         scattering.setPhotonField(urb);
     775            2 : }
     776              : 
     777            1 : TEST(ElasticScattering, secondaries) {
     778              :         // Test the creation of cosmic ray photons.
     779              :         // This test can stochastically fail.
     780            1 :         ref_ptr<PhotonField> cmb = new CMB();
     781            1 :         ElasticScattering scattering(cmb);
     782            1 :         Candidate c;
     783            1 :         int id = nucleusId(12, 6);
     784            1 :         c.current.setId(id);
     785            1 :         c.current.setEnergy(200 * EeV);
     786            1 :         c.setCurrentStep(400 * Mpc);
     787            1 :         scattering.process(&c);
     788              : 
     789            1 :         EXPECT_GT(c.secondaries.size(), 0);
     790              : 
     791           10 :         for (int i = 0; i < c.secondaries.size(); i++) {
     792            9 :                 int id = (*c.secondaries[i]).current.getId();
     793            9 :                 EXPECT_EQ(id, 22);
     794            9 :                 double energy = (*c.secondaries[i]).current.getEnergy();
     795            9 :                 EXPECT_GT(energy, 0);
     796            9 :                 EXPECT_LT(energy, 200 * EeV);
     797              :         }
     798            2 : }
     799              : 
     800              : // PhotoPionProduction --------------------------------------------------------
     801            1 : TEST(PhotoPionProduction, allBackgrounds) {
     802              :         // Test if all interaction data files can be loaded.
     803            1 :         ref_ptr<PhotonField> cmb = new CMB();
     804            1 :         PhotoPionProduction ppp(cmb);
     805            1 :         ref_ptr<PhotonField> irb = new IRB_Kneiske04();
     806            1 :         ppp.setPhotonField(irb);
     807            1 :         irb = new IRB_Stecker05();
     808            1 :         ppp.setPhotonField(irb);
     809            1 :         irb = new IRB_Franceschini08();
     810            1 :         ppp.setPhotonField(irb);
     811            1 :         irb = new IRB_Finke10();
     812            1 :         ppp.setPhotonField(irb);
     813            1 :         irb = new IRB_Dominguez11();
     814            1 :         ppp.setPhotonField(irb);
     815            1 :         irb = new IRB_Gilmore12();
     816            1 :         ppp.setPhotonField(irb);
     817            1 :         irb = new IRB_Stecker16_upper();
     818            1 :         ppp.setPhotonField(irb);
     819            1 :         irb = new IRB_Stecker16_lower();
     820            1 :         ppp.setPhotonField(irb);
     821            1 :         irb = new IRB_Finke22();
     822            1 :         ppp.setPhotonField(irb);
     823            1 :         ref_ptr<PhotonField> urb = new URB_Protheroe96();
     824            1 :         ppp.setPhotonField(urb);
     825            1 :         urb = new URB_Nitu21();
     826            2 :         ppp.setPhotonField(urb);
     827            2 : }
     828              : 
     829            1 : TEST(PhotoPionProduction, proton) {
     830              :         // Test photopion interaction for 100 EeV proton.
     831              :         // This test can stochastically fail.
     832            1 :         ref_ptr<PhotonField> cmb = new CMB();
     833            1 :         PhotoPionProduction ppp(cmb);
     834            1 :         Candidate c(nucleusId(1, 1), 100 * EeV);
     835            1 :         c.setCurrentStep(1000 * Mpc);
     836            1 :         ppp.process(&c);
     837              : 
     838              :         // expect energy loss
     839            1 :         EXPECT_LT(c.current.getEnergy(), 100. * EeV);
     840              : 
     841              :         // expect nucleon number conservation
     842            1 :         EXPECT_EQ(1, massNumber(c.current.getId()));
     843              : 
     844              :         // expect no (nucleonic) secondaries
     845            1 :         EXPECT_EQ(0, c.secondaries.size());
     846            2 : }
     847              : 
     848            1 : TEST(PhotoPionProduction, helium) {
     849              :         // Test photo-pion interaction for 400 EeV He nucleus.
     850              :         // This test can stochastically fail.
     851            1 :         ref_ptr<PhotonField> cmb = new CMB();
     852            1 :         PhotoPionProduction ppp(cmb);
     853            1 :         Candidate c;
     854            1 :         c.current.setId(nucleusId(4, 2));
     855            1 :         c.current.setEnergy(400. * EeV);
     856            1 :         c.setCurrentStep(1000 * Mpc);
     857            1 :         ppp.process(&c);
     858            1 :         EXPECT_LT(c.current.getEnergy(), 400. * EeV);
     859            1 :         int id = c.current.getId();
     860            1 :         EXPECT_TRUE(massNumber(id) < 4);
     861            1 :         EXPECT_TRUE(c.secondaries.size() > 0);
     862            2 : }
     863              : 
     864            1 : TEST(PhotoPionProduction, thisIsNotNucleonic) {
     865              :         // Test if nothing happens to an electron.
     866            1 :         ref_ptr<PhotonField> cmb = new CMB();
     867            1 :         PhotoPionProduction ppp(cmb);
     868            1 :         Candidate c;
     869            1 :         c.current.setId(11); // electron
     870            1 :         c.current.setEnergy(10 * EeV);
     871            1 :         c.setCurrentStep(100 * Mpc);
     872            1 :         ppp.process(&c);
     873            1 :         EXPECT_EQ(11, c.current.getId());
     874            1 :         EXPECT_EQ(10 * EeV, c.current.getEnergy());
     875            2 : }
     876              : 
     877            1 : TEST(PhotoPionProduction, limitNextStep) {
     878              :         // Test if the interaction limits the next propagation step.
     879            1 :         ref_ptr<PhotonField> cmb = new CMB();
     880            1 :         PhotoPionProduction ppp(cmb);
     881            1 :         Candidate c(nucleusId(1, 1), 200 * EeV);
     882            1 :         c.setNextStep(std::numeric_limits<double>::max());
     883            1 :         ppp.process(&c);
     884            1 :         EXPECT_LT(c.getNextStep(), std::numeric_limits<double>::max());
     885            2 : }
     886              : 
     887            1 : TEST(PhotoPionProduction, secondaries) {
     888              :         // Test photo-pion interaction for 100 EeV proton.
     889              :         // This test can stochastically fail.
     890            1 :         ref_ptr<PhotonField> cmb = new CMB();
     891            1 :         PhotoPionProduction ppp(cmb, true, true, true);
     892            1 :         Candidate c(nucleusId(1, 1), 100 * EeV);
     893            1 :         c.setCurrentStep(1000 * Mpc);
     894            1 :         ppp.process(&c);
     895              :         // there should be secondaries
     896            1 :         EXPECT_GT(c.secondaries.size(), 1);
     897            2 : }
     898              : 
     899            1 : TEST(PhotoPionProduction, sampling) {
     900              :         // Specific test of photon sampling of photo-pion production
     901              :         // by testing the calculated pEpsMax for CMB(), also indirectly
     902              :         // testing epsMinInteraction and logSampling (default).
     903            1 :         ref_ptr<PhotonField> cmb = new CMB(); //create CMB instance
     904              :         double energy = 1.e10; //1e10 GeV
     905              :         bool onProton = true; //proton
     906              :         double z = 0; //no redshift
     907            1 :         PhotoPionProduction ppp(cmb, true, true, true);
     908            1 :         double correctionFactor = ppp.getCorrectionFactor(); //get current correctionFactor
     909            2 :         double epsMin = std::max(cmb -> getMinimumPhotonEnergy(z) / eV, 0.00710614); // 0.00710614 = epsMinInteraction(onProton,energy)
     910            1 :         double epsMax = cmb -> getMaximumPhotonEnergy(z) / eV;
     911            1 :         double pEpsMax = ppp.probEpsMax(onProton, energy, z, epsMin, epsMax) / correctionFactor;
     912            1 :         EXPECT_DOUBLE_EQ(pEpsMax,132673934934.922);
     913            2 : }
     914              : 
     915            1 : TEST(PhotoPionProduction, interactionTag) {
     916            1 :         PhotoPionProduction ppp(new CMB());
     917              : 
     918              :         // test default interactionTag
     919            1 :         EXPECT_TRUE(ppp.getInteractionTag() == "PPP");
     920              : 
     921              :         // test secondary tag
     922            1 :         ppp.setHavePhotons(true);
     923            2 :         Candidate c(nucleusId(1,1), 100 * EeV);
     924           11 :         for(int i = 0; i <10; i++) 
     925           10 :                 ppp.performInteraction(&c, true);
     926            1 :         EXPECT_TRUE(c.secondaries[0] -> getTagOrigin() == "PPP");
     927              : 
     928              :         // test custom interactionTag
     929            1 :         ppp.setInteractionTag("myTag");
     930            1 :         EXPECT_TRUE(ppp.getInteractionTag() == "myTag");
     931            1 : }
     932              : 
     933              : // Redshift -------------------------------------------------------------------
     934            2 : TEST(Redshift, simpleTest) {
     935              :         // Test if redshift is decreased and adiabatic energy loss is applied.
     936              :         Redshift redshift;
     937              : 
     938            1 :         Candidate c;
     939            1 :         c.setRedshift(0.024);
     940            1 :         c.current.setEnergy(100 * EeV);
     941            1 :         c.setCurrentStep(1 * Mpc);
     942              : 
     943            1 :         redshift.process(&c);
     944            1 :         EXPECT_GT(0.024, c.getRedshift()); // expect redshift decrease
     945            1 :         EXPECT_GT(100, c.current.getEnergy() / EeV); // expect energy loss
     946            2 : }
     947              : 
     948            2 : TEST(Redshift, limitRedshiftDecrease) {
     949              :         // Test if the redshift decrease is limited to z_updated >= 0.
     950              :         Redshift redshift;
     951              : 
     952            1 :         Candidate c;
     953            1 :         c.setRedshift(0.024); // roughly corresponds to 100 Mpc
     954            1 :         c.setCurrentStep(150 * Mpc);
     955              : 
     956            1 :         redshift.process(&c);
     957            1 :         EXPECT_DOUBLE_EQ(0, c.getRedshift());
     958            2 : }
     959              : 
     960              : // EMPairProduction -----------------------------------------------------------
     961            1 : TEST(EMPairProduction, allBackgrounds) {
     962              :         // Test if interaction data files are loaded.
     963            1 :         ref_ptr<PhotonField> cmb = new CMB();
     964            2 :         EMPairProduction em(cmb);
     965            1 :         ref_ptr<PhotonField> ebl = new IRB_Kneiske04();
     966            1 :         em.setPhotonField(ebl);
     967            1 :         ref_ptr<PhotonField> urb = new URB_Protheroe96();
     968            1 :         em.setPhotonField(urb);
     969              :         
     970            1 :         ebl = new IRB_Stecker05();
     971            1 :         em.setPhotonField(ebl);
     972            1 :         ebl = new IRB_Franceschini08();
     973            1 :         em.setPhotonField(ebl);
     974            1 :         ebl = new IRB_Finke10();
     975            1 :         em.setPhotonField(ebl);
     976            1 :         ebl = new IRB_Dominguez11();
     977            1 :         em.setPhotonField(ebl);
     978            1 :         ebl = new IRB_Gilmore12();
     979            1 :         em.setPhotonField(ebl);
     980            1 :         ebl = new IRB_Stecker16_upper();
     981            1 :         em.setPhotonField(ebl);
     982            1 :         ebl = new IRB_Stecker16_lower();
     983            1 :         em.setPhotonField(ebl);
     984            1 :         ebl = new IRB_Finke22();
     985            1 :         em.setPhotonField(ebl);
     986            1 :         urb = new URB_Fixsen11();
     987            1 :         em.setPhotonField(urb);
     988            1 :         urb = new URB_Nitu21();
     989            2 :         em.setPhotonField(urb);
     990            2 : }
     991              : 
     992            1 : TEST(EMPairProduction, limitNextStep) {
     993              :         // Test if the interaction limits the next propagation step.
     994            1 :         ref_ptr<PhotonField> cmb = new CMB();
     995            2 :         EMPairProduction m(cmb);
     996            1 :         Candidate c(22, 1E17 * eV);
     997            1 :         c.setNextStep(std::numeric_limits<double>::max());
     998            1 :         m.process(&c);
     999            1 :         EXPECT_LT(c.getNextStep(), std::numeric_limits<double>::max());
    1000            2 : }
    1001              : 
    1002            1 : TEST(EMPairProduction, secondaries) {
    1003              :         // Test if secondaries are correctly produced.
    1004            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1005            1 :         ref_ptr<PhotonField> irb = new IRB_Saldana21();
    1006            1 :         ref_ptr<PhotonField> urb = new URB_Nitu21();
    1007            2 :         EMPairProduction m(cmb);
    1008            1 :         m.setHaveElectrons(true);
    1009            1 :         m.setThinning(0.);
    1010              : 
    1011              :         std::vector<ref_ptr<PhotonField>> fields;
    1012            1 :         fields.push_back(cmb);
    1013            1 :         fields.push_back(irb);
    1014            1 :         fields.push_back(urb);
    1015              : 
    1016              :         // loop over photon backgrounds
    1017            4 :         for (int f = 0; f < fields.size(); f++) {
    1018            3 :                 m.setPhotonField(fields[f]);
    1019          423 :                 for (int i = 0; i < 140; i++) { // loop over energies Ep = (1e10 - 1e23) eV
    1020          420 :                         double Ep = pow(10, 9.05 + 0.1 * i) * eV;
    1021          420 :                         Candidate c(22, Ep);
    1022          420 :                         c.setCurrentStep(1e4 * Mpc);
    1023          420 :                         m.process(&c);
    1024              : 
    1025              :                         // pass if no interaction has ocurred (no tabulated rates)
    1026          420 :                         if (c.isActive())
    1027              :                                 continue;
    1028              : 
    1029              :                         // expect 2 secondaries
    1030          245 :                         EXPECT_EQ(c.secondaries.size(), 2);
    1031              : 
    1032              :                         // expect electron / positron with energies 0 < E < Ephoton
    1033              :                         double Etot = 0;
    1034          735 :                         for (int j = 0; j < c.secondaries.size(); j++) {
    1035          490 :                                 Candidate s = *c.secondaries[j];
    1036          490 :                                 EXPECT_EQ(abs(s.current.getId()), 11);
    1037          490 :                                 EXPECT_GT(s.current.getEnergy(), 0);
    1038          490 :                                 EXPECT_LT(s.current.getEnergy(), Ep);
    1039          490 :                                 Etot += s.current.getEnergy();
    1040          490 :                         }
    1041              : 
    1042              :                         // test energy conservation 
    1043          245 :                 EXPECT_DOUBLE_EQ(Ep, Etot);
    1044          420 :                 }
    1045              :         }
    1046            2 : }
    1047              : 
    1048            2 : TEST(EMPairProduction, interactionTag) {
    1049            2 :         EMPairProduction m(new CMB());
    1050              : 
    1051              :         // test default interactionTag
    1052            1 :         EXPECT_TRUE(m.getInteractionTag() == "EMPP");
    1053              : 
    1054              :         // test secondary tag
    1055            1 :         m.setHaveElectrons(true);
    1056            2 :         Candidate c(22, 1 * EeV);
    1057            1 :         m.performInteraction(&c);
    1058            1 :         EXPECT_TRUE(c.secondaries[0] -> getTagOrigin() == "EMPP");
    1059              : 
    1060              :         // test custom tag
    1061            1 :         m.setInteractionTag("myTag");
    1062            1 :         EXPECT_TRUE(m.getInteractionTag() == "myTag");
    1063            1 : }
    1064              : 
    1065              : // EMDoublePairProduction -----------------------------------------------------
    1066            1 : TEST(EMDoublePairProduction, allBackgrounds) {
    1067              :         // Test if interaction data files are loaded.
    1068            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1069            2 :         EMDoublePairProduction em(cmb);
    1070            1 :         ref_ptr<PhotonField> ebl = new IRB_Kneiske04();
    1071            1 :         em.setPhotonField(ebl);
    1072            1 :         ref_ptr<PhotonField> urb = new URB_Protheroe96();
    1073            1 :         em.setPhotonField(urb);
    1074              :   
    1075            1 :         ebl = new IRB_Stecker05();
    1076            1 :         em.setPhotonField(ebl);
    1077            1 :         ebl = new IRB_Franceschini08();
    1078            1 :         em.setPhotonField(ebl);
    1079            1 :         ebl = new IRB_Finke10();
    1080            1 :         em.setPhotonField(ebl);
    1081            1 :         ebl = new IRB_Dominguez11();
    1082            1 :         em.setPhotonField(ebl);
    1083            1 :         ebl = new IRB_Gilmore12();
    1084            1 :         em.setPhotonField(ebl);
    1085            1 :         ebl = new IRB_Stecker16_upper();
    1086            1 :         em.setPhotonField(ebl);
    1087            1 :         ebl = new IRB_Stecker16_lower();
    1088            1 :         em.setPhotonField(ebl);
    1089            1 :         ebl = new IRB_Finke22();
    1090            1 :         em.setPhotonField(ebl);
    1091            1 :         urb = new URB_Fixsen11();
    1092            1 :         em.setPhotonField(urb);
    1093            1 :         urb = new URB_Nitu21();
    1094            2 :         em.setPhotonField(urb);
    1095            2 : }
    1096              : 
    1097            1 : TEST(EMDoublePairProduction, limitNextStep) {
    1098              :         // Test if the interaction limits the next propagation step.
    1099            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1100            2 :         EMDoublePairProduction m(cmb);
    1101            1 :         Candidate c(22, 1E17 * eV);
    1102            1 :         c.setNextStep(std::numeric_limits<double>::max());
    1103            1 :         m.process(&c);
    1104            1 :         EXPECT_LT(c.getNextStep(), std::numeric_limits<double>::max());
    1105            2 : }
    1106              : 
    1107            1 : TEST(EMDoublePairProduction, secondaries) {
    1108              :         // Test if secondaries are correctly produced.
    1109            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1110            1 :         ref_ptr<PhotonField> irb = new IRB_Saldana21();
    1111            1 :         ref_ptr<PhotonField> urb = new URB_Nitu21();
    1112            2 :         EMPairProduction m(cmb);
    1113            1 :         m.setHaveElectrons(true);
    1114            1 :         m.setThinning(0.);
    1115              : 
    1116              :         std::vector<ref_ptr<PhotonField>> fields;
    1117            1 :         fields.push_back(cmb);
    1118            1 :         fields.push_back(irb);
    1119            1 :         fields.push_back(urb);
    1120              : 
    1121              :         // loop over photon backgrounds
    1122            4 :         for (int f = 0; f < fields.size(); f++) {
    1123            3 :                 m.setPhotonField(fields[f]);
    1124              :                 
    1125              :                 // loop over energies Ep = (1e9 - 1e23) eV
    1126          423 :                 for (int i = 0; i < 140; i++) {
    1127          420 :                         double Ep = pow(10, 9.05 + 0.1 * i) * eV;
    1128          420 :                         Candidate c(22, Ep);
    1129          420 :                         c.setCurrentStep(1e4 * Mpc); // use lower value so that the test can run faster
    1130          420 :                         m.process(&c);
    1131              : 
    1132              :                         // pass if no interaction has occured (no tabulated rates)
    1133          420 :                         if (c.isActive())
    1134              :                                 continue;
    1135              :                         
    1136              :                         // expect 2 secondaries (only one pair is considered)
    1137          252 :                         EXPECT_EQ(c.secondaries.size(), 2);
    1138              : 
    1139              :                         // expect electron / positron with energies 0 < E < Ephoton
    1140              :                         double Etot = 0;
    1141          756 :                         for (int j = 0; j < c.secondaries.size(); j++) {
    1142          504 :                                 Candidate s = *c.secondaries[j];
    1143          504 :                                 EXPECT_EQ(abs(s.current.getId()), 11);
    1144          504 :                                 EXPECT_GT(s.current.getEnergy(), 0);
    1145          504 :                                 EXPECT_LT(s.current.getEnergy(), Ep);
    1146          504 :                                 Etot += s.current.getEnergy();
    1147          504 :                         }
    1148              : 
    1149              :                         // test energy conservation
    1150          252 :                         EXPECT_NEAR(Ep, Etot, 1E-9);
    1151          420 :                 }
    1152              :         }
    1153            2 : }
    1154              : 
    1155            2 : TEST(EMDoublePairProduction, interactionTag) {
    1156            2 :         EMDoublePairProduction m(new CMB());
    1157              : 
    1158              :         // test default interactionTag
    1159            1 :         EXPECT_TRUE(m.getInteractionTag() == "EMDP");
    1160              : 
    1161              :         // test secondary tag
    1162            1 :         m.setHaveElectrons(true);
    1163            2 :         Candidate c(22, 1 * EeV);
    1164            1 :         m.performInteraction(&c);
    1165            1 :         EXPECT_TRUE(c.secondaries[0] -> getTagOrigin() == "EMDP");
    1166              : 
    1167              :         // test custom tag
    1168            1 :         m.setInteractionTag("myTag");
    1169            1 :         EXPECT_TRUE(m.getInteractionTag() == "myTag");
    1170            1 : }
    1171              : 
    1172              : // EMTripletPairProduction ----------------------------------------------------
    1173            1 : TEST(EMTripletPairProduction, allBackgrounds) {
    1174              :         // Test if interaction data files are loaded.
    1175            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1176            2 :         EMTripletPairProduction em(cmb);
    1177            1 :         ref_ptr<PhotonField> ebl = new IRB_Kneiske04();
    1178            1 :         em.setPhotonField(ebl);
    1179            1 :         ref_ptr<PhotonField> urb = new URB_Protheroe96();
    1180            1 :         em.setPhotonField(urb);
    1181              :   
    1182            1 :         ebl = new IRB_Stecker05();
    1183            1 :         em.setPhotonField(ebl);
    1184            1 :         ebl = new IRB_Franceschini08();
    1185            1 :         em.setPhotonField(ebl);
    1186            1 :         ebl = new IRB_Finke10();
    1187            1 :         em.setPhotonField(ebl);
    1188            1 :         ebl = new IRB_Dominguez11();
    1189            1 :         em.setPhotonField(ebl);
    1190            1 :         ebl = new IRB_Gilmore12();
    1191            1 :         em.setPhotonField(ebl);
    1192            1 :         ebl = new IRB_Stecker16_upper();
    1193            1 :         em.setPhotonField(ebl);
    1194            1 :         ebl = new IRB_Stecker16_lower();
    1195            1 :         em.setPhotonField(ebl);
    1196            1 :         ebl = new IRB_Finke22();
    1197            1 :         em.setPhotonField(ebl);
    1198            1 :         urb = new URB_Fixsen11();
    1199            1 :         em.setPhotonField(urb);
    1200            1 :         urb = new URB_Nitu21();
    1201            2 :         em.setPhotonField(urb);
    1202            2 : }
    1203              : 
    1204            1 : TEST(EMTripletPairProduction, limitNextStep) {
    1205              :         // Test if the interaction limits the next propagation step.
    1206            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1207            2 :         EMTripletPairProduction m(cmb);
    1208            1 :         Candidate c(11, 1E17 * eV);
    1209            1 :         c.setNextStep(std::numeric_limits<double>::max());
    1210            1 :         m.process(&c);
    1211            1 :         EXPECT_LT(c.getNextStep(), std::numeric_limits<double>::max());
    1212            2 : }
    1213              : 
    1214            1 : TEST(EMTripletPairProduction, secondaries) {
    1215              :         // Test if secondaries are correctly produced.
    1216            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1217            1 :         ref_ptr<PhotonField> irb = new IRB_Saldana21();
    1218            1 :         ref_ptr<PhotonField> urb = new URB_Nitu21();
    1219            2 :         EMPairProduction m(cmb);
    1220            1 :         m.setHaveElectrons(true);
    1221            1 :         m.setThinning(0.);
    1222              : 
    1223              :         std::vector<ref_ptr<PhotonField>> fields;
    1224            1 :         fields.push_back(cmb);
    1225            1 :         fields.push_back(irb);
    1226            1 :         fields.push_back(urb);
    1227              : 
    1228              :         // loop over photon backgrounds
    1229            4 :         for (int f = 0; f < fields.size(); f++) {
    1230            3 :                 m.setPhotonField(fields[f]);
    1231              :                 
    1232              :                 // loop over energies Ep = (1e9 - 1e23) eV
    1233          423 :                 for (int i = 0; i < 140; i++) {
    1234              : 
    1235          420 :                         double Ep = pow(10, 9.05 + 0.1 * i) * eV;
    1236          420 :                         Candidate c(11, Ep);
    1237          420 :                         c.setCurrentStep(1e4 * Mpc); // use lower value so that the test can run faster
    1238          420 :                         m.process(&c);
    1239              : 
    1240              :                         // pass if no interaction has occured (no tabulated rates)
    1241          420 :                         if (c.current.getEnergy() == Ep)
    1242              :                                 continue;
    1243              : 
    1244              :                         // expect positive energy of primary electron
    1245            0 :                         EXPECT_GT(c.current.getEnergy(), 0);
    1246            0 :                         double Etot = c.current.getEnergy();
    1247              : 
    1248              :                         // expect electron / positron with energies 0 < E < Ephoton
    1249            0 :                         for (int j = 0; j < c.secondaries.size(); j++) {
    1250            0 :                                 Candidate s = *c.secondaries[j];
    1251            0 :                                 EXPECT_EQ(abs(s.current.getId()), 11);
    1252            0 :                                 EXPECT_GT(s.current.getEnergy(), 0);
    1253            0 :                                 EXPECT_LT(s.current.getEnergy(), Ep);
    1254            0 :                                 Etot += s.current.getEnergy();
    1255            0 :                         }
    1256              : 
    1257              :                         // test energy conservation
    1258            0 :                         EXPECT_NEAR(Ep, Etot, 1e-9);
    1259          420 :                 }
    1260              :         }
    1261            2 : }
    1262              : 
    1263            2 : TEST(EMTripletPairProduction, interactionTag) {
    1264            2 :         EMTripletPairProduction m(new CMB());
    1265              : 
    1266              :         // test default interactionTag
    1267            1 :         EXPECT_TRUE(m.getInteractionTag() == "EMTP");
    1268              : 
    1269              :         // test secondary tag
    1270            1 :         m.setHaveElectrons(true);
    1271            2 :         Candidate c(11, 1 * EeV);
    1272            1 :         m.performInteraction(&c);
    1273            1 :         EXPECT_TRUE(c.secondaries[0] -> getTagOrigin() == "EMTP");
    1274              : 
    1275              :         // test custom tag
    1276            1 :         m.setInteractionTag("myTag");
    1277            1 :         EXPECT_TRUE(m.getInteractionTag() == "myTag");
    1278            1 : }
    1279              : 
    1280              : // EMInverseComptonScattering -------------------------------------------------
    1281            1 : TEST(EMInverseComptonScattering, allBackgrounds) {
    1282              :         // Test if interaction data files are loaded.
    1283            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1284            2 :         EMInverseComptonScattering em(cmb);
    1285            1 :         ref_ptr<PhotonField> ebl = new IRB_Kneiske04();
    1286            1 :         em.setPhotonField(ebl);
    1287            1 :         ref_ptr<PhotonField> urb = new URB_Protheroe96();
    1288            1 :         em.setPhotonField(urb);
    1289              :   
    1290            1 :         ebl = new IRB_Stecker05();
    1291            1 :         em.setPhotonField(ebl);
    1292            1 :         ebl = new IRB_Franceschini08();
    1293            1 :         em.setPhotonField(ebl);
    1294            1 :         ebl = new IRB_Finke10();
    1295            1 :         em.setPhotonField(ebl);
    1296            1 :         ebl = new IRB_Dominguez11();
    1297            1 :         em.setPhotonField(ebl);
    1298            1 :         ebl = new IRB_Gilmore12();
    1299            1 :         em.setPhotonField(ebl);
    1300            1 :         ebl = new IRB_Stecker16_upper();
    1301            1 :         em.setPhotonField(ebl);
    1302            1 :         ebl = new IRB_Stecker16_lower();
    1303            1 :         em.setPhotonField(ebl);
    1304            1 :         ebl = new IRB_Finke22();
    1305            1 :         em.setPhotonField(ebl);
    1306            1 :         urb = new URB_Fixsen11();
    1307            1 :         em.setPhotonField(urb);
    1308            1 :         urb = new URB_Nitu21();
    1309            2 :         em.setPhotonField(urb);
    1310            2 : }
    1311              : 
    1312            1 : TEST(EMInverseComptonScattering, limitNextStep) {
    1313              :         // Test if the interaction limits the next propagation step.
    1314            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1315            2 :         EMInverseComptonScattering m(cmb);
    1316            1 :         Candidate c(11, 1E17 * eV);
    1317            1 :         c.setNextStep(std::numeric_limits<double>::max());
    1318            1 :         m.process(&c);
    1319            1 :         EXPECT_LT(c.getNextStep(), std::numeric_limits<double>::max());
    1320            2 : }
    1321              : 
    1322            1 : TEST(EMInverseComptonScattering, secondaries) {
    1323              :         // Test if secondaries are correctly produced.
    1324            1 :         ref_ptr<PhotonField> cmb = new CMB();
    1325            1 :         ref_ptr<PhotonField> irb = new IRB_Saldana21();
    1326            1 :         ref_ptr<PhotonField> urb = new URB_Nitu21();
    1327            2 :         EMPairProduction m(cmb);
    1328            1 :         m.setHaveElectrons(true);
    1329            1 :         m.setThinning(0.);
    1330              : 
    1331              :         std::vector<ref_ptr<PhotonField>> fields;
    1332            1 :         fields.push_back(cmb);
    1333            1 :         fields.push_back(irb);
    1334            1 :         fields.push_back(urb);
    1335              : 
    1336              :         // loop over photon backgrounds
    1337            4 :         for (int f = 0; f < fields.size(); f++) {
    1338            3 :                 m.setPhotonField(fields[f]);
    1339              :                 
    1340              :                 // loop over energies Ep = (1e9 - 1e23) eV
    1341          423 :                 for (int i = 0; i < 140; i++) {
    1342          420 :                         double Ep = pow(10, 9.05 + 0.1 * i) * eV;
    1343          420 :                         Candidate c(11, Ep);
    1344          420 :                         c.setCurrentStep(1e3 * Mpc); // use lower value so that the test can run faster
    1345          420 :                         m.process(&c);
    1346              : 
    1347              :                         // pass if no interaction has occured (no tabulated rates)
    1348          420 :                         if (c.current.getEnergy() == Ep)
    1349              :                                 continue;
    1350              :                         
    1351              :                         // expect positive energy of primary electron
    1352            0 :                         EXPECT_GT(c.current.getEnergy(), 0);
    1353              : 
    1354              :                         // expect photon with energy 0 < E < Ephoton
    1355            0 :                         Candidate s = *c.secondaries[0];
    1356            0 :                         EXPECT_EQ(abs(s.current.getId()), 22);
    1357            0 :                         EXPECT_TRUE(s.current.getEnergy() >= 0.);
    1358            0 :                         EXPECT_TRUE(s.current.getEnergy() < Ep);
    1359              : 
    1360              : 
    1361            0 :                         double Etot = c.current.getEnergy();
    1362            0 :                         for (int j = 0; j < c.secondaries.size(); j++) {
    1363            0 :                                 s = *c.secondaries[j];
    1364            0 :                                 Etot += s.current.getEnergy();
    1365              :                         }
    1366            0 :                         EXPECT_NEAR(Ep, Etot, 1e-9); 
    1367          420 :                 }
    1368              :         }
    1369            2 : }
    1370              : 
    1371            2 : TEST(EMInverseComptonScattering, interactionTag) {
    1372            2 :         EMInverseComptonScattering m(new CMB());
    1373              : 
    1374              :         // test default interactionTag
    1375            1 :         EXPECT_TRUE(m.getInteractionTag() == "EMIC");
    1376              : 
    1377              :         // test secondary tag
    1378            1 :         m.setHavePhotons(true);
    1379            2 :         Candidate c(11, 1 * PeV);
    1380            1 :         m.performInteraction(&c);
    1381            1 :         EXPECT_TRUE(c.secondaries[0] -> getTagOrigin() == "EMIC");
    1382              : 
    1383              :         // test custom tag
    1384            1 :         m.setInteractionTag("myTag");
    1385            1 :         EXPECT_TRUE(m.getInteractionTag() == "myTag");
    1386            1 : }
    1387              : 
    1388              : // SynchrotronRadiation -------------------------------------------------
    1389            1 : TEST(SynchrotronRadiation, interactionTag) {
    1390            1 :         SynchrotronRadiation s(1 * muG, true);
    1391              : 
    1392              :         // test default interactionTag
    1393            1 :         EXPECT_TRUE(s.getInteractionTag() == "SYN");
    1394              : 
    1395              :         // test secondary tag
    1396            2 :         Candidate c(11, 10 * PeV);
    1397            1 :         c.setCurrentStep(1 * pc);
    1398            1 :         s.process(&c);
    1399            1 :         EXPECT_TRUE(c.secondaries[0] -> getTagOrigin() == "SYN");
    1400              : 
    1401              :         // test custom tag
    1402            1 :         s.setInteractionTag("myTag");
    1403            1 :         EXPECT_TRUE(s.getInteractionTag() == "myTag");
    1404            1 : }
    1405              : 
    1406            1 : TEST(SynchrotronRadiation, simpleTestRMS) {
    1407              :         // test initialisation with B_rms
    1408              : 
    1409              :         // check default values 
    1410            1 :         SynchrotronRadiation sync;
    1411              : 
    1412            1 :         EXPECT_EQ(sync.getBrms(), 0);
    1413            1 :         EXPECT_FALSE(sync.getHavePhotons());
    1414            1 :         EXPECT_EQ(sync.getThinning(), 0);
    1415            1 :         EXPECT_EQ(sync.getLimit(), 0.1);
    1416            1 :         EXPECT_EQ(sync.getMaximumSamples(), 0);
    1417            1 :         EXPECT_EQ(sync.getSecondaryThreshold(), 1 * MeV);
    1418              : 
    1419              :         // init with custom values 
    1420            1 :         double b = 1 * muG; 
    1421            1 :         double thinning = 0.23;
    1422            1 :         int samples = 4; 
    1423            1 :         double limit = 0.123;
    1424            2 :         SynchrotronRadiation sync2(b, true, thinning, samples, limit);
    1425              : 
    1426            1 :         EXPECT_EQ(sync2.getBrms(), b);
    1427            1 :         EXPECT_TRUE(sync2.getHavePhotons());
    1428            1 :         EXPECT_EQ(sync2.getThinning(), thinning);
    1429            1 :         EXPECT_EQ(sync2.getLimit(), limit);
    1430            1 :         EXPECT_EQ(sync2.getMaximumSamples(), samples);
    1431            1 :         EXPECT_EQ(sync2.getSecondaryThreshold(), 1 * MeV);
    1432            1 : }
    1433              : 
    1434            1 : TEST(SynchrotronRadiation, simpleTestField) {
    1435              :         // test initialisation with field 
    1436              : 
    1437              :         // check default values 
    1438              :         Vector3d b(0, 0, 1 * muG);
    1439            1 :         ref_ptr<MagneticField> field = new UniformMagneticField(b);
    1440            1 :         SynchrotronRadiation sync(field);
    1441              : 
    1442            1 :         EXPECT_EQ(sync.getBrms(), 0);
    1443            1 :         EXPECT_FALSE(sync.getHavePhotons());
    1444            1 :         EXPECT_EQ(sync.getThinning(), 0);
    1445            1 :         EXPECT_EQ(sync.getLimit(), 0.1);
    1446            1 :         EXPECT_EQ(sync.getMaximumSamples(), 0);
    1447            1 :         EXPECT_EQ(sync.getSecondaryThreshold(), 1 * MeV);
    1448            1 :         Vector3d fieldAtPosition = sync.getField() -> getField(Vector3d(1, 2 , 3));
    1449            1 :         EXPECT_EQ(fieldAtPosition.getR(), b.getR());
    1450              : 
    1451              :         // init with custom values 
    1452            1 :         double thinning = 0.23;
    1453            1 :         int samples = 4; 
    1454            1 :         double limit = 0.123;
    1455            2 :         SynchrotronRadiation sync2(field, true, thinning, samples, limit);
    1456              : 
    1457            1 :         EXPECT_EQ(sync2.getBrms(), 0);
    1458            1 :         EXPECT_TRUE(sync2.getHavePhotons());
    1459            1 :         EXPECT_EQ(sync2.getThinning(), thinning);
    1460            1 :         EXPECT_EQ(sync2.getLimit(), limit);
    1461            1 :         EXPECT_EQ(sync2.getMaximumSamples(), samples);
    1462            1 :         EXPECT_EQ(sync2.getSecondaryThreshold(), 1 * MeV);
    1463            1 :         fieldAtPosition = sync2.getField() -> getField(Vector3d(1, 2 , 3));
    1464            1 :         EXPECT_EQ(fieldAtPosition.getR(), b.getR());
    1465            2 : }
    1466              : 
    1467            1 : TEST(SynchrotronRadiation, getSetFunctions) {
    1468            1 :         SynchrotronRadiation sync;
    1469              : 
    1470              :         // have photons
    1471            1 :         sync.setHavePhotons(true);
    1472            1 :         EXPECT_TRUE(sync.getHavePhotons());
    1473              : 
    1474              :         // Brms 
    1475            1 :         sync.setBrms(5 * muG);
    1476            1 :         EXPECT_EQ(sync.getBrms(), 5 * muG);
    1477              : 
    1478              :         // thinning 
    1479            1 :         sync.setThinning(0.345);
    1480            1 :         EXPECT_EQ(sync.getThinning(), 0.345);
    1481              : 
    1482              :         // limit
    1483            1 :         sync.setLimit(0.234);
    1484            1 :         EXPECT_EQ(sync.getLimit(), 0.234);
    1485              : 
    1486              :         // max samples
    1487            1 :         sync.setMaximumSamples(12345);
    1488            1 :         EXPECT_EQ(sync.getMaximumSamples(), 12345);
    1489              : 
    1490              :         // field 
    1491              :         Vector3d b(1,2,3);
    1492            1 :         ref_ptr<MagneticField> field = new UniformMagneticField(b);
    1493            1 :         sync.setField(field);
    1494            2 :         EXPECT_TRUE(field == sync.getField()); // same pointer
    1495              : 
    1496              :         // secondary threshold
    1497            1 :         sync.setSecondaryThreshold(1 * eV); 
    1498            1 :         EXPECT_EQ(sync.getSecondaryThreshold(), 1 * eV);
    1499            1 : }
    1500              : 
    1501            1 : TEST(SynchrotronRadiation, energyLoss) {
    1502              :         double brms = 1 * muG; 
    1503              :         double step = 1 * kpc; 
    1504            1 :         SynchrotronRadiation sync(brms, false);
    1505              : 
    1506              :         double dE, lf, Rg, dEdx;
    1507            1 :         Candidate c(11); 
    1508            1 :         c.setCurrentStep(step);
    1509            1 :         c.setNextStep(step);
    1510              :         double charge = eplus;
    1511              : 
    1512              :         // 1 GeV 
    1513            1 :         c.current.setEnergy(1 * GeV);
    1514            1 :         lf = c.current.getLorentzFactor();
    1515              :         Rg = 1 * GeV / charge / c_light / (brms * sqrt(2. / 3) ); // factor 2/3 for avg magnetic field direction.  
    1516            1 :         dEdx = 1. / 6 / M_PI / epsilon0 * pow(lf * lf - 1, 2) * pow(charge / Rg, 2); // Jackson p. 770 (14.31)
    1517            1 :         dE = dEdx * step;
    1518            1 :         sync.process(&c);
    1519            1 :         EXPECT_NEAR(1 * GeV - c.current.getEnergy(), dE, 0.01 * dE);
    1520              : 
    1521              :         // 100 GeV
    1522            1 :         c.current.setEnergy(100 * GeV);
    1523            1 :         lf = c.current.getLorentzFactor();
    1524              :         Rg = 100 * GeV / charge / c_light / (brms * sqrt(2. / 3) ); // factor 2/3 for avg magnetic field direction.  
    1525            1 :         dEdx = 1. / 6 / M_PI / epsilon0 * pow(lf * lf - 1, 2) * pow(charge / Rg, 2); // Jackson p. 770 (14.31)
    1526            1 :         dE = dEdx * step;
    1527            1 :         sync.process(&c);
    1528            1 :         EXPECT_NEAR(100 * GeV - c.current.getEnergy(), dE, 0.01 * dE);
    1529              : 
    1530              :         // 10 TeV
    1531            1 :         c.current.setEnergy(10 * TeV);
    1532            1 :         lf = c.current.getLorentzFactor();
    1533              :         Rg = 10 * TeV / charge / c_light / (brms * sqrt(2. / 3) ); // factor 2/3 for avg magnetic field direction.  
    1534            1 :         dEdx = 1. / 6 / M_PI / epsilon0 * pow(lf * lf - 1, 2) * pow(charge / Rg, 2); // Jackson p. 770 (14.31)
    1535            1 :         dE = dEdx * step;
    1536            1 :         sync.process(&c);
    1537            1 :         EXPECT_NEAR(10 * TeV - c.current.getEnergy(), dE, 0.01 * dE);
    1538              : 
    1539              :         // 1 PeV
    1540            1 :         c.current.setEnergy(1 * PeV);
    1541            1 :         lf = c.current.getLorentzFactor();
    1542              :         Rg = 1 * PeV / charge / c_light / (brms * sqrt(2. / 3) ); // factor 2/3 for avg magnetic field direction.  
    1543            1 :         dEdx = 1. / 6 / M_PI / epsilon0 * pow(lf * lf - 1, 2) * pow(charge / Rg, 2); // Jackson p. 770 (14.31)
    1544            1 :         dE = dEdx * step;
    1545            1 :         sync.process(&c);
    1546            1 :         EXPECT_NEAR(1 * PeV - c.current.getEnergy(), dE, 0.01 * dE);
    1547            1 : }
    1548              : 
    1549            1 : TEST(SynchrotronRadiation, PhotonEnergy) {
    1550              :         double brms = 1 * muG; 
    1551            1 :         SynchrotronRadiation sync(brms, true);
    1552            1 :         sync.setSecondaryThreshold(0.); // allow all secondaries for testing
    1553            1 :         sync.setMaximumSamples(1000); // reduce the amount of generated secondaries
    1554              : 
    1555              :         double E = 1 * TeV;
    1556            1 :         Candidate c(11, E);
    1557            1 :         c.setCurrentStep(10 * pc); 
    1558            1 :         c.setNextStep(10 * pc);
    1559              :         
    1560            1 :         double lf = c.current.getLorentzFactor();
    1561              :         double Rg = E / eplus / c_light / (brms * sqrt(2. / 3) ); // factor 2/3 for avg magnetic field direction. 
    1562            1 :         double Ecrit = 3. / 4 * h_planck / M_PI * c_light * pow(lf, 3) / Rg;
    1563              : 
    1564            1 :         sync.process(&c);
    1565            1 :         EXPECT_TRUE(c.secondaries.size() > 0);       // must have secondaries
    1566              : 
    1567              :         // check avg energy of the secondary photons 
    1568              :         double Esec = 0; 
    1569              :         double weightSum = 0;
    1570         1001 :         for (size_t i = 0; i < c.secondaries.size(); i++) {
    1571         1000 :                 double weight = c.secondaries[i]->getWeight();
    1572         1000 :                 Esec += c.secondaries[i] -> current.getEnergy()*weight;
    1573         1000 :                 weightSum += weight;
    1574              :         }
    1575            1 :         Esec /= weightSum;
    1576              : 
    1577            1 :         EXPECT_NEAR(Esec, Ecrit, Ecrit);
    1578            1 : }
    1579              : 
    1580            0 : int main(int argc, char **argv) {
    1581            0 :         ::testing::InitGoogleTest(&argc, argv);
    1582            0 :         return RUN_ALL_TESTS();
    1583              : }
    1584              : 
    1585              : } // namespace crpropa
        

Generated by: LCOV version 2.0-1