#![cfg_attr(feature = "no_std", no_std)]
#![allow(non_snake_case)]
use core::f64::consts::PI;
const EV_SQ_KM_TO_GEV_OVER4: f64 = 1e-9 / 1.97327e-7 * 1e3 / 4.0;
const YE_RHO_E_TO_A: f64 = 1.52e-4;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct VacuumParameters {
pub s12sq: f64,
pub s13sq: f64,
pub s23sq: f64,
pub delta: f64,
pub Dmsq21: f64,
pub Dmsq31: f64,
pub L: f64,
pub E: f64,
pub antineutrino: bool,
}
impl VacuumParameters {
pub fn nufit52_no(l: f64, e: f64) -> Self {
Self {
s12sq: 0.307,
s13sq: 0.02203,
s23sq: 0.546,
delta: 1.36 * PI,
Dmsq21: 7.42e-5,
Dmsq31: 2.517e-3,
L: l,
E: e,
antineutrino: false,
}
}
pub fn nufit52_io(l: f64, e: f64) -> Self {
Self {
s12sq: 0.307,
s13sq: 0.02219,
s23sq: 0.539,
delta: 1.56 * PI,
Dmsq21: 7.42e-5,
Dmsq31: -2.498e-3,
L: l,
E: e,
antineutrino: false,
}
}
pub fn effective_delta(&self) -> f64 {
if self.antineutrino { -self.delta } else { self.delta }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MatterParameters {
pub s12sq: f64,
pub s13sq: f64,
pub s23sq: f64,
pub delta: f64,
pub Dmsq21: f64,
pub Dmsq31: f64,
pub L: f64,
pub E: f64,
pub rho: f64,
pub Ye: f64,
pub N_Newton: u8,
pub antineutrino: bool,
}
impl MatterParameters {
pub fn nufit52_no(l: f64, e: f64) -> Self {
Self {
s12sq: 0.307,
s13sq: 0.02203,
s23sq: 0.546,
delta: 1.36 * PI,
Dmsq21: 7.42e-5,
Dmsq31: 2.517e-3,
L: l,
E: e,
rho: 2.6,
Ye: 0.5,
N_Newton: 0,
antineutrino: false,
}
}
pub fn from_vacuum(vac: &VacuumParameters, rho: f64, ye: f64, n_newton: u8) -> Self {
Self {
s12sq: vac.s12sq,
s13sq: vac.s13sq,
s23sq: vac.s23sq,
delta: vac.delta,
Dmsq21: vac.Dmsq21,
Dmsq31: vac.Dmsq31,
L: vac.L,
E: vac.E,
rho,
Ye: ye,
N_Newton: n_newton,
antineutrino: vac.antineutrino,
}
}
pub fn effective_delta(&self) -> f64 {
if self.antineutrino { -self.delta } else { self.delta }
}
pub fn matter_sign(&self) -> f64 {
if self.antineutrino { -1.0 } else { 1.0 }
}
}
pub type ProbabilityMatrix = [[f64; 3]; 3];
pub fn probability_vacuum_lbl(parameters: &VacuumParameters) -> ProbabilityMatrix {
let VacuumParameters {
s12sq,
s13sq,
s23sq,
delta: _,
Dmsq21,
Dmsq31,
L,
E,
antineutrino: _,
} = *parameters;
let effective_delta = parameters.effective_delta();
let c13sq = 1.0 - s13sq;
let Ue3sq = s13sq;
let Ue2sq = c13sq * s12sq;
let Um3sq = c13sq * s23sq;
let Ut2sq = s13sq * s12sq * s23sq;
let Um2sq = (1.0 - s12sq) * (1.0 - s23sq);
let Jrr = (Um2sq * Ut2sq).sqrt();
let sind = effective_delta.sin();
let cosd = effective_delta.cos();
let Um2sq = Um2sq + Ut2sq - 2.0 * Jrr * cosd;
let Jvac = 8.0 * Jrr * c13sq * sind;
let Ue1sq = 1.0 - Ue3sq - Ue2sq;
let Um1sq = 1.0 - Um3sq - Um2sq;
let Ut3sq = 1.0 - Um3sq - Ue3sq;
let Ut2sq = 1.0 - Um2sq - Ue2sq;
let Ut1sq = 1.0 - Um1sq - Ue1sq;
let Lover4E = EV_SQ_KM_TO_GEV_OVER4 * L / E;
let D21 = Dmsq21 * Lover4E;
let D31 = Dmsq31 * Lover4E;
let sinD21 = D21.sin();
let sinD31 = D31.sin();
let sinD32 = (D31 - D21).sin();
let triple_sin = sinD21 * sinD31 * sinD32;
let sinsqD21_2 = 2.0 * sinD21 * sinD21;
let sinsqD31_2 = 2.0 * sinD31 * sinD31;
let sinsqD32_2 = 2.0 * sinD32 * sinD32;
let Pme_CPC = (Ut3sq - Um2sq * Ue1sq - Um1sq * Ue2sq) * sinsqD21_2
+ (Ut2sq - Um3sq * Ue1sq - Um1sq * Ue3sq) * sinsqD31_2
+ (Ut1sq - Um3sq * Ue2sq - Um2sq * Ue3sq) * sinsqD32_2;
let Pme_CPV = -Jvac * triple_sin;
let Pmm = 1.0
- 2.0
* (Um2sq * Um1sq * sinsqD21_2
+ Um3sq * Um1sq * sinsqD31_2
+ Um3sq * Um2sq * sinsqD32_2);
let Pee = 1.0
- 2.0
* (Ue2sq * Ue1sq * sinsqD21_2
+ Ue3sq * Ue1sq * sinsqD31_2
+ Ue3sq * Ue2sq * sinsqD32_2);
let mut probs = [[0.0; 3]; 3];
probs[0][0] = Pee; probs[0][1] = Pme_CPC - Pme_CPV; probs[0][2] = 1.0 - Pee - probs[0][1];
probs[1][0] = Pme_CPC + Pme_CPV; probs[1][1] = Pmm; probs[1][2] = 1.0 - probs[1][0] - Pmm;
probs[2][0] = 1.0 - Pee - probs[1][0]; probs[2][1] = 1.0 - probs[0][1] - Pmm; probs[2][2] = 1.0 - probs[0][2] - probs[1][2];
probs
}
pub fn probability_matter_lbl(parameters: &MatterParameters) -> ProbabilityMatrix {
let MatterParameters {
s12sq,
s13sq,
s23sq,
delta: _,
Dmsq21,
Dmsq31,
L,
E,
rho,
Ye,
N_Newton,
antineutrino: _,
} = *parameters;
let effective_delta = parameters.effective_delta();
let matter_sign = parameters.matter_sign();
let c13sq = 1.0 - s13sq;
let Ue2sq = c13sq * s12sq;
let Ue3sq = s13sq;
let Um3sq = c13sq * s23sq;
let Ut2sq = s13sq * s12sq * s23sq;
let Um2sq = (1.0 - s12sq) * (1.0 - s23sq);
let Jrr = (Um2sq * Ut2sq).sqrt();
let sind = effective_delta.sin();
let cosd = effective_delta.cos();
let Um2sq = Um2sq + Ut2sq - 2.0 * Jrr * cosd;
let Jmatter = 8.0 * Jrr * c13sq * sind;
let Amatter = matter_sign * Ye * rho * E * YE_RHO_E_TO_A;
let Dmsqee = Dmsq31 - s12sq * Dmsq21;
let A_sum = Dmsq21 + Dmsq31; let See = A_sum - Dmsq21 * Ue2sq - Dmsq31 * Ue3sq;
let Tmm_base = Dmsq21 * Dmsq31;
let Tee = Tmm_base * (1.0 - Ue3sq - Ue2sq);
let C = Amatter * Tee;
let A = A_sum + Amatter;
let xmat = Amatter / Dmsqee;
let tmp = 1.0 - xmat;
let mut lambda3 = Dmsq31 + 0.5 * Dmsqee * (xmat - 1.0 + (tmp * tmp + 4.0 * s13sq * xmat).sqrt());
let B = Tmm_base + Amatter * See; for _ in 0..N_Newton {
lambda3 = (lambda3 * lambda3 * (lambda3 - A) + C)
/ (lambda3 * (2.0 * lambda3 - A) + B);
}
let tmp = A - lambda3;
let Dlambda21 = (tmp * tmp - 4.0 * C / lambda3).sqrt();
let lambda2 = 0.5 * (A - lambda3 + Dlambda21);
let Dlambda32 = lambda3 - lambda2;
let Dlambda31 = Dlambda32 + Dlambda21;
let PiDlambdaInv = 1.0 / (Dlambda31 * Dlambda32 * Dlambda21);
let Xp3 = PiDlambdaInv * Dlambda21;
let Xp2 = -PiDlambdaInv * Dlambda31;
let Ue3sq = (lambda3 * (lambda3 - See) + Tee) * Xp3;
let Ue2sq = (lambda2 * (lambda2 - See) + Tee) * Xp2;
let Smm = A - Dmsq21 * Um2sq - Dmsq31 * Um3sq;
let Tmm = Tmm_base * (1.0 - Um3sq - Um2sq) + Amatter * (See + Smm - A_sum);
let Um3sq = (lambda3 * (lambda3 - Smm) + Tmm) * Xp3;
let Um2sq = (lambda2 * (lambda2 - Smm) + Tmm) * Xp2;
let Jmatter = Jmatter * Dmsq21 * Dmsq31 * (Dmsq31 - Dmsq21) * PiDlambdaInv;
let Ue1sq = 1.0 - Ue3sq - Ue2sq;
let Um1sq = 1.0 - Um3sq - Um2sq;
let Ut3sq = 1.0 - Um3sq - Ue3sq;
let Ut2sq = 1.0 - Um2sq - Ue2sq;
let Ut1sq = 1.0 - Um1sq - Ue1sq;
let Lover4E = EV_SQ_KM_TO_GEV_OVER4 * L / E;
let D21 = Dlambda21 * Lover4E;
let D32 = Dlambda32 * Lover4E;
let sinD21 = D21.sin();
let sinD31 = (D32 + D21).sin();
let sinD32 = D32.sin();
let triple_sin = sinD21 * sinD31 * sinD32;
let sinsqD21_2 = 2.0 * sinD21 * sinD21;
let sinsqD31_2 = 2.0 * sinD31 * sinD31;
let sinsqD32_2 = 2.0 * sinD32 * sinD32;
let Pme_CPC = (Ut3sq - Um2sq * Ue1sq - Um1sq * Ue2sq) * sinsqD21_2
+ (Ut2sq - Um3sq * Ue1sq - Um1sq * Ue3sq) * sinsqD31_2
+ (Ut1sq - Um3sq * Ue2sq - Um2sq * Ue3sq) * sinsqD32_2;
let Pme_CPV = -Jmatter * triple_sin;
let Pmm = 1.0
- 2.0
* (Um2sq * Um1sq * sinsqD21_2
+ Um3sq * Um1sq * sinsqD31_2
+ Um3sq * Um2sq * sinsqD32_2);
let Pee = 1.0
- 2.0
* (Ue2sq * Ue1sq * sinsqD21_2
+ Ue3sq * Ue1sq * sinsqD31_2
+ Ue3sq * Ue2sq * sinsqD32_2);
let mut probs = [[0.0; 3]; 3];
probs[0][0] = Pee;
probs[0][1] = Pme_CPC - Pme_CPV;
probs[0][2] = 1.0 - Pee - probs[0][1];
probs[1][0] = Pme_CPC + Pme_CPV;
probs[1][1] = Pmm;
probs[1][2] = 1.0 - probs[1][0] - Pmm;
probs[2][0] = 1.0 - Pee - probs[1][0];
probs[2][1] = 1.0 - probs[0][1] - Pmm;
probs[2][2] = 1.0 - probs[0][2] - probs[1][2];
probs
}
#[derive(Debug, Clone, Copy)]
pub struct VacuumBatch {
Ue1sq: f64,
Ue2sq: f64,
Ue3sq: f64,
Um1sq: f64,
Um2sq: f64,
Um3sq: f64,
Ut1sq: f64,
Ut2sq: f64,
Ut3sq: f64,
Jvac: f64,
Dmsq21: f64,
Dmsq31: f64,
}
impl VacuumBatch {
pub fn new(
s12sq: f64,
s13sq: f64,
s23sq: f64,
delta: f64,
Dmsq21: f64,
Dmsq31: f64,
antineutrino: bool,
) -> Self {
let c13sq = 1.0 - s13sq;
let effective_delta = if antineutrino { -delta } else { delta };
let Ue3sq = s13sq;
let Ue2sq = c13sq * s12sq;
let Um3sq = c13sq * s23sq;
let Ut2sq_temp = s13sq * s12sq * s23sq;
let Um2sq_temp = (1.0 - s12sq) * (1.0 - s23sq);
let Jrr = (Um2sq_temp * Ut2sq_temp).sqrt();
let sind = effective_delta.sin();
let cosd = effective_delta.cos();
let Um2sq = Um2sq_temp + Ut2sq_temp - 2.0 * Jrr * cosd;
let Jvac = 8.0 * Jrr * c13sq * sind;
let Ue1sq = 1.0 - Ue3sq - Ue2sq;
let Um1sq = 1.0 - Um3sq - Um2sq;
let Ut3sq = 1.0 - Um3sq - Ue3sq;
let Ut2sq = 1.0 - Um2sq - Ue2sq;
let Ut1sq = 1.0 - Um1sq - Ue1sq;
Self {
Ue1sq, Ue2sq, Ue3sq,
Um1sq, Um2sq, Um3sq,
Ut1sq, Ut2sq, Ut3sq,
Jvac,
Dmsq21, Dmsq31,
}
}
pub fn from_params(params: &VacuumParameters) -> Self {
Self::new(
params.s12sq,
params.s13sq,
params.s23sq,
params.delta,
params.Dmsq21,
params.Dmsq31,
params.antineutrino,
)
}
pub fn nufit52_no() -> Self {
Self::new(0.307, 0.02203, 0.546, 1.36 * PI, 7.42e-5, 2.517e-3, false)
}
pub fn nufit52_io() -> Self {
Self::new(0.307, 0.02219, 0.539, 1.56 * PI, 7.42e-5, -2.498e-3, false)
}
#[inline]
pub fn probability_at(&self, l: f64, e: f64) -> ProbabilityMatrix {
let Lover4E = EV_SQ_KM_TO_GEV_OVER4 * l / e;
let D21 = self.Dmsq21 * Lover4E;
let D31 = self.Dmsq31 * Lover4E;
let sinD21 = D21.sin();
let sinD31 = D31.sin();
let sinD32 = (D31 - D21).sin();
let triple_sin = sinD21 * sinD31 * sinD32;
let sinsqD21_2 = 2.0 * sinD21 * sinD21;
let sinsqD31_2 = 2.0 * sinD31 * sinD31;
let sinsqD32_2 = 2.0 * sinD32 * sinD32;
let Pme_CPC = (self.Ut3sq - self.Um2sq * self.Ue1sq - self.Um1sq * self.Ue2sq) * sinsqD21_2
+ (self.Ut2sq - self.Um3sq * self.Ue1sq - self.Um1sq * self.Ue3sq) * sinsqD31_2
+ (self.Ut1sq - self.Um3sq * self.Ue2sq - self.Um2sq * self.Ue3sq) * sinsqD32_2;
let Pme_CPV = -self.Jvac * triple_sin;
let Pmm = 1.0
- 2.0
* (self.Um2sq * self.Um1sq * sinsqD21_2
+ self.Um3sq * self.Um1sq * sinsqD31_2
+ self.Um3sq * self.Um2sq * sinsqD32_2);
let Pee = 1.0
- 2.0
* (self.Ue2sq * self.Ue1sq * sinsqD21_2
+ self.Ue3sq * self.Ue1sq * sinsqD31_2
+ self.Ue3sq * self.Ue2sq * sinsqD32_2);
let mut probs = [[0.0; 3]; 3];
probs[0][0] = Pee;
probs[0][1] = Pme_CPC - Pme_CPV;
probs[0][2] = 1.0 - Pee - probs[0][1];
probs[1][0] = Pme_CPC + Pme_CPV;
probs[1][1] = Pmm;
probs[1][2] = 1.0 - probs[1][0] - Pmm;
probs[2][0] = 1.0 - Pee - probs[1][0];
probs[2][1] = 1.0 - probs[0][1] - Pmm;
probs[2][2] = 1.0 - probs[0][2] - probs[1][2];
probs
}
#[cfg(not(feature = "no_std"))]
pub fn spectrum(&self, l: f64, energies: &[f64], initial_flavor: usize) -> Vec<(f64, f64, f64, f64)> {
energies.iter().map(|&e| {
let probs = self.probability_at(l, e);
let row = initial_flavor.min(2);
(e, probs[row][0], probs[row][1], probs[row][2])
}).collect()
}
#[cfg(not(feature = "no_std"))]
pub fn baseline_scan(&self, e: f64, baselines: &[f64], initial_flavor: usize) -> Vec<(f64, f64, f64, f64)> {
baselines.iter().map(|&l| {
let probs = self.probability_at(l, e);
let row = initial_flavor.min(2);
(l, probs[row][0], probs[row][1], probs[row][2])
}).collect()
}
}
pub fn normalize_probabilities(probs: &mut ProbabilityMatrix) {
for row in probs.iter_mut() {
for p in row.iter_mut() {
*p = p.clamp(0.0, 1.0);
}
let sum: f64 = row.iter().sum();
if sum > 0.0 && (sum - 1.0).abs() > 1e-10 {
for p in row.iter_mut() {
*p /= sum;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1e-10;
#[test]
fn test_vacuum_unitarity() {
let params = VacuumParameters::nufit52_no(1000.0, 2.0);
let probs = probability_vacuum_lbl(¶ms);
for (i, row) in probs.iter().enumerate() {
let sum: f64 = row.iter().sum();
assert!(
(sum - 1.0).abs() < EPSILON,
"Row {} sum = {}, expected 1.0",
i,
sum
);
}
}
#[test]
fn test_vacuum_zero_distance() {
let params = VacuumParameters::nufit52_no(0.0, 2.0);
let probs = probability_vacuum_lbl(¶ms);
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(probs[i][j] - expected).abs() < EPSILON,
"probs[{}][{}] = {}, expected {}",
i,
j,
probs[i][j],
expected
);
}
}
}
#[test]
fn test_matter_unitarity() {
let params = MatterParameters::nufit52_no(1300.0, 2.5);
let mut probs = probability_matter_lbl(¶ms);
normalize_probabilities(&mut probs);
for (i, row) in probs.iter().enumerate() {
let sum: f64 = row.iter().sum();
assert!(
(sum - 1.0).abs() < 1e-6,
"Row {} sum = {}, expected 1.0",
i,
sum
);
}
}
#[test]
fn test_matter_enhances_appearance() {
let vac = VacuumParameters::nufit52_no(1300.0, 2.5);
let mat = MatterParameters::nufit52_no(1300.0, 2.5);
let probs_vac = probability_vacuum_lbl(&vac);
let probs_mat = probability_matter_lbl(&mat);
assert!(
(probs_mat[1][0] - probs_vac[1][0]).abs() > 0.001,
"Matter effect should modify P(νμ → νe)"
);
}
#[test]
fn test_cp_conjugate() {
let mut params_pos = VacuumParameters::nufit52_no(1000.0, 2.0);
params_pos.delta = 1.36 * PI;
let mut params_neg = params_pos;
params_neg.delta = -1.36 * PI;
let probs_pos = probability_vacuum_lbl(¶ms_pos);
let probs_neg = probability_vacuum_lbl(¶ms_neg);
assert!(
(probs_pos[1][0] - probs_neg[1][0]).abs() > 1e-6,
"CP conjugate probabilities should differ"
);
}
#[test]
fn test_batch_vacuum_consistency() {
let batch = VacuumBatch::new(0.307, 0.0218, 0.545, 1.36 * PI, 7.42e-5, 2.517e-3, false);
let energies = [0.5, 1.0, 2.0, 3.0, 5.0];
let l = 295.0;
for &e in &energies {
let probs_batch = batch.probability_at(l, e);
let probs_single = probability_vacuum_lbl(&VacuumParameters {
s12sq: 0.307,
s13sq: 0.0218,
s23sq: 0.545,
delta: 1.36 * PI,
Dmsq21: 7.42e-5,
Dmsq31: 2.517e-3,
L: l,
E: e,
antineutrino: false,
});
for i in 0..3 {
for j in 0..3 {
assert!(
(probs_batch[i][j] - probs_single[i][j]).abs() < EPSILON,
"Batch and single differ at E={}: [{},{}] {} vs {}",
e, i, j, probs_batch[i][j], probs_single[i][j]
);
}
}
}
}
#[test]
fn test_antineutrino_vacuum() {
let l = 1300.0;
let e = 2.5;
let nu_params = VacuumParameters::nufit52_no(l, e);
let mut nubar_params = nu_params;
nubar_params.antineutrino = true;
let nu_probs = probability_vacuum_lbl(&nu_params);
let nubar_probs = probability_vacuum_lbl(&nubar_params);
assert!(
(nu_probs[0][0] - nubar_probs[0][0]).abs() < EPSILON,
"Pee should be the same for ν and ν̄"
);
assert!(
(nu_probs[1][1] - nubar_probs[1][1]).abs() < EPSILON,
"Pmm should be the same for ν and ν̄"
);
assert!(
(nu_probs[1][0] - nubar_probs[0][1]).abs() < EPSILON,
"CPT: P(νμ→νe) should equal P(ν̄e→ν̄μ)"
);
assert!(
(nu_probs[0][1] - nubar_probs[1][0]).abs() < EPSILON,
"CPT: P(νe→νμ) should equal P(ν̄μ→ν̄e)"
);
}
#[test]
fn test_antineutrino_matter() {
let l = 1300.0;
let e = 2.5;
let nu_params = MatterParameters::nufit52_no(l, e);
let mut nubar_params = nu_params;
nubar_params.antineutrino = true;
let nu_probs = probability_matter_lbl(&nu_params);
let nubar_probs = probability_matter_lbl(&nubar_params);
let diff = (nu_probs[1][0] - nubar_probs[1][0]).abs();
assert!(
diff > 0.01,
"Matter effect should differ for ν and ν̄: diff = {}",
diff
);
}
}