use crate::mbe_params::L_MAX;
pub const PHASE_KERNEL_D: usize = 19;
pub const GAMMA_PHASE: f64 = 0.44;
fn phase_kernel() -> &'static [f64; PHASE_KERNEL_D + 1] {
use std::sync::OnceLock;
static KERNEL: OnceLock<[f64; PHASE_KERNEL_D + 1]> = OnceLock::new();
KERNEL.get_or_init(|| {
let mut k = [0.0_f64; PHASE_KERNEL_D + 1];
for m in 1..=PHASE_KERNEL_D {
k[m] = GAMMA_PHASE * 2.0 / (core::f64::consts::PI * m as f64);
}
k
})
}
pub fn ambe_phase_regen(
m_bar: &[f32],
phi_regen: &mut [f64; L_MAX as usize + 1],
) {
let l_hat = m_bar.len();
debug_assert!(l_hat <= L_MAX as usize, "phase_regen: L̃ > L_MAX");
let mut b = [0.0_f64; L_MAX as usize + 1];
for l in 1..=l_hat {
let m = f64::from(m_bar[l - 1]);
b[l] = if m > 1e-10 {
m.log2()
} else {
(1e-10_f64).log2()
};
}
let kernel = phase_kernel();
for l in 1..=l_hat {
let mut acc = 0.0_f64;
for m in 1..=PHASE_KERNEL_D {
let b_plus = if l + m <= L_MAX as usize { b[l + m] } else { 0.0 };
let b_minus = if l > m { b[l - m] } else { 0.0 };
acc += kernel[m] * (b_plus - b_minus);
}
phi_regen[l] = acc;
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOLERANCE: f64 = 1e-12;
#[test]
fn phase_kernel_first_entry_is_zero() {
assert_eq!(phase_kernel()[0], 0.0);
}
#[test]
fn phase_kernel_first_nonzero_matches_patent_constant() {
let expected = 0.44 * 2.0 / core::f64::consts::PI;
assert!((phase_kernel()[1] - expected).abs() < TOLERANCE);
}
#[test]
fn phase_kernel_decay_is_one_over_m() {
let kernel = phase_kernel();
let target = kernel[1];
for m in 2..=PHASE_KERNEL_D {
let scaled = kernel[m] * m as f64;
assert!(
(scaled - target).abs() < TOLERANCE,
"m={m}: {scaled} vs {target}",
);
}
}
#[test]
fn regen_of_constant_log_spectrum_is_zero() {
let l_hat = 2 * PHASE_KERNEL_D + 1; let m_bar: Vec<f32> = vec![4.0; l_hat]; let mut phi = [0.0_f64; L_MAX as usize + 1];
ambe_phase_regen(&m_bar, &mut phi);
let l_center = PHASE_KERNEL_D + 1; assert!(
phi[l_center].abs() < TOLERANCE,
"center regen not zero: phi[{l_center}] = {}",
phi[l_center],
);
}
#[test]
fn regen_of_monotonic_ramp_is_positive() {
let l_hat = 2 * PHASE_KERNEL_D + 1;
let m_bar: Vec<f32> = (1..=l_hat).map(|l| (2.0_f32).powi(l as i32 - 20)).collect();
let mut phi = [0.0_f64; L_MAX as usize + 1];
ambe_phase_regen(&m_bar, &mut phi);
let l_center = PHASE_KERNEL_D + 1;
assert!(
phi[l_center] > 0.0,
"ramp regen not positive: phi[{l_center}] = {}",
phi[l_center],
);
}
#[test]
fn regen_antisymmetric_kernel_zero_on_symmetric_log_spectrum() {
let l_hat = 2 * PHASE_KERNEL_D + 1; let l_center = PHASE_KERNEL_D + 1; let m_bar: Vec<f32> = (1..=l_hat)
.map(|l| {
let d = (l as i32 - l_center as i32).abs() as f32;
(2.0_f32).powf(-d)
})
.collect();
let mut phi = [0.0_f64; L_MAX as usize + 1];
ambe_phase_regen(&m_bar, &mut phi);
assert!(
phi[l_center].abs() < TOLERANCE,
"symmetric regen not zero: phi[{l_center}] = {}",
phi[l_center],
);
}
#[test]
fn regen_zero_amplitude_treated_as_floor_not_neg_infinity() {
let m_bar: Vec<f32> = vec![0.0; 40];
let mut phi = [0.0_f64; L_MAX as usize + 1];
ambe_phase_regen(&m_bar, &mut phi);
for l in 1..=40 {
assert!(phi[l].is_finite(), "phi[{l}] = {} not finite", phi[l]);
}
}
#[test]
fn regen_writes_only_up_to_l_hat() {
let m_bar: Vec<f32> = vec![1.0; 10];
let mut phi = [1.0_f64; L_MAX as usize + 1];
ambe_phase_regen(&m_bar, &mut phi);
assert_eq!(phi[0], 1.0);
for l in 11..=L_MAX as usize {
assert_eq!(phi[l], 1.0, "phi[{l}] should be untouched");
}
}
}