def velocity_integral_without_time(halo_model=None): """Precomputes the inverse_speed_integral by fixing the upper bound to vmax=konst and varring the lower bound vmin only used i the case where there is no time dependence in vmax i.e. t=None""" halo_model = wr.StandardHaloModel() if halo_model is None else halo_model _v_mins = np.linspace(0.0001, 1, 1000) * wr.v_max( None, halo_model.v_esc) # v_max carries units _ims = np.array([ quad(lambda v: 1 / v * halo_model.velocity_dist(v, None), _v_min, wr.v_max(None, halo_model.v_esc))[0] for _v_min in _v_mins ]) """ _ims = np.zeros(len(_v_mins)) integrand=np.zeros(len(_v_mins)) for _v_min,index in zip(_v_mins,range(len(_v_mins))): f = lambda v: 1/v*halo_model.velocity_dist(v,None) #velocity_dist returns units integrand[index]= f(_v_min) _ims[index] = quad(f, _v_min, wr.v_max(None, halo_model.v_esc))[0]""" # Store interpolator in unit-dependent numbers # have to pass unit carring velocity to the interpolator # and return carryies units of the integral [1/velo] inv_mean_speed = interp1d(_v_mins, _ims, fill_value=0, bounds_error=False) # If we don't have 0 < v_min < v_max, we want to return 0 # so the integrand vanishes return inv_mean_speed
def rate_bremsstrahlung(w, mw, sigma_nucleon, interaction='SI', m_med=float('inf'), t=None, **kwargs): """Differential rate per unit detector mass and recoil energy of Bremsstrahlung elastic WIMP-nucleus scattering. :param w: Bremsstrahlung photon energy :param mw: Mass of WIMP :param sigma_nucleon: WIMP/nucleon cross-section :param m_med: Mediator mass. If not given, assumed very heavy. :param t: A J2000.0 timestamp. If not given, a conservative velocity distribution is used. :param interaction: string describing DM-nucleus interaction. See sigma_erec for options :param progress_bar: if True, show a progress bar during evaluation (if w is an array) Further kwargs are passed to scipy.integrate.quad numeric integrator (e.g. error tolerance). """ vmin = vmin_w(w, mw) if vmin >= wr.v_max(t): return 0 def integrand(v): return (sigma_w(w, v, mw, sigma_nucleon, interaction, m_med) * v * wr.observed_speed_dist(v, t)) return wr.rho_dm() / mw * (1 / wr.mn()) * quad(integrand, vmin, wr.v_max(t), **kwargs)[0]
def rate_elastic(erec, mw, sigma_nucleon, interaction='SI', m_med=float('inf'), t=None, material='Xe', halo_model=None, **kwargs): """Differential rate per unit detector mass and recoil energy of elastic WIMP scattering :param erec: recoil energy :param mw: WIMP mass :param sigma_nucleon: WIMP/nucleon cross-section :param interaction: string describing DM-nucleus interaction, see sigma_erec for options :param m_med: Mediator mass. If not given, assumed very heavy. :param t: A J2000.0 timestamp. If not given, conservative velocity distribution is used. :param halo_model: class (default to standard halo model) containing velocity distribution :param progress_bar: if True, show a progress bar during evaluation (if erec is an array) :param material: name of the detection material (default is 'Xe') Further kwargs are passed to scipy.integrate.quad numeric integrator (e.g. error tolerance). Analytic expressions are known for this rate, but they are not used here. """ halo_model = wr.StandardHaloModel() if halo_model is None else halo_model v_min = vmin_elastic(erec, mw, material) if v_min >= wr.v_max(t, halo_model.v_esc): return 0 def integrand(v): return (sigma_erec( erec, v, mw, sigma_nucleon, interaction, m_med, material=material) * v * halo_model.velocity_dist(v, t)) return halo_model.rho_dm / mw * (1 / mn(material)) * quad( integrand, v_min, wr.v_max(t, halo_model.v_esc), **kwargs)[0]
def rate_dme(erec, n, l, mw, sigma_dme, t=None, **kwargs): """Return differential rate of dark matter electron scattering vs energy (i.e. dr/dE, not dr/dlogE) :param erec: Electronic recoil energy :param n: Principal quantum numbers of the shell that is hit :param l: Angular momentum quantum number of the shell that is hit :param mw: DM mass :param sigma_dme: DM-free electron scattering cross-section at fixed momentum transfer q=0 :param t: A J2000.0 timestamp. If not given, a conservative velocity distribution is used. """ shell = shell_str(n, l) eb = binding_es_for_dme(n, l) # No bounds are given for the q integral # but the form factors are only specified in a limited range of q qmax = (np.exp(shell_data[shell]['lnqs'].max()) * (nu.me * nu.c0 * nu.alphaFS)) if t is None: # Use precomputed inverse mean speed, # so we only have to do a single integral def diff_xsec(q): vmin = v_min_dme(eb, erec, q, mw) result = q * dme_ionization_ff(shell, erec, q) # Note the interpolator is in kms, not unit-carrying numbers # see above result *= inverse_mean_speed_kms(vmin / (nu.km / nu.s)) result /= (nu.km / nu.s) return result r = quad(diff_xsec, 0, qmax)[0] else: # Have to do double integral # Note dblquad expects the function to be f(y, x), not f(x, y)... def diff_xsec(v, q): result = q * dme_ionization_ff(shell, erec, q) result *= 1 / v * wr.observed_speed_dist(v, t) return result r = dblquad(diff_xsec, 0, qmax, lambda q: v_min_dme(eb, erec, q, mw), lambda _: wr.v_max(t), **kwargs)[0] mu_e = mw * nu.me / (mw + nu.me) return ( # Convert cross-section to rate, as usual wr.rho_dm() / mw * (1 / wr.mn()) # d/lnE -> d/E * 1 / erec # Prefactors in cross-section * sigma_dme / (8 * mu_e**2) * r)
def rate_elastic(erec, mw, sigma_nucleon, interaction='SI', m_med=float('inf'), t=None, **kwargs): """Differential rate per unit detector mass and recoil energy of elastic WIMP scattering :param erec: recoil energy :param mw: WIMP mass :param sigma_nucleon: WIMP/nucleon cross-section :param interaction: string describing DM-nucleus interaction, see sigma_erec for options :param m_med: Mediator mass. If not given, assumed very heavy. :param t: A J2000.0 timestamp. If not given, conservative velocity distribution is used. :param progress_bar: if True, show a progress bar during evaluation (if erec is an array) Further kwargs are passed to scipy.integrate.quad numeric integrator (e.g. error tolerance). Analytic expressions are known for this rate, but they are not used here. """ v_min = vmin_elastic(erec, mw) if v_min >= wr.v_max(t): return 0 def integrand(v): return (sigma_erec(erec, v, mw, sigma_nucleon, interaction, m_med) * v * wr.observed_speed_dist(v, t)) return wr.rho_dm() / mw * (1 / mn()) * quad(integrand, v_min, wr.v_max(t), **kwargs)[0]
def rate_migdal(w, mw, sigma_nucleon, interaction='SI', m_med=float('inf'), include_approx_nr=False, t=None, **kwargs): """Differential rate per unit detector mass and deposited ER energy of Migdal effect WIMP-nucleus scattering :param w: ER energy deposited in detector through Migdal effect :param mw: Mass of WIMP :param sigma_nucleon: WIMP/nucleon cross-section :param interaction: string describing DM-nucleus interaction. See sigma_erec for options :param m_med: Mediator mass. If not given, assumed very heavy. :param include_approx_nr: If True, instead return differential rate per *detected* energy, including the contribution of the simultaneous NR signal approximately, assuming q_{NR} = 0.15. This is how https://arxiv.org/abs/1707.07258 presented the Migdal spectra. :param t: A J2000.0 timestamp. If not given, conservative velocity distribution is used. :param progress_bar: if True, show a progress bar during evaluation (if w is an array) Further kwargs are passed to scipy.integrate.quad numeric integrator (e.g. error tolerance). """ include_approx_nr = 1 if include_approx_nr else 0 result = 0 for state, binding_e in binding_es_for_migdal.items(): binding_e *= nu.eV # Only consider n=3 and n=4 # n=5 is the valence band so unreliable in in liquid # n=1,2 contribute very little if state[0] not in ['3', '4']: continue # Lookup for differential probability (units of ev^-1) p = interp1d(df_migdal['E'].values * nu.eV, df_migdal[state].values / nu.eV, bounds_error=False, fill_value=0) def diff_rate(v, erec): # Observed energy = energy of emitted electron # + binding energy of state eelec = w - binding_e - include_approx_nr * erec * 0.15 if eelec < 0: return 0 return ( # Usual elastic differential rate, # common constants follow at end wr.sigma_erec( erec, v, mw, sigma_nucleon, interaction, m_med=m_med) * v * wr.observed_speed_dist(v, t) # Migdal effect |Z|^2 # TODO: ?? what is explicit (eV/c)**2 doing here? * (nu.me * (2 * erec / wr.mn())**0.5 / (nu.eV / nu.c0))**2 / (2 * np.pi) * p(eelec)) # Note dblquad expects the function to be f(y, x), not f(x, y)... r = dblquad( diff_rate, 0, wr.e_max(mw, wr.v_max(t)), lambda erec: vmin_migdal( w - include_approx_nr * erec * 0.15, erec, mw), lambda _: wr.v_max(t), **kwargs)[0] result += r return wr.rho_dm() / mw * (1 / wr.mn()) * result
def rate_dme(erec, n, l, mw, sigma_dme, inv_mean_speed=None, halo_model=None, f_dm='1', t=None, **kwargs): """Return differential rate of dark matter electron scattering vs energy (i.e. dr/dE, not dr/dlogE) :param erec: Electronic recoil energy :param n: Principal quantum numbers of the shell that is hit :param l: Angular momentum quantum number of the shell that is hit :param mw: DM mass :param sigma_dme: DM-free electron scattering cross-section at fixed momentum transfer q=0 :param f_dm: One of the following: '1': |F_DM|^2 = 1, contact interaction / heavy mediator (default) '1_q': |F_DM|^2 = (\alpha m_e c / q), dipole moment '1_q2': |F_DM|^2 = (\alpha m_e c / q)^2, ultralight mediator :param t: A J2000.0 timestamp. :param halo_model, Halo to be used if not given, the standard is used. :param inv_mean_speed: function to compute inverse_mean_speed integral for a given vmin """ halo_model = wr.StandardHaloModel() if halo_model is None else halo_model inv_mean_speed = inv_mean_speed shell = shell_str(n, l) eb = binding_es_for_dme(n, l) f_dm = { '1': lambda q: 1, '1_q': lambda q: nu.alphaFS * nu.me * nu.c0 / q, '1_q2': lambda q: (nu.alphaFS * nu.me * nu.c0 / q)**2 }[f_dm] # No bounds are given for the q integral # but the form factors are only specified in a limited range of q qmax = (np.exp(shell_data[shell]['lnqs'].max()) * (nu.me * nu.c0 * nu.alphaFS)) if t is None and inv_mean_speed is not None: # Use precomputed inverse mean speed so we only have to do a single integral def diff_xsec(q): vmin = v_min_dme(eb, erec, q, mw) result = q * dme_ionization_ff( shell, erec, q) * f_dm(q)**2 * inv_mean_speed(vmin) # Note the interpolator return carryies units return result r = quad(diff_xsec, 0, qmax)[0] else: # Have to do double integral # Note dblquad expects the function to be f(y, x), not f(x, y)... def diff_xsec(v, q): result = q * dme_ionization_ff(shell, erec, q) * f_dm(q)**2 result *= 1 / v * halo_model.velocity_dist(v, t) return result r = dblquad(diff_xsec, 0, qmax, lambda q: v_min_dme(eb, erec, q, mw), lambda _: wr.v_max(t, halo_model.v_esc), **kwargs)[0] mu_e = mw * nu.me / (mw + nu.me) return (halo_model.rho_dm / mw * (1 / wr.mn()) # d/lnE -> d/E * 1 / erec # Prefactors in cross-section * sigma_dme / (8 * mu_e**2) * r)
}[shell_str(n, l)] * nu.eV @export def v_min_dme(eb, erec, q, mw): """Minimal DM velocity for DM-electron scattering :param eb: binding energy of shell :param erec: electronic recoil energy energy :param q: momentum transfer :param mw: DM mass """ return (erec + eb) / q + q / (2 * mw) # Precompute velocity integrals for t=None _v_mins = np.linspace(0, 1, 1000) * wr.v_max() _ims = np.array([ quad(lambda v: 1 / v * wr.observed_speed_dist(v), _v_min, wr.v_max())[0] for _v_min in _v_mins ]) # Store interpolator in km/s rather than unit-dependent numbers # so we don't have to recalculate them when nu.reset_units() is called inverse_mean_speed_kms = interp1d( _v_mins / (nu.km / nu.s), _ims * (nu.km / nu.s), # If we don't have 0 < v_min < v_max, we want to return 0 # so the integrand vanishes fill_value=0, bounds_error=False)