use std::f64::consts::PI;
pub fn quenching_factor_lindhard(e_r_kev: f64, z: u32, a: u32) -> f64 {
let k = 0.133 * (z as f64).powf(2.0 / 3.0) * (a as f64).powf(-0.5);
let epsilon = 11.5 * e_r_kev * (z as f64).powf(-7.0 / 3.0);
let g = 3.0 * epsilon.powf(0.15) + 0.7 * epsilon.powf(0.6) + epsilon;
k * g / (1.0 + k * g)
}
pub fn quenching_factor_xenon(e_r_kev: f64) -> f64 {
quenching_factor_lindhard(e_r_kev, 54, 131)
}
pub fn quenching_factor_germanium(e_r_kev: f64) -> f64 {
quenching_factor_lindhard(e_r_kev, 32, 73)
}
pub fn quenching_factor_sodium(e_r_kev: f64) -> f64 {
0.3 * (1.0 + 0.01 * e_r_kev) / (1.0 + 0.05 * e_r_kev)
}
pub fn quenching_factor_iodine(e_r_kev: f64) -> f64 {
0.09 * (1.0 + 0.004 * e_r_kev) / (1.0 + 0.02 * e_r_kev)
}
pub fn nuclear_response_m0(q_fm_inv: f64, a: u32, z: u32) -> f64 {
let r = effective_nuclear_radius_fm(a);
let qr = q_fm_inv * r;
let ff = helm_ff_squared(qr, a);
let coherent = (z as f64).powi(2) + ((a - z) as f64).powi(2);
coherent * ff
}
pub fn nuclear_response_m1(q_fm_inv: f64, a: u32, z: u32) -> f64 {
let r = effective_nuclear_radius_fm(a);
let qr = q_fm_inv * r;
let ff = helm_ff_squared(qr, a);
let q_gev = q_fm_inv * 0.197;
let m_n = 0.938;
(z as f64 - (a - z) as f64).powi(2) * ff * (q_gev / (2.0 * m_n)).powi(2)
}
fn helm_ff_squared(qr: f64, a: u32) -> f64 {
if qr.abs() < 1e-10 {
return 1.0;
}
let s = 0.9;
let rn = effective_nuclear_radius_fm(a);
let j1_over_qr = (qr.sin() - qr * qr.cos()) / (qr * qr * qr);
let val = 3.0 * j1_over_qr * (-0.5 * (qr * s / rn).powi(2)).exp();
val * val
}
fn effective_nuclear_radius_fm(a: u32) -> f64 {
let c = 1.23 * (a as f64).powf(1.0 / 3.0) - 0.6;
let a_skin = 0.52;
let s = 0.9;
(c * c + 7.0 / 3.0 * PI * PI * a_skin * a_skin - 5.0 * s * s).sqrt()
}
pub fn spin_structure_proton_xe131() -> (f64, f64, f64) {
(0.5, 0.010, -0.227)
}
pub fn spin_structure_neutron_xe131() -> (f64, f64, f64) {
(0.5, -0.009, 0.339)
}
pub fn spin_structure_proton_ge73() -> (f64, f64, f64) {
(4.5, 0.030, 0.378)
}
pub fn spin_structure_fluorine19() -> (f64, f64, f64) {
(0.5, 0.477, -0.004)
}
pub fn diurnal_modulation_velocity_shift(
v_lab_km_s: f64,
v_earth_rot_km_s: f64,
hour: f64,
latitude_deg: f64,
) -> f64 {
let lat = latitude_deg * PI / 180.0;
let omega = 2.0 * PI * hour / 24.0;
let delta_v = v_earth_rot_km_s * lat.cos() * omega.cos();
v_lab_km_s + delta_v
}
pub fn annual_modulation_amplitude(
m_dm_gev: f64,
m_nucleus_gev: f64,
sigma_cm2: f64,
e_r_kev: f64,
v_0_km_s: f64,
v_earth_km_s: f64,
) -> f64 {
let mu = m_dm_gev * m_nucleus_gev / (m_dm_gev + m_nucleus_gev);
let e_r_gev = e_r_kev * 1e-6;
let v_min = (m_nucleus_gev * e_r_gev / (2.0 * mu * mu)).sqrt() * 3e8 / 1e3;
let v_0 = v_0_km_s;
let eta_plus = (-((v_min) / (v_0 + v_earth_km_s)).powi(2)).exp();
let eta_minus = (-((v_min) / (v_0 - v_earth_km_s)).powi(2)).exp();
sigma_cm2 * (eta_plus - eta_minus).abs() / 2.0
}
pub fn directional_recoil_rate(
cos_gamma: f64,
v_lab_km_s: f64,
v_0_km_s: f64,
v_min_km_s: f64,
) -> f64 {
let v_lab = v_lab_km_s * 1e3;
let v_0 = v_0_km_s * 1e3;
let v_min = v_min_km_s * 1e3;
let v_eff = v_lab * cos_gamma;
let exponent = -((v_min - v_eff).powi(2)) / (v_0 * v_0);
if exponent < -50.0 {
return 0.0;
}
exponent.exp() / (PI * v_0 * v_0).sqrt()
}
pub fn head_tail_asymmetry(v_lab_km_s: f64, v_0_km_s: f64) -> f64 {
let x = v_lab_km_s / v_0_km_s;
let forward = (-((1.0 - x) / 1.0).powi(2)).exp();
let backward = (-((1.0 + x) / 1.0).powi(2)).exp();
(forward - backward) / (forward + backward)
}
pub fn neutrino_floor_xe_cm2(m_dm_gev: f64) -> f64 {
if m_dm_gev < 6.0 {
return 1e-45 * (6.0 / m_dm_gev).powi(2);
}
if m_dm_gev < 10.0 {
return 4e-46;
}
if m_dm_gev < 100.0 {
return 2e-48 * (m_dm_gev / 10.0);
}
2e-47
}
pub fn neutrino_floor_ge_cm2(m_dm_gev: f64) -> f64 {
if m_dm_gev < 4.0 {
return 5e-44 * (4.0 / m_dm_gev).powi(2);
}
if m_dm_gev < 8.0 {
return 1e-44;
}
if m_dm_gev < 100.0 {
return 5e-47 * (m_dm_gev / 8.0);
}
6e-46
}
pub fn minimum_detectable_mass_gev(
e_threshold_kev: f64,
m_nucleus_gev: f64,
v_esc_km_s: f64,
) -> f64 {
let e_thr_gev = e_threshold_kev * 1e-6;
let v_max = v_esc_km_s * 1e3;
let c = 3e8;
m_nucleus_gev * e_thr_gev / (2.0 * v_max * v_max / (c * c) * m_nucleus_gev)
}