All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
HNLMakeDecay_tool.cc
Go to the documentation of this file.
1 /**
2  *
3  */
4 
5 // Framework Includes
6 #include "art/Framework/Core/EDProducer.h"
7 #include "art/Framework/Principal/Event.h"
8 #include "art/Framework/Principal/Handle.h"
9 #include "art/Framework/Services/Registry/ServiceHandle.h"
10 #include "art/Persistency/Common/PtrMaker.h"
11 #include "art/Utilities/ToolMacros.h"
12 #include "cetlib/cpu_timer.h"
13 #include "fhiclcpp/ParameterSet.h"
14 #include "messagefacility/MessageLogger/MessageLogger.h"
15 
17 
18 // local includes
21 
22 // LArSoft includes
24 #include "CLHEP/Random/RandomEngine.h"
25 #include "CLHEP/Random/JamesRandom.h"
26 #include "CLHEP/Random/RandFlat.h"
27 #include "nusimdata/SimulationBase/MCParticle.h"
28 
29 // std includes
30 #include <string>
31 #include <iostream>
32 #include <memory>
33 #include <utility>
34 
35 // math
36 #include <math.h>
37 #include <gsl/gsl_integration.h>
38 
39 // constants
40 #include "TDatabasePDG.h"
41 
42 #include "HNLDecayDalitz.h"
43 
44 //------------------------------------------------------------------------------------------------------------------------------------------
45 // implementation follows
46 
47 namespace evgen {
48 namespace ldm {
49 /**
50  * @brief HNLMakeDecay class definiton
51  *
52  * Implementation of HNL decay ->mupi taken from:
53  * https://arxiv.org/abs/1610.08512
54  */
55 class HNLMakeDecay : public IMeVPrtlDecay {
56 public:
57  /**
58  * @brief Constructor
59  */
60  HNLMakeDecay(fhicl::ParameterSet const &pset);
61 
62  /**
63  * @brief Destructor
64  */
65  ~HNLMakeDecay();
66 
67  void configure(fhicl::ParameterSet const &pset) override;
68 
69  bool Decay(const MeVPrtlFlux &flux, const TVector3 &in, const TVector3 &out, MeVPrtlDecay &decay, double &weight) override;
70 
71  // returns the max weight of configured
72  double MaxWeight() override {
73  return fMaxWeight;
74  }
75 
76 private:
77  // Internal data
78  double fMaxWeight;
79  gsl_integration_workspace *fIntegrator;
80  unsigned fIntegratorSize;
81 
82  // Configure the MaxWeight
83  double fReferenceUE4;
84  double fReferenceUM4;
89 
90  // Guardrail for small decay lengths
92 
93  // Configure the particle
94  bool fMajorana;
95 
96  // Internal struct for holding decay information
97  struct DecayFinalState {
98  double width;
99  std::vector<TLorentzVector> mom;
100  std::vector<int> pdg;
101  };
102 
103  // In the threebody-decay case, we need to specify the three momentum vectors, not just the overall
104  // magnitude
106  TLorentzVector A;
107  TLorentzVector B;
108  TLorentzVector C;
109  };
110 
112 
113  std::map<std::string, HNLDecayFunction> fAvailableDecays;
114  std::map<std::string, double> fAvailableDecayMasses;
115  std::vector<std::string> fDecayConfig;
116  std::vector<HNLDecayFunction> fSelectedDecays;
117 
118  // Helper functions
119  double CalculateMaxWeight();
120  double CalculateKDARDecayLength();
121  ThreebodyMomentum isotropic_threebody_momentum(double parent_mass, double childA_mass, double childB_mass, double childC_mass);
122  double I1(double x, double y, double z);
123  double I2(double x, double y, double z);
124  double NuDiLepDecayWidth(double hnl_mass, double ue4, double um4, bool nu_is_muon, bool lep_is_muon);
125 
126  // Decay implementation functions
127  DecayFinalState NuMupMum(const MeVPrtlFlux &flux);
128  DecayFinalState LepPi(const MeVPrtlFlux &flux, bool is_muon);
129  DecayFinalState EPi(const MeVPrtlFlux &flux) { return LepPi(flux, false); }
130  DecayFinalState MuPi(const MeVPrtlFlux &flux) { return LepPi(flux, true); }
131 };
132 
133 // helpers
134 double lambda(double a, double b, double c) {
135  return a*a + b*b + c*c - 2*a*b - 2*b*c - 2*c*a;
136 }
137 
138 
139 // Valid for decays where the matix element has no kinematic dependence (i.e. a constant Dalitz density)
140 HNLMakeDecay::ThreebodyMomentum HNLMakeDecay::isotropic_threebody_momentum(double parent_mass, double childA_mass, double childB_mass, double childC_mass) {
141  ThreebodyMomentum ret;
142  double sumofdaughtermass = childA_mass + childB_mass + childC_mass;
143  if (parent_mass < sumofdaughtermass) { // shouldn't happen
144  return ret;
145  }
146 
147  double E_A, E_B, E_C;
148  double P_A, P_B, P_C;
149  double P_max, P_sum;
150  // Kinetic Energy is distributed uniformly to daughters, with the constraint that
151  // total momentum is conserved. Randomly allocate energy until a possible such configuration
152  // is found
153  do {
154  double r1 = GetRandom();
155  double r2 = GetRandom();
156 
157  E_A = childA_mass + (parent_mass - sumofdaughtermass) * std::min(r1, r2);
158  E_B = childB_mass + (parent_mass - sumofdaughtermass) * std::min(1-r1,1-r2);
159  E_C = childC_mass + (parent_mass - sumofdaughtermass) * abs(r1-r2);
160 
161  P_A = sqrt(E_A*E_A - childA_mass*childA_mass);
162  P_B = sqrt(E_B*E_B - childB_mass*childB_mass);
163  P_C = sqrt(E_C*E_C - childC_mass*childC_mass);
164 
165  P_max = std::max(std::max(P_A,P_B),P_C);
166  P_sum = P_A + P_B + P_C;
167 
168  } while(P_max > P_sum - P_max);
169 
170  // Found a valid momentum allocation!
171 
172  // Pick a random direction for A, have the direction of B, C work to conserve momentum
173  TVector3 dirA = RandomUnitVector();
174 
175  // daughter particles B and C have the same momentum perpindicular to the direction of A
176  // Solving for the direction along the axis of particle A gives:
177  double cos_thAB = (P_C*P_C - P_B*P_B - P_A*P_A) / (2. * P_A * P_B);
178  double sin_thAB = sqrt(1. - cos_thAB * cos_thAB);
179  double cos_thAC = (P_B*P_B - P_C*P_C - P_A*P_A) / (2. * P_A * P_C);
180  double sin_thAC = sqrt(1. - cos_thAC * cos_thAC);
181 
182  // The azimuthal angle of B and C about A is distributed uniformly
183  double gammaB = (2*GetRandom() - 1.) * M_PI;
184  double gammaC = -gammaB;
185 
186  TVector3 dirB(
187  sin_thAB*cos(gammaB)*dirA.CosTheta()*sin(dirA.Phi()) - sin_thAB*sin(gammaB)*sin(dirA.Phi()) + cos_thAB*sqrt(1.-dirA.CosTheta()*dirA.CosTheta()) * cos(dirA.Phi()),
188  sin_thAB*cos(gammaB)*dirA.CosTheta()*cos(dirA.Phi()) - sin_thAB*sin(gammaB)*cos(dirA.Phi()) + cos_thAB*sqrt(1.-dirA.CosTheta()*dirA.CosTheta()) * sin(dirA.Phi()),
189  -sin_thAB*cos(gammaB)*sqrt(1. - dirA.CosTheta() * dirA.CosTheta()) + cos_thAB*dirA.CosTheta());
190 
191  TVector3 dirC(
192  sin_thAC*cos(gammaC)*dirA.CosTheta()*sin(dirA.Phi()) - sin_thAC*sin(gammaC)*sin(dirA.Phi()) + cos_thAC*sqrt(1.-dirA.CosTheta()*dirA.CosTheta()) * cos(dirA.Phi()),
193  sin_thAC*cos(gammaC)*dirA.CosTheta()*cos(dirA.Phi()) - sin_thAC*sin(gammaC)*cos(dirA.Phi()) + cos_thAC*sqrt(1.-dirA.CosTheta()*dirA.CosTheta()) * sin(dirA.Phi()),
194  -sin_thAC*cos(gammaC)*sqrt(1. - dirA.CosTheta() * dirA.CosTheta()) + cos_thAC*dirA.CosTheta());
195 
196  ret.A = TLorentzVector(P_A*dirA, E_A);
197  ret.B = TLorentzVector(P_B*dirB, E_B);
198  ret.C = TLorentzVector(P_C*dirC, E_C);
199 
200  return ret;
201 }
202 
203 double I1_integrand(double s, void *param) {
204  double *xyz = (double *)param;
205  double x = xyz[0];
206  double y = xyz[1];
207  double z = xyz[2];
208 
209  return 12.*(s - x*x - y*y)*(1 + z*z - s)*sqrt(lambda(s,x*x,y*y))*sqrt(lambda(1.,s,z*z))/s;
210 }
211 
212 double I2_integrand(double s, void *param) {
213  double *xyz = (double *)param;
214  double x = xyz[0];
215  double y = xyz[1];
216  double z = xyz[2];
217 
218  return 24*y*z*(1. + x*x - s)*sqrt(lambda(s,y*y,z*z))*sqrt(lambda(1.,s,z*z))/s;
219 }
220 
221 double HNLMakeDecay::I1(double x, double y, double z) {
222  gsl_function F;
223  double xyz[3];
224  xyz[0] = x;
225  xyz[1] = y;
226  xyz[2] = z;
227  F.function = &I1_integrand;
228  F.params = xyz;
229 
230  double result, error;
231  gsl_integration_qags(&F, (x+y)*(x+y), (1.-z)*(1.-z), 0., 1e-7, fIntegratorSize, fIntegrator, &result, &error);
232 
233  return result;
234 }
235 
236 double HNLMakeDecay::I2(double x, double y, double z) {
237  gsl_function F;
238  double xyz[3];
239  xyz[0] = x;
240  xyz[1] = y;
241  xyz[2] = z;
242  F.function = &I2_integrand;
243  F.params = xyz;
244 
245  double result, error;
246  gsl_integration_qags(&F, (y+z)*(y+z), (1.-x)*(1.-x), 0., 1e-7, fIntegratorSize, fIntegrator, &result, &error);
247 
248  return result;
249 }
250 
251 
252 double HNLMakeDecay::NuDiLepDecayWidth(double hnl_mass, double ue4, double um4, bool nu_is_muon, bool lep_is_muon) {
253  double hnl_mass_pow5 = hnl_mass*hnl_mass*hnl_mass*hnl_mass*hnl_mass;
254  double u4 = nu_is_muon ? um4 : ue4;
255  double lep_mass = lep_is_muon ? Constants::Instance().muon_mass : Constants::Instance().elec_mass;
256 
257  double Gfermi = Constants::Instance().Gfermi;
258  double gL = Constants::Instance().gL;
259  double gR = Constants::Instance().gR;
260 
261  if (hnl_mass < lep_mass * 2.) return 0.;
262 
263  int CC = (lep_is_muon == nu_is_muon);
264 
265  double I1val = I1(0., lep_mass / hnl_mass, lep_mass / hnl_mass);
266  double I2val = I2(0., lep_mass / hnl_mass, lep_mass / hnl_mass);
267 
268  double width = (Gfermi*Gfermi*hnl_mass_pow5) * u4 * ((gL*gR/*NC*/ + CC*gR/*CC*/)*I1val + (gL*gL+gR*gR+CC*(1+2.*gL))*I2val);
269 
270  return width;
271 }
272 
275 
276  double muon_mass = Constants::Instance().muon_mass;
277 
278  // Decay not kinematically allowed
279  if (2*muon_mass > flux.mass) {
280  ret.width = 0.;
281  return ret;
282  }
283 
284  double ue4 = flux.C1;
285  double um4 = flux.C2;
286  double nue_width = NuDiLepDecayWidth(flux.mass, ue4, um4, false, true);
287  double numu_width = NuDiLepDecayWidth(flux.mass, ue4, um4, true, true);
288 
289  ret.width = nue_width + numu_width;
290 
291  if (ret.width == 0.) return ret;
292 
293  // Majorana gets an extra factor b.c. it can go to nu and nubar
294  if (fMajorana) ret.width *= 2;
295 
296  // Three body decay
297  //
298  // TODO: account for anisotropies in decay
299  ThreebodyMomentum momenta = isotropic_threebody_momentum(flux.mass, 0., muon_mass, muon_mass);
300 
301  // pick whether the neutrino is nue or numu
302  int nu_pdg_sign;
303  if (fMajorana) {
304  nu_pdg_sign = (GetRandom() > 0.5) ? 1:-1;
305  }
306  else {
307  // same as the HNL
308  nu_pdg_sign = (flux.secondary_pdg > 0) ? -1 : 1;
309  }
310  int nu_pdg = nu_pdg_sign * ((GetRandom() > numu_width / (numu_width + nue_width)) ? 14 : 12);
311  ret.pdg.push_back(nu_pdg);
312  ret.mom.push_back(momenta.A);
313 
314  ret.pdg.push_back(13);
315  ret.mom.push_back(momenta.B);
316  ret.pdg.push_back(-13);
317  ret.mom.push_back(momenta.C);
318 
319  return ret;
320 }
321 
324  double lep_mass = is_muon ? Constants::Instance().muon_mass : Constants::Instance().elec_mass;
325  int lep_pdg = is_muon ? 13 : 11;
326 
327  double piplus_mass = Constants::Instance().piplus_mass;
328  double Gfermi = Constants::Instance().Gfermi;
329  double fpion = Constants::Instance().fpion;
330  double abs_Vud_squared = Constants::Instance().abs_Vud_squared;
331 
332  // Decay not kinematically allowed
333  if (lep_mass + piplus_mass > flux.mass) {
334  ret.width = 0.;
335  return ret;
336  }
337 
338  double u4 = is_muon ? flux.C2 : flux.C1;
339  double lep_ratio = (lep_mass * lep_mass) / (flux.mass * flux.mass);
340  double pion_ratio = (piplus_mass * piplus_mass) / (flux.mass * flux.mass);
341  double Ifunc = ((1 + lep_ratio + pion_ratio)*(1.+lep_ratio) - 4*lep_ratio) * sqrt(lambda(1., lep_ratio, pion_ratio));
342  ret.width = u4 * (Gfermi * Gfermi *fpion * fpion * abs_Vud_squared * flux.mass * flux.mass * flux.mass * Ifunc) / (16 * M_PI);
343 
344  // Majorana gets an extra factor b.c. it can go to pi+l- and pi-l+
345  if (fMajorana) ret.width *= 2;
346 
347  // Majorana decays don't conserve lepton number, Dirac decay's do
348  int lep_pdg_sign;
349  if (fMajorana) {
350  lep_pdg_sign = (GetRandom() > 0.5) ? 1 : -1;
351  }
352  else {
353  // Dirac HNL caries opposite lepton number to production lepton
354  lep_pdg_sign = (flux.secondary_pdg > 0) ? -1 : 1;
355  }
356 
357  // Use rejection sampling to draw a direction for the child particles
358  //
359  // Work in the lab frame
360  double dalitz_max = HNLLepPiDalitzMax(Constants::Instance().kplus_mass, flux.sec.M(), flux.mass, piplus_mass, lep_mass);
361  double this_dalitz = 0.;
362  double p = evgen::ldm::twobody_momentum(flux.mass, lep_mass, piplus_mass);
363  TLorentzVector LB;
364  TLorentzVector PI;
365  do {
366  TVector3 dir = RandomUnitVector();
367  LB = TLorentzVector(p*dir, sqrt(p*p + lep_mass*lep_mass));
368  PI = TLorentzVector(-p*dir, sqrt(p*p + piplus_mass*piplus_mass));
369  LB.Boost(flux.mom.BoostVector());
370  PI.Boost(flux.mom.BoostVector());
371 
372  this_dalitz = ((flux.secondary_pdg > 0 ) != (lep_pdg_sign > 0)) ? \
373  evgen::ldm::HNLLepPiLNCDalitz(flux.kmom, flux.sec, flux.mom, PI, LB):
374  evgen::ldm::HNLLepPiLNVDalitz(flux.kmom, flux.sec, flux.mom, PI, LB);
375 
376  assert(this_dalitz < dalitz_max);
377  if (this_dalitz > dalitz_max) {
378  std::cerr << "VERY VERY BAD!!!! Incorrect dalitz max!!!\n";
379  std::cout << "VERY VERY BAD!!!! Incorrect dalitz max!!!\n";
380  std::cout << "PK: " << flux.kmom.E() << " " << flux.kmom.Px() << " " << flux.kmom.Py() << " " << flux.kmom.Pz() << std::endl;
381  std::cout << "PA: " << flux.sec.E() << " " << flux.sec.Px() << " " << flux.sec.Py() << " " << flux.sec.Pz() << std::endl;
382  std::cout << "PN: " << flux.mom.E() << " " << flux.mom.Px() << " " << flux.mom.Py() << " " << flux.mom.Pz() << std::endl;
383  std::cout << "PP: " << PI.E() << " " << PI.Px() << " " << PI.Py() << " " << PI.Pz() << std::endl;
384  std::cout << "PB: " << LB.E() << " " << LB.Px() << " " << LB.Py() << " " << LB.Pz() << std::endl;
385 
386  std::cout << "This Dalitz: " << this_dalitz << std::endl;
387  std::cout << "Max Dalitz: " << dalitz_max << std::endl;
388  std::cout << "LNC: " << ((flux.secondary_pdg > 0 ) != (lep_pdg_sign > 0)) << std::endl;
389 
390  exit(1);
391  }
392  } while (GetRandom() > this_dalitz / dalitz_max);
393 
394  // lepton
395  ret.mom.push_back(LB);
396  ret.pdg.push_back(lep_pdg*lep_pdg_sign);
397 
398  // pion
399  ret.mom.emplace_back(PI);
400  ret.pdg.push_back(211*lep_pdg_sign); // negative of lepton-charge has same-sign-PDG code
401 
402  return ret;
403 }
404 
406  double ue4 = fReferenceUE4;
407  double um4 = fReferenceUM4;
408  double hnl_mass = fReferenceHNLMass;
409  // If the muon coupling is turned on, the lower energy / lower decay length HNL would have a muon
410  // secondary
411  double lep_mass = (um4 > 0.) ? Constants::Instance().muon_mass : Constants::Instance().elec_mass;
412  double P = twobody_momentum(Constants::Instance().kplus_mass, lep_mass, hnl_mass);
413  double E = sqrt(P*P + hnl_mass * hnl_mass);
414 
415  // make a fake HNL flux with these parameters
416  MeVPrtlFlux hnl;
417  hnl.C1 = ue4;
418  hnl.C2 = um4;
419  hnl.mass = hnl_mass;
420  hnl.mom = TLorentzVector(P * TVector3(1, 0, 0), E);
421  hnl.secondary_pdg = -1; // doesn't affect the end width, just make it viable
422 
423  // Mock up the secondary momenta -- this shouldn't affect width and just
424  // is there to make sure the decay functions work correctly
425  hnl.kmom = TLorentzVector(TVector3(0, 0, 0), Constants::Instance().kplus_mass);
426  hnl.sec = TLorentzVector(-P * TVector3(1, 0, 0), sqrt(P*P + lep_mass*lep_mass));
427 
428  double width = 0.;
430  width += ((*this.*F)(hnl)).width;
431  }
432 
433  double lifetime_ns = Constants::Instance().hbar / width;
434  double mean_dist = lifetime_ns * hnl.mom.Gamma() * hnl.mom.Beta() * Constants::Instance().c_cm_per_ns;
435 
436  return mean_dist;
437 }
438 
440  double ue4 = fReferenceUE4;
441  double um4 = fReferenceUM4;
442  double hnl_mass = fReferenceHNLMass;
443  double length = fReferenceRayLength;
444  double E = fReferenceHNLEnergy;
445  double P = sqrt(E*E - hnl_mass * hnl_mass);
446 
447  // Compute the boost to get the HNL with the correct energy
448  double lep_mass = (um4 > 0) ? Constants::Instance().muon_mass : Constants::Instance().elec_mass;
449  double p0 = twobody_momentum(Constants::Instance().kplus_mass, lep_mass, hnl_mass);
450  double e0 = sqrt(p0*p0 + hnl_mass*hnl_mass);
451  double beta = (-e0*p0 + E*P) / (E*E + p0*p0);
452  TVector3 boost(beta, 0, 0);
453 
454  // make a fake HNL flux with these parameters
455  MeVPrtlFlux hnl;
456  hnl.C1 = ue4;
457  hnl.C2 = um4;
458  hnl.mass = hnl_mass;
459  hnl.mom = TLorentzVector(p0 * TVector3(1, 0, 0), e0);
460  hnl.mom.Boost(boost);
461  hnl.secondary_pdg = -1; // doesn't affect the end width, just make it viable
462 
463  std::cout << "Reference Energy: " << E << std::endl;
464  std::cout << "HNL 4P: " << hnl.mom.E() << " " << hnl.mom.Px() << std::endl;
465 
466  // Mock up the secondary momenta -- this shouldn't affect width and just
467  // is there to make sure the decay functions work correctly
468  hnl.kmom = TLorentzVector(TVector3(0, 0, 0), Constants::Instance().kplus_mass);
469  hnl.kmom.Boost(boost);
470  hnl.sec = TLorentzVector(-p0 * TVector3(1, 0, 0), sqrt(p0*p0 + lep_mass*lep_mass));
471  hnl.sec.Boost(boost);
472 
473  double width = 0.;
475  width += ((*this.*F)(hnl)).width;
476  }
477 
478  std::cout << "REFERENCE WIDTH: " << width << std::endl;
479 
480  double lifetime_ns = Constants::Instance().hbar / width;
481  double mean_dist = lifetime_ns * hnl.mom.Gamma() * hnl.mom.Beta() * Constants::Instance().c_cm_per_ns;
482 
483  std::cout << "REFERENCE DECAY LENGTH: " << mean_dist << std::endl;
484 
485  return length / mean_dist;
486 }
487 
488 
489 HNLMakeDecay::HNLMakeDecay(fhicl::ParameterSet const &pset):
490  IMeVPrtlStage("HNLMakeDecay")
491 {
492  this->configure(pset);
493 }
494 
495 //------------------------------------------------------------------------------------------------------------------------------------------
496 
498 {
499  gsl_integration_workspace_free(fIntegrator);
500 }
501 
502 //------------------------------------------------------------------------------------------------------------------------------------------
503 void HNLMakeDecay::configure(fhicl::ParameterSet const &pset)
504 {
505  fIntegratorSize = 1000;
506 
507  fIntegrator = gsl_integration_workspace_alloc(fIntegratorSize);
508 
509  // Setup available decays
514  // TODO: make available
515  // fAvailableDecays["nu_mu_mu"] = &HNLMakeDecay::NuMupMum;
516 
517  // Select which ones are configued
518  fDecayConfig = pset.get<std::vector<std::string>>("Decays");
519 
520  for (const std::string &d: fDecayConfig) {
521  if (fAvailableDecays.count(d)) {
522  fSelectedDecays.push_back(fAvailableDecays.at(d));
523  }
524  else {
525  std::cerr << "ERROR: Selected unavailable decay (" << d << ")" << std::endl;
526  }
527  }
528  fReferenceUE4 = pset.get<double>("ReferenceUE4");
529  fReferenceUM4 = pset.get<double>("ReferenceUM4");
530  fReferenceHNLMass = pset.get<double>("ReferenceHNLMass");
531  fReferenceRayLength = pset.get<double>("ReferenceRayLength");
532 
533  fReferenceHNLEnergy = pset.get<double>("ReferenceHNLEnergy", -1);
534  fReferenceHNLKaonEnergy = pset.get<double>("ReferenceHNLEnergyFromKaonEnergy", -1.);
535  if (fReferenceHNLEnergy < 0. && fReferenceHNLKaonEnergy > 0.) {
537  fReferenceHNLEnergy = forwardPrtlEnergy(Constants::Instance().kplus_mass, lep_mass, fReferenceHNLMass, fReferenceHNLKaonEnergy);
538  }
539 
540  fMinDetectorDistance = pset.get<double>("MinDetectorDistance", 100e2); // 100m for NuMI -> SBN/ICARUS
541 
542  fMajorana = pset.get<bool>("Majorana");
543 
545 
546  // Guard against bad couplings that will mess with decay weighting approximations
547  double kdar_decay_length = CalculateKDARDecayLength();
548  if (kdar_decay_length < fMinDetectorDistance) {
549  throw cet::exception("HNLMakeDecay Tool: BAD CONFIG. Existing configuration results in a KDAR-flux partial decay length (" +
550  std::to_string(kdar_decay_length) + ") that is smaller than the configured min distance to the detector (" + std::to_string(fMinDetectorDistance) + ").");
551  }
552 }
553 
554 bool HNLMakeDecay::Decay(const MeVPrtlFlux &flux, const TVector3 &in, const TVector3 &out, MeVPrtlDecay &decay, double &weight) {
555  // Check that the mass/decay configuration is allowed
556  bool has_allowed_decay = false;
557  for (const std::string &d: fDecayConfig) {
559  has_allowed_decay = true;
560  break;
561  }
562  }
563 
564  if (!has_allowed_decay) {
565  throw cet::exception("HNLMakeDecay Tool: BAD MASS. Configured mass (" + std::to_string(flux.mass) +
566  ") is smaller than any configured decay.");
567  }
568 
569  // Run the selected decay channels
570  std::vector<HNLMakeDecay::DecayFinalState> decays;
571  double total_width = 0.;
573  decays.push_back((*this.*F)(flux));
574  total_width += decays.back().width;
575  }
576 
577  std::cout << "TOTAL DECAY WIDTH: " << total_width << std::endl;
578  if (total_width == 0.) return false;
579 
580  // pick one
581  double sum_width = 0.;
582  int idecay = decays.size()-1;
583  double rand = GetRandom();
584  for (unsigned i = 0; i < decays.size()-1; i++) {
585  sum_width += decays[i].width;
586  if (rand < sum_width / total_width) {
587  idecay = i;
588  break;
589  }
590  }
591 
592  // Pick a random decay position -- assumes it is uniform across the detector
593  TVector3 decay_pos = in + GetRandom() * (out - in);
594 
595  // Get the decay probability
596 
597  // total lifetime
598  double lifetime_ns = Constants::Instance().hbar / total_width;
599 
600  // multiply by gamma*v to get the length
601  double mean_dist = lifetime_ns * flux.mom.Gamma() * flux.mom.Beta() * Constants::Instance().c_cm_per_ns;
602 
603  // Get the weight (NOTE: this negelects the probability that the HNL decays before the detector)
604  // I.e. it is only valid in the limit mean_dist >> 100m (distance from beam to SBN)
605  if (mean_dist < fMinDetectorDistance) {
606  std::cerr << "ERROR: bad mean_dist value (" << mean_dist << "). Decay weighting approximations invalid. Ignoring event." << std::endl;
607  std::cout << "ERROR: bad mean_dist value (" << mean_dist << "). Decay weighting approximations invalid. Ignoring event." << std::endl;
608  return false;
609  }
610 
611  // saves the weight
612  weight = (out - in).Mag() / mean_dist;
613 
614  // Save the decay info
615  decay.pos = TLorentzVector(decay_pos, TimeOfFlight(flux, decay_pos));
616  for (const TLorentzVector &p: decays[idecay].mom) {
617  decay.daughter_mom.push_back(p.Vect());
618  decay.daughter_e.push_back(p.E());
619  }
620  decay.daughter_pdg = decays[idecay].pdg;
621  decay.decay_width = total_width;
622  decay.mean_lifetime = lifetime_ns;
623  decay.mean_distance = mean_dist;
624 
625  return true;
626 }
627 
628 DEFINE_ART_CLASS_TOOL(HNLMakeDecay)
629 
630 } // namespace ldm
631 } // namespace evgen
process_name opflash particleana ie ie ie z
TLorentzVector mom
Definition: MeVPrtlFlux.h:14
process_name opflash particleana ie x
BEGIN_PROLOG could also be cerr
std::map< std::string, double > fAvailableDecayMasses
ThreebodyMomentum isotropic_threebody_momentum(double parent_mass, double childA_mass, double childB_mass, double childC_mass)
double lambda(double a, double b, double c)
DecayFinalState EPi(const MeVPrtlFlux &flux)
double NuDiLepDecayWidth(double hnl_mass, double ue4, double um4, bool nu_is_muon, bool lep_is_muon)
pdgs p
Definition: selectors.fcl:22
void configure(fhicl::ParameterSet const &pset) override
Interface for configuring the particular algorithm tool.
HNLMakeDecay class definiton.
double twobody_momentum(double parent_mass, double childA_mass, double childB_mass)
Definition: Constants.cpp:73
process_name E
DecayFinalState LepPi(const MeVPrtlFlux &flux, bool is_muon)
gsl_integration_workspace * fIntegrator
std::vector< HNLDecayFunction > fSelectedDecays
constexpr double PI
process_name gaushit a
std::map< std::string, HNLDecayFunction > fAvailableDecays
T abs(T value)
TLorentzVector kmom
Definition: MeVPrtlFlux.h:13
IMeVPrtlDecay interface class definiton.
Definition: IMeVPrtlDecay.h:36
process_name opflash particleana ie ie y
IMeVPrtlStage interface class definiton. General interface behind each stage. Provides random number ...
Definition: IMeVPrtlStage.h:36
double I1_integrand(double s, void *param)
double HNLLepPiLNVDalitz(TLorentzVector K, TLorentzVector LA, TLorentzVector N, TLorentzVector PI, TLorentzVector LB)
Provides a base class aware of world box coordinates.
DecayFinalState(HNLMakeDecay::* HNLDecayFunction)(const MeVPrtlFlux &flux)
HNLMakeDecay(fhicl::ParameterSet const &pset)
Constructor.
tuple dir
Definition: dropbox.py:28
static const Constants & Instance()
Definition: Constants.h:68
std::vector< TVector3 > daughter_mom
Definition: MeVPrtlDecay.h:16
std::vector< double > daughter_e
Definition: MeVPrtlDecay.h:17
process_name showerreco Particles Coinciding wih the Vertex services ScanOptions nu_mu CC
double I2(double x, double y, double z)
if &&[-z"$BASH_VERSION"] then echo Attempting to switch to bash bash shellSwitch exit fi &&["$1"= 'shellSwitch'] shift declare a IncludeDirectives for Dir in
double I1(double x, double y, double z)
double I2_integrand(double s, void *param)
std::vector< std::string > fDecayConfig
std::string to_string(WindowPattern const &pattern)
then echo File list $list not found else cat $list while read file do echo $file sed s
Definition: file_to_url.sh:60
DecayFinalState NuMupMum(const MeVPrtlFlux &flux)
std::vector< int > daughter_pdg
Definition: MeVPrtlDecay.h:18
do i e
double HNLLepPiDalitzMax(double mK, double mA, double mN, double mP, double mB)
double forwardPrtlEnergy(double parentM, double secM, double prtlM, double parentE)
Definition: Constants.cpp:212
double MaxWeight() override
double TimeOfFlight(const MeVPrtlFlux &flux, TVector3 decay)
Definition: IMeVPrtlDecay.h:47
DecayFinalState MuPi(const MeVPrtlFlux &flux)
BEGIN_PROLOG could also be cout
This is an interface for an art Tool which decays &quot;Prtl&quot; inside a detector volume. It maps MeVPrtlFlux to MeVPrtlDecay.
TLorentzVector sec
Definition: MeVPrtlFlux.h:16
double HNLLepPiLNCDalitz(TLorentzVector K, TLorentzVector LA, TLorentzVector N, TLorentzVector PI, TLorentzVector LB)
bool Decay(const MeVPrtlFlux &flux, const TVector3 &in, const TVector3 &out, MeVPrtlDecay &decay, double &weight) override