use super::common::{Beam, ComplexMatrix, JonesError, JonesMatrix, JonesVector, Result};
use super::composite::CompositeElement;
use super::hwp::HalfWavePlate;
use super::identity::IdentityElement;
use super::polarizer::Polarizer;
use super::qwp::QuarterWavePlate;
use super::retarder::Retarder;
use super::rotator::PolarizationRotator;
#[cfg(test)]
use proptest::option;
#[cfg(test)]
use proptest::prelude::*;
#[derive(Debug, Copy, Clone)]
pub enum OpticalElement {
Polarizer(Polarizer),
PolarizationRotator(PolarizationRotator),
Retarder(Retarder),
QuarterWavePlate(QuarterWavePlate),
HalfWavePlate(HalfWavePlate),
Identity(IdentityElement),
Composite(CompositeElement),
}
impl OpticalElement {
pub fn matrix(&self) -> ComplexMatrix {
match self {
OpticalElement::Polarizer(pol) => pol.matrix(),
OpticalElement::PolarizationRotator(rot) => rot.matrix(),
OpticalElement::Retarder(ret) => ret.matrix(),
OpticalElement::QuarterWavePlate(qwp) => qwp.matrix(),
OpticalElement::HalfWavePlate(hwp) => hwp.matrix(),
OpticalElement::Identity(id) => id.matrix(),
OpticalElement::Composite(comp) => comp.matrix(),
}
}
}
#[cfg(test)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum OpticalElementType {
Polarizer,
PolarizationRotator,
Retarder,
QuarterWavePlate,
HalfWavePlate,
Identity,
Composite,
Any,
}
#[cfg(test)]
impl Default for OpticalElementType {
fn default() -> Self {
OpticalElementType::Any
}
}
#[cfg(test)]
impl Arbitrary for OpticalElement {
type Parameters = OpticalElementType;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
match args {
OpticalElementType::Polarizer => any::<Polarizer>()
.prop_map(|x| OpticalElement::Polarizer(x))
.boxed(),
OpticalElementType::PolarizationRotator => any::<PolarizationRotator>()
.prop_map(|x| OpticalElement::PolarizationRotator(x))
.boxed(),
OpticalElementType::Retarder => any::<Retarder>()
.prop_map(|x| OpticalElement::Retarder(x))
.boxed(),
OpticalElementType::QuarterWavePlate => any::<QuarterWavePlate>()
.prop_map(|x| OpticalElement::QuarterWavePlate(x))
.boxed(),
OpticalElementType::HalfWavePlate => any::<HalfWavePlate>()
.prop_map(|x| OpticalElement::HalfWavePlate(x))
.boxed(),
OpticalElementType::Identity => any::<IdentityElement>()
.prop_map(|x| OpticalElement::Identity(x))
.boxed(),
OpticalElementType::Composite => any::<CompositeElement>()
.prop_map(|x| OpticalElement::Composite(x))
.boxed(),
OpticalElementType::Any => prop_oneof![
any::<IdentityElement>().prop_map(|x| OpticalElement::Identity(x)),
any::<Polarizer>().prop_map(|x| OpticalElement::Polarizer(x)),
any::<PolarizationRotator>().prop_map(|x| OpticalElement::PolarizationRotator(x)),
any::<QuarterWavePlate>().prop_map(|x| OpticalElement::QuarterWavePlate(x)),
any::<HalfWavePlate>().prop_map(|x| OpticalElement::HalfWavePlate(x)),
any::<Retarder>().prop_map(|x| OpticalElement::Retarder(x)),
any::<CompositeElement>().prop_map(|x| OpticalElement::Composite(x)),
]
.boxed(),
}
.boxed()
}
}
#[derive(Debug, Clone)]
pub struct OpticalSystem {
pub beam: Option<Beam>,
pub elements: Option<Vec<OpticalElement>>,
}
impl OpticalSystem {
pub fn new() -> Self {
OpticalSystem {
beam: None,
elements: None,
}
}
pub fn add_beam(self, beam: Beam) -> Self {
OpticalSystem {
beam: Some(beam),
elements: self.elements,
}
}
pub fn add_element(self, elem: OpticalElement) -> Self {
let elements = match self.elements {
Some(elements) => {
let mut new_elements = elements.clone();
new_elements.push(elem);
new_elements
}
None => vec![elem],
};
OpticalSystem {
beam: self.beam,
elements: Some(elements),
}
}
pub fn add_elements(self, elems: Vec<OpticalElement>) -> Self {
if self.elements.is_none() {
let system = OpticalSystem {
beam: self.beam,
elements: Some(elems),
};
return system;
}
let mut elements = self.elements.clone().unwrap();
elements.extend(elems);
OpticalSystem {
beam: self.beam,
elements: Some(elements),
}
}
pub fn add_beam_mut(&mut self, beam: Beam) {
self.beam = Some(beam);
}
pub fn clear_beam(&mut self) {
self.beam = None;
}
pub fn add_element_mut(&mut self, elem: OpticalElement) {
let elems = self.elements.take();
match elems {
Some(mut elems) => {
elems.push(elem);
self.elements = Some(elems);
},
None => {
self.elements = Some(vec![elem]);
}
}
}
pub fn add_elements_mut(&mut self, elems: Vec<OpticalElement>) {
let existing = self.elements.take();
match existing {
Some(mut existing) => {
existing.extend(elems);
self.elements = Some(existing);
},
None => {
self.elements = Some(elems);
}
}
}
pub fn clear_elements(&mut self) {
self.elements = None;
}
pub fn propagate(&self) -> Result<Beam> {
if self.elements.is_none() {
return Err(JonesError::NoElements);
}
if self.beam.is_none() {
return Err(JonesError::NoBeam);
}
let composite = self.composed_elements().unwrap();
Ok(self.beam.clone().unwrap().apply_element(composite))
}
#[allow(clippy::let_and_return)]
pub fn composed_elements(&self) -> Result<CompositeElement> {
use self::OpticalElement::*;
if self.elements.is_none() {
return Err(JonesError::NoElements);
}
if self.beam.is_none() {
return Err(JonesError::NoBeam);
}
let ident = IdentityElement::new();
let composite_mat =
self.elements
.clone()
.unwrap()
.iter()
.fold(ident.matrix(), |acc, elem| {
let mat = match *elem {
Polarizer(pol) => pol.matrix(),
PolarizationRotator(rot) => rot.matrix(),
Retarder(ret) => ret.matrix(),
QuarterWavePlate(qwp) => qwp.matrix(),
HalfWavePlate(hwp) => hwp.matrix(),
Identity(id) => id.matrix(),
Composite(comp) => comp.matrix(),
};
let new = mat * acc;
new
});
Ok(CompositeElement::from_matrix(composite_mat))
}
}
#[cfg(test)]
impl Arbitrary for OpticalSystem {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
(
option::of(any::<Beam>()),
option::of(any::<Vec<OpticalElement>>()),
)
.prop_map(|(beam, elements)| OpticalSystem { beam, elements })
.boxed()
}
}
#[cfg(test)]
mod test {
use super::*;
use jones::common::{are_rel_eq, Angle};
use na::Matrix2;
use num::complex::Complex;
#[test]
fn test_elements_are_applied_in_order() {
let beam = Beam::linear(Angle::Degrees(0.0));
let zero_beam = Beam::new(Complex::new(0_f64, 0_f64), Complex::new(0_f64, 0_f64));
let pol = Polarizer::new(Angle::Degrees(90.0));
let rot = PolarizationRotator::new(Angle::Degrees(90.0));
let system = OpticalSystem::new()
.add_beam(beam)
.add_element(OpticalElement::Polarizer(pol))
.add_element(OpticalElement::PolarizationRotator(rot));
let beam_after = system.propagate();
assert!(beam_after.is_ok());
assert_beam_approx_eq!(beam_after.unwrap(), zero_beam);
}
#[test]
fn test_element_is_added_to_system() {
let ident = IdentityElement::new();
let mut system = OpticalSystem::new().add_element(OpticalElement::Identity(ident));
assert_eq!(system.elements.clone().unwrap().len(), 1);
system = system.add_element(OpticalElement::Identity(ident));
assert_eq!(system.elements.clone().unwrap().len(), 2);
}
proptest!{
#[test]
fn test_element_is_added_in_place(mut sys: OpticalSystem, elem: OpticalElement) {
let len_before = match sys.elements.clone() {
Some(elems) => {
elems.len()
},
None => 0
};
let len_expected = len_before + 1;
sys.add_element_mut(elem);
let len_after = sys.elements.clone().unwrap().len();
prop_assert_eq!(len_after, len_expected);
}
#[test]
fn test_elements_are_added_in_place(mut sys: OpticalSystem, elems: Vec<OpticalElement>) {
let len_before = match sys.elements.clone() {
Some(existing) => {
existing.len()
},
None => 0
};
let len_to_add = elems.len();
let len_expected = len_before + len_to_add;
sys.add_elements_mut(elems);
let len_after = sys.elements.clone().unwrap().len();
prop_assert_eq!(len_after, len_expected);
}
#[test]
fn test_beam_is_added_in_place(mut sys: OpticalSystem, beam: Beam) {
let beam_clone = beam.clone();
sys.add_beam_mut(beam);
let in_system = sys.beam.take().unwrap();
prop_assert_beam_approx_eq!(in_system, beam_clone);
}
#[test]
fn test_beam_is_cleared(mut sys: OpticalSystem) {
prop_assume!(sys.beam.is_some());
sys.clear_beam();
prop_assert!(sys.beam.is_none());
}
#[test]
fn test_elements_are_cleared(mut sys: OpticalSystem) {
prop_assume!(sys.elements.is_some());
sys.clear_elements();
prop_assert!(sys.elements.is_none());
}
#[test]
fn test_elements_are_added_to_system_during_construction(elems: Vec<OpticalElement>) {
let num_elems = elems.len();
let system = OpticalSystem::new().add_elements(elems);
let num_elems_in_system = system.elements.clone().unwrap().len();
prop_assert_eq!(num_elems, num_elems_in_system);
}
#[test]
fn test_additional_elements_are_added_to_system(elems1: Vec<OpticalElement>, elems2: Vec<OpticalElement>) {
let len1 = elems1.len();
let len2 = elems2.len();
let mut system = OpticalSystem::new().add_elements(elems1);
let len_after_elems1 = system.elements.clone().unwrap().len();
prop_assert_eq!(len_after_elems1, len1);
system = system.add_elements(elems2);
let len_after_elems2 = system.elements.clone().unwrap().len();
let expected_len = len1 + len2;
prop_assert_eq!(len_after_elems2, expected_len);
}
#[test]
fn test_beam_passes_through(beam: Beam) {
let ident = IdentityElement::new();
let element = OpticalElement::Identity(ident);
let system = OpticalSystem::new()
.add_beam(beam.clone())
.add_element(element);
let beam_after = system.propagate();
assert!(beam_after.is_ok());
assert_beam_approx_eq!(beam_after.unwrap(), beam);
}
#[test]
fn test_single_composed_element_is_returned_untouched(beam: Beam, elem: OpticalElement) {
let system = OpticalSystem::new()
.add_beam(beam)
.add_element(elem.clone());
let composed = system.composed_elements().unwrap().matrix();
let original = elem.matrix();
prop_assert_matrix_approx_eq!(composed, original);
}
#[test]
fn test_composed_identity_looks_like_identity(beam: Beam) {
let ident = IdentityElement::new();
let system = OpticalSystem::new()
.add_beam(beam)
.add_element(OpticalElement::Identity(ident.clone()))
.add_element(OpticalElement::Identity(ident.clone()))
.add_element(OpticalElement::Identity(ident.clone()));
let composed = system.composed_elements().unwrap().matrix();
prop_assert_matrix_approx_eq!(composed, ident.matrix());
}
#[test]
fn test_mat_composed_with_identity_is_untouched(beam: Beam, elem: CompositeElement) {
let ident = IdentityElement::new();
let system = OpticalSystem::new()
.add_beam(beam)
.add_element(OpticalElement::Identity(ident))
.add_element(OpticalElement::Composite(elem.clone()));
let composed = system.composed_elements().unwrap().matrix();
prop_assert_matrix_approx_eq!(composed, elem.matrix());
}
#[test]
fn test_system_propagates_as_if_elements_applied_individually(beam: Beam, elements: Vec<OpticalElement>) {
let system = OpticalSystem::new()
.add_beam(beam.clone())
.add_elements(elements.clone());
let by_propagation = system.propagate();
prop_assume!(by_propagation.is_ok());
let by_propagation = by_propagation.unwrap();
let mut by_application = beam;
for elem in elements {
match elem {
OpticalElement::Identity(inner) => by_application = by_application.apply_element(inner),
OpticalElement::Composite(inner) => by_application = by_application.apply_element(inner),
OpticalElement::PolarizationRotator(inner) => by_application = by_application.apply_element(inner),
OpticalElement::Polarizer(inner) => by_application = by_application.apply_element(inner),
OpticalElement::QuarterWavePlate(inner) => by_application = by_application.apply_element(inner),
OpticalElement::HalfWavePlate(inner) => by_application = by_application.apply_element(inner),
OpticalElement::Retarder(inner) => by_application = by_application.apply_element(inner),
}
}
prop_assert_beam_approx_eq!(by_propagation, by_application);
}
#[test]
fn test_composed_matrix_looks_like_multiplied_individuals(beam: Beam, elements: Vec<OpticalElement>) {
let system = OpticalSystem::new()
.add_beam(beam.clone())
.add_elements(elements.clone());
let composed = system.composed_elements().unwrap().matrix();
let mut multiplied: Matrix2<Complex<f64>> = Matrix2::identity();
for elem in elements {
match elem {
OpticalElement::Identity(inner) => multiplied = inner.matrix() * multiplied,
OpticalElement::Composite(inner) => multiplied = inner.matrix() * multiplied,
OpticalElement::PolarizationRotator(inner) => multiplied = inner.matrix() * multiplied,
OpticalElement::Polarizer(inner) => multiplied = inner.matrix() * multiplied,
OpticalElement::QuarterWavePlate(inner) => multiplied = inner.matrix() * multiplied,
OpticalElement::HalfWavePlate(inner) => multiplied = inner.matrix() * multiplied,
OpticalElement::Retarder(inner) => multiplied = inner.matrix() * multiplied,
}
}
prop_assert_matrix_approx_eq!(composed, multiplied);
}
}
}