use super::{Converter, Equivalency};
use crate::constants::SPEED_OF_LIGHT;
use crate::dimension::{Dimension, Rational16};
use crate::quantity::Quantity;
use crate::unit::Unit;
fn is_velocity(unit: &Unit) -> bool {
let velocity_dim = Dimension::LENGTH.mul(&Dimension::TIME.pow(Rational16::new(-1, 1)));
unit.dimension() == velocity_dim
}
fn is_frequency(unit: &Unit) -> bool {
unit.dimension() == Dimension::TIME.inv()
}
pub fn doppler_radio(rest_freq: Quantity) -> Equivalency {
let nu0_hz = rest_freq.value() * rest_freq.unit().scale();
Equivalency::new("doppler_radio", move |from, to| {
let (is_freq_to_vel, _from_scale, _to_scale) = if is_frequency(from) && is_velocity(to) {
(true, from.scale(), to.scale())
} else if is_velocity(from) && is_frequency(to) {
(false, from.scale(), to.scale())
} else {
return None;
};
let nu0 = nu0_hz;
if is_freq_to_vel {
Some(Converter::new(
move |nu_si| {
if nu_si <= 0.0 {
return Err(format!("frequency must be positive, got {}", nu_si));
}
Ok(SPEED_OF_LIGHT * (1.0 - nu_si / nu0))
},
move |v_si| {
if v_si.abs() >= SPEED_OF_LIGHT {
return Err(format!(
"velocity must be less than speed of light, got {} m/s",
v_si
));
}
Ok(nu0 * (1.0 - v_si / SPEED_OF_LIGHT))
},
))
} else {
Some(Converter::new(
move |v_si| {
if v_si.abs() >= SPEED_OF_LIGHT {
return Err(format!(
"velocity must be less than speed of light, got {} m/s",
v_si
));
}
Ok(nu0 * (1.0 - v_si / SPEED_OF_LIGHT))
},
move |nu_si| {
if nu_si <= 0.0 {
return Err(format!("frequency must be positive, got {}", nu_si));
}
Ok(SPEED_OF_LIGHT * (1.0 - nu_si / nu0))
},
))
}
})
}
pub fn doppler_optical(rest_freq: Quantity) -> Equivalency {
let nu0_hz = rest_freq.value() * rest_freq.unit().scale();
Equivalency::new("doppler_optical", move |from, to| {
let is_freq_to_vel = if is_frequency(from) && is_velocity(to) {
true
} else if is_velocity(from) && is_frequency(to) {
false
} else {
return None;
};
let nu0 = nu0_hz;
if is_freq_to_vel {
Some(Converter::new(
move |nu_si| {
if nu_si <= 0.0 {
return Err(format!("frequency must be positive, got {}", nu_si));
}
Ok(SPEED_OF_LIGHT * (nu0 / nu_si - 1.0))
},
move |v_si| {
if v_si <= -SPEED_OF_LIGHT {
return Err(format!(
"velocity must be greater than -c, got {} m/s",
v_si
));
}
Ok(nu0 / (1.0 + v_si / SPEED_OF_LIGHT))
},
))
} else {
Some(Converter::new(
move |v_si| {
if v_si <= -SPEED_OF_LIGHT {
return Err(format!(
"velocity must be greater than -c, got {} m/s",
v_si
));
}
Ok(nu0 / (1.0 + v_si / SPEED_OF_LIGHT))
},
move |nu_si| {
if nu_si <= 0.0 {
return Err(format!("frequency must be positive, got {}", nu_si));
}
Ok(SPEED_OF_LIGHT * (nu0 / nu_si - 1.0))
},
))
}
})
}
pub fn doppler_relativistic(rest_freq: Quantity) -> Equivalency {
let nu0_hz = rest_freq.value() * rest_freq.unit().scale();
Equivalency::new("doppler_relativistic", move |from, to| {
let is_freq_to_vel = if is_frequency(from) && is_velocity(to) {
true
} else if is_velocity(from) && is_frequency(to) {
false
} else {
return None;
};
let nu0 = nu0_hz;
let nu0_sq = nu0 * nu0;
if is_freq_to_vel {
Some(Converter::new(
move |nu_si| {
if nu_si <= 0.0 {
return Err(format!("frequency must be positive, got {}", nu_si));
}
let nu_sq = nu_si * nu_si;
Ok(SPEED_OF_LIGHT * (nu0_sq - nu_sq) / (nu0_sq + nu_sq))
},
move |v_si| {
if v_si.abs() >= SPEED_OF_LIGHT {
return Err(format!(
"velocity must be less than speed of light, got {} m/s",
v_si
));
}
let beta = v_si / SPEED_OF_LIGHT;
Ok(nu0 * ((1.0 - beta) / (1.0 + beta)).sqrt())
},
))
} else {
Some(Converter::new(
move |v_si| {
if v_si.abs() >= SPEED_OF_LIGHT {
return Err(format!(
"velocity must be less than speed of light, got {} m/s",
v_si
));
}
let beta = v_si / SPEED_OF_LIGHT;
Ok(nu0 * ((1.0 - beta) / (1.0 + beta)).sqrt())
},
move |nu_si| {
if nu_si <= 0.0 {
return Err(format!("frequency must be positive, got {}", nu_si));
}
let nu_sq = nu_si * nu_si;
Ok(SPEED_OF_LIGHT * (nu0_sq - nu_sq) / (nu0_sq + nu_sq))
},
))
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::systems::si::{GHZ, HZ, KM, M, S};
fn km_per_s() -> Unit {
KM / S
}
#[test]
fn test_doppler_radio_redshift() {
let rest_freq = 115.27120 * GHZ.clone();
let observed = 115.0 * GHZ.clone();
let velocity = observed
.to_equiv(&km_per_s(), doppler_radio(rest_freq.clone()))
.unwrap();
let expected = SPEED_OF_LIGHT * (1.0 - 115.0 / 115.27120) / 1000.0; assert!((velocity.value() - expected).abs() / expected.abs() < 1e-6);
}
#[test]
fn test_doppler_radio_blueshift() {
let rest_freq = 115.27120 * GHZ.clone();
let observed = 116.0 * GHZ.clone();
let velocity = observed
.to_equiv(&km_per_s(), doppler_radio(rest_freq))
.unwrap();
assert!(velocity.value() < 0.0); }
#[test]
fn test_doppler_optical() {
let rest_freq = 1e15 * HZ.clone();
let observed = (1e15 / 1.1) * HZ.clone();
let velocity = observed
.to_equiv(&km_per_s(), doppler_optical(rest_freq))
.unwrap();
let expected = SPEED_OF_LIGHT * 0.1 / 1000.0; assert!((velocity.value() - expected).abs() / expected < 1e-6);
}
#[test]
fn test_doppler_relativistic_low_velocity() {
let rest_freq = 1e9 * HZ.clone();
let velocity = 1.0 * km_per_s();
let freq_radio = velocity
.to_equiv(&HZ, doppler_radio(rest_freq.clone()))
.unwrap();
let freq_optical = velocity
.to_equiv(&HZ, doppler_optical(rest_freq.clone()))
.unwrap();
let freq_rel = velocity
.to_equiv(&HZ, doppler_relativistic(rest_freq.clone()))
.unwrap();
assert!((freq_radio.value() - freq_optical.value()).abs() / freq_radio.value() < 1e-6);
assert!((freq_radio.value() - freq_rel.value()).abs() / freq_radio.value() < 1e-6);
}
fn m_per_s() -> Unit {
M / S
}
#[test]
fn test_superluminal_radio_fails() {
let rest_freq = 1e9 * HZ.clone();
let velocity = SPEED_OF_LIGHT * m_per_s();
let result = velocity.to_equiv(&HZ, doppler_radio(rest_freq));
assert!(result.is_err());
}
#[test]
fn test_superluminal_relativistic_fails() {
let rest_freq = 1e9 * HZ.clone();
let velocity = SPEED_OF_LIGHT * m_per_s();
let result = velocity.to_equiv(&HZ, doppler_relativistic(rest_freq));
assert!(result.is_err());
}
#[test]
fn test_zero_frequency_fails() {
let rest_freq = 1e9 * HZ.clone();
let frequency = 0.0 * HZ.clone();
let result = frequency.to_equiv(&km_per_s(), doppler_radio(rest_freq));
assert!(result.is_err());
}
#[test]
fn test_negative_frequency_fails() {
let rest_freq = 1e9 * HZ.clone();
let frequency = -1e9 * HZ.clone();
let result = frequency.to_equiv(&km_per_s(), doppler_relativistic(rest_freq));
assert!(result.is_err());
}
}