"""Polarization
==================
Here we implement the ``Polarization`` class that we use to define the polarization of lasers
Remarks
--------
We define the polarization in the **frame of the laser**, with laser propagation along z.
We denote **x** as the **'horizontal'** axis and **y** as the **'vertical'** axis.
For circular polarizations, we take the **observer convention**.
To have a combined formalism for all polarization, in the end we define a **polarization vector** ``p_vec``,
following the Poincarré formalism. **ATTENTION:** there might be different ways of defining this vector,
refer to the package documentation for a thorough definition.
In the case of 'linear' polarization, an additionnal argument ``angle`` has to be provided, that gives
the angle of the linear polarization with respect to the x axis. Hence ``angle = 0`` corresponds to a
linear polarization along x, and ``angle = pi/2`` to a linear polarization along y
In the case of 'vector' polarization, polarization vector has to be given with the ``vec`` argument. ``vec``
are the cartesian coordinates of the vector in the (x,y,z) basis. For instance :
| ``> vec = (1, 0, 0)`` : linear polarization along x
| ``> vec = (0, 1, 0)`` : linear polarization along y
| ``> vec = (0, 0, 1)`` : circular right polarization
| ``> vec = (0, 0, -1)`` : circular left polarization
**ATTENTION** : for ease of use, the vector does not have to be normalized, but the resulting one will
be.
"""
# % IMPORTS
import numpy as np
from abc import ABC, abstractmethod
# % LOCAL IMPORTS
from ...utils.infostring import InfoString
# % ABSTRACT CLASS
[docs]
class Polarization(ABC):
"""An object to handle laser polarization."""
def __init__(self):
self._vector = None
self._u = None
self._v = None
# -- PROPERTIES
@property
def vector(self):
vector = np.asanyarray(self._vector)
return vector
# -- METHODS
[docs]
def get_polarization_vector_angles(self) -> tuple:
"""Returns the angles describing the current polarization vector.
(see documentation for thorough description)
Returns
-------
u, v : floats
the u (polar) and v (azimuthal) angles
Notes
-----
The polarization is decribed in the Poincarré/Bloch-like sphere as a vector.
This function yields the angles u (polar) and v (azimuthal)
Note that we do not use theta or phi as those angles are already used to
describe the orientation of the laser propagation vector in the ``LaserBeam`` class
"""
u = self._u
v = self._v
return u, v
[docs]
def refresh_polarization_vector_angles(self):
"""Updates the polarization vector angles u & v to match the current value of the
polarization vector.
"""
x, y, z = self.vector
u = np.arctan2(np.sqrt(x**2 + y**2), z)
v = np.arctan2(y, x)
self._u = u
self._v = v
[docs]
def get_polarization_vector_projection(self, target: str) -> complex:
"""Returns the scalar projection of the current polarization vector on a target polarization state
Parameters
----------
target : str
the state on which to project (see docstring Notes)
Returns
-------
proj: complex
the projection
Notes
-----
The polarization Psi is defined as :
|Psi⟩ = exp(-i*v) cos(u/2) |R⟩ + exp(i*v) sin(u/2) |L⟩
with |R⟩, |L⟩ the right- and left-handed circular polarization states. We also have
|x⟩ = |V⟩ = (1/sqrt(2)) (|L⟩ + |R⟩)
|y⟩ = |H⟩ = (i/sqrt(2)) (|L⟩ - |L⟩)
Target should refer to the special polarization states defined in the class :
>>> 'vertical', 'horizontal', 'circular left', 'circular right'
and corresponding shorthands:
>>> 'V' or 'x', 'H' or 'y', 'R', 'L'
"""
# get angle values
u, v = self.get_polarization_vector_angles()
# common calculations
A = np.exp(-1j * v) * np.cos(u / 2)
B = np.exp(1j * v) * np.sin(u / 2)
# return projection on desired vector
match target.upper():
case "V" | "X" | "VERTICAL":
proj = (A + B) / np.sqrt(2)
case "H" | "Y" | "HORIZONTAL":
proj = 1j * (A - B) / np.sqrt(2)
case "R" | "CIRCULAR RIGHT":
proj = A
case "L" | "CIRCULAR LEFT":
proj = B
case _:
GOOD = ["vertical", "horizontal", "circular left", "circular right"]
raise ValueError(f"Wrong value for target state, shoud be in {GOOD}")
return proj
[docs]
def get_polarization_vector_projection_norm(self, target: str) -> float:
"""Returns the squared norm of scalar projection of the current polarization vector on a target polarization state
Parameters
----------
target : str
the state on which to project (see docstring Notes)
Returns
-------
norm: float
the squared norm of the projection
Notes
-----
The polarization Psi is defined as :
|Psi⟩ = exp(-i*v) cos(u/2) |R⟩ + exp(i*v) sin(u/2) |L⟩
with |R⟩, |L⟩ the right- and left-handed circular polarization states. We also have
|x⟩ = |V⟩ = (1/sqrt(2)) (|L⟩ + |R⟩)
|y⟩ = |H⟩ = (i/sqrt(2)) (|L⟩ - |L⟩)
Target should refer to the special polarization states defined in the class :
>>> 'vertical', 'horizontal', 'circular left', 'circular right'
and corresponding shorthands:
>>> 'V' or 'x', 'H' or 'y', 'R', 'L'
"""
# get angle values
u, v = self.get_polarization_vector_angles()
# return projection on desired vector
match target.upper():
case "V" | "X" | "VERTICAL":
norm = 0.5 * (1 + 2 * np.cos(u / 2) * np.sin(u / 2) * np.cos(2 * v))
case "H" | "Y" | "HORIZONTAL":
norm = 0.5 * (1 - 2 * np.cos(u / 2) * np.sin(u / 2) * np.cos(2 * v))
case "R" | "CIRCULAR RIGHT":
norm = np.cos(u / 2) ** 2
case "L" | "CIRCULAR LEFT":
norm = np.sin(u / 2) ** 2
case _:
GOOD = ["vertical", "horizontal", "circular left", "circular right"]
raise ValueError(f"Wrong value for target state, shoud be in {GOOD}")
return norm
[docs]
def gen_infostring_obj(self):
"""Returns an info string object for the current polarization state"""
# - init InfoString object
info = InfoString("POLARIZATION PROPERTIES")
# - populate info string
# object settings
info.add_section("Polarization settings")
if isinstance(self, Linear):
info.add_element("type", self.type)
info.add_element("angle", f"{self.angle / np.pi:.2f} pi")
elif isinstance(self, Vector):
info.add_element("type", self.type)
info.add_element("vector", self.vector)
else:
info.add_element("type", self.type)
# vector
info.add_section("Polarization vector")
u, v = self.get_polarization_vector_angles()
x, y, z = self.vector
info.add_element("coords", f"({x:.2f}, {y:.2f}, {z:.2f})")
info.add_element("polar angle u", f"{u/np.pi:.2f} pi")
info.add_element("azimt angle v", f"{v/np.pi:.2f} pi")
# Projections (amplitudes)
u, v = self.get_polarization_vector_angles()
info.add_section("Projections (amplitudes)")
for target in ["vertical", "horizontal", "circular left", "circular right"]:
proj = self.get_polarization_vector_projection(target)
if target == "circular right":
info.add_element(target, f"{proj:.2f}")
else:
info.add_element(target, f"{proj:.2f}")
# Projections (norm)
u, v = self.get_polarization_vector_angles()
info.add_section("Projections (squared norm)")
for target in ["vertical", "horizontal", "circular left", "circular right"]:
proj = self.get_polarization_vector_projection_norm(target)
if target == "circular right":
info.add_element(target, f"{proj:.2f}")
else:
info.add_element(target, f"{proj:.2f}")
return info
def gen_info_string(self, **kwargs):
return self.gen_infostring_obj().generate(**kwargs)
def print_info(self):
print(self.gen_info_string())
# % ACTUAL IMPLEMENTATIONS
[docs]
class Vertical(Polarization):
"""Vertical polarization (along x in the laser frame)"""
def __init__(self):
super(Vertical, self).__init__()
self.type = "Vertical"
self._vector = (1, 0, 0)
self.refresh_polarization_vector_angles()
[docs]
class Horizontal(Polarization):
"""Horizontal polarization (along y in the laser frame)"""
def __init__(self):
super(Horizontal, self).__init__()
self.type = "Horizontal"
self._vector = (0, 1, 0)
self.refresh_polarization_vector_angles()
[docs]
class CircularLeft(Polarization):
"""Circular Left polarization (observer point of vue)"""
def __init__(self):
super(CircularLeft, self).__init__()
self.type = "Circular Left"
self._vector = (0, 0, -1)
self.refresh_polarization_vector_angles()
[docs]
class CircularRight(Polarization):
"""Circular Right polarization (observer point of vue)"""
def __init__(self):
super(CircularRight, self).__init__()
self.type = "Circular Right"
self._vector = (0, 0, 1)
self.refresh_polarization_vector_angles()
[docs]
class Linear(Polarization):
"""Arbitrary linear polarization.
Here, ``angle`` is the angle of the linear polarization with respect to the x axis.
Hence ``angle = 0`` corresponds to a linear polarization along x, and ``angle = pi/2`` to a linear polarization along y
Parameters
----------
angle : float
angle of the arbitrary linear polarization w.r.t the x axis (radians)
"""
def __init__(self, angle: float):
super(Linear, self).__init__()
self.type = "Linear"
self.angle = angle
@property
def angle(self) -> float:
"""float: angle of the arbitrary linear polarization w.r.t the x axis (radians)"""
return self._angle
@angle.setter
def angle(self, value: float) -> None:
# convert int into float
if isinstance(value, int):
value = float(value)
if not isinstance(value, float):
raise ValueError("Angle must be a float")
self._angle = value
self._vector = (np.cos(self.angle), np.sin(self.angle), 0)
self.refresh_polarization_vector_angles()
[docs]
class Vector(Polarization):
"""Allows to define an arbitrary polarization.
The polarization vector has to be given with the ``vector`` argument.
``vector`` are the cartesian coordinates of the vector in the (x,y,z) basis.
For instance :
| ``> vec = (1, 0, 0)`` : linear polarization along x
| ``> vec = (0, 1, 0)`` : linear polarization along y
| ``> vec = (0, 0, 1)`` : circular right polarization
| ``> vec = (0, 0, -1)`` : circular left polarization
**ATTENTION** : for ease of use, the vector does not have to be normalized, but the resulting one will
be.
Parameters
----------
vector : array of shape (,3)
polarization vector cartesian coordinates in laser frame
(see documentation for its exact definition)
"""
def __init__(self, vector: np.ndarray):
super(Vector, self).__init__()
self.type = "Vector"
self.vector = vector
@property
def vector(self):
"""array of shape (3,) : polarization vector cartesian coordinates in laser frame"""
vector = np.asanyarray(self._vector)
return vector
@vector.setter
def vector(self, value: np.ndarray) -> None:
# convert to array
value = np.asanyarray(value)
if value.size != 3:
raise ValueError("'vector' should be of size 3")
# normalize
norm = np.linalg.norm(value)
if norm == 0:
raise ValueError("Wrong value for 'vector'': norm is zero")
self._vector = value / norm
self.refresh_polarization_vector_angles()