use std::f64::consts::PI;
use crate::constants::{HBAR, KB};
use crate::error::{self, Result};
pub fn magnon_distribution(omega: f64, temperature: f64, chemical_potential: f64) -> Result<f64> {
if temperature <= 0.0 {
return Err(error::invalid_param(
"temperature",
"must be positive for Bose-Einstein distribution",
));
}
if omega < 0.0 {
return Err(error::invalid_param("omega", "must be non-negative"));
}
let energy = HBAR * omega;
let exponent = (energy - chemical_potential) / (KB * temperature);
if exponent <= 0.0 {
return Err(error::numerical_error(
"chemical potential exceeds or equals magnon energy; \
Bose-Einstein distribution diverges",
));
}
if exponent > 700.0 {
return Ok((-exponent).exp());
}
let n = 1.0 / (exponent.exp() - 1.0);
Ok(n)
}
pub fn classical_distribution(
omega: f64,
temperature: f64,
chemical_potential: f64,
) -> Result<f64> {
if temperature <= 0.0 {
return Err(error::invalid_param("temperature", "must be positive"));
}
let energy = HBAR * omega;
let exponent = (energy - chemical_potential) / (KB * temperature);
Ok((-exponent).exp())
}
pub fn critical_density(effective_mass: f64, temperature: f64) -> Result<f64> {
if effective_mass <= 0.0 {
return Err(error::invalid_param("effective_mass", "must be positive"));
}
if temperature <= 0.0 {
return Err(error::invalid_param(
"temperature",
"must be positive for BEC critical density",
));
}
let zeta_3_2: f64 = 2.612_375_348_685_488;
let thermal_factor = effective_mass * KB * temperature / (2.0 * PI * HBAR * HBAR);
let n_c = zeta_3_2 * thermal_factor.powf(1.5);
Ok(n_c)
}
pub fn bec_temperature(magnon_density: f64, effective_mass: f64) -> Result<f64> {
if magnon_density <= 0.0 {
return Err(error::invalid_param("magnon_density", "must be positive"));
}
if effective_mass <= 0.0 {
return Err(error::invalid_param("effective_mass", "must be positive"));
}
let zeta_3_2: f64 = 2.612_375_348_685_488;
let prefactor = 2.0 * PI * HBAR * HBAR / (effective_mass * KB);
let density_factor = (magnon_density / zeta_3_2).powf(2.0 / 3.0);
Ok(prefactor * density_factor)
}
#[derive(Debug, Clone)]
pub struct MagnonCondensate {
pub temperature: f64,
pub magnon_density: f64,
pub chemical_potential: f64,
pub effective_mass: f64,
pub interaction_strength: f64,
pub condensate_fraction: f64,
}
impl MagnonCondensate {
pub fn new(
temperature: f64,
magnon_density: f64,
effective_mass: f64,
interaction_strength: f64,
) -> Result<Self> {
if temperature < 0.0 {
return Err(error::invalid_param("temperature", "must be non-negative"));
}
if magnon_density <= 0.0 {
return Err(error::invalid_param("magnon_density", "must be positive"));
}
if effective_mass <= 0.0 {
return Err(error::invalid_param("effective_mass", "must be positive"));
}
let t_bec = bec_temperature(magnon_density, effective_mass)?;
let condensate_fraction = if temperature < f64::EPSILON {
1.0
} else if temperature < t_bec {
1.0 - (temperature / t_bec).powf(1.5)
} else {
0.0
};
let chemical_potential = if temperature < t_bec {
0.0 } else {
let ratio = (t_bec / temperature).powf(1.5);
if ratio >= 1.0 {
0.0
} else {
KB * temperature * (1.0 - ratio).ln()
}
};
Ok(Self {
temperature,
magnon_density,
chemical_potential,
effective_mass,
interaction_strength,
condensate_fraction,
})
}
pub fn transition_temperature(&self) -> Result<f64> {
bec_temperature(self.magnon_density, self.effective_mass)
}
pub fn condensate_density(&self) -> f64 {
self.condensate_fraction * self.magnon_density
}
pub fn thermal_density(&self) -> f64 {
(1.0 - self.condensate_fraction) * self.magnon_density
}
pub fn healing_length(&self) -> Result<f64> {
let n0 = self.condensate_density();
if n0 <= 0.0 {
return Err(error::numerical_error(
"no condensate present; healing length is undefined",
));
}
if self.interaction_strength <= 0.0 {
return Err(error::invalid_param(
"interaction_strength",
"must be positive for healing length calculation",
));
}
let xi = HBAR / (2.0 * self.effective_mass * self.interaction_strength * n0).sqrt();
Ok(xi)
}
pub fn sound_speed(&self) -> Result<f64> {
let n0 = self.condensate_density();
if n0 <= 0.0 {
return Err(error::numerical_error(
"no condensate present; sound speed is undefined",
));
}
if self.interaction_strength <= 0.0 {
return Err(error::invalid_param(
"interaction_strength",
"must be positive for sound speed calculation",
));
}
let cs = (self.interaction_strength * n0 / self.effective_mass).sqrt();
Ok(cs)
}
}
#[derive(Debug, Clone)]
pub struct ParametricPumping {
pub pump_frequency: f64,
pub damping: f64,
pub coupling_strength: f64,
}
impl ParametricPumping {
pub fn new(pump_frequency: f64, damping: f64, coupling_strength: f64) -> Result<Self> {
if pump_frequency <= 0.0 {
return Err(error::invalid_param("pump_frequency", "must be positive"));
}
if damping <= 0.0 {
return Err(error::invalid_param("damping", "must be positive"));
}
if coupling_strength <= 0.0 {
return Err(error::invalid_param(
"coupling_strength",
"must be positive",
));
}
Ok(Self {
pump_frequency,
damping,
coupling_strength,
})
}
pub fn magnon_frequency(&self) -> f64 {
self.pump_frequency / 2.0
}
pub fn suhl_threshold(&self) -> f64 {
let omega_k = self.magnon_frequency();
2.0 * self.damping * omega_k / self.coupling_strength
}
pub fn growth_rate(&self, pump_field: f64) -> f64 {
let omega_k = self.magnon_frequency();
self.coupling_strength * pump_field / 2.0 - self.damping * omega_k
}
}
#[derive(Debug, Clone, Copy)]
pub struct Complex {
pub re: f64,
pub im: f64,
}
impl Complex {
pub fn new(re: f64, im: f64) -> Self {
Self { re, im }
}
pub fn from_polar(r: f64, theta: f64) -> Self {
Self {
re: r * theta.cos(),
im: r * theta.sin(),
}
}
pub fn norm_sq(&self) -> f64 {
self.re * self.re + self.im * self.im
}
pub fn norm(&self) -> f64 {
self.norm_sq().sqrt()
}
pub fn phase(&self) -> f64 {
self.im.atan2(self.re)
}
pub fn conj(&self) -> Self {
Self {
re: self.re,
im: -self.im,
}
}
pub fn mul(&self, other: &Self) -> Self {
Self {
re: self.re * other.re - self.im * other.im,
im: self.re * other.im + self.im * other.re,
}
}
pub fn scale(&self, s: f64) -> Self {
Self {
re: self.re * s,
im: self.im * s,
}
}
pub fn add(&self, other: &Self) -> Self {
Self {
re: self.re + other.re,
im: self.im + other.im,
}
}
pub fn sub(&self, other: &Self) -> Self {
Self {
re: self.re - other.re,
im: self.im - other.im,
}
}
pub fn mul_i(&self) -> Self {
Self {
re: -self.im,
im: self.re,
}
}
}
#[derive(Debug, Clone)]
pub struct GrossPitaevskiiSolver {
pub psi: Vec<Complex>,
pub potential: Vec<f64>,
pub dx: f64,
pub n_grid: usize,
pub effective_mass: f64,
pub interaction_g: f64,
pub chemical_potential: f64,
pub dt: f64,
pub time: f64,
}
impl GrossPitaevskiiSolver {
pub fn new(
n_grid: usize,
dx: f64,
dt: f64,
effective_mass: f64,
interaction_g: f64,
chemical_potential: f64,
) -> Result<Self> {
if n_grid < 3 {
return Err(error::invalid_param(
"n_grid",
"must be at least 3 for finite differences",
));
}
if dx <= 0.0 {
return Err(error::invalid_param("dx", "must be positive"));
}
if dt <= 0.0 {
return Err(error::invalid_param("dt", "must be positive"));
}
if effective_mass <= 0.0 {
return Err(error::invalid_param("effective_mass", "must be positive"));
}
Ok(Self {
psi: vec![Complex::new(0.0, 0.0); n_grid],
potential: vec![0.0; n_grid],
dx,
n_grid,
effective_mass,
interaction_g,
chemical_potential,
dt,
time: 0.0,
})
}
pub fn set_uniform_condensate(&mut self, density: f64) {
let amplitude = density.abs().sqrt();
for psi_i in &mut self.psi {
*psi_i = Complex::new(amplitude, 0.0);
}
}
pub fn set_gaussian_condensate(
&mut self,
center: f64,
sigma: f64,
total_particles: f64,
momentum: f64,
) {
let mut norm_sq_sum = 0.0;
for i in 0..self.n_grid {
let x = i as f64 * self.dx;
let x0 = center * self.dx;
let gaussian =
(-((x - x0) * (x - x0)) / (2.0 * sigma * sigma * self.dx * self.dx)).exp();
let phase = momentum * x;
self.psi[i] = Complex::from_polar(gaussian, phase);
norm_sq_sum += gaussian * gaussian * self.dx;
}
if norm_sq_sum > 0.0 {
let scale = (total_particles / norm_sq_sum).sqrt();
for psi_i in &mut self.psi {
*psi_i = psi_i.scale(scale);
}
}
}
pub fn total_particles(&self) -> f64 {
self.psi.iter().map(|psi_i| psi_i.norm_sq()).sum::<f64>() * self.dx
}
pub fn total_energy(&self) -> f64 {
let kinetic_coeff = HBAR * HBAR / (2.0 * self.effective_mass);
let mut energy = 0.0;
for i in 0..self.n_grid {
let psi_i = &self.psi[i];
let density = psi_i.norm_sq();
let grad = self.gradient_at(i);
let grad_sq = grad.norm_sq();
energy += kinetic_coeff * grad_sq;
energy += self.potential[i] * density;
energy += 0.5 * self.interaction_g * density * density;
}
energy * self.dx
}
fn gradient_at(&self, i: usize) -> Complex {
let psi_prev = if i == 0 {
&self.psi[0]
} else {
&self.psi[i - 1]
};
let psi_next = if i == self.n_grid - 1 {
&self.psi[self.n_grid - 1]
} else {
&self.psi[i + 1]
};
psi_next.sub(psi_prev).scale(0.5 / self.dx)
}
fn laplacian_at(&self, i: usize) -> Complex {
let psi_i = &self.psi[i];
let psi_prev = if i == 0 { psi_i } else { &self.psi[i - 1] };
let psi_next = if i == self.n_grid - 1 {
psi_i
} else {
&self.psi[i + 1]
};
psi_next
.add(psi_prev)
.sub(&psi_i.scale(2.0))
.scale(1.0 / (self.dx * self.dx))
}
fn compute_rhs(&self) -> Vec<Complex> {
let kinetic_coeff = HBAR * HBAR / (2.0 * self.effective_mass);
let inv_hbar = 1.0 / HBAR;
let mut rhs = Vec::with_capacity(self.n_grid);
for i in 0..self.n_grid {
let psi_i = &self.psi[i];
let density = psi_i.norm_sq();
let kinetic = self.laplacian_at(i).scale(-kinetic_coeff);
let local_potential =
self.potential[i] + self.interaction_g * density - self.chemical_potential;
let potential_term = psi_i.scale(local_potential);
let h_psi = kinetic.add(&potential_term);
let dpsi_dt = Complex::new(h_psi.im, -h_psi.re).scale(inv_hbar);
rhs.push(dpsi_dt);
}
rhs
}
pub fn step_euler(&mut self) {
let rhs = self.compute_rhs();
for (i, rhs_val) in rhs.iter().enumerate().take(self.n_grid) {
self.psi[i] = self.psi[i].add(&rhs_val.scale(self.dt));
}
self.time += self.dt;
}
pub fn step_heun(&mut self) {
let k1 = self.compute_rhs();
let psi_orig: Vec<Complex> = self.psi.clone();
for (i, k1_val) in k1.iter().enumerate().take(self.n_grid) {
self.psi[i] = self.psi[i].add(&k1_val.scale(self.dt));
}
self.time += self.dt;
let k2 = self.compute_rhs();
for i in 0..self.n_grid {
let avg = k1[i].add(&k2[i]).scale(0.5 * self.dt);
self.psi[i] = psi_orig[i].add(&avg);
}
}
pub fn density_profile(&self) -> Vec<f64> {
self.psi.iter().map(|psi_i| psi_i.norm_sq()).collect()
}
pub fn phase_profile(&self) -> Vec<f64> {
self.psi.iter().map(|psi_i| psi_i.phase()).collect()
}
}
pub fn magnon_supercurrent(solver: &GrossPitaevskiiSolver, index: usize) -> Result<f64> {
if index >= solver.n_grid {
return Err(error::invalid_param("index", "out of bounds for the grid"));
}
let psi_i = &solver.psi[index];
let grad = solver.gradient_at(index);
let psi_conj = psi_i.conj();
let product = psi_conj.mul(&grad);
let current = HBAR / solver.effective_mass * product.im;
Ok(current)
}
pub fn supercurrent_profile(solver: &GrossPitaevskiiSolver) -> Vec<f64> {
(0..solver.n_grid)
.map(|i| magnon_supercurrent(solver, i).unwrap_or(0.0))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_EFFECTIVE_MASS: f64 = 1e-28; const TEST_DENSITY: f64 = 1e24; const TEST_INTERACTION: f64 = 1e-50;
#[test]
fn test_bec_temperature_is_positive_and_physical() {
let t_bec = bec_temperature(TEST_DENSITY, TEST_EFFECTIVE_MASS)
.expect("bec_temperature should succeed");
assert!(t_bec > 0.0, "BEC temperature must be positive");
assert!(
t_bec < 1e6,
"BEC temperature should be physically reasonable, got {}",
t_bec
);
}
#[test]
fn test_chemical_potential_below_band_minimum() {
let condensate = MagnonCondensate::new(
500.0, TEST_DENSITY,
TEST_EFFECTIVE_MASS,
TEST_INTERACTION,
)
.expect("MagnonCondensate::new should succeed");
if condensate.condensate_fraction == 0.0 {
assert!(
condensate.chemical_potential <= 0.0,
"Chemical potential must be ≤ ℏω_min (0 in our frame)"
);
}
let condensate_cold = MagnonCondensate::new(
0.001, TEST_DENSITY,
TEST_EFFECTIVE_MASS,
TEST_INTERACTION,
)
.expect("MagnonCondensate::new should succeed for cold system");
assert!(
condensate_cold.chemical_potential.abs() < 1e-30,
"Chemical potential should be pinned at band minimum below T_BEC"
);
}
#[test]
fn test_condensate_fraction_approaches_one_at_zero_temperature() {
let condensate = MagnonCondensate::new(
0.0, TEST_DENSITY,
TEST_EFFECTIVE_MASS,
TEST_INTERACTION,
)
.expect("MagnonCondensate::new should succeed at T=0");
assert!(
(condensate.condensate_fraction - 1.0).abs() < 1e-12,
"Condensate fraction must approach 1 as T -> 0, got {}",
condensate.condensate_fraction
);
}
#[test]
fn test_condensate_fraction_zero_above_t_bec() {
let t_bec = bec_temperature(TEST_DENSITY, TEST_EFFECTIVE_MASS)
.expect("bec_temperature should succeed");
let condensate = MagnonCondensate::new(
t_bec * 2.0,
TEST_DENSITY,
TEST_EFFECTIVE_MASS,
TEST_INTERACTION,
)
.expect("MagnonCondensate::new should succeed");
assert!(
condensate.condensate_fraction.abs() < 1e-12,
"Condensate fraction must be zero above T_BEC"
);
}
#[test]
fn test_magnon_distribution_classical_limit() {
let omega = 1e12; let temperature = 1000.0; let mu = -1e-20;
let _n_be = magnon_distribution(omega, temperature, mu)
.expect("magnon_distribution should succeed");
let _n_cl = classical_distribution(omega, temperature, mu)
.expect("classical_distribution should succeed");
let omega_large = 1e14;
let mu_far = -1e-18;
let n_be2 = magnon_distribution(omega_large, temperature, mu_far)
.expect("magnon_distribution should succeed");
let n_cl2 = classical_distribution(omega_large, temperature, mu_far)
.expect("classical_distribution should succeed");
if n_cl2 > 1e-100 {
let relative_diff = ((n_be2 - n_cl2) / n_cl2).abs();
assert!(
relative_diff < 0.1,
"BE should approach classical at high T with large energy gap, diff = {}",
relative_diff
);
}
}
#[test]
fn test_suhl_threshold_is_positive() {
let pumping = ParametricPumping::new(
2.0 * PI * 10.0e9, 0.001, 1e-24, )
.expect("ParametricPumping::new should succeed");
let threshold = pumping.suhl_threshold();
assert!(
threshold > 0.0,
"Suhl instability threshold must be positive"
);
}
#[test]
fn test_parametric_pumping_frequency() {
let omega_p = 2.0 * PI * 10.0e9;
let pumping = ParametricPumping::new(omega_p, 0.001, 1e-24)
.expect("ParametricPumping::new should succeed");
let omega_k = pumping.magnon_frequency();
assert!(
(omega_k - omega_p / 2.0).abs() < 1e-10,
"Parametric magnons should be at half the pump frequency"
);
}
#[test]
fn test_growth_rate_above_threshold() {
let pumping = ParametricPumping::new(2.0 * PI * 10.0e9, 0.001, 1e-24)
.expect("ParametricPumping::new should succeed");
let threshold = pumping.suhl_threshold();
let rate_below = pumping.growth_rate(threshold * 0.5);
assert!(
rate_below < 0.0,
"Growth rate should be negative below threshold"
);
let rate_above = pumping.growth_rate(threshold * 2.0);
assert!(
rate_above > 0.0,
"Growth rate should be positive above threshold"
);
}
#[test]
fn test_magnon_supercurrent_from_phase_gradient() {
let n_grid = 64;
let dx = 1e-6; let dt = 1e-15;
let m_star = 1e-28;
let g = 1e-38;
let mu = 0.0;
let mut solver = GrossPitaevskiiSolver::new(n_grid, dx, dt, m_star, g, mu)
.expect("GP solver creation should succeed");
let n0: f64 = 1e18; let k0: f64 = 1e4; let amplitude = n0.sqrt();
for i in 0..n_grid {
let x = i as f64 * dx;
solver.psi[i] = Complex::from_polar(amplitude, k0 * x);
}
let expected_current = HBAR * k0 / m_star * n0;
let j_mid = magnon_supercurrent(&solver, n_grid / 2)
.expect("supercurrent calculation should succeed");
let relative_error = ((j_mid - expected_current) / expected_current).abs();
assert!(
relative_error < 0.01,
"Supercurrent should match ℏk/m* × n₀, relative error = {}",
relative_error
);
}
#[test]
fn test_gross_pitaevskii_conserves_particle_number() {
let n_grid = 128;
let dx = 1e-7;
let dt = 1e-18;
let m_star = 1e-28;
let g = 1e-40;
let mu = 0.0;
let mut solver = GrossPitaevskiiSolver::new(n_grid, dx, dt, m_star, g, mu)
.expect("GP solver creation should succeed");
solver.set_gaussian_condensate(n_grid as f64 / 2.0, 10.0, 1000.0, 0.0);
let n_initial = solver.total_particles();
assert!(
n_initial > 0.0,
"Initial particle number should be positive"
);
for _ in 0..50 {
solver.step_heun();
}
let n_final = solver.total_particles();
let relative_change = ((n_final - n_initial) / n_initial).abs();
assert!(
relative_change < 0.01,
"Particle number should be approximately conserved, relative change = {}",
relative_change
);
}
#[test]
fn test_critical_density_increases_with_temperature() {
let m_star = 1e-28;
let nc_100 =
critical_density(m_star, 100.0).expect("critical_density should succeed at 100K");
let nc_200 =
critical_density(m_star, 200.0).expect("critical_density should succeed at 200K");
assert!(
nc_200 > nc_100,
"Critical density should increase with temperature"
);
}
#[test]
fn test_healing_length_and_sound_speed() {
let condensate =
MagnonCondensate::new(0.001, TEST_DENSITY, TEST_EFFECTIVE_MASS, TEST_INTERACTION)
.expect("MagnonCondensate::new should succeed");
let xi = condensate
.healing_length()
.expect("healing_length should succeed");
assert!(xi > 0.0, "Healing length must be positive");
let cs = condensate
.sound_speed()
.expect("sound_speed should succeed");
assert!(cs > 0.0, "Sound speed must be positive");
}
#[test]
fn test_invalid_parameters_return_errors() {
assert!(
MagnonCondensate::new(-1.0, TEST_DENSITY, TEST_EFFECTIVE_MASS, TEST_INTERACTION)
.is_err()
);
assert!(MagnonCondensate::new(1.0, 0.0, TEST_EFFECTIVE_MASS, TEST_INTERACTION).is_err());
assert!(MagnonCondensate::new(1.0, TEST_DENSITY, -1.0, TEST_INTERACTION).is_err());
assert!(magnon_distribution(1e12, -1.0, 0.0).is_err());
assert!(ParametricPumping::new(0.0, 0.001, 1e-24).is_err());
}
}