use num_complex::Complex64;
use crate::polarimetry::stokes::StokesVector;
#[derive(Debug, Clone)]
pub struct JonesVector {
pub ex: Complex64,
pub ey: Complex64,
}
impl JonesVector {
pub fn new(ex: Complex64, ey: Complex64) -> Self {
Self { ex, ey }
}
pub fn horizontal() -> Self {
Self::new(Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0))
}
pub fn vertical() -> Self {
Self::new(Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0))
}
pub fn diagonal_p45() -> Self {
let a = Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.0);
Self::new(a, a)
}
pub fn diagonal_m45() -> Self {
let a = Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.0);
Self::new(a, -a)
}
pub fn right_circular() -> Self {
let inv_sqrt2 = std::f64::consts::FRAC_1_SQRT_2;
Self::new(
Complex64::new(inv_sqrt2, 0.0),
Complex64::new(0.0, inv_sqrt2),
)
}
pub fn left_circular() -> Self {
let inv_sqrt2 = std::f64::consts::FRAC_1_SQRT_2;
Self::new(
Complex64::new(inv_sqrt2, 0.0),
Complex64::new(0.0, -inv_sqrt2),
)
}
pub fn linear(angle_rad: f64) -> Self {
Self::new(
Complex64::new(angle_rad.cos(), 0.0),
Complex64::new(angle_rad.sin(), 0.0),
)
}
pub fn intensity(&self) -> f64 {
self.ex.norm_sqr() + self.ey.norm_sqr()
}
pub fn normalize(&self) -> Self {
let i = self.intensity();
if i < f64::EPSILON {
return self.clone();
}
let scale = 1.0 / i.sqrt();
Self::new(self.ex * scale, self.ey * scale)
}
pub fn phase_difference(&self) -> f64 {
let phi_y = self.ey.arg();
let phi_x = self.ex.arg();
phi_y - phi_x
}
pub fn amplitude_ratio(&self) -> f64 {
let ax = self.ex.norm();
if ax < f64::EPSILON {
return f64::INFINITY;
}
self.ey.norm() / ax
}
pub fn to_stokes(&self) -> StokesVector {
let s0 = self.ex.norm_sqr() + self.ey.norm_sqr();
let s1 = self.ex.norm_sqr() - self.ey.norm_sqr();
let cross = self.ex.conj() * self.ey;
let s2 = 2.0 * cross.re;
let s3 = 2.0 * cross.im;
StokesVector::new_unchecked_pub(s0, s1, s2, s3)
}
}
#[derive(Debug, Clone)]
pub struct JonesMatrix {
pub m: [[Complex64; 2]; 2],
}
impl JonesMatrix {
pub fn new(m: [[Complex64; 2]; 2]) -> Self {
Self { m }
}
pub fn identity() -> Self {
let o = Complex64::new(0.0, 0.0);
let i = Complex64::new(1.0, 0.0);
Self::new([[i, o], [o, i]])
}
pub fn apply(&self, v: &JonesVector) -> JonesVector {
JonesVector::new(
self.m[0][0] * v.ex + self.m[0][1] * v.ey,
self.m[1][0] * v.ex + self.m[1][1] * v.ey,
)
}
pub fn cascade(&self, next: &JonesMatrix) -> JonesMatrix {
mat2x2_mul(next, self)
}
pub fn linear_polarizer(angle_rad: f64) -> Self {
let c = angle_rad.cos();
let s = angle_rad.sin();
let cc = Complex64::new(c * c, 0.0);
let ss = Complex64::new(s * s, 0.0);
let cs = Complex64::new(c * s, 0.0);
Self::new([[cc, cs], [cs, ss]])
}
pub fn half_wave_plate(fast_axis_rad: f64) -> Self {
Self::wave_plate(std::f64::consts::PI, fast_axis_rad)
}
pub fn quarter_wave_plate(fast_axis_rad: f64) -> Self {
Self::wave_plate(std::f64::consts::FRAC_PI_2, fast_axis_rad)
}
pub fn wave_plate(phase_rad: f64, fast_axis_rad: f64) -> Self {
let theta = fast_axis_rad;
let delta = phase_rad;
let half_delta = delta / 2.0;
let lam_fast = Complex64::new(0.0, half_delta).exp();
let lam_slow = Complex64::new(0.0, -half_delta).exp();
let c = theta.cos();
let s = theta.sin();
let c2 = c * c;
let s2 = s * s;
let cs = c * s;
let m00 = lam_fast * c2 + lam_slow * s2;
let m01 = (lam_fast - lam_slow) * cs;
let m10 = m01;
let m11 = lam_fast * s2 + lam_slow * c2;
Self::new([[m00, m01], [m10, m11]])
}
pub fn rotator(angle_rad: f64) -> Self {
let c = Complex64::new(angle_rad.cos(), 0.0);
let s = Complex64::new(angle_rad.sin(), 0.0);
Self::new([[c, -s], [s, c]])
}
pub fn beam_splitter(transmission: f64) -> Self {
let t = Complex64::new(transmission.clamp(0.0, 1.0), 0.0);
let o = Complex64::new(0.0, 0.0);
Self::new([[t, o], [o, t]])
}
pub fn phase_delay(delta_x: f64, delta_y: f64) -> Self {
let px = Complex64::new(0.0, delta_x).exp();
let py = Complex64::new(0.0, delta_y).exp();
let o = Complex64::new(0.0, 0.0);
Self::new([[px, o], [o, py]])
}
pub fn attenuator(amplitude_factor: f64) -> Self {
let a = Complex64::new(amplitude_factor.clamp(0.0, 1.0), 0.0);
let o = Complex64::new(0.0, 0.0);
Self::new([[a, o], [o, a]])
}
pub fn eigenvalues(&self) -> (Complex64, Complex64) {
let a = self.m[0][0];
let b = self.m[0][1];
let c = self.m[1][0];
let d = self.m[1][1];
let trace = a + d;
let det = a * d - b * c;
let discriminant = trace * trace - Complex64::new(4.0, 0.0) * det;
let sqrt_disc = csqrt(discriminant);
let lam1 = (trace + sqrt_disc) / 2.0;
let lam2 = (trace - sqrt_disc) / 2.0;
if lam1.norm() >= lam2.norm() {
(lam1, lam2)
} else {
(lam2, lam1)
}
}
pub fn transmission(&self, input: &JonesVector) -> f64 {
let i_in = input.intensity();
if i_in < f64::EPSILON {
return 0.0;
}
let output = self.apply(input);
output.intensity() / i_in
}
pub fn to_mueller(&self) -> crate::polarimetry::mueller::MuellerMatrix {
jones_to_mueller(self)
}
}
fn mat2x2_mul(a: &JonesMatrix, b: &JonesMatrix) -> JonesMatrix {
let mut r = [[Complex64::new(0.0, 0.0); 2]; 2];
for (i, r_row) in r.iter_mut().enumerate() {
for (j, r_cell) in r_row.iter_mut().enumerate() {
for k in 0..2 {
*r_cell += a.m[i][k] * b.m[k][j];
}
}
}
JonesMatrix::new(r)
}
fn csqrt(z: Complex64) -> Complex64 {
let r = z.norm().sqrt();
let theta = z.arg() / 2.0;
Complex64::new(r * theta.cos(), r * theta.sin())
}
pub(crate) fn jones_to_mueller(j: &JonesMatrix) -> crate::polarimetry::mueller::MuellerMatrix {
let zero = Complex64::new(0.0, 0.0);
let one = Complex64::new(1.0, 0.0);
let im = Complex64::new(0.0, 1.0);
let sigma: [[[Complex64; 2]; 2]; 4] = [
[[one, zero], [zero, one]],
[[zero, one], [one, zero]],
[[zero, -im], [im, zero]],
[[one, zero], [zero, -one]],
];
let j_dag: [[Complex64; 2]; 2] = [
[j.m[0][0].conj(), j.m[1][0].conj()],
[j.m[0][1].conj(), j.m[1][1].conj()],
];
let mut m = [[0.0f64; 4]; 4];
for row in 0..4 {
for col in 0..4 {
let mut a_mat = [[zero; 2]; 2];
for (i, a_row) in a_mat.iter_mut().enumerate() {
for (k, a_cell) in a_row.iter_mut().enumerate() {
for (l, &s_il) in sigma[row][i].iter().enumerate() {
*a_cell += s_il * j.m[l][k];
}
}
}
let mut b_mat = [[zero; 2]; 2];
for i in 0..2 {
for k in 0..2 {
for l in 0..2 {
b_mat[i][k] += sigma[col][i][l] * j_dag[l][k];
}
}
}
let mut tr = zero;
for i in 0..2 {
for l in 0..2 {
tr += a_mat[i][l] * b_mat[l][i];
}
}
m[row][col] = 0.5 * tr.re;
}
}
crate::polarimetry::mueller::MuellerMatrix::new(m)
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI};
const EPS: f64 = 1e-10;
const LOOSE: f64 = 1e-9;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_jones_horizontal_intensity() {
let h = JonesVector::horizontal();
assert!(approx_eq(h.intensity(), 1.0, EPS));
}
#[test]
fn test_jones_to_stokes_circular() {
let rcp = JonesVector::right_circular();
let s = rcp.to_stokes();
assert!(approx_eq(s.s[0], 1.0, LOOSE), "S0={}", s.s[0]);
assert!(s.s[1].abs() < LOOSE, "S1={}", s.s[1]);
assert!(s.s[2].abs() < LOOSE, "S2={}", s.s[2]);
assert!(approx_eq(s.s[3], 1.0, LOOSE), "S3={}", s.s[3]);
let lcp = JonesVector::left_circular();
let sl = lcp.to_stokes();
assert!(approx_eq(sl.s[3], -1.0, LOOSE), "S3={}", sl.s[3]);
}
#[test]
fn test_hwp_rotates_linear() {
let hwp = JonesMatrix::half_wave_plate(FRAC_PI_4 / 2.0);
let h = JonesVector::horizontal();
let out = hwp.apply(&h);
let s = out.to_stokes();
assert!(s.s[1].abs() < 1e-9, "S1={}", s.s[1]);
assert!(
approx_eq(s.s[2] / s.s[0], 1.0, 1e-9),
"S2/S0={}",
s.s[2] / s.s[0]
);
}
#[test]
fn test_qwp_h_gives_circular() {
let qwp = JonesMatrix::quarter_wave_plate(FRAC_PI_4);
let h = JonesVector::horizontal();
let out = qwp.apply(&h);
let s = out.to_stokes();
assert!(s.s[1].abs() < 1e-9, "S1={}", s.s[1]);
assert!(s.s[2].abs() < 1e-9, "S2={}", s.s[2]);
assert!(s.s[3].abs() > 0.9 * s.s[0], "S3={} S0={}", s.s[3], s.s[0]);
}
#[test]
fn test_polarizer_blocks_perp() {
let pol_v = JonesMatrix::linear_polarizer(FRAC_PI_2);
let h = JonesVector::horizontal();
let out = pol_v.apply(&h);
assert!(out.intensity() < 1e-20, "intensity={}", out.intensity());
}
#[test]
fn test_jones_cascade_two_hwp() {
let hwp = JonesMatrix::half_wave_plate(0.0);
let hwp2 = hwp.cascade(&hwp);
let h = JonesVector::horizontal();
let out = hwp2.apply(&h);
assert!(approx_eq(out.intensity(), h.intensity(), 1e-10));
assert!(out.ey.norm() < 1e-10, "Ey={}", out.ey.norm());
}
#[test]
fn test_jones_to_mueller_identity() {
let j = JonesMatrix::identity();
let m = j.to_mueller();
let identity_row = [1.0, 0.0, 0.0, 0.0];
for r in 0..4 {
for c in 0..4 {
let expected = if r == c { 1.0 } else { 0.0 };
assert!(
approx_eq(m.m[r][c], expected, 1e-10),
"M[{r}][{c}]={} expected {expected}",
m.m[r][c]
);
}
}
let _ = identity_row; }
#[test]
fn test_wave_plate_general() {
let wp = JonesMatrix::wave_plate(2.0 * PI, 0.0);
let h = JonesVector::horizontal();
let out = wp.apply(&h);
assert!(approx_eq(out.intensity(), h.intensity(), 1e-10));
}
#[test]
fn test_eigenvalues_identity() {
let j = JonesMatrix::identity();
let (l1, l2) = j.eigenvalues();
assert!(approx_eq(l1.re, 1.0, 1e-10));
assert!(approx_eq(l2.re, 1.0, 1e-10));
assert!(l1.im.abs() < 1e-10);
assert!(l2.im.abs() < 1e-10);
}
}