def vmin_migdal(w, erec, mw): """Return minimum WIMP velocity to make a Migdal signal with energy w, given elastic recoil energy erec and WIMP mass mw. """ y = (wr.mn() * erec / (2 * wr.mu_nucleus(mw)**2))**0.5 y += w / (2 * wr.mn() * erec)**0.5 return np.maximum(0, y)
def sigma_w_erec(w, erec, v, mw, sigma_nucleon, interaction='SI', m_med=float('inf')): """Differential WIMP-nucleus Bremsstrahlung cross section. From Kouvaris/Pradler [arxiv:1607.01789v2], eq. 8 :param w: Bremsstrahlung photon energy :param mw: WIMP mass :param erec: recoil energy :param v: WIMP speed (earth/detector frame) :param sigma_nucleon: WIMP/nucleon cross-section :param interaction: string describing DM-nucleus interaction. Default is 'SI' (spin-independent) :param m_med: Mediator mass. If not given, assumed very heavy. TODO: check for wmax! # What is this? Still relevant? """ # X-ray form factor form_atomic = np.abs(f1(w / nu.keV) + 1j * f2(w / nu.keV)) # Note mn -> mn c^2, Kouvaris/Pradtler and McCabe use natural units return (4 * nu.alphaFS / (3 * np.pi * w) * erec / (wr.mn() * nu.c0**2) * form_atomic**2 * wr.sigma_erec(erec, v, mw, sigma_nucleon, interaction, m_med))
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_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 erec_bound(sign, w, v, mw): """Bremsstrahlung scattering recoil energy kinematic limits From Kouvaris/Pradler [arxiv:1607.01789v2], eq. between 8 and 9, simplified by vmin (see above) :param sign: +1 to get upper limit, -1 to get lower limit :param w: Bremsstrahlung photon energy :param mw: WIMP mass :param v: WIMP speed (earth/detector frame) """ return (wr.mu_nucleus(mw)**2 * v**2 / wr.mn() * (1 - vmin_w(w, mw)**2 / (2 * v**2) + sign * (1 - vmin_w(w, mw)**2 / v**2)**0.5))
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))
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)