use std::f64::consts::TAU;
use std::fmt;
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
use num_complex::Complex64;
use crate::{Error, Result};
pub const SPEED_OF_LIGHT: f64 = 299_792_458.0;
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct Length(f64);
impl Length {
pub const ZERO: Length = Length(0.0);
pub const fn um(value: f64) -> Length {
Length(value)
}
pub const fn nm(value: f64) -> Length {
Length(value / 1000.0)
}
pub const fn to_um(self) -> f64 {
self.0
}
pub const fn to_nm(self) -> f64 {
self.0 * 1000.0
}
pub fn abs(self) -> Length {
Length(self.0.abs())
}
}
impl fmt::Display for Length {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} um", self.0)
}
}
impl Add for Length {
type Output = Length;
fn add(self, rhs: Length) -> Length {
Length(self.0 + rhs.0)
}
}
impl AddAssign for Length {
fn add_assign(&mut self, rhs: Length) {
self.0 += rhs.0;
}
}
impl Sub for Length {
type Output = Length;
fn sub(self, rhs: Length) -> Length {
Length(self.0 - rhs.0)
}
}
impl SubAssign for Length {
fn sub_assign(&mut self, rhs: Length) {
self.0 -= rhs.0;
}
}
impl Neg for Length {
type Output = Length;
fn neg(self) -> Length {
Length(-self.0)
}
}
impl Mul<f64> for Length {
type Output = Length;
fn mul(self, rhs: f64) -> Length {
Length(self.0 * rhs)
}
}
impl Mul<Length> for f64 {
type Output = Length;
fn mul(self, rhs: Length) -> Length {
Length(self * rhs.0)
}
}
impl Div<f64> for Length {
type Output = Length;
fn div(self, rhs: f64) -> Length {
Length(self.0 / rhs)
}
}
impl Div for Length {
type Output = f64;
fn div(self, rhs: Length) -> f64 {
self.0 / rhs.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Wavelength(f64);
impl Wavelength {
pub fn um(value: f64) -> Result<Wavelength> {
if value.is_finite() && value > 0.0 {
Ok(Wavelength(value))
} else {
Err(Error::invalid(
"wavelength",
format!("must be positive and finite, got {value} um"),
))
}
}
pub fn nm(value: f64) -> Result<Wavelength> {
Wavelength::um(value / 1000.0).map_err(|_| {
Error::invalid(
"wavelength",
format!("must be positive and finite, got {value} nm"),
)
})
}
pub(crate) const fn from_um_unchecked(value: f64) -> Wavelength {
Wavelength(value)
}
pub const fn to_um(self) -> f64 {
self.0
}
pub const fn to_nm(self) -> f64 {
self.0 * 1000.0
}
pub const fn length(self) -> Length {
Length(self.0)
}
pub fn frequency(self) -> Frequency {
Frequency(1.0 / self.0)
}
pub fn wavenumber(self) -> f64 {
TAU / self.0
}
}
impl fmt::Display for Wavelength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} um", self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Frequency(f64);
impl Frequency {
pub fn natural(value: f64) -> Result<Frequency> {
if value.is_finite() && value > 0.0 {
Ok(Frequency(value))
} else {
Err(Error::invalid(
"frequency",
format!("must be positive and finite, got {value} c/um"),
))
}
}
pub fn thz(value: f64) -> Result<Frequency> {
Frequency::natural(value * 1e12 * 1e-6 / SPEED_OF_LIGHT).map_err(|_| {
Error::invalid(
"frequency",
format!("must be positive and finite, got {value} THz"),
)
})
}
pub const fn to_natural(self) -> f64 {
self.0
}
pub fn to_thz(self) -> f64 {
self.0 * SPEED_OF_LIGHT / 1e-6 / 1e12
}
pub fn angular(self) -> f64 {
TAU * self.0
}
pub fn wavelength(self) -> Wavelength {
Wavelength(1.0 / self.0)
}
}
impl fmt::Display for Frequency {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} c/um", self.0)
}
}
pub fn refractive_index(permittivity: Complex64) -> Complex64 {
let n = permittivity.sqrt();
if n.im < 0.0 { -n } else { n }
}
pub fn permittivity(refractive_index: Complex64) -> Complex64 {
refractive_index * refractive_index
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() <= tol * b.abs().max(1.0)
}
#[test]
fn lengths_convert_between_micrometres_and_nanometres() {
assert_eq!(Length::nm(220.0).to_um(), 0.22);
assert_eq!(Length::um(0.5).to_nm(), 500.0);
assert_eq!(Length::um(1.0) + Length::nm(500.0), Length::um(1.5));
assert_eq!(Length::um(3.0) / Length::um(1.5), 2.0);
assert_eq!(-Length::um(2.0), Length::um(-2.0));
assert_eq!(2.0 * Length::um(1.25), Length::um(2.5));
}
#[test]
fn wavelengths_must_be_positive_and_finite() {
assert!(Wavelength::um(1.55).is_ok());
for bad in [0.0, -1.55, f64::NAN, f64::INFINITY] {
let e = Wavelength::um(bad).unwrap_err();
assert!(
matches!(
e,
Error::InvalidValue {
what: "wavelength",
..
}
),
"{bad}"
);
}
let e = Wavelength::nm(-1550.0).unwrap_err();
assert!(e.to_string().contains("-1550 nm"), "{e}");
}
#[test]
fn frequencies_must_be_positive_and_finite() {
for bad in [0.0, -1.0, f64::NAN] {
assert!(Frequency::natural(bad).is_err());
assert!(Frequency::thz(bad).is_err());
}
}
#[test]
fn frequency_and_wavelength_are_reciprocal_in_natural_units() {
let lam = Wavelength::um(1.55).unwrap();
let f = lam.frequency();
assert!(close(f.to_natural(), 1.0 / 1.55, 1e-15));
assert!(close(f.wavelength().to_um(), 1.55, 1e-15));
assert!(close(f.angular(), TAU / 1.55, 1e-15));
assert!(close(lam.wavenumber(), TAU / 1.55, 1e-15));
}
#[test]
fn terahertz_use_the_exact_speed_of_light() {
let f = Wavelength::um(1.55).unwrap().frequency();
assert!(close(f.to_thz(), 299_792_458.0 / 1.55e-6 / 1e12, 1e-14));
assert!(close(f.to_thz(), 193.414_489_032_258, 1e-12));
let back = Frequency::thz(f.to_thz()).unwrap();
assert!(close(back.wavelength().to_um(), 1.55, 1e-14));
}
#[test]
fn the_refractive_index_of_a_lossy_medium_attenuates() {
let n = refractive_index(Complex64::new(12.0, 0.5));
assert!(n.re > 0.0 && n.im > 0.0, "{n}");
assert!(close(permittivity(n).re, 12.0, 1e-14) && close(permittivity(n).im, 0.5, 1e-14));
let k0 = Wavelength::um(1.55).unwrap().wavenumber();
let at = |x: f64| (Complex64::i() * n * k0 * x).exp().norm();
assert!(at(1.0) < at(0.0));
}
#[test]
fn a_lossless_dielectric_has_a_real_index() {
let n = refractive_index(Complex64::new(2.085, 0.0));
assert_eq!(n.im, 0.0);
assert!(close(n.re, 2.085f64.sqrt(), 1e-15));
}
#[test]
fn an_ideal_metal_has_an_imaginary_index() {
let n = refractive_index(Complex64::new(-16.0, 0.0));
assert!(n.re.abs() < 1e-15 && close(n.im, 4.0, 1e-15), "{n}");
}
#[test]
fn a_forward_wave_has_a_phase_that_grows_with_x_and_falls_with_t() {
let (k, w) = (2.0, 3.0);
let phase = |x: f64, t: f64| Complex64::new(0.0, k * x - w * t).exp().arg();
assert!(phase(0.1, 0.0) > phase(0.0, 0.0));
assert!(phase(0.0, 0.1) < phase(0.0, 0.0));
}
#[test]
fn the_amplitude_of_a_real_signal_is_recovered_with_e_plus_i_w_t() {
let a = Complex64::new(0.7, -0.4);
let w = TAU * 0.65;
let periods = 20.0;
let n = 20_000;
let t_end = periods * TAU / w;
let dt = t_end / n as f64;
let mut sum = Complex64::new(0.0, 0.0);
for i in 0..n {
let t = (i as f64 + 0.5) * dt;
let s = (a * Complex64::new(0.0, -w * t).exp()).re;
sum += s * Complex64::new(0.0, w * t).exp() * dt;
}
let got = sum * (2.0 / t_end);
assert!((got - a).norm() < 1e-9, "{got} vs {a}");
}
}