use core::f64::consts::PI;
use metrology::{ALPHA, C, DIRAC_FLUX, E_CHARGE, H, HBAR, M_E, MU_0};
pub fn dirac_charge(n: i32) -> f64 {
f64::from(n) * DIRAC_FLUX / MU_0
}
pub fn dirac_field(r: f64, n: i32) -> f64 {
f64::from(n) * HBAR / (2.0 * E_CHARGE * r * r)
}
pub fn dirac_radius_for_field(b: f64, n: i32) -> f64 {
(f64::from(n) * HBAR / (2.0 * E_CHARGE * b)).sqrt()
}
pub fn dirac_vector_potential(r: [f64; 3], n_hat: [f64; 3], n: i32) -> [f64; 3] {
let len = (r[0] * r[0] + r[1] * r[1] + r[2] * r[2]).sqrt();
let along = r[0] * n_hat[0] + r[1] * n_hat[1] + r[2] * n_hat[2];
let k = MU_0 * dirac_charge(n) / (4.0 * PI * len * (len + along));
[
k * (n_hat[1] * r[2] - n_hat[2] * r[1]),
k * (n_hat[2] * r[0] - n_hat[0] * r[2]),
k * (n_hat[0] * r[1] - n_hat[1] * r[0]),
]
}
pub fn monopole_field_energy(r0: f64, n: i32) -> f64 {
let g = dirac_charge(n);
MU_0 * g * g / (8.0 * PI * r0)
}
pub fn monopole_energy_gain(n: i32, b: f64, l: f64) -> f64 {
dirac_charge(n) * b * l
}
pub fn flux_from_events(events: f64, t: f64, area: f64) -> f64 {
events / (t * area * 2.0 * PI)
}
pub fn spin_ice_charge(moment: f64, a: f64) -> f64 {
2.0 * moment / (1.5f64.sqrt() * a)
}
pub fn synthetic_monopole_circulation(m_atom: f64) -> f64 {
2.0 * H / m_atom
}
pub fn skyrmion_emergent_field(area: f64) -> f64 {
DIRAC_FLUX / area
}
pub fn parker_bound(m_gev: f64) -> f64 {
1e-15 * (m_gev / 1e17).max(1.0) * 1e4
}
pub fn parker_seed_bound(m_gev: f64) -> f64 {
(m_gev / 1e17 + 3e-6) * 1e-16 * 1e4
}
pub fn catalysis_cross_section(sigma0: f64, beta: f64, low_beta: Option<(f64, f64)>) -> f64 {
let sigma = sigma0 / beta;
match low_beta {
Some((beta0, gamma)) if beta < beta0 => sigma * (beta / beta0).powf(gamma),
_ => sigma,
}
}
pub fn catalysis_length(sigma: f64, n: f64) -> f64 {
1.0 / (sigma * n)
}
pub fn superk_catalysis_limit(beta: f64, sigma0_mb: f64, f_pi: f64) -> Option<f64> {
(beta <= 1e-3).then(|| 6.3e-24 * (beta / 1e-3).powi(2) / (sigma0_mb * f_pi / 0.5) * 1e4)
}
pub fn dyonic_level(z: f64, n_radial: u32, j: f64, m: f64) -> Option<f64> {
let rydberg = ALPHA * ALPHA * M_E * C * C / 2.0;
(j >= m.abs()).then(|| {
let d = f64::from(n_radial) + 0.5 + ((j + 0.5).powi(2) - m * m).sqrt();
-z * z * rydberg / (d * d)
})
}
pub fn gamma_hat(z: f64, a: f64, mu: f64) -> f64 {
a / z * mu
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Binding {
Binds { j: f64 },
No,
Undetermined,
}
pub fn monopole_binding(z: f64, a: f64, spin: f64, mu: f64, n: i32) -> Binding {
let m = f64::from(n).abs() / 2.0;
if z == 0.0 {
return if mu.abs() > 3.0 / (4.0 * m) { Binding::Binds { j: 0.5 } } else { Binding::No };
}
let l = m * z;
if spin == 0.0 || mu == 0.0 {
return Binding::No; }
if l < spin {
return Binding::Undetermined;
}
let g = gamma_hat(z, a, mu);
if g > 1.0 + 1.0 / (4.0 * l) {
return Binding::Binds { j: l - spin };
}
if spin == 0.5 {
let j = l + 0.5;
return if (g - 1.0).abs() > (j * j + j - l * l) / l { Binding::Binds { j } } else { Binding::No };
}
let (plus, minus) = os_lambdas(g, spin, l);
if plus > 0.25 || minus > 0.25 { Binding::Binds { j: l - spin + 1.0 } } else { Binding::Undetermined }
}
fn os_lambdas(g: f64, spin: f64, l: f64) -> (f64, f64) {
let x = g * l / spin;
let root = ((l + 1.0).powi(2) + (2.0 * spin - 1.0 - l) * x + x * x / 4.0).sqrt();
let mid = (spin - 0.5) * x - 2.0 * l - 1.0;
(mid + root, mid - root)
}
fn fast_corrections(n: i32) -> Option<(f64, f64)> {
let k = if n.abs() == 1 { 0.406 } else { 0.346 };
let b = match n.abs() {
1 => 0.248,
2 => 0.672,
3 => 1.022,
6 => 1.685,
_ => return None,
};
Some((k, b))
}
fn fermi_speed(n_e: f64) -> f64 {
HBAR / M_E * (3.0 * PI * PI * n_e).cbrt()
}
pub fn monopole_dedx_fast(n: i32, beta: f64, n_e: f64, i: f64, delta: f64) -> Option<f64> {
let gamma = 1.0 / (1.0 - beta * beta).sqrt();
let (k, b) = fast_corrections(n)?;
let mec2 = M_E * C * C;
let hbar_c = HBAR * C;
(beta > 0.05 && gamma <= 100.0).then(|| {
let log = (2.0 * mec2 * (beta * gamma).powi(2) / i).ln();
PI * n_e * f64::from(n * n) * hbar_c * hbar_c / mec2 * (log + k / 2.0 - delta / 2.0 - 0.5 - b)
})
}
pub fn monopole_dedx_slow(n: i32, beta: f64, n_e: f64) -> Option<f64> {
let v_f = fermi_speed(n_e);
let log = (2.0 * v_f / (ALPHA * C)).ln() - 0.5;
let hbar_c = HBAR * C;
((1e-3..=1e-2).contains(&beta) && log > 0.0)
.then(|| 1.37 * PI / 2.0 * n_e * f64::from(n * n) * hbar_c * hbar_c * beta / (M_E * C * v_f) * log)
}
#[cfg(test)]
mod tests {
use super::*;
use metrology::{AMU, EV, GEV, MEV, MU_B, N_A, PARSEC};
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
const FM: f64 = 1e-15;
#[test]
fn the_dirac_charge_and_its_flux() {
let g = dirac_charge(1);
assert!(close(g, 3.291e-9, 2e-4) && close(dirac_charge(-2), -2.0 * g, 1e-15));
assert!(close(g / (E_CHARGE * C), 1.0 / (2.0 * ALPHA), 1e-9) && close(g / (E_CHARGE * C), 68.52, 1e-4));
assert!(close(E_CHARGE * g, H / MU_0, 1e-15));
for r in [1e-15, 5.291_772_109e-11, 1.0] {
assert!(close(4.0 * PI * r * r * dirac_field(r, 1), DIRAC_FLUX, 1e-12));
assert!(close(dirac_field(r, 1), MU_0 * g / (4.0 * PI * r * r), 1e-12));
}
assert!(close(dirac_field(1e-15, 1), 3.291e14, 2e-4));
assert!(close(dirac_field(5.291_772_109e-11, 1), 1.1752e5, 2e-4));
}
#[test]
fn the_radius_where_a_pole_makes_a_given_field() {
assert!(close(dirac_radius_for_field(1e15, 1) / FM, 0.5737, 1e-3));
assert!(close(dirac_radius_for_field(280e-4, 1) / 1e-9, 108.5, 2e-3));
assert!(close(dirac_radius_for_field(460e-4, 1) / 1e-9, 84.65, 2e-3));
let r = dirac_radius_for_field(7.0, 3);
assert!(close(dirac_field(r, 3), 7.0, 1e-14));
}
#[test]
fn the_vector_potential_curls_to_the_radial_field() {
let curl = |p: [f64; 3], n_hat: [f64; 3]| {
let d = 1e-6;
let partial = |i: usize, j: usize| {
let (mut a, mut b) = (p, p);
a[j] += d;
b[j] -= d;
(dirac_vector_potential(a, n_hat, 1)[i] - dirac_vector_potential(b, n_hat, 1)[i]) / (2.0 * d)
};
[partial(2, 1) - partial(1, 2), partial(0, 2) - partial(2, 0), partial(1, 0) - partial(0, 1)]
};
let n_hats = [[0.0, 0.0, 1.0], [0.6, 0.0, 0.8], [0.0, -1.0, 0.0]];
let points: [[f64; 3]; 3] = [[0.3, -0.4, 0.5], [-0.2, 0.7, 0.1], [1.1, 0.2, -0.3]];
for (p, n_hat) in points.into_iter().zip(n_hats) {
let r = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt();
let b = curl(p, n_hat);
for i in 0..3 {
assert!(close(b[i], dirac_field(r, 1) * p[i] / r, 1e-5), "{p:?} {i}");
}
}
assert!(close(curl([0.3, -0.4, 0.5], [0.0, 0.0, 1.0])[2], 4.654e-16, 1e-3));
assert!(!dirac_vector_potential([0.0, 0.0, -2.0], [0.0, 0.0, 1.0], 1)[0].is_finite());
}
#[test]
fn field_energy_and_the_monopole_mass() {
let k = monopole_field_energy(FM, 1);
assert!(close(k, HBAR * C / (8.0 * ALPHA * FM), 1e-9) && close(k / MEV, 3380.1, 1e-4));
assert!(close(monopole_field_energy(1.0, 1) / (938.272_088_16 * MEV) / FM, 3.6025, 1e-3));
assert!(close(monopole_field_energy(1.0, 1), 5.416e-25, 1e-3));
assert!(close(1.0 / (8.0 * ALPHA), 17.129, 1e-4));
assert!(close(monopole_field_energy(FM, 2), 4.0 * k, 1e-14));
}
#[test]
fn energy_gained_in_a_field() {
assert!(close(monopole_energy_gain(1, 1e-4, 0.1) / (1e3 * EV), 205.4, 1e-3));
assert!(close(monopole_energy_gain(1, 3e-10, 0.5257 * 1e3 * PARSEC), 1e11 * GEV, 2e-3));
assert!(close(monopole_energy_gain(1, 120e-4, 1.0), 3.949e-11, 1e-3));
}
#[test]
fn cabrera_1982() {
let f = flux_from_events(1.0, 151.0 * 86_400.0, 20e-4);
assert!(close(f * 1e-4, 6.10e-10, 2e-3));
}
#[test]
fn emergent_and_synthetic_monopoles() {
let q = spin_ice_charge(10.0 * MU_B, 3.54e-10);
assert!(close(q, 4.278e-13, 1e-3) && close(q / (MU_B / 1e-10), 4.6, 5e-3));
let r_e = ALPHA * HBAR / (M_E * C);
assert!(close(q / dirac_charge(1), 20.0 * r_e / (1.5f64.sqrt() * 3.54e-10), 1e-6));
assert!(close(dirac_charge(1) / q, 7692.0, 1e-3));
assert!(close(synthetic_monopole_circulation(86.909_180_529 * AMU), 9.183e-9, 1e-3));
assert!(close(synthetic_monopole_circulation(1.0), 4.0 * PI * HBAR, 1e-15));
assert!(close(skyrmion_emergent_field(1.6543e-15), 2.5, 1e-4));
}
#[test]
fn flux_bounds() {
for (m, parker, seed) in [(1e15, 1e-15, 1.0003e-18), (1e17, 1e-15, 1.000_003e-16), (1e19, 1e-13, 1.000_000_03e-14)] {
assert!(close(parker_bound(m) * 1e-4, parker, 1e-12) && close(parker_seed_bound(m) * 1e-4, seed, 1e-12), "{m}");
}
assert!(close(parker_seed_bound(0.0) * 1e-4, 3e-22, 1e-12));
assert!(close(flux_from_events(1.0, 151.0 * 86_400.0, 20e-4) / parker_bound(1e16), 6.1e5, 2e-3));
}
#[test]
fn catalysis() {
assert!(close(catalysis_cross_section(1.7e-29, 1e-3, None), 1.7e-26, 1e-12));
assert!(close(catalysis_cross_section(1.7e-29, 1e-3, Some((1e-4, 2.0))), 1.7e-26, 1e-12));
assert!(close(catalysis_cross_section(1.0, 5e-4, Some((1e-3, 1.0))), 1.0 / 5e-4 * 0.5, 1e-12));
let n_h = 2.0 * 917.0 / 0.018_015 * N_A;
assert!(close(n_h, 6.131e28, 1e-3) && close(catalysis_length(1.7e-27, n_h), 0.959e-2, 1e-3));
let f = |b: f64, s: f64| superk_catalysis_limit(b, s, 0.5).unwrap() * 1e-4;
assert!(close(f(1e-3, 1.0), 6.3e-24, 1e-12) && close(f(1e-4, 1.0), 6.3e-26, 1e-12) && close(f(1e-3, 10.0), 6.3e-25, 1e-12));
assert!((1e8..1e9).contains(&(parker_bound(1e17) / superk_catalysis_limit(1e-3, 1.0, 0.5).unwrap())));
assert!(superk_catalysis_limit(2e-3, 1.0, 0.5).is_none());
}
#[test]
fn hydrogen_with_a_dirac_pole() {
let ev = |e: Option<f64>| e.unwrap() / EV;
assert!(close(ev(dyonic_level(1.0, 0, 0.0, 0.0)), -13.605_693, 1e-7));
assert!(close(ev(dyonic_level(1.0, 0, 0.5, 0.5)), -7.291_27, 1e-5));
assert!(close(dyonic_level(1.0, 0, 0.5, 0.5).unwrap() / dyonic_level(1.0, 0, 0.0, 0.0).unwrap(), 0.535_898, 1e-5));
assert!(dyonic_level(1.0, 0, 0.0, 0.5).is_none());
}
#[test]
fn which_nuclei_bind() {
for (z, a, mu, g) in [(1.0, 1.0, 2.792_847, 2.79), (2.0, 3.0, -2.127_625, -3.20), (4.0, 9.0, -1.177_43, -2.66), (13.0, 27.0, 3.6407, 7.56), (48.0, 113.0, -0.622_26, -1.46), (1.0, 2.0, 0.857_438, 1.71)] {
assert!(close(gamma_hat(z, a, mu), g, 6e-3), "{z} {a}");
}
for (z, a, s, mu, want) in [(13.0, 27.0, 2.5, 3.6414, [42.659, 35.513, 15.141]), (11.0, 23.0, 1.5, 2.2175, [20.001, 12.417, -2.416])] {
let (g, l) = (gamma_hat(z, a, mu), z / 2.0);
let (plus, minus) = os_lambdas(g, s, l);
assert!(close(g * l - l, want[0], 1e-4) && close(plus, want[1], 1e-4) && close(minus, want[2], 1e-3), "{z}");
}
for (g, s, l, plus) in [(1.556_683, 1.5, 2.0, true), (8.943_317, 1.5, 2.0, false), (1.373_638, 2.5, 6.5, true), (4.216_105, 2.5, 6.5, false), (2.183_824, 1.0, 2.0, true)] {
let (p, m) = os_lambdas(g, s, l);
assert!(((if plus { p } else { m }) - 0.25).abs() < 5e-5, "{g}");
}
use Binding::*;
let cases = [
((1.0, 1.0, 0.5, 2.792_847, 1), Binds { j: 0.0 }),
((2.0, 3.0, 0.5, -2.127_625, 1), Binds { j: 1.5 }),
((48.0, 113.0, 0.5, -0.622_26, 1), Binds { j: 24.5 }),
((13.0, 27.0, 2.5, 3.6407, 1), Binds { j: 4.0 }),
((1.0, 2.0, 1.0, 0.857_438, 2), Binds { j: 0.0 }),
((0.0, 1.0, 0.5, -1.913_043, 1), Binds { j: 0.5 }),
((26.0, 57.0, 0.5, 0.0902, 1), No),
((8.0, 16.0, 0.0, 0.0, 1), No),
((4.0, 9.0, 1.5, -1.177_43, 1), Undetermined),
((8.0, 17.0, 2.5, -1.893_543, 1), Undetermined),
];
for ((z, a, s, mu, n), want) in cases {
assert_eq!(monopole_binding(z, a, s, mu, n), want, "{z} {a}");
}
}
#[test]
fn stopping_power() {
let n_e = |rho: f64| rho * N_A * 14.0 / 0.028_085_5;
assert!(close(n_e(2330.0), 6.994_44e29, 1e-5) && close(fermi_speed(n_e(4300.0)) * 100.0, 3.9e8, 1e-3));
let slow = monopole_dedx_slow(1, 1e-3, n_e(2330.0)).unwrap();
assert!(close(slow, 9.8239e-10, 1e-4) && close(slow / 1.37 / MEV * 1e-2, 45.0, 6e-3));
assert!(monopole_dedx_slow(1, 0.1, n_e(2330.0)).is_none() && monopole_dedx_slow(1, 5e-3, 1e29).is_none());
assert!(close(monopole_dedx_slow(2, 5e-3, n_e(2330.0)).unwrap(), 4.0 * 5.0 * slow, 1e-12));
let fast = monopole_dedx_fast(1, 0.5, n_e(2330.0), 173.0 * EV, 0.0).unwrap();
assert!(close(fast / GEV * 1e-2, 11.788, 1e-4));
let mec2 = M_E * C * C;
let bracket = (2.0 * mec2 * 0.25 / 0.75 / (173.0 * EV)).ln() + 0.406 / 2.0 - 0.5 - 0.248;
let bethe = 0.307_075 * 14.0 / 28.0855 * 2.33 / (2.0 * ALPHA).powi(2) * MEV * 1e2;
assert!(close(fast / bracket, bethe, 1e-5));
assert!(monopole_dedx_fast(4, 0.5, 1e29, 173.0 * EV, 0.0).is_none() && monopole_dedx_fast(1, 0.01, 1e29, 173.0 * EV, 0.0).is_none());
}
}