diff --git a/src/DarkNews/GenLauncher.py b/src/DarkNews/GenLauncher.py index 5fa4bd8..304119e 100644 --- a/src/DarkNews/GenLauncher.py +++ b/src/DarkNews/GenLauncher.py @@ -94,6 +94,7 @@ THREE_PORTAL_ARGS = [ "gD", "epsilon", + "epsilonZ", "alphaD", "epsilon2", "chi", @@ -482,7 +483,7 @@ def _create_all_MC_cases(self, **kwargs): "UPSCATTERED_NUS": self.upscattered_nus, "OUTGOING_NUS": self.outgoing_nus, "DECAY_PRODUCTS": [self.decay_product], - "SCATTERING_REGIMES": ["coherent", "p-el"], # , 'n-el'], + "SCATTERING_REGIMES": ["coherent", "p-el", "n-el"], } # override default with kwargs scope.update(kwargs) diff --git a/src/DarkNews/ModelContainer.py b/src/DarkNews/ModelContainer.py index 7b474fc..6c6f977 100644 --- a/src/DarkNews/ModelContainer.py +++ b/src/DarkNews/ModelContainer.py @@ -92,6 +92,7 @@ THREE_PORTAL_ARGS = [ "gD", "epsilon", + "epsilonZ", "alphaD", "epsilon2", "chi", @@ -404,7 +405,7 @@ def _create_all_model_cases(self, **kwargs): "UPSCATTERED_NUS": self.upscattered_nus, "OUTGOING_NUS": self.outgoing_nus, "DECAY_PRODUCTS": [self.decay_product], - "SCATTERING_REGIMES": ["coherent", "p-el"], # , 'n-el'], + "SCATTERING_REGIMES": ["coherent", "p-el", "n-el"], "NUCLEAR_TARGETS": [NuclearTarget(_t) for _t in self.nuclear_targets], } # override default with kwargs diff --git a/src/DarkNews/__init__.py b/src/DarkNews/__init__.py index 7ce6df1..2c07eb0 100755 --- a/src/DarkNews/__init__.py +++ b/src/DarkNews/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.4.2" +__version__ = "0.4.3" import sys diff --git a/src/DarkNews/integrands.py b/src/DarkNews/integrands.py index 06d7468..c009260 100755 --- a/src/DarkNews/integrands.py +++ b/src/DarkNews/integrands.py @@ -3,8 +3,9 @@ from collections import OrderedDict import logging -logger = logging.getLogger('logger.' + __name__) -prettyprinter = logging.getLogger('prettyprinter.' + __name__) + +logger = logging.getLogger("logger." + __name__) +prettyprinter = logging.getLogger("prettyprinter." + __name__) from DarkNews import Cfourvec as Cfv from DarkNews import phase_space @@ -71,14 +72,14 @@ def __call__(self, x, jac): M = ups_case.target.mass Q2lmin = np.log(phase_space.upscattering_Q2min(Enu, ups_case.m_ups, M)) - Q2lmax = np.log(np.minimum(phase_space.upscattering_Q2max(Enu, ups_case.m_ups, M), self.QMAX ** 2)) + Q2lmax = np.log(np.minimum(phase_space.upscattering_Q2max(Enu, ups_case.m_ups, M), self.QMAX**2)) Q2l = (Q2lmax - Q2lmin) * x[:, 0] + Q2lmin Q2 = np.exp(Q2l) - s = M ** 2 + 2 * Enu * M # massless projectile + s = M**2 + 2 * Enu * M # massless projectile t = -Q2 - u = 2 * M ** 2 + ups_case.m_ups ** 2 - s - t # massless projectile + u = 2 * M**2 + ups_case.m_ups**2 - s - t # massless projectile ############################################## # Upscattering amplitude squared (spin summed -- not averaged) @@ -99,6 +100,7 @@ def __call__(self, x, jac): return self.int_dic + class HNLDecay(vg.BatchIntegrand): def __init__(self, dim, dec_case, diagram="total"): """ @@ -121,8 +123,7 @@ def __init__(self, dim, dec_case, diagram="total"): self.norm = {} if type(self.decay_case) == proc.FermionDileptonDecay: - if (self.decay_case.scalar_on_shell and self.decay_case.vector_off_shell)\ - or (self.decay_case.scalar_off_shell and self.decay_case.vector_on_shell): + if (self.decay_case.scalar_on_shell and self.decay_case.vector_off_shell) or (self.decay_case.scalar_off_shell and self.decay_case.vector_on_shell): self.norm["diff_decay_rate_0"] = 1 self.norm["diff_decay_rate_1"] = 1 elif self.decay_case.scalar_off_shell and self.decay_case.vector_off_shell: @@ -168,15 +169,22 @@ def __call__(self, x, jac): i_var += 1 self.int_dic["diff_decay_rate_0"] = dr.diff_gamma_Ni_to_Nj_V( - cost=cost, vertex_ij=self.decay_case.Dih, mi=m_parent, mj=m_daughter, mV=self.decay_case.mzprime, HNLtype=self.decay_case.HNLtype, h=self.decay_case.h_parent, + cost=cost, + vertex_ij=self.decay_case.Dih, + mi=m_parent, + mj=m_daughter, + mV=self.decay_case.mzprime, + HNLtype=self.decay_case.HNLtype, + h=self.decay_case.h_parent, ) self.int_dic["diff_decay_rate_0"] *= 2 # hypercube jacobian - # mediator decay M --> ell+ ell- - self.int_dic["diff_decay_rate_1"] = dr.gamma_V_to_ell_ell(vertex=self.decay_case.TheoryModel.deV, mV=self.decay_case.mzprime, m_ell=self.decay_case.mm) * np.full_like( - self.int_dic["diff_decay_rate_0"], 1.0 - ) + self.int_dic["diff_decay_rate_1"] = dr.gamma_V_to_ell_ell( + vertex=np.sqrt(self.decay_case.TheoryModel.deV**2 + self.decay_case.TheoryModel.deA**2), + mV=self.decay_case.mzprime, + m_ell=self.decay_case.mm, + ) * np.full_like(self.int_dic["diff_decay_rate_0"], 1.0) elif self.decay_case.vector_off_shell and self.decay_case.scalar_on_shell: # decay nu_parent -> nu_daughter mediator @@ -185,15 +193,21 @@ def __call__(self, x, jac): i_var += 1 self.int_dic["diff_decay_rate_0"] = dr.diff_gamma_Ni_to_Nj_S( - cost=cost, vertex_ij=self.decay_case.Sih, mi=m_parent, mj=m_daughter, mS=self.decay_case.mhprime, HNLtype=self.decay_case.HNLtype, h=self.decay_case.h_parent, + cost=cost, + vertex_ij=self.decay_case.Sih, + mi=m_parent, + mj=m_daughter, + mS=self.decay_case.mhprime, + HNLtype=self.decay_case.HNLtype, + h=self.decay_case.h_parent, ) self.int_dic["diff_decay_rate_0"] *= 2 # hypercube jacobian ############################################## # mediator decay M --> ell+ ell- - self.int_dic["diff_decay_rate_1"] = dr.gamma_S_to_ell_ell(vertex=self.decay_case.TheoryModel.deS, mS=self.decay_case.mhprime, m_ell=self.decay_case.mm) * np.full_like( - self.int_dic["diff_decay_rate_0"], 1.0 - ) + self.int_dic["diff_decay_rate_1"] = dr.gamma_S_to_ell_ell( + vertex=self.decay_case.TheoryModel.deS, mS=self.decay_case.mhprime, m_ell=self.decay_case.mm + ) * np.full_like(self.int_dic["diff_decay_rate_0"], 1.0) elif self.decay_case.vector_off_shell and self.decay_case.scalar_off_shell: ############################################## @@ -216,7 +230,7 @@ def __call__(self, x, jac): u = (umax - umin) * x[:, i_var] + umin i_var += 1 - v = np.sum(masses ** 2) - u - t + v = np.sum(masses**2) - u - t c3 = (2.0) * x[:, i_var] - 1.0 i_var += 1 @@ -235,7 +249,7 @@ def __call__(self, x, jac): logger.error("Could not find decay integrand.") raise ValueError("Integrand not found for this model.") - elif type(self.decay_case) == proc.FermionSinglePhotonDecay: + elif type(self.decay_case) == proc.FermionSinglePhotonDecay: ############################################## # decay nu_parent -> nu_daughter gamma @@ -244,7 +258,12 @@ def __call__(self, x, jac): i_var += 1 self.int_dic["diff_decay_rate_0"] = dr.diff_gamma_Ni_to_Nj_gamma( - cost=cost, vertex_ij=self.decay_case.Tih, mi=m_parent, mj=m_daughter, HNLtype=self.decay_case.HNLtype, h=self.decay_case.h_parent, + cost=cost, + vertex_ij=self.decay_case.Tih, + mi=m_parent, + mj=m_daughter, + HNLtype=self.decay_case.HNLtype, + h=self.decay_case.h_parent, ) # hypercube jacobian (vegas hypercube --> physical limits) transformation @@ -254,7 +273,6 @@ def __call__(self, x, jac): logger.error("Could not find decay integrand.") raise ValueError("Integrand not found for this model.") - ############################################## # storing normalization factor that guarantees that integrands are O(1) numbers # normalize integrands to be O(1) @@ -264,7 +282,7 @@ def __call__(self, x, jac): # return all differential quantities of interest return self.int_dic - + class UpscatteringHNLDecay(vg.BatchIntegrand): def __init__(self, dim, Emin, Emax, MC_case): @@ -290,8 +308,9 @@ def __init__(self, dim, Emin, Emax, MC_case): self.norm["diff_event_rate"] = 1 self.norm["diff_flux_avg_xsec"] = 1 if self.MC_case.decays_to_dilepton: - if (self.MC_case.decay_case.scalar_on_shell and self.MC_case.decay_case.vector_off_shell)\ - or (self.MC_case.decay_case.scalar_off_shell and self.MC_case.decay_case.vector_on_shell): + if (self.MC_case.decay_case.scalar_on_shell and self.MC_case.decay_case.vector_off_shell) or ( + self.MC_case.decay_case.scalar_off_shell and self.MC_case.decay_case.vector_on_shell + ): self.norm["diff_decay_rate_0"] = 1 self.norm["diff_decay_rate_1"] = 1 elif self.MC_case.decay_case.scalar_off_shell and self.MC_case.decay_case.vector_off_shell: @@ -340,9 +359,9 @@ def __call__(self, x, jac): i_var += 1 Q2 = np.exp(Q2l) - s_scatt = M ** 2 + 2 * Enu * M # massless projectile + s_scatt = M**2 + 2 * Enu * M # massless projectile t_scatt = -Q2 - u_scatt = 2 * M ** 2 + m_parent ** 2 - s_scatt + Q2 # massless projectile + u_scatt = 2 * M**2 + m_parent**2 - s_scatt + Q2 # massless projectile diff_xsec = amps.upscattering_dxsec_dQ2([s_scatt, t_scatt, u_scatt], ups_case) @@ -367,14 +386,20 @@ def __call__(self, x, jac): i_var += 1 self.int_dic["diff_decay_rate_0"] = dr.diff_gamma_Ni_to_Nj_V( - cost=cost, vertex_ij=decay_case.Dih, mi=m_parent, mj=m_daughter, mV=decay_case.mzprime, HNLtype=decay_case.HNLtype, h=decay_case.h_parent, + cost=cost, + vertex_ij=decay_case.Dih, + mi=m_parent, + mj=m_daughter, + mV=decay_case.mzprime, + HNLtype=decay_case.HNLtype, + h=decay_case.h_parent, ) self.int_dic["diff_decay_rate_0"] *= 2 # hypercube jacobian # mediator decay M --> ell+ ell- - self.int_dic["diff_decay_rate_1"] = dr.gamma_V_to_ell_ell(vertex=decay_case.TheoryModel.deV, mV=decay_case.mzprime, m_ell=decay_case.mm) * np.full_like( - self.int_dic["diff_decay_rate_0"], 1.0 - ) + self.int_dic["diff_decay_rate_1"] = dr.gamma_V_to_ell_ell( + vertex=np.sqrt(self.decay_case.TheoryModel.deV**2 + self.decay_case.TheoryModel.deA**2), mV=decay_case.mzprime, m_ell=decay_case.mm + ) * np.full_like(self.int_dic["diff_decay_rate_0"], 1.0) elif decay_case.vector_off_shell and decay_case.scalar_on_shell: # decay nu_parent -> nu_daughter mediator @@ -383,15 +408,21 @@ def __call__(self, x, jac): i_var += 1 self.int_dic["diff_decay_rate_0"] = dr.diff_gamma_Ni_to_Nj_S( - cost=cost, vertex_ij=decay_case.Sih, mi=m_parent, mj=m_daughter, mS=decay_case.mhprime, HNLtype=decay_case.HNLtype, h=decay_case.h_parent, + cost=cost, + vertex_ij=decay_case.Sih, + mi=m_parent, + mj=m_daughter, + mS=decay_case.mhprime, + HNLtype=decay_case.HNLtype, + h=decay_case.h_parent, ) self.int_dic["diff_decay_rate_0"] *= 2 # hypercube jacobian ############################################## # mediator decay M --> ell+ ell- - self.int_dic["diff_decay_rate_1"] = dr.gamma_S_to_ell_ell(vertex=decay_case.TheoryModel.deS, mS=decay_case.mhprime, m_ell=decay_case.mm) * np.full_like( - self.int_dic["diff_decay_rate_0"], 1.0 - ) + self.int_dic["diff_decay_rate_1"] = dr.gamma_S_to_ell_ell( + vertex=decay_case.TheoryModel.deS, mS=decay_case.mhprime, m_ell=decay_case.mm + ) * np.full_like(self.int_dic["diff_decay_rate_0"], 1.0) elif decay_case.vector_off_shell and decay_case.scalar_off_shell: ############################################## @@ -414,7 +445,7 @@ def __call__(self, x, jac): u = (umax - umin) * x[:, i_var] + umin i_var += 1 - v = np.sum(masses ** 2) - u - t + v = np.sum(masses**2) - u - t c3 = (2.0) * x[:, i_var] - 1.0 i_var += 1 @@ -442,7 +473,12 @@ def __call__(self, x, jac): i_var += 1 self.int_dic["diff_decay_rate_0"] = dr.diff_gamma_Ni_to_Nj_gamma( - cost=cost, vertex_ij=decay_case.Tih, mi=m_parent, mj=m_daughter, HNLtype=decay_case.HNLtype, h=decay_case.h_parent, + cost=cost, + vertex_ij=decay_case.Tih, + mi=m_parent, + mj=m_daughter, + HNLtype=decay_case.HNLtype, + h=decay_case.h_parent, ) # hypercube jacobian (vegas hypercube --> physical limits) transformation @@ -452,7 +488,6 @@ def __call__(self, x, jac): logger.error("Could not find decay integrand.") raise ValueError("Integrand not found for this model.") - ############################################## # storing normalization factor that guarantees that integrands are O(1) numbers # loop over decay processes @@ -548,7 +583,7 @@ def get_momenta_from_vegas_samples(vsamples, MC_case): ######################## # HNL decay N_decay_samples = {"unit_cost": np.array(vsamples[2])} - + # Ni (k1) --> Nj (k2) Z' (k3) masses_decay = { "m1": mh, # Ni @@ -602,7 +637,12 @@ def get_momenta_from_vegas_samples(vsamples, MC_case): "m4": mf, } # Nj # Phnl, pe-, pe+, pnu - (P1LAB_decay, P2LAB_decay, P3LAB_decay, P4LAB_decay,) = phase_space.three_body_decay(N_decay_samples, boost=boost_scattered_N, **masses_decay) + ( + P1LAB_decay, + P2LAB_decay, + P3LAB_decay, + P4LAB_decay, + ) = phase_space.three_body_decay(N_decay_samples, boost=boost_scattered_N, **masses_decay) four_momenta["P_decay_N_parent"] = P1LAB_decay four_momenta["P_decay_ell_minus"] = P2LAB_decay @@ -632,6 +672,7 @@ def get_momenta_from_vegas_samples(vsamples, MC_case): return four_momenta + def get_decay_momenta_from_vegas_samples(vsamples, decay_case, PN_LAB): """ Construct the four momenta of all final state particles in the decay process from the @@ -734,7 +775,12 @@ def get_decay_momenta_from_vegas_samples(vsamples, decay_case, PN_LAB): "m4": mf, } # Nj # Phnl, pe-, pe+, pnu - (P1LAB_decay, P2LAB_decay, P3LAB_decay, P4LAB_decay,) = phase_space.three_body_decay(N_decay_samples, boost=boost_scattered_N, **masses_decay) + ( + P1LAB_decay, + P2LAB_decay, + P3LAB_decay, + P4LAB_decay, + ) = phase_space.three_body_decay(N_decay_samples, boost=boost_scattered_N, **masses_decay) four_momenta["P_decay_N_parent"] = P1LAB_decay four_momenta["P_decay_ell_minus"] = P2LAB_decay @@ -762,4 +808,4 @@ def get_decay_momenta_from_vegas_samples(vsamples, decay_case, PN_LAB): four_momenta["P_decay_N_daughter"] = P2LAB_decay four_momenta["P_decay_photon"] = P3LAB_decay - return four_momenta \ No newline at end of file + return four_momenta diff --git a/src/DarkNews/model.py b/src/DarkNews/model.py index 4b0e99e..04f2786 100755 --- a/src/DarkNews/model.py +++ b/src/DarkNews/model.py @@ -128,7 +128,7 @@ def _initialize_spectrum(self): def _update_spectrum(self): # mass mixing between Z' and Z # NOT YET FUNCTIONAL - self.is_mass_mixed = False # (self.epsilonZ != 0.0) + self.is_mass_mixed = hasattr(self, "epsilonZ") and self.epsilonZ != 0.0 self.has_Zboson_coupling = np.any(self.c_aj[3:, :] != 0) if self.has_Zboson_coupling: @@ -409,11 +409,17 @@ def set_vertices(self): self.s2chi = (1.0 - self.cosof2chi) / 2.0 self.c2chi = 1 - self.s2chi - entry_22 = self.c2chi - const.s2w * self.s2chi - (self.mzprime / const.m_Z) ** 2 - self.tanof2beta = const.sw * self.sinof2chi / (entry_22) - self.beta = const.sw * self.chi - self.sinof2beta = const.sw * self.sinof2chi / np.sqrt(entry_22**2 + self.sinof2chi**2 * const.s2w) - self.cosof2beta = entry_22 / np.sqrt(entry_22**2 + self.sinof2chi**2 * const.s2w) + # entry_22 = self.c2chi - const.s2w * self.s2chi - (self.mzprime / const.m_Z) ** 2 + # self.tanof2beta = const.sw * self.sinof2chi / (entry_22) + + # Mass mixing and kinetic mixing + entry_11 = const.sw * self.sinof2chi + 2 * self.epsilonZ * np.sqrt(self.c2chi) + entry_22 = self.c2chi - const.s2w * self.s2chi - (self.mzprime / const.m_Z) ** 2 - 2 * self.epsilonZ * const.sw * np.sqrt(self.s2chi) + self.tanof2beta = entry_11 / entry_22 + + # self.beta = const.sw * self.chi + self.sinof2beta = entry_11 / np.sqrt(entry_22**2 + entry_11**2) + self.cosof2beta = entry_22 / np.sqrt(entry_22**2 + entry_11**2) # ##################### if self.tanof2beta != 0: diff --git a/src/DarkNews/nuclear_tools.py b/src/DarkNews/nuclear_tools.py index c88cfa2..a138210 100644 --- a/src/DarkNews/nuclear_tools.py +++ b/src/DarkNews/nuclear_tools.py @@ -7,21 +7,24 @@ from particle import literals as lp import logging -logger = logging.getLogger('logger.' + __name__) + +logger = logging.getLogger("logger." + __name__) from DarkNews.const_dics import fourier_bessel_dic from DarkNews import const + # to replace certain lambda functions -def zero_func(x): +def zero_func(x): return 0.0 + class NuclearTarget: def __init__(self, name): """ Main DarkNews class for the nucleus to be used in a neutrino scattering event. - It contains target properties like number of protons, neutrons, informations on the mass, as well + It contains target properties like number of protons, neutrons, informations on the mass, as well as all the Nuclear Data Table information on: nuclear_Eb, atomic_Eb, @@ -101,7 +104,7 @@ def get_constituent_nucleon(self, name): return self.BoundNucleon(self, name) class BoundNucleon: - """ for scattering on bound nucleon in the nuclear target + """for scattering on bound nucleon in the nuclear target Inner Class @@ -144,30 +147,30 @@ def __init__(self, nucleus, name): def assign_form_factors(target): """ - Here we define nuclear form factors following: - http://discovery.phys.virginia.edu/research/groups/ncd/index.html + Here we define nuclear form factors following: + http://discovery.phys.virginia.edu/research/groups/ncd/index.html - When available, we use data from Nuclear Data Tables (74, 87, and 95), stored in "include/aux_data/mass20.txt": - "The Ame2020 atomic mass evaluation (I)" by W.J.Huang, M.Wang, F.G.Kondev, G.Audi and S.Naimi - Chinese Physics C45, 030002, March 2021. - "The Ame2020 atomic mass evaluation (II)" by M.Wang, W.J.Huang, F.G.Kondev, G.Audi and S.Naimi - Chinese Physics C45, 030003, March 2021. + When available, we use data from Nuclear Data Tables (74, 87, and 95), stored in "include/aux_data/mass20.txt": + "The Ame2020 atomic mass evaluation (I)" by W.J.Huang, M.Wang, F.G.Kondev, G.Audi and S.Naimi + Chinese Physics C45, 030002, March 2021. + "The Ame2020 atomic mass evaluation (II)" by M.Wang, W.J.Huang, F.G.Kondev, G.Audi and S.Naimi + Chinese Physics C45, 030003, March 2021. - Element properties are stored in elements_dic.To access individual elements we use the format: + Element properties are stored in elements_dic.To access individual elements we use the format: - key = 'name+A', e.g. key = 'Pb208' or 'C12'. + key = 'name+A', e.g. key = 'Pb208' or 'C12'. - All units in GeV, except otherwise specified. + All units in GeV, except otherwise specified. - Args: - target (DarkNews.nuclear_tools.Target): instance of main DarkNews target object. + Args: + target (DarkNews.nuclear_tools.Target): instance of main DarkNews target object. """ # Nucleus if target.is_nucleus: try: a = fourier_bessel_dic[target.name.lower()] ## stored with lower case formatting - fcoh = partial(nuclear_F1_fourier_bessel_EM,array_coeff=a) + fcoh = partial(nuclear_F1_fourier_bessel_EM, array_coeff=a) except KeyError: logger.warning(f"Warning: nuclear density for {target.name} not tabulated in Nuclear Data Table. Using symmetrized Fermi form factor instead.") fcoh = partial(nuclear_F1_Fsym_EM, A=target.A) @@ -211,12 +214,12 @@ def sph_bessel_0(x): # all units in fm def fourier_bessel_form_factor_terms(q, R, n): x = q * R - return sph_bessel_0(x) * R ** 3 / ((n * np.pi) ** 2 - x ** 2) * (-1) ** n + return sph_bessel_0(x) * R**3 / ((n * np.pi) ** 2 - x**2) * (-1) ** n # all units in fm def fourier_bessel_integral_terms(R, n): - return R ** 3 * (-1) ** n / np.pi ** 2 / n ** 2 + return R**3 * (-1) ** n / np.pi**2 / n**2 # Q2 in GeV^2 and other units in fm @@ -238,7 +241,7 @@ def nuclear_F1_fourier_bessel_EM(Q2, array_coeff): # Nested dic containing all elements # approximate formula in D. Lunney, J.M. Pearson and C. Thibault, Rev. Mod. Phys.75, 1021 (2003) def electron_binding_energy(Z): - return (14.4381 * Z ** 2.39 + 1.55468e-6 * Z ** 5.35) * 1e-9 # GeV + return (14.4381 * Z**2.39 + 1.55468e-6 * Z**5.35) * 1e-9 # GeV elements_dic = {} @@ -282,35 +285,37 @@ def electron_binding_energy(Z): # NUCLEAR AND NUCLEON FORM FACTORS MAG_N = -1.913 MAG_P = 2.792 + + ## dipole parametrization def D(Q2): MV = 0.843 # GeV - return 1.0 / ((1 + Q2 / MV ** 2) ** 2) + return 1.0 / ((1 + Q2 / MV**2) ** 2) ## nucleon def nucleon_F1_EM(Q2, tau3): # pick nucleon mag moment MAG = (MAG_P + MAG_N + tau3 * (MAG_P - MAG_N)) / 2.0 - tau = -Q2 / 4.0 / const.m_proton ** 2 + tau = -Q2 / 4.0 / const.m_proton**2 return (D(Q2) - tau * MAG * D(Q2)) / (1 - tau) def nucleon_F2_EM(Q2, tau3): # pick nucleon mag moment MAG = (MAG_P + MAG_N + tau3 * (MAG_P - MAG_N)) / 2.0 - tau = -Q2 / 4.0 / const.m_proton ** 2 + tau = -Q2 / 4.0 / const.m_proton**2 return (MAG * D(Q2) - D(Q2)) / (1 - tau) def nucleon_F1_NC(Q2, tau3): - tau = -Q2 / 4.0 / const.m_proton ** 2 + tau = -Q2 / 4.0 / const.m_proton**2 f = (0.5 - const.s2w) * (tau3) * (1 - tau * (1 + MAG_P - MAG_N)) / (1 - tau) - const.s2w * (1 - tau * (1 + MAG_P + MAG_N)) / (1 - tau) return f * D(Q2) def nucleon_F2_NC(Q2, tau3): - tau = -Q2 / 4.0 / const.m_proton ** 2 + tau = -Q2 / 4.0 / const.m_proton**2 f = (0.5 - const.s2w) * (tau3) * (MAG_P - MAG_N) / (1 - tau) - const.s2w * (MAG_P + MAG_N) / (1 - tau) return f * D(Q2) @@ -318,13 +323,21 @@ def nucleon_F2_NC(Q2, tau3): def nucleon_F3_NC(Q2, tau3): MA = 1.02 # GeV gA = 1.26 - f = gA * tau3 / 2.0 / (1 + Q2 / MA ** 2) ** 2 + f = gA * tau3 / 2.0 / (1 + Q2 / MA**2) ** 2 return f -## symmetrized fermi nuclear -def f(Q,a,r0): - return 3.0 * np.pi * a / (r0 ** 2 + np.pi ** 2 * a ** 2) * (np.pi * a * (1.0 / np.tanh(np.pi * a * Q)) * np.sin(Q * r0) - r0 * np.cos(Q * r0)) / (Q * r0 * np.sinh(np.pi * Q * a)) +# symmetrized fermi nuclear +def f(Q, a, r0): + return ( + 3.0 + * np.pi + * a + / (r0**2 + np.pi**2 * a**2) + * (np.pi * a * (1.0 / np.tanh(np.pi * a * Q)) * np.sin(Q * r0) - r0 * np.cos(Q * r0)) + / (Q * r0 * np.sinh(np.pi * Q * a)) + ) + def nuclear_F1_Fsym_EM(Q2, A): Q = np.sqrt(Q2) @@ -332,15 +345,14 @@ def nuclear_F1_Fsym_EM(Q2, A): r0 = 1.03 * (A ** (1.0 / 3.0)) * const.fm_to_GeV # GeV^-1 # tolerance = Q < 5 # clean_FF = ma.masked_array( - # data=3.0 * np.pi * a / (r0 ** 2 + np.pi ** 2 * a ** 2) * + # data=3.0 * np.pi * a / (r0 ** 2 + np.pi ** 2 * a ** 2) * # (np.pi * a * (1.0 / np.tanh(np.pi * a * Q)) * np.sin(Q * r0) - r0 * np.cos(Q * r0)) / # (Q * r0 * np.sinh(np.pi * Q * a)), # mask=~tolerance, # fill_value=0.0, # ) # return clean_FF.filled() - return np.piecewise(Q, [Q<5, Q>=5], [partial(f,a=a,r0=r0), zero_func]) - + return np.piecewise(Q, [Q < 5, Q >= 5], [partial(f, a=a, r0=r0), zero_func]) def j1(z): diff --git a/src/DarkNews/parsing_tools.py b/src/DarkNews/parsing_tools.py index 882fa6b..b4744cb 100755 --- a/src/DarkNews/parsing_tools.py +++ b/src/DarkNews/parsing_tools.py @@ -1,5 +1,6 @@ import argparse + def add_common_bsm_arguments(parser, DEFAULTS): #### name of the generation and model @@ -85,11 +86,13 @@ def add_three_portal_arguments(parser, DEFAULTS): parser.add_argument("--alphaD", type=float, help="U(1)_d alpha_dark = (g_dark^2 /4 pi)") ##### kinetic mixing options - parser.add_argument("--epsilon", type=float, help="epsilon") + parser.add_argument("--epsilon", type=float, help="epsilon (kinetic mixing)") parser.add_argument("--epsilon2", type=float, help="epsilon^2") parser.add_argument("--alpha_epsilon2", type=float, help="alpha_QED*epsilon^2") parser.add_argument("--chi", type=float, help="chi") + parser.add_argument("--epsilonZ", type=float, help="epsilonZ (Z-Z' mass mixing)") + parser.add_argument("--theta", type=float, help="Scalar mixing angle between h-h'") @@ -218,19 +221,31 @@ def add_mc_arguments(parser, DEFAULTS): parser.add_argument("--parquet", help="If true, prints pandas dataframe to .parquet files. Loses metadata in attrs.", action="store_true") parser.add_argument("--numpy", help="If true, prints events as ndarrays in a .npy file", action="store_true") parser.add_argument("--hepevt", help="If true, print events to HEPEVT-formatted text files (does not save event weights)", action="store_true") - parser.add_argument("--hepevt_legacy", + parser.add_argument( + "--hepevt_legacy", help="If true, print events to a legacy HEPEVT format (saving weights next to the number of particle in the event and without linebreaks in particle entries)", action="store_true", ) parser.add_argument("--hepmc2", help="If true, prints events to HepMC2 format.", action="store_true") parser.add_argument("--hepmc3", help="If true, prints events to HepMC3 format.", action="store_true") parser.add_argument("--hep_unweight", help="unweight events when printing in standard HEP formats (needs large neval)", action="store_true") - parser.add_argument("--unweighted_hep_events",type=int, + parser.add_argument( + "--unweighted_hep_events", + type=int, help="number of unweighted events to accept in any of the standard HEP formats. Has to be much smaller than neval for unweight procedure to work.", ) - parser.add_argument("--sparse", type=int, help="Specify the level of sparseness of the internal dataframe and output. Not supported for HEPevt. Allowed values are 0--4, from most to least saved information.", choices=DEFAULTS._choices["sparse"]) - parser.add_argument("--print_to_float32", help="Use float32 instead of default float64 when printing output to save storage space. Requires sparse >= 1.", action="store_true") + parser.add_argument( + "--sparse", + type=int, + help="Specify the level of sparseness of the internal dataframe and output. Not supported for HEPevt. Allowed values are 0--4, from most to least saved information.", + choices=DEFAULTS._choices["sparse"], + ) + parser.add_argument( + "--print_to_float32", + help="Use float32 instead of default float64 when printing output to save storage space. Requires sparse >= 1.", + action="store_true", + ) parser.add_argument("--path", type=str, help="path where to save run's outputs") parser.add_argument("--seed", type=int, help="numpy seed to be used by vegas.") diff --git a/src/DarkNews/phase_space.py b/src/DarkNews/phase_space.py index fbf738c..545e7c4 100644 --- a/src/DarkNews/phase_space.py +++ b/src/DarkNews/phase_space.py @@ -2,16 +2,18 @@ import numpy.ma as ma import logging -logger = logging.getLogger('logger.' + __name__) + +logger = logging.getLogger("logger." + __name__) from . import Cfourvec as Cfv ######################################################## # Kinematical limits + # 2 --> 2 scattering def upscattering_Q2max(Enu, mHNL, M): - s = 2 * Enu * M + M ** 2 + s = 2 * Enu * M + M**2 return ( 1 / 2 @@ -33,14 +35,29 @@ def upscattering_Q2max(Enu, mHNL, M): def upscattering_Q2min(Enu, mHNL, M): - s = 2 * Enu * M + M ** 2 + s = 2 * Enu * M + M**2 r = mHNL / np.sqrt(s) m = M / np.sqrt(s) small_r = r < 1e-3 # large cancellations at play -- expanding for small r q2min = ma.masked_array( - data=1/2 * (1+ ((m) ** (4)+ (-1 * (r) ** (2)+ (-1 * ((((-1 + (m) ** (2))) ** (2) + (-2 * (1 + (m) ** (2)) * (r) ** (2) + (r) ** (4)))) ** (1 / 2)+ (m) ** (2) * (-2 + (-1 * (r) ** (2) + ((((-1 + (m) ** (2))) ** (2) + (-2 * (1 + (m) ** (2)) * (r) ** (2) + (r) ** (4)))) ** (1 / 2)))))))* s, + data=1 + / 2 + * ( + 1 + + ( + (m) ** (4) + + ( + -1 * (r) ** (2) + + ( + -1 * ((((-1 + (m) ** (2))) ** (2) + (-2 * (1 + (m) ** (2)) * (r) ** (2) + (r) ** (4)))) ** (1 / 2) + + (m) ** (2) * (-2 + (-1 * (r) ** (2) + ((((-1 + (m) ** (2))) ** (2) + (-2 * (1 + (m) ** (2)) * (r) ** (2) + (r) ** (4)))) ** (1 / 2))) + ) + ) + ) + ) + * s, mask=small_r, fill_value=(m) ** (2) * ((-1 + (m) ** (2))) ** (-2) * (r) ** (4) * s, ) @@ -58,7 +75,8 @@ def three_body_umax(m1, m2, m3, m4, t): ( ((-1 * (m2) ** (2) + 1 / 4 * (t) ** (-1) * (((m2) ** (2) + (-1 * (m3) ** (2) + t))) ** (2))) ** (1 / 2) + -1 * ((-1 * (m4) ** (2) + 1 / 4 * (t) ** (-1) * ((-1 * (m1) ** (2) + ((m4) ** (2) + t))) ** (2))) ** (1 / 2) - )) ** (2) + ) + ) ** (2) def three_body_umin(m1, m2, m3, m4, t): @@ -66,7 +84,8 @@ def three_body_umin(m1, m2, m3, m4, t): ( ((-1 * (m2) ** (2) + 1 / 4 * (t) ** (-1) * (((m2) ** (2) + (-1 * (m3) ** (2) + t))) ** (2))) ** (1 / 2) + ((-1 * (m4) ** (2) + 1 / 4 * (t) ** (-1) * ((-1 * (m1) ** (2) + ((m4) ** (2) + t))) ** (2))) ** (1 / 2) - )) ** (2) + ) + ) ** (2) def three_body_tmax(m1, m2, m3, m4): @@ -84,29 +103,29 @@ def two_to_two_scatter(samples, m1=1.0, m2=0.0, m3=1.0, m4=0.0): if "Eprojectile" in samples.keys(): Eprojectile = samples["Eprojectile"] - s = m2 ** 2 + 2 * Eprojectile * m2 + m1 ** 2 + s = m2**2 + 2 * Eprojectile * m2 + m1**2 sample_size = np.shape(Eprojectile)[0] else: logger.error("Error! Could not determine the projectile energy") - E1CM = (s + m1 ** 2 - m2 ** 2) / 2.0 / np.sqrt(s) - E2CM = (s - m1 ** 2 + m2 ** 2) / 2.0 / np.sqrt(s) - E3CM = (s + m3 ** 2 - m4 ** 2) / 2.0 / np.sqrt(s) - E4CM = (s - m3 ** 2 + m4 ** 2) / 2.0 / np.sqrt(s) + E1CM = (s + m1**2 - m2**2) / 2.0 / np.sqrt(s) + E2CM = (s - m1**2 + m2**2) / 2.0 / np.sqrt(s) + E3CM = (s + m3**2 - m4**2) / 2.0 / np.sqrt(s) + E4CM = (s - m3**2 + m4**2) / 2.0 / np.sqrt(s) - p1CM = np.sqrt(E1CM ** 2 - m1 ** 2) - p2CM = np.sqrt(E2CM ** 2 - m2 ** 2) - p3CM = np.sqrt(E3CM ** 2 - m3 ** 2) - p4CM = np.sqrt(E4CM ** 2 - m4 ** 2) + p1CM = np.sqrt(E1CM**2 - m1**2) + p2CM = np.sqrt(E2CM**2 - m2**2) + p3CM = np.sqrt(E3CM**2 - m3**2) + p4CM = np.sqrt(E4CM**2 - m4**2) # if massless proj and elastic in one, watch out for cancellations if m1 == 0 and m2 == m4: - Q2min = upscattering_Q2min(Enu = (s - m2**2)/2/m2, mHNL = m3, M = m2) - Q2max = upscattering_Q2max(Enu = (s - m2**2)/2/m2, mHNL = m3, M = m2) + Q2min = upscattering_Q2min(Enu=(s - m2**2) / 2 / m2, mHNL=m3, M=m2) + Q2max = upscattering_Q2max(Enu=(s - m2**2) / 2 / m2, mHNL=m3, M=m2) else: - # general case - Q2min = -(m1 ** 2 + m3 ** 2 - 2 * (E1CM * E3CM - p1CM * p3CM)) - Q2max = -(m1 ** 2 + m3 ** 2 - 2 * (E1CM * E3CM + p1CM * p3CM)) + # general case + Q2min = -(m1**2 + m3**2 - 2 * (E1CM * E3CM - p1CM * p3CM)) + Q2max = -(m1**2 + m3**2 - 2 * (E1CM * E3CM + p1CM * p3CM)) if "unit_Q2" in samples.keys(): Q2l = (np.log(Q2max) - np.log(Q2min)) * samples["unit_Q2"] + np.log(Q2min) @@ -118,7 +137,7 @@ def two_to_two_scatter(samples, m1=1.0, m2=0.0, m3=1.0, m4=0.0): Q2 = Cfv.random_generator(sample_size, Q2min, Q2max) # KINEMATICS TO LAB FRAME - costN = (-Q2 - m1 ** 2 - m3 ** 2 + 2 * E1CM * E3CM) / (2 * p1CM * p3CM) + costN = (-Q2 - m1**2 - m3**2 + 2 * E1CM * E3CM) / (2 * p1CM * p3CM) beta = -p2CM / E2CM # MINUS SIGN -- from CM to LAB gamma = 1.0 / np.sqrt(1.0 - beta * beta) @@ -157,7 +176,7 @@ def two_body_decay(samples, boost=False, m1=1, m2=0, m3=0): # get sample size of the first item sample_size = np.shape(list(samples.values())[0])[0] - # cosine of the angle between k3 and z axis + # cosine of the angle between k2 and z axis if "unit_cost" in samples.keys(): cost = 2 * samples["unit_cost"] - 1 elif "cost" in samples.keys(): @@ -167,13 +186,13 @@ def two_body_decay(samples, boost=False, m1=1, m2=0, m3=0): cost = Cfv.random_generator(sample_size, -1, 1) E1CM_decay = np.full_like(cost, m1) - E2CM_decay = np.full_like(cost, (m1 ** 2 + m2 ** 2 - m3 ** 2) / 2.0 / m1) - E3CM_decay = np.full_like(cost, (m1 ** 2 - m2 ** 2 + m3 ** 2) / 2.0 / m1) + E2CM_decay = np.full_like(cost, (m1**2 + m2**2 - m3**2) / 2.0 / m1) + E3CM_decay = np.full_like(cost, (m1**2 - m2**2 + m3**2) / 2.0 / m1) - p2CM_decay = np.full_like(cost, np.sqrt(E2CM_decay ** 2 - m2 ** 2)) - p3CM_decay = np.full_like(cost, np.sqrt(E3CM_decay ** 2 - m3 ** 2)) + p2CM_decay = np.full_like(cost, np.sqrt(E2CM_decay**2 - m2**2)) + p3CM_decay = np.full_like(cost, np.sqrt(E3CM_decay**2 - m3**2)) - # azimuthal angle of k3 + # azimuthal angle of k2 if "unit_phiz" in samples.keys(): phiz = 2 * samples["unit_phiz"] - 1 elif "phiz" in samples.keys(): @@ -183,13 +202,13 @@ def two_body_decay(samples, boost=False, m1=1, m2=0, m3=0): phiz = Cfv.random_generator(sample_size, 0.0, 2 * np.pi) P1CM_decay = Cfv.build_fourvec(E1CM_decay, p2CM_decay * 0.0, cost / cost, phiz * 0) - P2CM_decay = Cfv.build_fourvec(E2CM_decay, -p2CM_decay, cost, phiz) - P3CM_decay = Cfv.build_fourvec(E3CM_decay, p2CM_decay, cost, phiz) + P2CM_decay = Cfv.build_fourvec(E2CM_decay, p2CM_decay, cost, phiz) + P3CM_decay = Cfv.build_fourvec(E3CM_decay, -p2CM_decay, cost, phiz) # four-momenta in the LAB frame if boost: EP_LAB = boost["EP_LAB"] - p1_LAB = np.sqrt(EP_LAB ** 2 - m1 ** 2) + p1_LAB = np.sqrt(EP_LAB**2 - m1**2) costP_LAB = boost["costP_LAB"] phiP_LAB = boost["phiP_LAB"] @@ -251,15 +270,15 @@ def three_body_decay(samples, boost=False, m1=1, m2=0, m3=0, m4=0): u = Cfv.random_generator(sample_size, uminus, uplus) # Mandelstam v = m_34^2 - v = m1 ** 2 + m2 ** 2 + m3 ** 2 + m4 ** 2 - u - t + v = m1**2 + m2**2 + m3**2 + m4**2 - u - t - E2CM_decay = (m1 ** 2 + m2 ** 2 - v) / 2.0 / m1 - E3CM_decay = (m1 ** 2 + m3 ** 2 - u) / 2.0 / m1 - E4CM_decay = (m1 ** 2 + m4 ** 2 - t) / 2.0 / m1 + E2CM_decay = (m1**2 + m2**2 - v) / 2.0 / m1 + E3CM_decay = (m1**2 + m3**2 - u) / 2.0 / m1 + E4CM_decay = (m1**2 + m4**2 - t) / 2.0 / m1 - p2CM_decay = np.sqrt(E2CM_decay * E2CM_decay - m2 ** 2) - p3CM_decay = np.sqrt(E3CM_decay * E3CM_decay - m3 ** 2) - p4CM_decay = np.sqrt(E4CM_decay * E4CM_decay - m4 ** 2) + p2CM_decay = np.sqrt(E2CM_decay * E2CM_decay - m2**2) + p3CM_decay = np.sqrt(E3CM_decay * E3CM_decay - m3**2) + p4CM_decay = np.sqrt(E4CM_decay * E4CM_decay - m4**2) # Polar angle of P_3 if "unit_c3" in samples.keys(): @@ -282,7 +301,7 @@ def three_body_decay(samples, boost=False, m1=1, m2=0, m3=0, m4=0): phi34 = Cfv.random_generator(sample_size, 0, 2 * np.pi) # polar angle of P_4 wrt to P_3 is a known function of u and v - c_theta34 = (t + u - m2 ** 2 - m1 ** 2 + 2 * E3CM_decay * E4CM_decay) / (2 * p3CM_decay * p4CM_decay) + c_theta34 = (t + u - m2**2 - m1**2 + 2 * E3CM_decay * E4CM_decay) / (2 * p3CM_decay * p4CM_decay) # p1 P1CM_decay = Cfv.build_fourvec(m1 * np.ones(sample_size), np.zeros(sample_size), np.ones(sample_size), np.zeros(sample_size)) @@ -301,13 +320,13 @@ def three_body_decay(samples, boost=False, m1=1, m2=0, m3=0, m4=0): ### Transform from CM into the LAB frame # Decaying neutrino - P1LAB_decay = Cfv.Tinv(P1CM_decay, -np.sqrt(EN_LAB ** 2 - m1 ** 2) / EN_LAB, costN_LAB, phiN_LAB) + P1LAB_decay = Cfv.Tinv(P1CM_decay, -np.sqrt(EN_LAB**2 - m1**2) / EN_LAB, costN_LAB, phiN_LAB) # Outgoing neutrino - P2LAB_decay = Cfv.Tinv(P2CM_decay, -np.sqrt(EN_LAB ** 2 - m1 ** 2) / EN_LAB, costN_LAB, phiN_LAB) + P2LAB_decay = Cfv.Tinv(P2CM_decay, -np.sqrt(EN_LAB**2 - m1**2) / EN_LAB, costN_LAB, phiN_LAB) # Outgoing lepton minus (3) - P3LAB_decay = Cfv.Tinv(P3CM_decay, -np.sqrt(EN_LAB ** 2 - m1 ** 2) / EN_LAB, costN_LAB, phiN_LAB) + P3LAB_decay = Cfv.Tinv(P3CM_decay, -np.sqrt(EN_LAB**2 - m1**2) / EN_LAB, costN_LAB, phiN_LAB) # Outgoing lepton plus (4) - P4LAB_decay = Cfv.Tinv(P4CM_decay, -np.sqrt(EN_LAB ** 2 - m1 ** 2) / EN_LAB, costN_LAB, phiN_LAB) + P4LAB_decay = Cfv.Tinv(P4CM_decay, -np.sqrt(EN_LAB**2 - m1**2) / EN_LAB, costN_LAB, phiN_LAB) return P1LAB_decay, P2LAB_decay, P3LAB_decay, P4LAB_decay diff --git a/src/DarkNews/processes.py b/src/DarkNews/processes.py index 925332e..8cdd2a3 100644 --- a/src/DarkNews/processes.py +++ b/src/DarkNews/processes.py @@ -20,9 +20,35 @@ class UpscatteringProcess: - """ - Describes the process of upscattering with arbitrary vertices and masses - + """ + A class to describe the process of neutrino upscattering, which involves a neutrino scattering off a target and gaining energy in the process. + This class supports various scattering regimes (coherent, proton elastic, and neutron elastic), and allows for the calculation of total and differential cross sections for these processes. + + Attributes: + nuclear_target (object): The nuclear target involved in the scattering process. + scattering_regime (str): The regime of scattering, e.g., 'coherent', 'p-el' (proton elastic), 'n-el' (neutron elastic). + target (object): The actual target of the scattering, which could be the whole nucleus, a constituent nucleon, or constituent quarks, depending on the scattering regime. + target_multiplicity (int): The number of targets involved in the scattering process, relevant for calculating cross sections. + nu_projectile (object): The incoming neutrino involved in the upscattering process. + nu_upscattered (object): The upscattered neutrino resulting from the scattering process. + TheoryModel (object): The theoretical model used to describe the interactions in the upscattering process. + helicity (str): The helicity configuration of the upscattering process, either 'conserving' or 'flipping'. + MA (float): The mass of the target involved in the scattering. + mzprime (float): The mass of the Z' boson in the theory model, if applicable. + mhprime (float): The mass of the H' boson in the theory model, if applicable. + m_ups (float): The mass of the upscattered neutrino. + Cij, Cji, Vij, Vji, Sij, Sji, Tij, Tji (float): Coupling constants for the interaction vertices involved in the upscattering process. + Chad, Vhad, Shad (float): Hadronic coupling constants for the interaction vertices. + Cprimehad (float): Mass-mixed vertex coupling constant for the hadronic interaction. + Ethreshold (float): The minimum energy threshold for the upscattering process to occur. + vectorized_total_xsec (function): A vectorized function to calculate the total cross section for the upscattering process. + calculable_diagrams (list): A list of diagrams that can be calculated for the upscattering process. + + Methods: + __init__(self, nu_projectile, nu_upscattered, nuclear_target, scattering_regime, TheoryModel, helicity): Initializes the upscattering process with specified parameters. + scalar_total_xsec(self, Enu, diagram="total", NINT=MC.NINT, NEVAL=MC.NEVAL, NINT_warmup=MC.NINT_warmup, NEVAL_warmup=MC.NEVAL_warmup, savefile_xsec=None, savefile_norm=None): Calculates the scalar total cross section for a given neutrino energy and diagram. + total_xsec(self, Enu, diagrams=["total"], NINT=MC.NINT, NEVAL=MC.NEVAL, NINT_warmup=MC.NINT_warmup, NEVAL_warmup=MC.NEVAL_warmup, seed=None, savestr=None): Calculates the total cross section for the upscattering process for a fixed neutrino energy. + diff_xsec_Q2(self, Enu, Q2, diagrams=["total"]): Calculates the differential cross section for the upscattering process as a function of the squared momentum transfer Q2. """ def __init__(self, nu_projectile, nu_upscattered, nuclear_target, scattering_regime, TheoryModel, helicity):