polarization/jones/
common.rs

1//! Types and definitions used in other modules.
2#![macro_use]
3use std::error;
4pub use std::f64::consts::PI as pi;
5use std::fmt;
6use std::result;
7
8use na::{Matrix2, Vector2};
9use num::complex::Complex;
10#[cfg(test)]
11use proptest::prelude::*;
12
13/// A more convenient synonym for the type of 2x2 Jones matrices.
14pub type ComplexMatrix = Matrix2<Complex<f64>>;
15
16/// A more convenient synonym for the type of 2x1 Jones vectors.
17pub type ComplexVector = Vector2<Complex<f64>>;
18
19/// The result type used by `jones`.
20///
21/// Each error contains an `ErrorKind` to indicate the kind of error encountered.
22pub type Result<T> = result::Result<T, JonesError>;
23
24/// The different kinds of errors that may occur inside `polarization`.
25#[derive(Debug)]
26pub enum JonesError {
27    /// An error encountered when the calculated intensity becomes infinite.
28    ///
29    /// Calculating the intensity involves squaring the components of the Jones vector.
30    /// If the components of the vector are large enough, the intensity may become
31    /// infinite.
32    IntensityTooLarge,
33
34    /// An error encountered when a beam is missing from an optical system.
35    NoBeam,
36
37    /// An error encountered when an optical system contains no elements.
38    NoElements,
39
40    /// An error not covered by the other kinds of errors.
41    Other(String),
42}
43
44impl error::Error for JonesError {
45    fn description(&self) -> &str {
46        ""
47    }
48
49    fn cause(&self) -> Option<&error::Error> {
50        None
51    }
52}
53
54impl fmt::Display for JonesError {
55    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56        match self {
57            JonesError::IntensityTooLarge => write!(f, "Intensity error: Intensity is too large"),
58            JonesError::Other(ref msg) => write!(f, "Other error: {}", msg),
59            JonesError::NoBeam => {
60                write!(f, "Optical system error: the system does not have a beam")
61            }
62            JonesError::NoElements => write!(f, "Optical system error: the system has no elements"),
63        }
64    }
65}
66
67/// An angle.
68///
69/// Angles or phases are more commonly written in radians in physics, but may be more
70/// convenient to write in degrees. Furthermore, explicitly denoting the units for
71/// angles prevents confusion or mistakes.
72#[derive(Debug, Copy, Clone, PartialEq)]
73pub enum Angle {
74    Degrees(f64),
75    Radians(f64),
76}
77
78#[cfg(test)]
79impl Arbitrary for Angle {
80    type Parameters = ();
81    type Strategy = BoxedStrategy<Self>;
82
83    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
84        prop_oneof![
85            (any::<f64>()).prop_map(|x| Angle::Degrees(x)),
86            (any::<f64>()).prop_map(|x| Angle::Radians(x)),
87        ]
88        .boxed()
89    }
90}
91
92/// The handedness of a circularly polarized beam.
93///
94/// A circularly polarized beam may either be left- or right-hand circularly polarized,
95/// as determined by the right hand rule.
96#[derive(Debug, Copy, Clone, PartialEq, Eq)]
97pub enum Handedness {
98    Left,
99    Right,
100}
101
102#[cfg(test)]
103impl Arbitrary for Handedness {
104    type Parameters = ();
105    type Strategy = BoxedStrategy<Self>;
106
107    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
108        prop_oneof![Just(Handedness::Left), Just(Handedness::Right),].boxed()
109    }
110}
111
112/// The types of polarization handled by Jones calculus.
113///
114/// While more exotic forms of polarization are possible i.e. vector polarizations, the
115/// most common type of polarization are linear, circular, and elliptical. The
116/// `Elliptical` variant allows you to specify an arbitrary polarization.
117#[derive(Debug, Clone, PartialEq)]
118pub enum Polarization {
119    Linear(Angle),
120    Circular(Handedness),
121    Elliptical(Complex<f64>, Complex<f64>),
122}
123
124#[cfg(test)]
125pub fn any_linear_polarization() -> impl Strategy<Value = Polarization> {
126    any::<Angle>().prop_map(|angle| Polarization::Linear(angle))
127}
128
129#[cfg(test)]
130pub fn any_circular_polarization() -> impl Strategy<Value = Polarization> {
131    any::<Handedness>().prop_map(|h| Polarization::Circular(h))
132}
133
134#[cfg(test)]
135pub fn any_elliptical_polarization() -> impl Strategy<Value = Polarization> {
136    (any::<f64>(), any::<f64>(), any::<f64>(), any::<f64>()).prop_map(|(xr, xi, yr, yi)| {
137        let x = Complex::new(xr, xi);
138        let y = Complex::new(yr, yi);
139        Polarization::Elliptical(x, y)
140    })
141}
142
143#[cfg(test)]
144#[derive(Debug, Copy, Clone, PartialEq, Eq)]
145pub enum PolarizationKind {
146    Linear,
147    Circular,
148    Elliptical,
149    Any,
150}
151
152#[cfg(test)]
153impl Default for PolarizationKind {
154    fn default() -> Self {
155        PolarizationKind::Any
156    }
157}
158
159#[cfg(test)]
160impl Arbitrary for Polarization {
161    type Parameters = PolarizationKind;
162    type Strategy = BoxedStrategy<Polarization>;
163
164    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
165        match args {
166            PolarizationKind::Linear => any_linear_polarization().boxed(),
167            PolarizationKind::Circular => any_circular_polarization().boxed(),
168            PolarizationKind::Elliptical => any_elliptical_polarization().boxed(),
169            PolarizationKind::Any => prop_oneof![
170                any_linear_polarization(),
171                any_circular_polarization(),
172                any_elliptical_polarization(),
173            ]
174            .boxed(),
175        }
176    }
177}
178
179pub trait JonesVector {
180    /// Construct a beam from the specified type of polarization. The intensity
181    /// of the beam will be 1.
182    fn from_polarization(pol: Polarization) -> Self;
183
184    /// The intensity of the beam represented by the Jones vector. Note that
185    /// this is simply `V* x V` and is reported as a dimensionless quantity.
186    fn intensity(&self) -> Result<f64>;
187
188    /// For the vector `V = (A, B)`, return a new vector with the phase common
189    /// to `A` and `B` removed. For the returned vector `V' = (A', B')`, `A'`
190    /// will be real, and `B'` will have a phase relative to `A'`.
191    fn remove_common_phase(&self) -> Self;
192
193    /// For the vector `V = (A, B)`, remove the phase common to both `A` and `B`
194    /// in place. See `remove_common_phase` for more details.
195    fn remove_common_phase_mut(&mut self);
196
197    /// Returns the relative phase between the x- and y-components of the vector
198    /// in degrees.
199    fn relative_phase_deg(&self) -> f64;
200
201    /// Returns the relative phase between the x- and y-components of the vector
202    /// in radians.
203    fn relative_phase_rad(&self) -> f64;
204
205    /// Returns the 2x1 Jones vector.
206    fn vector(&self) -> ComplexVector;
207
208    /// Returns a Jones vector that is the result of passing the current vector through
209    /// the provided optical element.
210    fn apply_element<T: JonesMatrix>(&self, elem: T) -> Self;
211
212    /// Replace the current Jones vector with the result of passing it through the
213    /// provided optical element.
214    fn apply_element_mut<T: JonesMatrix>(&mut self, elem: T);
215
216    /// Return the x-component of the Jones vector
217    fn x(&self) -> Complex<f64>;
218
219    /// Return the y-component of the Jones vector
220    fn y(&self) -> Complex<f64>;
221}
222
223/// An ideal coherent light source i.e. an ideal laser beam.
224#[derive(Debug, Clone, PartialEq)]
225pub struct Beam {
226    vec: ComplexVector,
227}
228
229impl Beam {
230    /// Construct a new beam with arbitrary x- and y-components.
231    pub fn new(x: Complex<f64>, y: Complex<f64>) -> Self {
232        Beam {
233            vec: ComplexVector::new(x, y),
234        }
235    }
236
237    /// Construct a linearly polarized beam at the angle `angle`, where the angle is
238    /// measured counter-clockwise from the x-axis.
239    pub fn linear(angle: Angle) -> Self {
240        let rad = match angle {
241            Angle::Radians(rad) => rad,
242            Angle::Degrees(deg) => deg.to_radians(),
243        };
244        let x = Complex::<f64>::new(rad.cos(), 0.0);
245        let y = Complex::<f64>::new(rad.sin(), 0.0);
246        Beam {
247            vec: ComplexVector::new(x, y),
248        }
249    }
250
251    /// Construct a circularly polarized beam with the specified handedness.
252    pub fn circular(hand: Handedness) -> Self {
253        let norm = (1_f64 / 2_f64).sqrt();
254        let i = Complex::<f64>::i();
255        let x = Complex::<f64>::new(norm, 0.0);
256        let y = match hand {
257            Handedness::Left => norm * i,
258            Handedness::Right => -norm * i,
259        };
260        Beam {
261            vec: ComplexVector::new(x, y),
262        }
263    }
264
265    /// Construct a beam from an existing 2x1 vector.
266    ///
267    /// This method is most useful for constructing a `Beam` when you have just received
268    /// a `ComplexVector` as the result of some other operation.
269    pub fn from_vec(v: ComplexVector) -> Self {
270        Beam { vec: v }
271    }
272}
273
274impl JonesVector for Beam {
275    fn from_polarization(pol: Polarization) -> Self {
276        use self::Polarization::*;
277
278        match pol {
279            Linear(angle) => Beam::linear(angle),
280            Circular(hand) => Beam::circular(hand),
281            Elliptical(x, y) => Beam::new(x, y),
282        }
283    }
284
285    fn intensity(&self) -> Result<f64> {
286        let conj = ComplexVector::new(self.vec.x.conj(), self.vec.y.conj());
287        // The dot product of v* and v should be real, but Rust doesn't know that
288        let num = conj.dot(&self.vec).re;
289        if num.is_infinite() {
290            Err(JonesError::IntensityTooLarge)
291        } else {
292            Ok(num)
293        }
294    }
295
296    fn remove_common_phase(&self) -> Self {
297        let (x_mag, x_phase) = self.vec.x.to_polar();
298        let (y_mag, y_phase) = self.vec.y.to_polar();
299        let mut new_y_mag = y_mag;
300        let new_x_phase = 0.0;
301        let mut new_y_phase = y_phase - x_phase;
302        if (y_mag == 0.0) || (y_mag == -0.0) {
303            new_y_phase = 0.0;
304            new_y_mag = 0.0; // turn -0.0 into 0.0 so that we don't get a phase of pi from the minus sign
305        } else {
306            // Get the phase in the range [-pi, +pi]
307            while new_y_phase < -pi {
308                new_y_phase += 2.0 * pi;
309            }
310            while new_y_phase > pi {
311                new_y_phase -= 2.0 * pi;
312            }
313        }
314        let new_y = Complex::from_polar(&new_y_mag, &new_y_phase);
315        let new_x = Complex::<f64>::new(x_mag, new_x_phase);
316        Beam {
317            vec: ComplexVector::new(new_x, new_y),
318        }
319    }
320
321    fn remove_common_phase_mut(&mut self) {
322        let (x_mag, x_phase) = self.vec.x.to_polar();
323        let (y_mag, y_phase) = self.vec.y.to_polar();
324        let mut new_y_mag = y_mag;
325        let new_x_phase = 0.0;
326        let mut new_y_phase = y_phase - x_phase;
327        if (y_mag == 0.0) || (y_mag == -0.0) {
328            new_y_phase = 0.0;
329            new_y_mag = 0.0; // turn -0.0 into 0.0 so that we don't get a phase of pi from the minus sign
330        } else {
331            // Get the phase in the range [-pi, +pi]
332            while new_y_phase < -pi {
333                new_y_phase += 2.0 * pi;
334            }
335            while new_y_phase > pi {
336                new_y_phase -= 2.0 * pi;
337            }
338        }
339        self.vec.x = Complex::from_polar(&x_mag, &new_x_phase);
340        self.vec.y = Complex::from_polar(&new_y_mag, &new_y_phase);
341    }
342
343    fn relative_phase_deg(&self) -> f64 {
344        let (_, x_phase) = self.vec.x.to_polar();
345        let (_, y_phase) = self.vec.y.to_polar();
346        y_phase.to_degrees() - x_phase.to_degrees()
347    }
348
349    fn relative_phase_rad(&self) -> f64 {
350        let (_, x_phase) = self.vec.x.to_polar();
351        let (_, y_phase) = self.vec.y.to_polar();
352        y_phase - x_phase
353    }
354
355    fn vector(&self) -> ComplexVector {
356        self.vec
357    }
358
359    fn apply_element<T: JonesMatrix>(&self, elem: T) -> Self {
360        let vec_after = elem.matrix() * self.vec;
361        Beam::from_vec(vec_after)
362    }
363
364    fn apply_element_mut<T: JonesMatrix>(&mut self, elem: T) {
365        self.vec = elem.matrix() * self.vec;
366    }
367
368    fn x(&self) -> Complex<f64> {
369        self.vec.x
370    }
371
372    fn y(&self) -> Complex<f64> {
373        self.vec.y
374    }
375}
376
377#[cfg(test)]
378impl Arbitrary for Beam {
379    type Parameters = PolarizationKind;
380    type Strategy = BoxedStrategy<Beam>;
381
382    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
383        match args {
384            PolarizationKind::Linear => any_linear_beam().boxed(),
385            PolarizationKind::Circular => any_circular_beam().boxed(),
386            PolarizationKind::Elliptical => any_elliptical_beam().boxed(),
387            PolarizationKind::Any => prop_oneof![
388                any_linear_beam(),
389                any_circular_beam(),
390                any_elliptical_beam(),
391            ]
392            .boxed(),
393        }
394    }
395}
396
397#[cfg(test)]
398pub fn any_linear_beam() -> impl Strategy<Value = Beam> {
399    any::<Angle>().prop_map(|angle| Beam::linear(angle))
400}
401
402#[cfg(test)]
403pub fn any_circular_beam() -> impl Strategy<Value = Beam> {
404    any::<Handedness>().prop_map(|h| Beam::circular(h))
405}
406
407#[cfg(test)]
408pub fn any_elliptical_beam() -> impl Strategy<Value = Beam> {
409    (any_complex(), any_complex()).prop_map(|(x, y)| Beam::new(x, y))
410}
411
412pub trait JonesMatrix {
413    /// Return the optical element rotated by the given angle.
414    fn rotated(&self, angle: Angle) -> Self;
415
416    /// Rotate the optical element in-place by the given angle.
417    fn rotate(&mut self, angle: Angle);
418
419    /// Returns the 2x2 Jones matrix of the optical element.
420    fn matrix(&self) -> ComplexMatrix;
421}
422
423/// Rotate an optical element by transforming its Jones matrix.
424pub fn rotate_matrix(mat: &ComplexMatrix, angle: &Angle) -> ComplexMatrix {
425    let rad = match *angle {
426        Angle::Radians(rad) => rad,
427        Angle::Degrees(deg) => deg.to_radians(),
428    };
429    let rot_mat = Matrix2::new(
430        Complex::new(rad.cos(), 0_f64),
431        Complex::new(rad.sin(), 0_f64),
432        Complex::new(-rad.sin(), 0_f64),
433        Complex::new(rad.cos(), 0_f64),
434    );
435    let rot_mat_inv = Matrix2::new(
436        Complex::new(rad.cos(), 0_f64),
437        Complex::new(-rad.sin(), 0_f64),
438        Complex::new(rad.sin(), 0_f64),
439        Complex::new(rad.cos(), 0_f64),
440    );
441    rot_mat_inv * mat * rot_mat
442}
443
444#[cfg(test)]
445pub fn any_complex() -> impl Strategy<Value = Complex<f64>> {
446    (nice_f64(), nice_f64())
447        .prop_map(|(x, y)| Complex::new(x, y))
448        .boxed()
449}
450
451#[cfg(test)]
452pub fn nice_f64() -> impl Strategy<Value = f64> {
453    (-1e3_f64..1e3_f64)
454        .prop_filter("f64 shouldn't be too big", |x| x.abs() < 1e3)
455        .prop_filter("f64 shouldn't be too small, but may be zero", |x| {
456            (x.abs() > 1e-3) || (*x == 0.0)
457        })
458}
459
460#[cfg(test)]
461pub fn float_angle() -> impl Strategy<Value = f64> {
462    (0_f64..180_f64).boxed()
463}
464
465#[cfg(test)]
466pub fn float_is_well_behaved(x: f64) -> bool {
467    let not_nan = !x.is_nan();
468    let not_too_small = x > -1e12;
469    let not_too_big = x < 1e12;
470    return not_nan && not_too_small && not_too_big;
471}
472
473#[cfg(test)]
474macro_rules! assert_complex_approx_eq {
475    ($x:expr, $y:expr) => {
476        assert_approx_eq!($x.re, $y.re);
477        assert_approx_eq!($x.im, $y.im);
478    };
479}
480
481#[cfg(test)]
482macro_rules! assert_beam_approx_eq {
483    ($x:expr, $y:expr) => {
484        let vec1 = $x.vector();
485        let vec2 = $y.vector();
486        assert_complex_approx_eq!(vec1[0], vec2[0]);
487        assert_complex_approx_eq!(vec1[1], vec2[1]);
488    };
489}
490
491#[cfg(test)]
492macro_rules! assert_matrix_approx_eq {
493    ($x:expr, $y:expr) => {
494        assert_complex_approx_eq!($x[(0, 0)], $y[(0, 0)]);
495        assert_complex_approx_eq!($x[(0, 1)], $y[(0, 1)]);
496        assert_complex_approx_eq!($x[(1, 0)], $y[(1, 0)]);
497        assert_complex_approx_eq!($x[(1, 1)], $y[(1, 1)]);
498    };
499}
500
501#[cfg(test)]
502pub fn are_rel_eq(x: f64, y: f64, frac: f64) -> bool {
503    let diff = (x - y).abs();
504    let avg = ((x + y) / 2.0).abs();
505    let mut limit = avg * frac;
506    if limit < 1e-6 {
507        limit = 1e-6;
508    }
509    diff < limit
510}
511
512#[cfg(test)]
513macro_rules! prop_assert_approx_eq {
514    ($x:expr, $y:expr) => {
515        prop_assert!(are_rel_eq($x, $y, 1e-3))
516    };
517}
518
519#[cfg(test)]
520macro_rules! prop_assert_complex_approx_eq {
521    ($x:expr, $y:expr) => {
522        prop_assert_approx_eq!($x.re, $y.re);
523        prop_assert_approx_eq!($x.im, $y.im);
524    };
525}
526
527#[cfg(test)]
528macro_rules! prop_assert_beam_approx_eq {
529    ($x:expr, $y:expr) => {
530        let vec1 = $x.vector();
531        let vec2 = $y.vector();
532        prop_assert_complex_approx_eq!(vec1[0], vec2[0]);
533        prop_assert_complex_approx_eq!(vec1[1], vec2[1]);
534    };
535}
536
537#[cfg(test)]
538macro_rules! prop_assert_matrix_approx_eq {
539    ($x:expr, $y:expr) => {
540        prop_assert_complex_approx_eq!($x[(0, 0)], $y[(0, 0)]);
541        prop_assert_complex_approx_eq!($x[(0, 1)], $y[(0, 1)]);
542        prop_assert_complex_approx_eq!($x[(1, 0)], $y[(1, 0)]);
543        prop_assert_complex_approx_eq!($x[(1, 1)], $y[(1, 1)]);
544    };
545}
546
547// Beam tests
548#[cfg(test)]
549mod test {
550    use super::*;
551
552    proptest! {
553       #[test]
554       fn test_intensity_one_real_component(n: f64) {
555           let beam = Beam::new(
556               Complex::new(n, 0_f64),
557               Complex::new(0_f64, 0_f64),
558           );
559           let intensity = beam.intensity();
560           prop_assume!(intensity.is_ok());
561           prop_assert_approx_eq!(n.powi(2), intensity.unwrap());
562       }
563
564       #[test]
565       fn test_intensity_is_never_negative(beam: Beam) {
566           prop_assume!(beam.intensity().is_ok());
567           let intensity = beam.intensity().unwrap();
568           prop_assert!(intensity >= 0.0);
569       }
570
571       #[test]
572       fn test_intensity_mag_with_complex(x in any_complex(), y in any_complex()) {
573           let xr = x.re;
574           let xi = x.im;
575           let yr = y.re;
576           let yi = y.im;
577           let by_hand = xr.powi(2) + xi.powi(2) + yr.powi(2) + yi.powi(2);
578           let beam = Beam::new(x, y);
579           prop_assume!(beam.intensity().is_ok());
580           prop_assert_approx_eq!(beam.intensity().unwrap(), by_hand);
581       }
582
583       #[test]
584       fn test_common_phase_preserves_x_mag(beam: Beam) {
585           let old_x_mag = beam.x().norm();
586           let new_beam = beam.remove_common_phase();
587           let new_x_mag = new_beam.x().norm();
588           prop_assert_eq!(old_x_mag, new_x_mag);
589       }
590
591       #[test]
592       fn test_common_phase_preserves_y_mag(beam: Beam) {
593           let old_y_mag = beam.y().norm();
594           let new_beam = beam.remove_common_phase();
595           let new_y_mag = new_beam.y().norm();
596           prop_assert!((old_y_mag - new_y_mag).abs() < 1e-6);
597       }
598
599       #[test]
600       fn test_common_phase_zeroes_x_phase(beam: Beam) {
601           let new_beam = beam.remove_common_phase();
602           let new_phase = new_beam.x().arg();
603           prop_assert!(new_phase.abs() < 1e-6);
604       }
605
606       #[test]
607       fn test_common_phase_correct_y_phase(beam: Beam) {
608           let old_y_phase = beam.y().arg();
609           let old_x_phase = beam.x().arg();
610           let new_beam = beam.remove_common_phase();
611           let new_y_phase = new_beam.y().arg();
612           let mut expected_y_phase = old_y_phase - old_x_phase;
613           if beam.y().norm() == 0.0 {
614               // Regardless of the difference between old_y_phase and old_x_phase, if the magnitude of
615               // y is +0.0, the new phase will be wiped out. The complex number is stored as real and
616               // imaginary parts computed as mag*cos(phase) and mag*sin(phase) respectively, so when
617               // the magnitude is zero, mag*sin(phase) is also zero. This makes it such that the phase
618               // you use to construct the complex number will not be the phase returned by
619               // beam.y().arg().
620               expected_y_phase = 0.0;
621           } else {
622               // Get the phase in the range [-pi, pi]
623               while expected_y_phase < -pi {
624                   expected_y_phase = expected_y_phase + 2.0 * pi;
625               }
626               while expected_y_phase > pi {
627                   expected_y_phase = expected_y_phase - 2.0 * pi;
628               }
629           }
630           // If the magnitude is zero, the phase will also be set to zero due to how
631           // Complex::from_polar is implemented, so the phases won't match up. In this
632           // test I'll check that the magnitude is zero if the phases don't match.
633           if (expected_y_phase - new_y_phase).abs() > 1e-6 {
634               let new_norm = new_beam.y().norm();
635               if new_norm.abs() == 0.0 {
636                   prop_assert!(new_y_phase.abs() < 1e-6);
637               }
638           } else {
639               prop_assert!((expected_y_phase - new_y_phase).abs() < 1e-6);
640           }
641       }
642
643       #[test]
644       fn test_common_phase_preserves_intensity(beam: Beam) {
645           let old_intensity = beam.intensity();
646           prop_assume!(old_intensity.is_ok());
647           let new_beam = beam.remove_common_phase();
648           let new_intensity = new_beam.intensity();
649           prop_assert!(new_intensity.is_ok());
650           prop_assert!((new_intensity.unwrap() - old_intensity.unwrap()).abs() < 1e-6);
651       }
652
653        #[test]
654        fn test_common_phase_mut_preserves_x_mag(beam: Beam) {
655            let old_x_mag = beam.x().norm();
656            beam.remove_common_phase();
657            let new_x_mag = beam.x().norm();
658            prop_assert_eq!(old_x_mag, new_x_mag);
659        }
660
661        #[test]
662        fn test_common_phase_mut_preserves_y_mag(beam: Beam) {
663            let old_y_mag = beam.y().norm();
664            beam.remove_common_phase();
665            let new_y_mag = beam.y().norm();
666            prop_assert!((old_y_mag - new_y_mag).abs() < 1e-6);
667        }
668
669        #[test]
670        fn test_common_phase_mut_zeroes_x_phase(mut beam: Beam) {
671            beam.remove_common_phase_mut();
672            let new_phase = beam.x().arg();
673            prop_assert!(new_phase.abs() < 1e-6);
674        }
675
676        #[test]
677        fn test_common_phase_mut_correct_y_phase(mut beam: Beam) {
678            let old_y_phase = beam.y().arg();
679            let old_x_phase = beam.x().arg();
680            beam.remove_common_phase_mut();
681            let new_y_phase = beam.y().arg();
682            let mut expected_y_phase = old_y_phase - old_x_phase;
683            if beam.y().norm() == 0.0 {
684               // Regardless of the difference between old_y_phase and old_x_phase, if the magnitude of
685               // y is +0.0, the new phase will be wiped out. The complex number is stored as real and
686               // imaginary parts computed as mag*cos(phase) and mag*sin(phase) respectively, so when
687               // the magnitude is zero, mag*sin(phase) is also zero. This makes it such that the phase
688               // you use to construct the complex number will not be the phase returned by
689               // beam.y().arg().
690                expected_y_phase = 0.0;
691            } else {
692                // Get the phase in the range [-pi, pi]
693                while expected_y_phase < - pi {
694                    expected_y_phase = expected_y_phase + 2.0 * pi;
695                }
696                while expected_y_phase > pi {
697                    expected_y_phase = expected_y_phase - 2.0 * pi;
698                }
699            }
700            // If the magnitude is zero, the phase will also be set to zero due to how
701            // Complex::from_polar is implemented, so the phases won't match up. In this
702            // test I'll check that the magnitude is zero if the phases don't match.
703            if (expected_y_phase - new_y_phase).abs() > 1e-6 {
704                let new_norm = beam.y().norm();
705                if new_norm.abs() == 0.0 {
706                    prop_assert!(new_y_phase.abs() < 1e-6);
707                }
708            } else {
709                prop_assert!((expected_y_phase - new_y_phase).abs() < 1e-6);
710            }
711        }
712
713        #[test]
714        fn test_common_phase_mut_preserves_intensity(beam: Beam) {
715            let old_intensity = beam.intensity();
716            prop_assume!(old_intensity.is_ok());
717            beam.remove_common_phase();
718            let new_intensity = beam.intensity();
719            prop_assert!(new_intensity.is_ok());
720            prop_assert!((new_intensity.unwrap() - old_intensity.unwrap()).abs() < 1e-6);
721        }
722
723
724       #[test]
725       fn test_relative_phase(x in any_complex(), y in any_complex()) {
726           let expected_rad = y.arg() - x.arg();
727           let expected_deg = expected_rad.to_degrees();
728           let beam = Beam::new(x, y);
729           prop_assert_approx_eq!(expected_rad, beam.relative_phase_rad());
730           prop_assert_approx_eq!(expected_deg, beam.relative_phase_deg());
731       }
732
733        #[test]
734        fn test_construct_beam_from_xy(x in any_complex(), y in any_complex()) {
735            let beam = Beam::new(x, y);
736            prop_assert_eq!(beam.x(), x);
737            prop_assert_eq!(beam.y(), y);
738        }
739
740        #[test]
741        fn test_rotate_360_degrees_returns_original(m00 in any_complex(),
742                                                    m01 in any_complex(),
743                                                    m10 in any_complex(),
744                                                    m11 in any_complex()) {
745            let mat = Matrix2::new(m00, m01, m10, m11);
746            let angle = Angle::Degrees(360_f64);
747            let rotated = rotate_matrix(&mat, &angle);
748            prop_assert_matrix_approx_eq!(mat, rotated);
749        }
750
751       #[test]
752       fn test_rotate_2pi_rad_returns_original(m00 in any_complex(),
753                                               m01 in any_complex(),
754                                               m10 in any_complex(),
755                                               m11 in any_complex()) {
756           let mat = Matrix2::new(m00, m01, m10, m11);
757           let angle = Angle::Radians(2.0 * pi);
758           let rotated = rotate_matrix(&mat, &angle);
759           prop_assert_matrix_approx_eq!(mat, rotated);
760       }
761
762       #[test]
763       fn test_rotate_0_degrees_returns_original(m00 in any_complex(),
764                                                 m01 in any_complex(),
765                                                 m10 in any_complex(),
766                                                 m11 in any_complex()) {
767           let mat = Matrix2::new(m00, m01, m10, m11);
768           let angle = Angle::Degrees(0_f64);
769           let rotated = rotate_matrix(&mat, &angle);
770           prop_assert_matrix_approx_eq!(mat, rotated);
771       }
772
773       #[test]
774       fn test_rotate_0_rad_returns_original(m00 in any_complex(),
775                                             m01 in any_complex(),
776                                             m10 in any_complex(),
777                                             m11 in any_complex()) {
778           let mat = Matrix2::new(m00, m01, m10, m11);
779           let angle = Angle::Radians(0_f64);
780           let rotated = rotate_matrix(&mat, &angle);
781           prop_assert_matrix_approx_eq!(mat, rotated);
782       }
783
784    }
785}