# !/usr/bin/env python3
# -*- coding: utf-8 -*-
# ------------------------------------
# Authors: Luis Miguel Sanchez Brea and Jesus del Hoyo
# Date: 2019/01/09 (version 1.0)
# License: GPL
# -------------------------------------
"""
Jones_matrix objects describe optical polarization elements in the Jones formalism.
**Class fields:**
* **M**: 2x2xN array containing all the Jones matrices.
* **name**: Name of the object for print purposes.
* **shape**: Shape desired for the outputs.
* **size**: Number of stored Jones matrices.
* **ndim**: Number of dimensions for representation purposes.
* **no_rotation**: If True, rotation method do not act upon the object. Useful for objects that shouldn't be rotated as mirrors.
* **type**: Type of the object ('Jones_matrix'). This is used for determining the object class as using isinstance may throw unexpected results in .ipynb files.
* **parameters**: parameters of the Jones matrices.
* **checks**: checks of the Jones matrices.
* **analysis**: analysis of the Jones matrices.
**Parent methods**
* **clear**: Removes data and name form Jones vector.
* **copy**: Creates a copy of the Jones_vector object.
* **stretch**: Stretches a Jones vector of size 1.
* **shape_like**: Takes the shape of another object to use as its own.
* **reshape**: Changes the shape of the object.
* **flatten**: Transforms N-D objects into 1-D objects (0-D if only 1 element).
* **flip**: Flips the object along some dimensions.
* **get_list**: Creates a list with single elements.
* **from_list**: Creates the object from a list of single elements.
* **concatenate**: Canocatenates several objects into a single one.
* **draw**: Draws the components of the object.
* **clear**: Clears the information of the object.
**Generation methods**
* **from_components**: Creates a Jones_matrix object directly from the 4 elements (m00, m01, m10, m11).
* **from_matrix**: Creates a Jones_matrix object directly from a 2x2xN numpy arrays.
* **from_list**: Creates a Jones_matrix object directly from a list of 2x2 numpy arrays.
* **from_Mueller**: Takes a non-depolarizing Mueller Matrix and converts into Jones matrix.
* **vacuum**: Creates the matrix for vacuum.
* **mirror**: Creates the matrix for a mirror. NOTE: Don't rotate this matrix.
* **filter_amplifier**: Creates the matrix for a neutral filter or amplifier element
* **diattenuator_perfect**: Creates a perfect linear diattenuator.
* **diattenuator_linear**: Creates a real polarizer with perpendicular axes.
* **diattenuator_retarder_linear**: Creates a linear diattenuator retarder with the same axes for diattenuation and retardance.
* **diattenuator_charac_angles**: Creates the most general homogeneous diattenuator with orthogonal eigenstates from the characteristic angles of the main eigenstate.
* **diattenuator_azimuth_ellipticity**: Creates the general homogeneous diattenuator with orthogonal eigenstates from the characteristic angles of the main eigenstate.
* **quarter_waveplate**: Creates a quarter-waveplate.
* **half_waveplate**: Creates a half-waveplate.
* **retarder_linear**: Creates a retarder using delay.
* **retarder_material**: Creates a retarder using physical properties of a anisotropic material.
* **retarder_charac_angles**: Creates the most general homogenous retarder with orthogonal eigenstates from the characteristic angles of the main eigenstate.
* **retarder_azimuth_ellipticity**: Creates the general homogeneous retarder with orthogonal eigenstates from the characteristic angles of the main eigenstate.
**Manipulation methods**
* **rotate**: Rotates the Jones matrix.
* **sum**: Calculates the summatory of the Jones matrices in the object.
* **prod**: Calculates the product of the Jones matrices in the object.
* **remove_global_phase**: Removes the phase introduced by the optical element (respect to J00).
* **add_global_phase**: Increases the phase introduced by the optical element.
* **set_global_phase**: Sets the phase introduced by the optical element.
* **reciprocal**: The optical element is fliped so the light transverses it in the opposite direction.
* **transpose**: Transposes the Jones matrix of the element.
* **hermitan**: Calculates the hermitan matrix of the Jones matrix.
* **inverse**: Calculates the inverse matrix of the Jones matrix.
**Parameters subclass methods**
* **matrix**: Gets a numpy array with all the matrices.
* **components**: Extracts the four components of the Jones matrix.
* **inhomogeneity**: Calculates the inhomogeneity parameter.
* **diattenuation / polarizance**: Calculates the diattenuation of the matrix.
* **retardance**: Calculates the retardance (or delay) introduced between the fast and slow axes.
* **global_phase**: Calculates the phase introduced by the optical element (respect to J00).
* **transmissions**: Calculates the maximum and minimum field and/or intensity transmissions.
* **mean_transmission**: Calculates the mean intensity transmission.
* **eig**: Calculates the eigenvalues and eigenstates (eigenvectors) of the Jones matrices.
* **eigenvalues**: Calculates the eigenvalues and of the Jones matrices.
* **eigenstates**: Calculates the eigenstates (eigenvectors) of the Jones matrices.
* **det**: Calculates the determinant and of the Jones matrices.
* **trace**: Calculates the trace of the Jones matrices.
* **norm**: Calculates the norm of the Jones matrices.
* **get_all**: Returns a dictionary with all the parameters of the object.
**Checks subclass methods**
* **is_phisycall**: Check if the Jones matrices correspond to physically realizable optical elements.
* **is_homogeneous**: Determines if the matrices correspond to homogeneous optical elements.
* **is_retarder**: Checks if the Jones matrices correspond to homogeneous retarders.
* **is_diattenuator / is_polarizer**: Checks if the Jones matrices correspond to homogeneous diattenuators.
* **is_symmetric**: Checks if the Jones matrices are symmetric.
* **is_conjugate_symmetric**: Checks if the Jones matrices are conjugate symmetric.
* **is_eigenstate**: Checks if a given light state is an eigenstate of the objct.
* **get_all**: Returns a dictionary with all the checks of the object.
**Analysis subclass methods**
* **decompose_pure**: Decomposes the Jones matrices in two: an homogeneous retarder and diattenuator.
* **diattenuator / polarizer**: Analyzes the Jones matrices as if they were diattenuators.
* **retarder**: Analyzes the Jones matrices as if they were retarders.
"""
from .utils import *
from .py_pol import Py_pol
from .jones_vector import Jones_vector, create_Jones_vectors
from . import degrees, eps, np, num_decimals, um, number_types
from copy import deepcopy
from functools import wraps
from cmath import exp as cexp
import copy as copy
import cmath
import warnings
warnings.filterwarnings('ignore')
X_jones = np.diag([1, -1])
N_print_list = 5
print_list_spaces = 3
empty_matrix = np.array(np.zeros((2, 2, 1), dtype=float))
change_names = True
tol_default = eps
################################################################################
# Functions
################################################################################
[docs]
def create_Jones_matrices(name='J', N=1, out_object=True):
"""Function that creates several Jones_matrix objects at the same time from a list of names or a number.
Parameters:
name (string or list): Name of the object for print purposes. Default: 'J'.
N (int): Number of created elements. Default: 1.
out_object (bool): If N=1 and out_object is True the output is a Jones_matrix instead of a list. Default: True.
Attributes:
self.parameters (class): Class containing the measurable parameters of the Jones matrices.
self.checks (class): Class containing the methods that check something about the Jones matrices.
Returns:
J (list or Jones_matrix): Result.
"""
J = []
if isinstance(name, list) or isinstance(name, tuple):
for n in name:
J.append(Jones_matrix(n))
else:
for _ in range(N):
J.append(Jones_matrix(name))
if len(J) == 1 and out_object:
J = J[0]
return J
[docs]
def set_printoptions(N_list=None, list_spaces=None):
"""Function that modifies the global print options parameters.
Parameters:
N_list (int): Number of matrices that will be printed as a list if the shape of the object is 1D. Default: None
list_spaces (int): Number of spaces between matrices if they are printed as a list. Default: None
"""
global N_print_list, print_list_spaces
if list_spaces is not None:
print_list_spaces = list_spaces
if N_list is not None:
N_print_list = N_list
################################################################################
# Main class
################################################################################
[docs]
class Jones_matrix(Py_pol):
"""Class for Jones matrices.
Parameters:
M (np.ndarray): 2x2xN array containing all the Jones matrices.
name (string): Name of the object for print purposes.
shape (tuple or list): Shape desired for the outputs.
size (int): Number of stored Jones matrices.
ndim (int): Number of dimensions for representation purposes.
no_rotation (bool): If True, rotation method do not act upon the object. Useful for objects that shouldn't be rotated as mirrors.
type (string): Type of the object ('Jones_matrix'). This is used for determining the object class as using isinstance may throw unexpected results in .ipynb files.
Attributes:
self.parameters (class): parameters of the Jones matrices.
self.checks (class): checks of the Jones matrices.
self.analysis (class): analysis of the Jones matrices.
"""
__array_priority__ = 20000
############################################################################
# Operations
############################################################################
def __init__(self, name='J'):
super().__init__(name=name, _class="Jones_matrix")
self.no_rotation = False
self.parameters = Parameters_Jones_Matrix(self)
self.checks = Checks_Jones_Matrix(self)
self.analysis = Analysis_Jones_Matrix(self)
def __add__(self, other):
"""Adds two Jones matrices.
Parameters:
other (Jones_matrix): 2nd Jones matrix to add.
Returns:
j3 (Jones_matrix): Result.
"""
try:
if other.type == 'Jones_matrix':
j3 = Jones_matrix()
j3.M = self.M + other.M
j3.shape = take_shape((self, other))
if change_names:
j3.name = self.name + " + " + other.name
return j3
else:
raise ValueError('other is {} instead of Jones_matrix.'.format(
other.type))
except:
raise ValueError('other is not a py_pol object')
def __sub__(self, other):
"""Substracts two Jones matrices.
Parameters:
other (Jones_matrix): 2nd Jones matrix to substract.
Returns:
j3 (Jones_matrix): Result.
"""
try:
if other.type == 'Jones_matrix':
j3 = Jones_matrix()
j3.M = self.M - other.M
j3.shape = take_shape((self, other))
if change_names:
j3.name = self.name + " - " + other.name
return j3
else:
raise ValueError('other is {} instead of Jones_matrix.'.format(
other.type))
except:
raise ValueError('other is not a py_pol object')
def __mul__(self, other):
"""
Multiplies the Jones matrix by a number, an array of numbers, a Jones vector or another Jones matrix.
Parameters:
other (float, numpy.ndarray, Jones_vector or Jones_matrix): 2nd object to multiply.
Returns:
j3 (Jones_matrix): Result.
"""
# Easy case, multiply by a number
if isinstance(other, number_types):
j3 = Jones_matrix()
j3.M = self.M * other
j3.shape = self.shape
if change_names:
j3.name = self.name + " * " + str(other)
# Multiply by an array of numbers
elif isinstance(other, np.ndarray):
j3 = Jones_matrix()
if other.size == self.size or self.size == 1:
if self.size == 1:
j3.M = np.multiply.outer(self.get_list(), other.flatten())
else:
j3.M = np.multiply.outer(np.ones(
(2, 2)), other.flatten()) * self.M
j3.shape = take_shape((self, other))
else:
raise ValueError(
'The number of elements in other and {} is not the same'.
format(self.name))
# Multiply by py_pol objects
elif other.type in ('Jones_vector', 'Jones_matrix'):
# Prepare variables
new_self, new_other = expand_objects([self, other], copy=True)
# print(new_self.M.shape, new_other.M.shape)
if other.type is 'Jones_vector':
j3 = Jones_vector()
else:
j3 = Jones_matrix()
# Multiply
Mf = matmul_pypol(new_self.M, new_other.M)
# if new_self.size == 1:
# Mf = new_self.get_list() @ new_other.get_list()
# else:
# # Move axes of the variables to allow multiplication
# M1 = np.moveaxis(new_self.M, 2, 0)
# if other.type is 'Jones_vector':
# M2 = np.moveaxis(new_other.M, 1, 0)
# M2 = np.expand_dims(M2, 2)
# Mf = M1 @ M2
# Mf = np.moveaxis(np.squeeze(Mf), 0, 1)
# else:
# M2 = np.moveaxis(new_other.M, 2, 0)
# Mf = M1 @ M2
# Mf = np.moveaxis(Mf, 0, 2)
j3.from_matrix(Mf)
j3.shape = take_shape((self, other))
if change_names:
j3.name = self.name + " * " + other.name
else:
raise ValueError('other thype ({}) is not correct'.format(
type(other)))
return j3
def __rmul__(self, other):
"""Multiplies a Jones matrix object by a number or array.
Parameters:
other (int, float, numpy.ndarray): 2nd element to multiply.
Returns:
j3 (Jones_matrix): Result.
"""
j3 = Jones_matrix()
# Easy case, multiply by a number
if isinstance(other, number_types):
j3.M = self.M * other
j3.shape = self.shape
if change_names:
j3.name = self.name + " * " + str(other)
# Multiply by an array of numbers
elif isinstance(other, np.ndarray):
if other.size == self.size or self.size == 1:
if self.size == 1:
j3.M = np.multiply.outer(self.get_list(), other.flatten())
else:
j3.M = np.multiply.outer(np.ones(
(2, 2)), other.flatten()) * self.M
j3.shape = take_shape((self, other.shape))
else:
raise ValueError(
'The number of elements in other and {} is not the same'.
format(self.name))
else:
raise ValueError('other thype ({}) is not correct'.format(
type(other)))
return j3
def __truediv__(self, other):
"""Divides a Jones matrix by a number or array.
Parameters:
other (int, float, numpy.ndarray): 2nd element to divide.
Returns:
j3 (Jones_matrix): Result.
"""
j3 = Jones_matrix()
# Easy case, multiply by a number
if isinstance(other, number_types):
j3.M = self.M / other
j3.shape = self.shape
if change_names:
j3.name = self.name + " / " + str(other)
# Multiply by an array of numbers
elif isinstance(other, np.ndarray):
if other.size == self.size or self.size == 1:
if self.size == 1:
j3.M = np.multiply.outer(self.get_list(),
1 / other.flatten())
else:
j3.M = np.multiply.outer(np.ones(
(2, 2)), 1 / other.flatten()) * self.M
j3.shape = take_shape((self, other))
else:
raise ValueError(
'The number of elements in other and {} is not the same'.
format(self.name))
else:
raise ValueError('other thype ({}) is not correct'.format(
type(other)))
return j3
def __repr__(self):
"""
Represents the Jones matrix with print().
"""
# Extract the components
J00, J01, J10, J11 = self.parameters.components()
if np.sum(self.M.imag**2)/self.size < tol_default:
J00, J01, J10, J11 = J00.real, J01.real, J10.real, J11.real
# If the object is empty, say it
if self.size == 0 or self.shape is None or np.all(self.M == 0):
return '{} is empty\n'.format(self.name)
# If the object is 0D or 1D, print it like a list or inline
# elif self.size == 1 or self.shape is None or len(self.shape) < 2:
elif self.ndim <= 1:
if self.size <= N_print_list:
list = self.get_list(out_number=False)
l0_name = "{} = \n".format(self.name)
l1_name = PrintMatrices(list, print_list_spaces)
return l0_name + l1_name
else:
l0_name = "{} J00 = {}".format(self.name, J00)
l1_name = " " * len(self.name) + " J01 = {}".format(J01)
l2_name = " " * len(self.name) + " J10 = {}".format(J10)
l3_name = " " * len(self.name) + " J11 = {}".format(J11)
# Print higher dimensionality as pure arrays
else:
l0_name = "{} J00 = \n{}".format(self.name, J00)
l1_name = "{} J01 = \n{}".format(self.name, J01)
l2_name = "{} J10 = \n{}".format(self.name, J10)
l3_name = "{} J11 = \n{}".format(self.name, J11)
return l0_name + '\n' + l1_name + '\n' + l2_name + '\n' + l3_name + '\n'
def __getitem__(self, index):
"""
Implements object extraction from indices.
"""
if change_names:
E = Jones_matrix(self.name + '_picked')
else:
E = Jones_matrix(self.name)
# If the indices are 1D, act upon the matrix directly
if isinstance(index, (int, slice)) and self.ndim > 1:
E.from_matrix(self.M[:, :, index])
elif isinstance(index,
np.ndarray) and index.ndim == 1 and self.ndim > 1:
E.from_matrix(self.M[:, :, index])
# If not, act upon the components
else:
J00, J01, J10, J11 = self.parameters.components(out_number=False)
M = np.array([[J00[index], J01[index]], [J10[index], J11[index]]])
E.from_matrix(M)
return E
def __setitem__(self, index, data):
"""
Implements object inclusion from indices.
"""
# Check that data is a correct pypol object
if self.type != data.type:
raise ValueError('data is type {} instead of {}.'.format(
data.type, self.type))
# Change to complex if necessary
if np.iscomplexobj(data.M):
self.M = np.array(self.M, dtype=complex)
# If the indices are 1D, act upon the matrix directly
if isinstance(index, int) and self.ndim > 1:
self.M[:, :, index] = np.squeeze(data.M)
elif isinstance(index, slice) and self.ndim > 1:
if data.size == 1:
if index.step is None:
step = 1
else:
step = index.step
N = int((index.stop - index.start) / step)
data2 = data.stretch(length=N, keep=True)
else:
data2 = data
self.M[:, :, index] = np.squeeze(data2.M)
elif isinstance(index,
np.ndarray) and index.ndim == 1 and self.ndim > 1:
self.M[:, :, index] = data.M
# If not, act upon the components
else:
J00, J01, J10, J11 = self.parameters.components(out_number=False)
J00_new, J01_new, J10_new, J11_new = data.parameters.components(
out_number=False)
J00[index] = np.squeeze(J00_new)
J01[index] = np.squeeze(J01_new)
J10[index] = np.squeeze(J10_new)
J11[index] = np.squeeze(J11_new)
self.from_components((J00, J01, J10, J11))
def __eq__(self, other):
"""
Implements equality operation.
"""
try:
if other.type == 'Jones_matrix':
j3 = self - other
norm = j3.parameters.norm()
cond = norm < tol_default
return cond
else:
return False
except:
return False
##################################################################
# Manipulation
##################################################################
# @_actualize_
[docs]
def rotate(self, angle=0, keep=False, change_name=change_names):
"""Rotates a jones_matrix a certain angle:
M_rotated = R(-angle) * self.M * R(angle)
Parameters:
angle (float): Rotation angle in radians. Default: 0
keep (bool): If True, the original element is not updated. Default: False.
change_name (bool): If True and angle is of size 1, changes the object name adding @ XX deg, being XX the total rotation angle. Default: True.
Returns:
(Jones_matrix): Rotated Jones matrix.
"""
# Don't rotate objects that shouldn't
if self.no_rotation:
print('Warning: Tried to rotate {}, which must not be rotated.'.
format(self.name))
return self
else:
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Prepare variables
angle, new_obj, new_shape = prepare_variables([angle],
expand=[True],
obj=new_obj,
give_shape=True)
# Calculate the rotation objects
Jneg, Jpos = create_Jones_matrices(('', ''))
Jneg.from_matrix(rotation_matrix_Jones(-angle))
Jpos.from_matrix(rotation_matrix_Jones(angle))
# Rotate
other = Jneg * (new_obj * Jpos)
new_obj.from_matrix(other.M)
# Update name
if change_name and angle.size == 1:
if np.abs(angle) > tol_default:
new_obj.name = new_obj.name + \
" @ {:1.2f} deg".format(angle[0] / degrees)
# Return
new_obj.shape, _ = select_shape(new_obj, new_shape)
return new_obj
# @_actualize_
[docs]
def reciprocal(self,
keep=False,
global_phase=0,
length=1,
shape_like=None,
shape=None,
change_name=change_names):
"""Calculates the reciprocal of the optical element, so the light tranverses it in the opposite direction. It is calculated as:
.. math:: J^{r}=\left[\begin{array}{cc}
1 & 0\\
0 & -1
\end{array}\right]J^{T}\left[\begin{array}{cc}
1 & 0\\
0 & -1
\end{array}\right]
References:
J.J. Gil, R. Ossikovsky "Polarized light and the Mueller Matrix approach", CRC Press (2016), pp 106.
Parameters:
keep (bool): If True, the original element is not updated. Default: False.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
change_name (bool): If True, changes the object name adding Recip. of at the beggining of the name. Default: True.
Returns:
(Jones_matrix): Result.
"""
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Extract the components
J00, J01, J10, J11 = new_obj.transpose(
keep=True).parameters.components(shape=False)
new_obj.from_components((J00, -J01, -J10, J11))
# Add the global phase
new_obj = new_obj.add_global_phase(global_phase,
length=length,
shape_like=shape_like,
shape=shape)
# Fix the name if required
if change_name:
new_obj.name = 'Reciprocal of ' + new_obj.name
return new_obj
[docs]
def transpose(self,
keep=False,
global_phase=0,
length=1,
shape_like=None,
shape=None,
change_name=change_names):
"""Calculates the transposed matrices of the Jones matrices.
Parameters:
keep (bool): if True, the original element is not updated. Default: False.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
change_name (bool): If True, changes the object name adding Recip. of at the beggining of the name. Default: True.
Returns:
(Jones_matrix): Modified object.
"""
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Caluclate inverse
new_obj.from_matrix(np.transpose(new_obj.M, axes=(1, 0, 2)))
# Add the global phase
new_obj = new_obj.add_global_phase(global_phase,
length=length,
shape_like=shape_like,
shape=shape)
# Fix the name if required
if change_name:
new_obj.name = 'Transpose of ' + new_obj.name
return new_obj
[docs]
def hermitian(self,
keep=False,
global_phase=0,
length=1,
shape_like=None,
shape=None,
change_name=change_names):
"""Calculates the hermitian conjugate matrix of the Mueller matrix.
Parameters:
keep (bool): if True, the original element is not updated. Default: False.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
change_name (bool): If True, changes the object name adding Recip. of at the beggining of the name. Default: True.
Returns:
(Jones_matrix): Modified object.
"""
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Caluclate inverse
new_obj.from_matrix(np.conj(np.transpose(new_obj.M, axes=(1, 0, 2))))
# Add the global phase
new_obj = new_obj.add_global_phase(global_phase,
length=length,
shape_like=shape_like,
shape=shape)
# Fix the name if required
if change_name:
new_obj.name = 'Hermitian of ' + new_obj.name
return new_obj
[docs]
def inverse(self,
keep=False,
global_phase=0,
length=1,
shape_like=None,
shape=None,
change_name=change_names):
"""Calculates the inverse matrix of the Jones matrix.
Parameters:
keep (bool): if True, the original element is not updated. Default: False.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
change_name (bool): If True, changes the object name adding Recip. of at the beggining of the name. Default: True.
Returns:
(Jones_matrix): Modified object.
"""
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Caluclate inverse
new_obj.from_matrix(inv_pypol(self.M))
# Add the global phase
new_obj = new_obj.add_global_phase(global_phase,
length=length,
shape_like=shape_like,
shape=shape)
new_obj.shape, _ = select_shape(shape_var=self.shape,
shape_like=shape_like,
shape_fun=shape)
# Fix the name if required
if change_name:
new_obj.name = 'Inverse of ' + new_obj.name
return new_obj
[docs]
def sum(self, axis=None, keep=False, change_name=change_names):
"""Calculates the sum of Jones matrices stored in the object.
Parameters:
axis (int, list or tuple): Axes along which the summatory is performed. If None, all matrices are summed.
keep (bool): If True, the original element is not updated. Default: False.
change_name (bool): If True, changes the object name adding Recip. of at the beggining of the name. Default: True.
Returns:
(Jones_matrix): Modified object.
"""
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Simple case
if axis is None or new_obj.ndim <= 1:
M = np.sum(new_obj.M, axis=2)
# Complicated case
else:
# Calculate maximum axis
if isinstance(axis, int):
axis = axis + 2
m = axis
else:
axis = np.array(axis) + 2
m = np.max(axis)
# Check that the axes are correct
if m >= new_obj.ndim + 2:
raise ValueError(
'Axis {} greater than the number of dimensions of {}, which is {}'
.format(m, new_obj.name, new_obj.ndim))
# Reshape M to fit the current shape
shape = [2, 2] + new_obj.shape
M = np.reshape(new_obj.M, shape)
# check if the axis is int or not
if isinstance(axis, int):
M = np.sum(M, axis=axis)
else:
M = np.sum(M, axis=tuple(axis))
# Create the object and return it
new_obj.from_matrix(M)
if change_names:
new_obj.name = 'Sum of ' + new_obj.name
return new_obj
[docs]
def prod(self, axis=None, keep=False, change_name=change_names):
"""Calculates the product of Jones matrices stored in the object.
Parameters:
axis (int, list or tuple): Axes along which the product is performed. If None, all matrices are multiplied.
keep (bool): if True, the original element is not updated. Default: False.
change_name (bool): If True, changes the object name adding Recip. of at the beggining of the name. Default: True.
Returns:
(Jones_matrix): Modified object.
"""
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Simple case
if axis is not None:
N_axis = np.array(axis).size
if axis is None or new_obj.ndim <= 1 or new_obj.ndim == N_axis:
M = new_obj.M[:, :, 0]
for ind in range(1, new_obj.size):
M = M @ new_obj.M[:, :, ind]
# Complicated case
else:
# Calculate maximum axis
if isinstance(axis, int):
m = axis + 2
else:
axis = np.array(axis)
m = np.max(axis) + 2
# Check that the axes are correct
if m >= new_obj.ndim + 2:
raise ValueError(
'Axis {} greater than the number of dimensions of {}, which is {}'
.format(m, new_obj.name, new_obj.ndim))
# Calculate shapes, sizes and indices
if isinstance(axis, int):
shape_removed = new_obj.shape[axis]
else:
shape_removed = np.array(new_obj.shape)[axis]
N_removed = np.prod(shape_removed)
ind_removed = combine_indices(
np.unravel_index(np.array(range(N_removed)), shape_removed))
shape_matrix = np.delete(new_obj.shape, axis)
N_matrix = np.prod(shape_matrix)
ind_matrix = combine_indices(
np.unravel_index(np.array(range(N_matrix)), shape_matrix))
shape_final = [2, 2] + list(shape_matrix)
axes_aux = np.array(range(2, new_obj.ndim + 2))
shape_orig = [2, 2] + list(new_obj.shape)
# Make the for loop of the matrix to be calculated
M_orig = np.reshape(new_obj.M, shape_orig)
M = np.zeros(shape_final)
for indM in range(N_matrix):
# Make the multiplication loop
indices = merge_indices(ind_matrix[indM], ind_removed[0], axis)
aux = multitake(M_orig, indices, axes_aux)
for indR in range(1, N_removed):
indices = merge_indices(ind_matrix[indM],
ind_removed[indR], axis)
aux = aux @ multitake(M_orig, indices, axes_aux)
# Store the result
ind_aux = tuple([0, 0] + list(ind_matrix[indM]))
M[ind_aux] = aux[0, 0]
ind_aux = tuple([0, 1] + list(ind_matrix[indM]))
M[ind_aux] = aux[0, 1]
ind_aux = tuple([1, 0] + list(ind_matrix[indM]))
M[ind_aux] = aux[1, 0]
ind_aux = tuple([1, 1] + list(ind_matrix[indM]))
M[ind_aux] = aux[1, 1]
# Create the object and return it
new_obj.from_matrix(M)
if change_names:
new_obj.name = 'Prod of ' + new_obj.name
return new_obj
# @_actualize_
[docs]
def remove_global_phase(self,
keep=False,
length=1,
shape_like=None,
shape=None):
"""Function that transforms the Jones vector removing the global phase, so J00 is real and positive.
Parameters:
keep (bool): If True, self is not updated. Default: False.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Simplified Jones matrix.
"""
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Calculate the phase and the components
old_shape = new_obj.shape
J00, J01, J10, J11 = self.parameters.components(shape=False)
phase = self.parameters.global_phase(shape=False)
# Remove the phase
phase = np.exp(1j * phase)
new_obj.from_components(
(J00 / phase, J01 / phase, J10 / phase, J11 / phase))
new_obj.shape = old_shape
# Return
return new_obj
[docs]
def add_global_phase(self,
global_phase=0,
keep=False,
length=1,
shape_like=None,
shape=None):
"""Function that adds a phase to the Jones matrix.
Parameters:
global_phase (float or np.ndarray): Phase to be added to the Jones matrix. Default: 0.
keep (bool): If True, self is not updated. Default: False.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Recalculated Jones matrix.
"""
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Prepare variables
global_phase, new_obj, new_shape = prepare_variables([global_phase],
expand=[True],
obj=new_obj,
give_shape=True)
# Add the phase
J00, J01, J10, J11 = new_obj.parameters.components(shape=False)
global_phase = np.exp(1j * global_phase)
new_obj.from_components((J00 * global_phase, J01 * global_phase,
J10 * global_phase, J11 * global_phase))
new_obj.shape, _ = select_shape(new_obj, new_shape)
# Return
return new_obj
[docs]
def set_global_phase(self,
global_phase=0,
keep=False,
length=1,
shape_like=None,
shape=None):
"""Function that sets the phase of the Jones matrix.
Parameters:
global_phase (float or np.ndarray): Phase to be added to the Jones matrix. Default: 0.
keep (bool): If True, self is not updated. Default: False.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Recalculated Jones matrix.
"""
# Act differently if we want to keep self intact
if keep:
new_obj = self.copy()
else:
new_obj = self
# Prepare variables
global_phase, new_obj, new_shape = prepare_variables([global_phase],
expand=[True],
obj=new_obj,
give_shape=True)
# Remove the current phase
new_obj.remove_global_phase(shape_like=shape_like, shape=shape)
# Add the phase
new_obj.add_global_phase(global_phase,
length=length,
shape_like=shape_like,
shape=shape)
new_obj.shape, _ = select_shape(new_obj, new_shape)
# Return
return new_obj
####################################################################
# Creation
####################################################################
[docs]
def from_components(self,
components,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates the Jones matrix object form the arrays of electric field components.
Parameters:
components (tuple or list): A 4 element tuple containing the 4 components of the Jones matrices (J00, J01, J10, J11).
global_phase (float or numpy.ndarray): Adds a global phase to the Jones matrix. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Prepare variables
J00, J01, J10, J11 = components
(J00, J01, J10, J11, global_phase), new_shape = prepare_variables(
vars=[J00, J01, J10, J11, global_phase],
expand=[True, True, True, True, False],
length=length,
give_shape=True)
# Add global Phase
if global_phase is not 0:
J00 = J00 * np.exp(1j * global_phase)
J01 = J01 * np.exp(1j * global_phase)
J10 = J10 * np.exp(1j * global_phase)
J11 = J11 * np.exp(1j * global_phase)
# Store
self.M = np.array([[J00, J01], [J10, J11]])
self.no_rotation = False
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
[docs]
def from_matrix(self,
M,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Create a Jones_matrix object from an external array.
Parameters:
M (numpy.ndarray): New matrix. At least two dimensions must be of size 2.
global_phase (float or numpy.ndarray): Adds a global phase to the Jones matrix. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Check if the matrix is of the correct Size
M = np.array(M)
s = M.size
# 1D and 2D
if M.ndim == 1 or M.ndim == 2:
if M.size % 4 == 0:
M = np.reshape(M, (2, 2, int(M.size / 4)))
else:
raise ValueError(
'M must have a number of elements multiple of 4.')
if M.size == 4:
sh = None
else:
sh = [int(M.size / 4)]
# 3D or more
elif M.ndim > 2:
sh = np.array(M.shape)
N = np.sum(sh == 2)
if N > 1:
# Find the matrix indices and the final shape
ind1 = np.argmin(~(sh == 2))
sh = np.delete(sh, ind1)
ind2 = np.argmin(~(sh == 2))
sh = np.delete(sh, ind2)
ind2 = ind2 + 1
# Calculate the components and construct the matrix from them
M = np.array([[
multitake(M, [0, 0], [ind1, ind2]).flatten(),
multitake(M, [0, 1], [ind1, ind2]).flatten()
],
[
multitake(M, [1, 0], [ind1, ind2]).flatten(),
multitake(M, [1, 1], [ind1, ind2]).flatten()
]])
else:
raise ValueError(
'M must have two elements in at least two dimensions.')
else:
raise ValueError('M can not be empty')
# Increase length if required
if M.size == 4 and length > 1:
M = np.multiply.outer(np.squeeze(M), np.ones(length))
# End operations
self.M = M
# self.size = M.size / 4
self.no_rotation = False
self.add_global_phase(global_phase)
self.shape, _ = select_shape(self,
shape_var=sh,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def from_Mueller(self, M, length=1, shape_like=None, shape=None):
"""Converts a pure Mueller matrix into Jones matrix object. Elements of Mueller object which are not pure are converted into NaN values. The values are found inverting the equation:
.. math:: M(J)=\left[\begin{array}{cccc}
1 & 0 & 0 & 1\\
1 & 0 & 0 & -1\\
0 & 1 & 1 & 0\\
0 & i & -i & 0
\end{array}\right]\left(J\otimes J^{*}\right)\left[\begin{array}{cccc}
1 & 0 & 0 & 1\\
1 & 0 & 0 & -1\\
0 & 1 & 1 & 0\\
0 & i & -i & 0
\end{array}\right]^{-1}
References:
Handbook of Optics vol 2. 22.36 (52-54)
Polarized Light and the Mueller matrix approach, J. J. Gil, pp 109.
Parameters:
M (Mueller): Mueller object.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (float or numpy.ndarray): Use the shape of this array. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Extract components from the Jones object and derivatives
M00, M01, M02, M03, M10, M11, M12, M13, M20, M21, M22, M23, M30, M31, M32, M33 = M.parameters.components(
shape=False)
# Calculate absolute value of the Jones components. Use abs to avoid -0
J00 = np.sqrt(np.abs(M00 + M01 + M10 + M11) / 2)
J01 = np.sqrt(np.abs(M00 - M01 + M10 - M11) / 2)
J10 = np.sqrt(np.abs(M00 + M01 - M10 - M11) / 2)
J11 = np.sqrt(np.abs(M00 - M01 - M10 + M11) / 2)
# Calculate the complex phases
phase_00 = M.parameters.global_phase(shape=False, give_nan=False)
phase_01 = np.arctan2(-M03 - M13, M02 + M12) + phase_00
phase_10 = np.arctan2(M30 + M31, M20 + M21) + phase_00
phase_11 = np.arctan2(M32 + M23, M22 + M33) + phase_00
self.from_components(
(J00 * np.exp(1j * phase_00), J01 * np.exp(1j * phase_01),
J10 * np.exp(1j * phase_10), J11 * np.exp(1j * phase_11)),
length=length,
shape=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def vacuum(self, global_phase=0, length=1, shape_like=None, shape=None):
"""Creates the matrix for vacuum i.e., an optically neutral element.
Parameters:
global_phase (float or numpy.ndarray): Adds a global phase to the Jones matrix. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Prepare variables
global_phase, new_shape = prepare_variables(vars=[global_phase],
expand=[True],
length=length,
give_shape=True)
# Calculate
global_phase = np.exp(1j * global_phase)
z = np.zeros_like(global_phase)
self.from_components((global_phase, z, z, global_phase))
self.no_rotation = False
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def filter_amplifier(self,
D=1,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates the Jones matrix of neutral filters or amplifiers.
Parameters:
D (float or numpy.ndarray): Attenuation (gain if > 1). Default: 1.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Prepare variables
(D,
global_phase), new_shape = prepare_variables(vars=[D, global_phase],
expand=[True, False],
length=length,
give_shape=True)
# Add global Phase
if global_phase is not 0:
D = D * np.exp(1j * global_phase)
# Calculate
z = np.zeros_like(D)
self.from_components((D, z, z, D))
self.no_rotation = False
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def mirror(self,
ref=1,
ref_field=None,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Jones matrix of a mirror.
Parameters:
ref (float or numpy.ndarray): Intensity reflectivity of the mirror. Default: 1.
ref_field (float or numpy.ndarray): Electric field reflectivity coefficient. If not None, it overrides REF. Default: None.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Use the field reflectivity
if ref_field is None:
ref_field = np.sqrt(ref)
# Prepare variables
(ref_field, global_phase), new_shape = prepare_variables(
vars=[ref_field, global_phase],
expand=[True, False],
length=length,
give_shape=True)
# Calculate
global_phase = np.exp(1j * global_phase)
z = np.zeros_like(global_phase)
self.from_components(
(ref_field * global_phase, z, z, -ref_field * global_phase))
self.no_rotation = True
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def diattenuator_perfect(self,
azimuth=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates a perfect diattenuator (polarizer).
Parameters:
azimuth (float or numpy.ndarray): rotation angle of the high transmission polarizer axis. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Prepare variables
(azimuth, global_phase), new_shape = prepare_variables(
vars=[azimuth, global_phase],
expand=[False, True],
length=length,
give_shape=True)
z = np.zeros_like(global_phase)
# Calculate
global_phase = np.exp(1j * global_phase)
self.M = np.array([[global_phase, z], [z, z]])
self.no_rotation = False
self.rotate(azimuth)
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def diattenuator_linear(self,
p1=1,
p2=0,
Tmax=None,
Tmin=None,
azimuth=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates a real polarizer with perpendicular axes:
.. math:: J\left(\theta=0\right)=\left[\begin{array}{cc}
p_{1} & 0\\
0 & p_{2}
\end{array}\right]'.
Parameters:
p1 (float or numpy.ndarray): Electric field transmission coefficient of the transmission eigenstate. Default: 1.
p2 (float or numpy.ndarray): Electric field transmission coefficient of the extinction eigenstate. Default: 0.
Tmax (float or numpy.ndarray): Maximum transmission. If not None, overrides p1. Default: None.
Tmin (float or numpy.ndarray): Minimum transmission. If not None, overrides p2. Default: None.
azimuth (float or numpy.ndarray): rotation angle of the high transmission polarizer axis. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Use field transmission coefficients
if Tmax is not None:
p1 = np.sqrt(Tmax)
if Tmin is not None:
p2 = np.sqrt(Tmin)
# Prepare variables
(p1, p2, azimuth, global_phase), new_shape = prepare_variables(
vars=[p1, p2, azimuth, global_phase],
expand=[True, True, False, False],
length=length,
give_shape=True)
z = np.zeros_like(p1)
# Add global Phase
if global_phase is not 0:
p1 = p1 * np.exp(1j * global_phase)
p2 = p2 * np.exp(1j * global_phase)
# Create the object
self.M = np.array([[p1, z], [z, p2]])
self.no_rotation = False
self.rotate(azimuth)
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def diattenuator_charac_angles(self,
p1=1,
p2=0,
Tmax=None,
Tmin=None,
alpha=0,
delay=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates the most general homogenous diattenuator with orthogonal
eigenstates from the characteristic angles of the main eigenstate.
References:
J.J. Gil, R. Ossikovsky "Polarized light and the Mueller Matrix approach", CRC Press (2016) pp 137.
Parameters:
p1 (float or numpy.ndarray): Electric field transmission coefficient of the transmission eigenstate. Default: 1.
p2 (float or numpy.ndarray): [0, 1] Square root of the lower transmission for the other eigenstate. Default: 0.
Tmax (float or numpy.ndarray): Maximum transmission. If not None, overrides p1. Default: None.
Tmin (float or numpy.ndarray): Minimum transmission. If not None, overrides p2. Default: None.
alpha (float or numpy.ndarray): [0, pi/2]: tan(alpha) is the ratio between field amplitudes of X and Y components. Default: 0.
delay (float or numpy.ndarray): [0, 2*pi]: phase difference between X and Y field components. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Use field transmission coefficients
if Tmax is not None:
p1 = np.sqrt(Tmax)
if Tmin is not None:
p2 = np.sqrt(Tmin)
# Prepare variables
(p1, p2, alpha, delay, global_phase), new_shape = prepare_variables(
vars=[p1, p2, alpha, delay, global_phase],
expand=[True, True, False, False, False],
length=length,
give_shape=True)
alpha = put_in_limits(alpha, "alpha")
delay = put_in_limits(delay, "delay")
# Compute the common operations
sa, ca = (np.sin(alpha), np.cos(alpha))
ed, edm = (np.exp(1j * delay), np.exp(-1j * delay))
# Calculate the Jones matrix
self.M = np.array(
[[p1 * ca**2 + p2 * sa**2, sa * ca * (p1 - p2) * edm],
[sa * ca * (p1 - p2) * ed, p2 * ca**2 + p1 * sa**2]])
self.no_rotation = False
self.add_global_phase(global_phase)
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def diattenuator_azimuth_ellipticity(self,
p1=1,
p2=0,
Tmax=None,
Tmin=None,
azimuth=0,
ellipticity=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates the general diattenuator with orthogonal eigenstates from the characteristic angles of the main eigenstate.
References:
J.J. Gil, R. Ossikovsky "Polarized light and the Mueller Matrix approach", CRC Press (2016) pp 137.
Parameters:
p1 (float or numpy.ndarray): [0, 1] Square root of the higher transmission for one eigenstate. Default: 1.
p2 (float or numpy.ndarray): [0, 1] Square root of the lower transmission for the other eigenstate. Default: 0.
Tmax (float or numpy.ndarray): Maximum transmission. If not None, overrides p1. Default: None.
Tmin (float or numpy.ndarray): Minimum transmission. If not None, overrides p2. Default: None.
azimuth (float): [0, pi]: Azimuth. Default: 0.
ellipticity (float): [-pi/4, pi/]: Ellipticity angle. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Prepare variables
alpha, delay = azimuth_elipt_2_charac_angles(azimuth=azimuth,
ellipticity=ellipticity)
# Calculate
self.diattenuator_charac_angles(p1=p1,
p2=p2,
Tmax=Tmax,
Tmin=Tmin,
alpha=alpha,
delay=delay,
global_phase=global_phase,
length=length,
shape_like=shape_like,
shape=shape)
return self
# @_actualize_
[docs]
def quarter_waveplate(self,
azimuth=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Jones matrix of an ideal quarter-waveplate :math:`\lambda/4`.
Parameters:
azimuth (float or numpy.ndarray): rotation angle of the fast state axis. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
self.retarder_linear(R=90 * degrees,
azimuth=azimuth,
global_phase=global_phase,
length=length,
shape_like=shape_like,
shape=shape)
return self
# @_actualize_
[docs]
def half_waveplate(self,
azimuth=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Jones matrix of an ideal half-waveplate :math:`\lambda/2`.
Parameters:
azimuth (float or numpy.ndarray): Rotation angle of the fast state axis. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
self.retarder_linear(R=180 * degrees,
azimuth=azimuth,
global_phase=global_phase,
length=length,
shape_like=shape_like,
shape=shape)
return self
# @_actualize_
[docs]
def retarder_linear(self,
R=90 * degrees,
azimuth=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates a linear retarder.
Parameters:
R (float): [0, pi] Retardance (delay between components). Default: 90 degrees.
azimuth (float or numpy.ndarray): Rotation angle of the fast state axis. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Prepare variables
R, new_shape = prepare_variables(vars=[R], expand=[True], length=length, give_shape=True)
# Calculate
M = np.array([[np.ones_like(R), np.zeros_like(R)],
[np.zeros_like(R), np.exp(-1j * R)]], dtype=complex)
self.M = M
self.no_rotation = False
self.rotate(azimuth)
self.set_global_phase(global_phase)
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def retarder_material(self,
ne=1,
no=1,
d=1 * um,
wavelength=0.6328 * um,
azimuth=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates a retarder using the physical properties of an anisotropic material.
.. math:: \phi = 2 \pi (n_e-n_o) d / \lambda.
Parameters:
ne (float or numpy.ndarray): Extraordinary index. Default: 1.
n0 (float or numpy.ndarray): Ordinary index. Default: 1.
d (float or numpy.ndarray): Thickness of the sheet in microns. Default: 1 um.
wavelength (float or numpy.ndarray): Wavelength of the illumination. Default: 0.6328 um.
azimuth (float or numpy.ndarray): Rotation angle of the fast state axis. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Prepare variables
(ne, no, d, wavelength, azimuth), new_shape = prepare_variables(
vars=[ne, no, d, wavelength, azimuth],
expand=[True, False, False, False, False],
length=length,
give_shape=True)
phase = 2 * np.pi * (ne - no) * d / wavelength
z = np.zeros_like(ne)
# Calculate
self.M = np.array([[np.ones_like(phase), np.zeros_like(phase)],
[np.zeros_like(phase), np.exp(-1j * phase)]], dtype=complex)
self.no_rotation = False
self.rotate(azimuth)
self.set_global_phase(global_phase)
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def retarder_charac_angles(self,
R=90 * degrees,
alpha=0,
delay=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Function that calculates the most general homogeneous diattenuator from the characteristic angles of the fast eigenstate.
References:
"Polarized light and the Mueller Matrix approach", J. J. Gil, pp 125.
Parameters:
R (float): [0, pi] Retardance (delay between components). Default: 90 degrees.
alpha (float): [0, pi/2]: tan(alpha) is the ratio between amplitudes of the eigenstates in Jones formalism. Default: 0.
delay (float): [0, 2*pi]: phase difference between both components of the eigenstates in Jones formalism. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Prepare variables
(R, alpha, delay, global_phase), new_shape = prepare_variables(
vars=[R, alpha, delay, global_phase],
expand=[True, True, True, False],
length=length,
give_shape=True)
alpha = put_in_limits(alpha, "alpha")
delay = put_in_limits(delay, "delay")
# Compute the common operations
sa, ca = (np.sin(alpha), np.cos(alpha))
s2a, sD = (np.sin(2 * alpha), np.sin(R / 2))
ed, edm = (np.exp(1j * delay), np.exp(-1j * delay))
eD, eDm = (np.exp(1j * R / 2), np.exp(-1j * R / 2))
# Calculate the Jones matrix
self.M = np.array([[ca**2 * eD + sa**2 * eDm, 1j * s2a * sD * edm],
[1j * s2a * sD * ed, ca**2 * eDm + sa**2 * eD]])
self.remove_global_phase()
# Rest of operations
self.no_rotation = False
self.add_global_phase(global_phase)
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
# @_actualize_
[docs]
def retarder_azimuth_ellipticity(self,
R=90 * degrees,
azimuth=0,
ellipticity=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Function that calculates the most general homogeneous diattenuator from the characteristic angles of the fast eigenstate.
References:
"Polarized light and the Mueller Matrix approach", J. J. Gil, pp 125.
Parameters:
R (float): [0, pi] Retardance (delay between components). Default: 90 degrees.
azimuth (float): [0, pi]: Azimuth. Default: 0.
ellipticity (float): [-pi/4, pi/4]: Ellipticity. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Prepare variables
alpha, delay = azimuth_elipt_2_charac_angles(azimuth=azimuth,
ellipticity=ellipticity)
# Calculate
self.retarder_charac_angles(R,
alpha,
delay,
global_phase=global_phase,
length=length,
shape_like=shape_like,
shape=shape)
return self
# @_actualize_
[docs]
def diattenuator_retarder_linear(self,
p1=1,
p2=0,
Tmax=None,
Tmin=None,
R=0,
azimuth=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates a linear diattenuator retarder with the same
axes for diattenuation and retardance. At 0 degrees, the matrix is of
the form:
.. math:: J\left(\theta=0\right)=\left[\begin{array}{cc}
p_{1} & 0\\
0 & p_{2}e^{i R}
\end{array}\right]'.
Parameters:
p1 (float or numpy.ndarray): Field transmission of the fast axis. Default: 1.
p2 (float or numpy.ndarray): Electric field transmission coefficient of the extinction eigenstate. Default: 0.
Tmax (float or numpy.ndarray): Maximum transmission. If not None, overrides p1. Default: None.
Tin (float or numpy.ndarray): Minimum transmission. If not None, overrides p2. Default: None.
R (float or numpy.ndarray): Retardance. Default: 0.
azimuth (float or numpy.ndarray): Rotation angle of the high transmission polarizer axis. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Use field transmission coefficients
if Tmax is not None:
p1 = np.sqrt(Tmax)
if Tmin is not None:
p2 = np.sqrt(Tmin)
# Prepare variables
(p1, p2, R, azimuth, global_phase), new_shape = prepare_variables(
vars=[p1, p2, R, azimuth, global_phase],
expand=[True, True, False, False, False],
length=length,
give_shape=True)
z = np.zeros_like(p1)
# Add global Phase
if global_phase is not 0:
p1 = p1 * np.exp(1j * global_phase)
p2 = p2 * np.exp(1j * global_phase)
# Create the object
self.M = np.array([[p1, z], [z, p2 * np.exp(1j * R)]])
self.no_rotation = False
self.rotate(azimuth)
self.shape, _ = select_shape(self,
shape_var=new_shape,
shape_fun=shape,
shape_like=shape_like)
return self
[docs]
def diattenuator_retarder_azimuth_ellipticity(self,
p1=1,
p2=0,
Tmax=None,
Tmin=None,
R=0,
azimuth=0,
ellipticity=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates the most general homogenous diattenuator retarder from the azimuth and ellipticity of the fast eigenstate.
Parameters:
p1 (float or numpy.ndarray): Field transmission of the fast axis. Default: 1.
p2 (float or numpy.ndarray): Electric field transmission coefficient of the extinction eigenstate. Default: 0.
Tmax (float or numpy.ndarray): Maximum transmission. If not None, overrides p1. Default: None.
Tmin (float or numpy.ndarray): Minimum transmission. If not None, overrides p2. Default: None.
R (float or numpy.ndarray): Retardance. Default: 0.
azimuth (float or numpy.ndarray): rotation angle of the high transmission polarizer axis. Default: 0.
ellipticity (float): [-pi/4, pi/]: Ellipticity angle. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Use field transmission coefficients
if Tmax is not None:
p1 = np.sqrt(Tmax)
if Tmin is not None:
p2 = np.sqrt(Tmin)
# Create the two objects
E1 = Jones_matrix()
E1.diattenuator_azimuth_ellipticity(p1=p1,
p2=p2,
azimuth=azimuth,
ellipticity=ellipticity,
shape=shape,
shape_like=shape_like,
length=length)
E2 = Jones_matrix()
E2.retarder_azimuth_ellipticity(R=R,
azimuth=azimuth,
ellipticity=ellipticity,
shape=shape,
shape_like=shape_like,
length=length)
# Multiply and extract
new_obj = E1 * E2
self.from_matrix(new_obj.M)
self.shape, _ = new_obj.shape, new_obj.ndim
return self
[docs]
def diattenuator_retarder_charac_angles(self,
p1=1,
p2=0,
Tmax=None,
Tmin=None,
R=0,
alpha=0,
delay=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates the most general homogenous diattenuator retarder from the characteristic angles of the fast eigenstate.
Parameters:
p1 (float or numpy.ndarray): Field transmission of the fast axis. Default: 1.
p2 (float or numpy.ndarray): Electric field transmission coefficient of the extinction eigenstate. Default: 0.
Tmax (float or numpy.ndarray): Maximum transmission. If not None, overrides p1. Default: None.
Tmin (float or numpy.ndarray): Minimum transmission. If not None, overrides p2. Default: None.
R (float or numpy.ndarray): Retardance. Default: 0.
alpha (float or numpy.ndarray): [0, pi/2]: tan(alpha) is the ratio between field amplitudes of X and Y components. Default: 0.
delay (float or numpy.ndarray): [0, 2*pi]: Phase difference between X and Y field components. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Use field transmission coefficients
if Tmax is not None:
p1 = np.sqrt(Tmax)
if Tmin is not None:
p2 = np.sqrt(Tmin)
# Create the two objects
E1, E2 = create_Jones_matrices(N=2)
E1.diattenuator_charac_angles(p1=p1,
p2=p2,
alpha=alpha,
delay=delay,
shape=shape,
shape_like=shape_like,
length=length)
E2.retarder_charac_angles(R=R,
alpha=alpha,
delay=delay,
shape=shape,
shape_like=shape_like,
length=length)
# Multiply and extract
new_obj = E1 * E2
self.from_matrix(new_obj.M)
self.shape, _ = new_obj.shape, new_obj.ndim
return self
[docs]
def general_eigenstates(self,
E1,
E2=None,
p1=1,
p2=0,
Tmax=None,
Tmin=None,
R=0,
global_phase=0,
length=1,
shape_like=None,
shape=None):
"""Creates the most general optical element from its eigenstates.
Parameters:
E1 (Jones_vector): First eigenstate.
E2 (Jones_vector): Second eigenstate. If None, E2 is taken as the perpendicular state to E1, so the optical object is homogenous. Default: None
p1 (float or numpy.ndarray): Field transmission of the fast axis. Default: 1.
p2 (float or numpy.ndarray): Electric field transmission coefficient of the extinction eigenstate. Default: 0.
Tmax (float or numpy.ndarray): Maximum transmission. If not None, overrides p1. Default: None.
Tmin (float or numpy.ndarray): Minimum transmission. If not None, overrides p2. Default: None.
R (float or numpy.ndarray): Retardance. Default: 0.
global_phase (float or numpy.ndarray): Global phase introduced by the optical element. Default: 0.
length (int): If final object is of size 1, it is stretched to match this size. Default: 1.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
Returns:
(Jones_matrix): Created object.
"""
# Use field transmission coefficients
if Tmax is not None:
p1 = np.sqrt(Tmax)
if Tmin is not None:
p2 = np.sqrt(Tmin)
# Main calculation
if E2 is None:
# Simple case: homogenous case
az, el = E1.parameters.azimuth_ellipticity()
self.diattenuator_retarder_azimuth_ellipticity(
p1=p1,
p2=p2,
R=R,
azimuth=az,
ellipticity=el,
shape=shape,
shape_like=shape_like,
length=length,
global_phase=global_phase)
else:
# Prepare variables
length = np.max([length, E1.size, E2.size])
(p1, p2, R, global_phase), new_shape = prepare_variables(
vars=[p1, p2, R, global_phase],
expand=[True, True, False, False],
length=length,
give_shape=True)
# Complicated case: inhomogeneous case
R = put_in_limits(R, 'Retardance')
# Create the diagonal matrix with the eigenvalues
d00 = p1 * np.exp(1j * global_phase)
d11 = p2 * np.exp(1j * (global_phase + R))
z = np.zeros_like(d00)
D = np.array([[d00, z], [z, d11]])
# Create the matrix with the eigenvectors as columns
V = np.array([E1.M, E2.M])
V = np.transpose(V, axes=(1, 0, 2))
V_inv = inv_pypol(V)
# Multiply the matrices
M = matmul_pypol(D, V_inv)
M = matmul_pypol(V, M)
# Create the object
self.from_matrix(M,
shape=shape,
shape_like=shape_like,
length=length,
global_phase=global_phase)
return self
################################################################################
# Parameters
################################################################################
[docs]
class Parameters_Jones_Matrix(object):
"""Class for Jones Matrix Parameters.
Parameters:
self.parent (Jones_matrix): Parent object.
"""
def __init__(self, Jones_matrix):
self.parent = Jones_matrix
def __repr__(self):
"""Print all parameters."""
self.get_all(verbose=True, draw=True)
return ''
[docs]
def get_all(self, verbose=False, draw=False):
"""Creates a dictionary with all the parameters of Jones Matrix.
Parameters:
verbose (bool): If True, print all parameters. Default: False.
draw (bool): If True, draw all plots/images of the parameters. Default: False.
Returns:
(dict): Dictionary with parameters of Jones Matrix.
"""
dict_params = {}
dict_params['J00'], dict_params['J01'], dict_params[
'J10'], dict_params['J11'] = self.components(verbose=verbose,
draw=draw)
dict_params['diattenuation'] = self.diattenuation(verbose=verbose,
draw=draw)
dict_params['retardance'] = self.retardance(verbose=verbose,
draw=draw)
dict_params['global_phase'] = self.global_phase(verbose=verbose,
draw=draw)
dict_params['inhomogeneity'] = self.inhomogeneity(verbose=verbose,
draw=draw)
dict_params['T_max'], dict_params[
'T_min'] = self.transmissions(verbose=verbose, draw=draw)
dict_params['p1'], dict_params[
'p2'] = self.transmissions(kind='field', verbose=verbose, draw=draw)
dict_params['mean_transmission'] = self.mean_transmission(
verbose=verbose, draw=draw)
dict_params['det'] = self.det(verbose=verbose, draw=draw)
dict_params['trace'] = self.trace(verbose=verbose, draw=draw)
dict_params['norm'] = self.norm(verbose=verbose, draw=draw)
dict_params['v1'], dict_params['v2'], dict_params[
'e1'], dict_params['e2'] = self.eig(verbose=verbose,
draw=draw)
return dict_params
[docs]
def matrix(self, shape=None, shape_like=None):
"""Returns the numpy array of Jones matrices.
Parameters:
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
Returns:
(numpy.ndarray) 2x2xN numpy array.
"""
shape, _ = select_shape(obj=self.parent,
shape_fun=shape,
shape_like=shape_like)
if shape is not None and len(shape) > 1:
shape = tuple([2, 2] + list(shape))
M = np.reshape(self.parent.M, shape)
else:
M = self.parent.M
return M
[docs]
def components(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False,
kind='jones'):
"""Extracts the matrix components of the Jones matrix.
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): if True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
J00 (numpy.ndarray): array of the 0, 0 element of the matrix.
J01 (numpy.ndarray): array of the 0, 1 element of the matrix.
J10 (numpy.ndarray): array of the 1, 0 element of the matrix.
J11 (numpy.ndarray): array of the 1, 1 element of the matrix.
"""
# Calculate the components
J00 = self.parent.M[0, 0, :]
J01 = self.parent.M[0, 1, :]
J10 = self.parent.M[1, 0, :]
J11 = self.parent.M[1, 1, :]
# If the result is a number and the user asks for it, return a float
if out_number and J00.size == 1:
J00, J01, J10, J11 = (J00[0], J01[0], J10[0], J11[0])
# Reshape if required
J00, J01, J10, J11 = reshape([J00, J01, J10, J11],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = 'The matrix components of {} are:'.format(
self.parent.name)
PrintParam(param=(J00, J01, J10, J11),
shape=self.parent.shape,
title=('J00', 'J01', 'J10', 'J11'),
heading=heading,
verbose=verbose,
draw=draw,
kind=kind)
# Return
return J00, J01, J10, J11
[docs]
def inhomogeneity(self,
method='val',
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Calculates the inhomogeneity parameter.
References:
Method EIG: J.J. Gil, R. Ossikovsky "Polarized light and the Mueller Matrix approach", CRC Press (2016), pp 119.
Method VAL: "Homogeneous and inhomogeneous Jones matrices", S.Y. Lu and R.A. Chipman, J. Opt. Soc. Am. A/Vol. 11, No. 2 pp. 766 (1994)
Parameters:
method (string): Method used for the calculation of the inhomogeneity parameter: EIG uses the eigenstates and VAL uses determinant, norm and trace.
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
eta (numpy.ndarray or float): Inhomogeneity parameter.
"""
if method in ('eig', 'Eig', 'EIG'):
# Calculate the egenstates
e1, e2 = self.eigenstates(shape=False)
# Calculate the parameter
eta = np.abs(np.sum(np.conj(e1) * e2, axis=0))
elif method in ('val', 'Val', 'VAL'):
# Calculate the values
det = self.det(out_number=False, shape=False)
trace = self.trace(out_number=False, shape=False)
norm2 = self.norm(out_number=False, shape=False)**2
# Calculate the parameter
a = norm2 - 0.5 * np.abs(trace)**2
b = 0.5 * np.abs(trace**2 - 4 * det)
eta = (a - b) / (a + b)
else:
raise ValueError('Method {} is not defined'.format(method))
# If the result is a number and the user asks for it, return a float
if out_number and eta.size == 1:
eta = eta[0]
# Reshape if neccessary
eta = reshape([eta],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = 'The inhomogeneity parameter of {} is:'.format(
self.parent.name)
PrintParam(param=eta,
shape=self.parent.shape,
title='Inhomogeneity',
heading=heading,
verbose=verbose,
draw=draw)
return eta
[docs]
def diattenuation(self,
remove_nan=False,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Calculation of the diattenuation of a Jones Matrix.
References:
"Homogeneous and inhomogeneous Jones matrices", S.Y. Lu and R.A. Chipman, J. Opt. Soc. Am. A/Vol. 11, No. 2 pp. 766 (1994)
Parameters:
remove_nan (bool): If True, np.nan values are substitued by 0. Default: False.
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
D (numpy.ndarray or float): Diattenuation.
"""
# Calculate eigenvalues, eigenstates and homogeneity
v1, v2 = self.eigenvalues(out_number=False, shape=False)
a1, a2 = (np.abs(v1), np.abs(v2))
eta = self.inhomogeneity(out_number=False, shape=False)
cond = eta < tol_default**2
D = np.zeros_like(v1)
# Case homogenous
if np.any(cond):
D[cond] = np.abs(a1[cond]**2 - a2[cond]**2) / (a1[cond]**2 +
a2[cond]**2)
# Case inhomogeneous
cond = ~cond
if np.any(cond):
num = 2 * (1 - eta[cond]**2) * a1[cond] * a2[cond]
den = a1[cond]**2 + a2[cond]**2 - eta[cond]**2 * \
(v1[cond] * np.conj(v2[cond]) + v2[cond] * np.conj(v1[cond]))
D[cond] = np.sqrt(1 - (num / den)**2)
# D must be real, but complex numbers are used during calculation
D = np.array(D, dtype=float)
# If there are nans and the user asks for it, change them for 0
cond = np.isnan(D)
if remove_nan and np.any(cond):
D[cond] = 0
# If the result is a number and the user asks for it, return a float
if out_number and D.size == 1:
D = D[0]
# Reshape if neccessary
D = reshape([D],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = 'The diattenuation of {} is:'.format(self.parent.name)
PrintParam(param=D,
shape=self.parent.shape,
title='Diattenuation',
heading=heading,
verbose=verbose,
draw=draw)
return D
[docs]
def polarizance(self,
remove_nan=False,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Calculation of the polarizance of the Jones matrices. In Jones formalism, this is the same as diattenuation.
Parameters:
remove_nan (bool): If True, np.nan values are substitued by 0. Default: False.
out_number (bool): # IDEA: f True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): if True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
P (numpy.ndarray or float): Polarizance.
"""
# Calculate the diattenuation
D = self.diattenuation(remove_nan=remove_nan,
out_number=out_number,
shape=shape,
shape_like=shape_like)
P = D
# Print the result if required
if verbose or draw:
heading = 'The polarizance of {} is:'.format(self.parent.name)
PrintParam(param=P,
shape=self.parent.shape,
title='Polarizance',
heading=heading,
verbose=verbose,
draw=draw)
return P
[docs]
def retardance(self,
remove_nan=False,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Calculation of the retardance (delay between eigenstates) of a Jones optical element.
References:
"Homogeneous and inhomogeneous Jones matrices", Shih-Yau Lu and Russell A. Chipman, J. Opt. Soc. Am. A/Vol. 11, No. 2 pp. 766 (1994)
Parameters:
remove_nan (bool): If True, np.nan values are substitued by 0. Default: False.
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
R (numpy.ndarray or float): Retardance.
"""
# Calculate the needed values
v1, v2 = self.eigenvalues(out_number=False, shape=False)
a1, a2 = (np.abs(v1), np.abs(v2))
M = np.moveaxis(self.parent.M, -1, 0)
det = self.det(out_number=False, shape=False)
trace = self.trace(out_number=False, shape=False)
norm2 = self.norm(out_number=False, shape=False)**2
eta = self.inhomogeneity(out_number=False, shape=False)
# Act differently if the object is homogeneous
cond1 = eta < tol_default**2
R = np.zeros_like(eta)
# Homogeneous case
if np.any(cond1):
cond2 = np.abs(det) < tol_default**2
R[cond1 * cond2] = 2 * np.arccos(
np.abs(trace[cond1 * cond2]) / np.sqrt(norm2[cond1 * cond2]))
cond2 = ~cond2
num = np.abs(trace[cond1 * cond2] +
det[cond1 * cond2] * np.conj(trace[cond1 * cond2]) /
np.abs(det[cond1 * cond2]))
den = 2 * np.sqrt(norm2[cond1 * cond2] +
2 * np.abs(det[cond1 * cond2]))
R[cond1 * cond2] = 2 * np.arccos(num / den)
# Inhomogeneous case
cond1 = ~cond1
if np.any(cond1):
num = (1 - eta[cond1]**2) * (a1[cond1] + a2[cond1])**2
den = (a1[cond1] + a2[cond1])**2 - eta[cond1]**2 * (
2 * v1[cond1] * a1[cond1] * a2[cond1] +
np.conj(v2[cond1] + v2[cond1] * np.conj(v1[cond1])))
co = np.cos((np.angle(v1[cond1]) - np.angle(v2[cond1])) / 2)
R[cond1] = 2 * np.arccos(np.sqrt(num / den) * co)
# D must be real, but complex numbers are used during calculation
R = np.array(R, dtype=float)
# If there are nans and the user asks for it, change them for 0
cond = np.isnan(R)
if remove_nan and np.any(cond):
R[cond] = 0
# If the result is a number and the user asks for it, return a float
if out_number and R.size == 1:
R = R[0]
# Reshape if neccessary
R = reshape([R],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = 'The retardance of {} is (deg.):'.format(
self.parent.name)
PrintParam(param=R / degrees,
shape=self.parent.shape,
title='Retardance (deg.)',
heading=heading,
verbose=verbose,
draw=draw)
return R
[docs]
def global_phase(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Calculates the phase of J00 (which is the reference for global phase in py_pol model).
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray) [0, 2*pi]: Global phase.
"""
# Calculate phase
J00, _, _, _ = self.components(out_number=out_number, shape=False)
phase = np.angle(J00) % (2 * np.pi)
# #If J00 is zero, extract the global phase of J11 element
# if not phase.all():
# _, _, _, J11 = self.components(out_number=out_number, shape=False)
# phase = np.angle(J11) % (2 * np.pi)
# Reshape if neccessary
phase = reshape([phase],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = 'The global phase of {} is (deg.):'.format(
self.parent.name)
PrintParam(param=phase / degrees,
shape=self.parent.shape,
title='Global phase (deg.)',
heading=heading,
verbose=verbose,
draw=draw)
return phase
[docs]
def transmissions(self,
kind='INTENSITY',
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Calculate the maximum and minimum transmitance of an optical element.
References:
Handbook of Optics vol 2. 22.32 (eq.38)
Parameters:
kind (str): There are three options, FIELD, INTENSITY or ALL. Defaut: 'INTENSITY'
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
T_max (numpy.ndarray): Maximum intensity transmission.
T_min (numpy.ndarray): Minimum intensity transmission.
p1 (numpy.ndarray): Maximum field transmission.
p2 (numpy.ndarray): Minimum field transmission.
"""
# Calculate the needed values
norm2 = self.norm(out_number=out_number,
shape=shape,
shape_like=shape_like)**2
det = self.det(out_number=out_number,
shape=shape,
shape_like=shape_like)
# Calculate transmissions
arg = norm2**2 - 4 * np.abs(det)**2
if arg.any() > -1e-10:
arg = np.abs(arg)
T_max = (norm2 + np.sqrt(arg)) / 2
T_min = (norm2 - np.sqrt(arg)) / 2
if kind.upper() in ('FIELD', 'ALL'):
p1 = np.sqrt(T_max)
p2 = np.sqrt(T_min)
# Print the result if required
if verbose or draw:
# Intensity
if kind.upper() in ('INTENSITY', 'ALL'):
heading = 'The intensity transmissions of {} are:'.format(
self.parent.name)
PrintParam(param=(T_max, T_min),
shape=self.parent.shape,
title=('Maximum (int.)', 'Minimum (int.)'),
heading=heading,
verbose=verbose,
draw=draw)
# Field
if kind.upper() in ('FIELD', 'ALL'):
heading = 'The field transmissions of {} are:'.format(
self.parent.name)
PrintParam(param=(p1, p2),
shape=self.parent.shape,
title=('Maximum (int.)', 'Minimum (int.)'),
heading=heading,
verbose=verbose,
draw=draw)
# Return
ret = []
if kind.upper() in ('INTENSITY', 'ALL'):
ret += [T_max, T_min]
if kind.upper() in ('FIELD', 'ALL'):
ret += [p1, p2]
return ret
[docs]
def mean_transmission(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Calculate the mean intensity transmitance of an optical element.
References:
Handbook of Optics vol 2. 22.32 (eq.38)
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray) [0, 1]: Result
"""
# Calculate the maximum and minimum transmissions
T_max, T_min = self.transmissions(out_number=out_number,
shape_like=shape_like,
shape=shape)
T = (T_max - T_min) / 2
# Print the result if required
if verbose or draw:
# Eigenvalues
heading = 'The mean transmission of {} is:'.format(
self.parent.name)
PrintParam(param=T,
shape=self.parent.shape,
title=('Mean trans.'),
heading=heading,
verbose=verbose,
draw=draw)
return T
[docs]
def eig(self,
as_objects=False,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Calculates the eigenvalues and eigenstates of the Jones object.
Parameters:
as_objects (bool): If True, the eigenvectors are extracted as py_pol objects. Default: False.
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): if True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
v1 (numpy.ndarray or float): First eigenvalue.
v2 (numpy.ndarray or float): Second eigenvalue.
e1 (numpy.ndarray or Jones_vector): First eigenstate.
e2 (numpy.ndarray or Jones_vector): Second eigenstate.
TODO: Maybe give values and states together as a matrix
"""
# Differenciate between conjugate symmetric matrices to assure real eigenvalues and orthogonal eigenvectors.
cond = self.parent.checks.is_conjugate_symmetric(out_number=False,
shape=False)
# Calculate the eigenstates
M = np.moveaxis(self.parent.M, -1, 0)
val, vect = np.linalg.eig(M)
if np.any(cond):
val2, vect2 = np.linalg.eigh(M)
# Order the values in the py_pol way
v1, v2 = (val[:, 0], val[:, 1])
e1 = np.array([vect[:, 0, 0], vect[:, 1, 0]])
e2 = np.array([vect[:, 0, 1], vect[:, 1, 1]])
if np.any(cond):
v1[cond], v2[cond] = (val2[cond, 0], val2[cond, 1])
e1[0, cond] = vect2[cond, 0, 0]
e1[1, cond] = vect2[cond, 1, 0]
e2[0, cond] = vect2[cond, 0, 1]
e2[1, cond] = vect2[cond, 1, 1]
if v1.size == 1 and v1.ndim > 1:
v1, v2 = (v1[0], v2[0])
# Size 1 objects need some care
if out_number and v1.size == 1:
v1, v2 = (v1[0], v2[0])
if ~out_number and v1.size == 1:
v1, v2 = (np.array([v1]), np.array([v2]))
# Reshape if neccessary
v1, v2 = reshape([v1, v2],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
e1x, e1y = (e1[0, :], e1[1, :])
e2x, e2y = (e2[0, :], e2[1, :])
e1x, e1y, e2x, e2y = reshape([e1x, e1y, e2x, e2y],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
new_shape = [2] + list(e1x.shape)
if len(new_shape) > 2:
e1 = np.reshape(e1, new_shape)
e2 = np.reshape(e2, new_shape)
# Print the result if required
if verbose or draw:
# Eigenvalues
heading = 'The eigenvalues of {} are:'.format(self.parent.name)
PrintParam(param=(v1, v2),
shape=self.parent.shape,
title=('v1', 'v2'),
heading=heading,
verbose=verbose,
draw=draw)
# Eigenvectors
heading = 'The eigenvectors of {} are:'.format(self.parent.name)
PrintParam(param=(e1x, e1y, e2x, e2y),
shape=self.parent.shape,
title=('e1x', 'e1y', 'e2x', 'e2y'),
heading=heading,
verbose=verbose,
draw=draw)
# Return
if as_objects:
E1 = Jones_vector(self.parent.name + ' e1')
E1.from_matrix(e1, shape=shape, shape_like=shape_like)
E2 = Jones_vector(self.parent.name + ' e2')
E2.from_matrix(e2, shape=shape, shape_like=shape_like)
else:
E1, E2 = (e1, e2)
return v1, v2, E1, E2
[docs]
def eigenvectors(self,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Calculates the eigenvectors of the Jones object.
Parameters:
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
e1 (numpy.ndarray): 2xN first eigenvectors matrix.
e2 (numpy.ndarray): 2xN second eigenvectors matrix.
"""
# Calculate
_, _, e1, e2 = self.eig(as_objects=True,
shape=shape,
shape_like=shape_like)
# Print the result if required
if verbose or draw:
# Eigenvectors
heading = 'The eigenvectors of {} are:'.format(self.parent.name)
e1x, e1y = (e1[0, :], e1[1, :])
e2x, e2y = (e2[0, :], e2[1, :])
e1x, e1y, e2x, e2y = reshape([e1x, e1y, e2x, e2y],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
PrintParam(param=(e1x, e1y, e2x, e2y),
shape=self.parent.shape,
title=('e1x', 'e1y', 'e2x', 'e2y'),
heading=heading,
verbose=verbose,
draw=draw)
return e1, e2
[docs]
def eigenstates(self,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Calculates the eigenstates of the Jones object. Very similar to eigenvectors, but the output are Jones_vector objects.
Parameters:
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): if True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
e1 (Jones_vector): First eigenstate.
e2 (Jones_vector): Second eigenstate.
"""
# Calculate
_, _, E1, E2 = self.eig(as_objects=True)
# Print the result if required
if verbose or draw:
# Eigenvectors
heading = 'The eigenvectors of {} are:'.format(self.parent.name)
e1x, e1y = (E1.M[0, :], E1.M[1, :])
e2x, e2y = (E2.M[0, :], E2.M[1, :])
e1x, e1y, e2x, e2y = reshape([e1x, e1y, e2x, e2y],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
PrintParam(param=(e1x, e1y, e2x, e2y),
shape=self.parent.shape,
title=('e1x', 'e1y', 'e2x', 'e2y'),
heading=heading,
verbose=verbose,
draw=draw)
return E1, E2
[docs]
def eigenvalues(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Calculates the eigenvalues and eigenstates of the Jones object.
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
v1 (numpy.ndarray or float): First eigenvalue.
v2 (numpy.ndarray or float): Second eigenvalue.
"""
# Differenciate between conjugate symmetric matrices to assure real eigenvalues and orthogonal eigenvectors.
cond = self.parent.checks.is_conjugate_symmetric(out_number=False,
shape=False)
# Calculate the eigenstates
M = np.moveaxis(self.parent.M, -1, 0)
val = np.linalg.eigvals(M)
if np.any(cond):
val2 = np.linalg.eigvalsh(M)
# Order the values in the py_pol way
v1, v2 = (val[:, 0], val[:, 1])
if np.any(cond):
v1[cond], v2[cond] = (val2[cond, 0], val2[cond, 1])
if v1.size == 1 and v1.ndim > 1:
v1, v2 = (v1[0], v2[0])
# Size 1 objects need some care
if out_number and v1.size == 1:
v1, v2 = (v1[0], v2[0])
if ~out_number and v1.size == 1:
v1, v2 = (np.array([v1]), np.array([v2]))
# Reshape if neccessary
v1, v2 = reshape([v1, v2],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
# Eigenvalues
heading = 'The eigenvalues of {} are:'.format(self.parent.name)
PrintParam(param=(v1, v2),
shape=self.parent.shape,
title=('v1', 'v2'),
heading=heading,
verbose=verbose,
draw=draw)
return v1, v2
[docs]
def det(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Calculates the determinants of the Jones matrices.
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray, float or complex): Result.
"""
# Calculate the eigenstates
M = np.moveaxis(self.parent.M, -1, 0)
det = np.linalg.det(M)
# If the result is a number and the user asks for it, return a float
if out_number and det.size == 1:
det = det[0]
# Reshape if neccessary
det = reshape([det],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = 'The determinant of {} is:'.format(self.parent.name)
PrintParam(param=det,
shape=self.parent.shape,
title='Determinant',
heading=heading,
verbose=verbose,
draw=draw)
return det
[docs]
def trace(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Calculates the trace of the Jones matrices.
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray, float or complex): Result.
"""
# Calculate the eigenstates
trace = np.trace(self.parent.M)
# If the result is a number and the user asks for it, return a float
if out_number and trace.size == 1:
trace = trace[0]
# Reshape if neccessary
trace = reshape([trace],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = 'The trace of {} is:'.format(self.parent.name)
PrintParam(param=trace,
shape=self.parent.shape,
title='Trace',
heading=heading,
verbose=verbose,
draw=draw)
return trace
[docs]
def norm(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Calculates the Frobenius norm of the Jones matrices.
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray, float or complex): Result.
"""
# Calculate the eigenstates
norm = np.linalg.norm(self.parent.M, axis=(0, 1))
# If the result is a number and the user asks for it, return a float
if out_number and norm.size == 1:
norm = norm[0]
# Reshape if neccessary
norm = reshape([norm],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = 'The norm of {} is:'.format(self.parent.name)
PrintParam(param=norm,
shape=self.parent.shape,
title='Norm',
heading=heading,
verbose=verbose,
draw=draw)
return norm
################################################################################
# Checks
################################################################################
[docs]
class Checks_Jones_Matrix(object):
"""Class for Jones matrix checks.
Parameters:
self.parent (Jones_matrix): Parent object.
"""
def __init__(self, Jones_matrix):
self.parent = Jones_matrix
def __repr__(self):
"""Print all parameters."""
self.get_all(verbose=True, draw=True)
return ''
[docs]
def get_all(self, verbose=False, draw=False):
"""Creates a dictionary with all the parameters of Jones Matrix.
Parameters:
verbose (bool): If True, print all parameters. Default: False.
draw (bool): If True, draw all plots/images of the parameters. Default: False.
Returns:
(dict): Dictionary with parameters of Jones Matrix.
"""
dict_params = {}
dict_params['is_physical'] = self.is_physical(verbose=verbose,
draw=draw)
dict_params['is_homogeneous'] = self.is_homogeneous(
verbose=verbose, draw=draw)
dict_params['is_retarder'] = self.is_retarder(verbose=verbose,
draw=draw)
dict_params['is_diattenuator'] = self.is_diattenuator(
verbose=verbose, draw=draw)
return dict_params
[docs]
def is_physical(self,
all_info=False,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Verifies that a Jones matrix is physically realizable. Take into account that amplifiers are not included in this category.
References:
R. Martinez-Herrero, P.M. Mejias, G.Piquero "Characterization of partially polarized light fields" Springer series in Optical sciences (2009) ISBN 978-3-642-01326-3, page 3, eqs. 1.4a and 1.4b.
Parameters:
all_info (bool): If True, the method returns the information regarding each condition separately. Default: False.
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray or bool): Result.
"""
# Calculate
M = np.moveaxis(self.parent.M, -1, 0)
# H = np.transpose(self.parent.M).conjugate()
det1 = np.linalg.det(M)
# trace1 = np.trace(M * H, axis1=1, axis2=2)
J00, J01, J10, J11 = self.parent.parameters.components(shape=False)
trace1 = np.abs(J00)**2 + np.abs(J01)**2 + np.abs(J10)**2 + np.abs(
J11)**2
condition1 = np.abs(det1) <= 1 # eq. 1.4a
condition2 = (trace1 >= 0) * (trace1 <= 2)
cond = condition1 * condition2
# Reshape if required
if all_info:
condition1, condition2 = reshape([condition1, condition2],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
else:
cond = reshape([cond],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = '{} is physically realizable:'.format(self.parent.name)
if all_info:
PrintParam(param=(condition1, condition2),
shape=self.parent.shape,
title=('Determinant cond.', 'Trace cond.'),
heading=heading,
verbose=verbose,
draw=draw)
else:
PrintParam(param=(cond),
shape=self.parent.shape,
title=('Physical'),
heading=heading,
verbose=verbose,
draw=draw)
# Return
if all_info:
return condition1, condition2
else:
return cond
[docs]
def is_homogeneous(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Determines if matrix is homogeneous (the two eigenstates are orthogonal) or not.
References:
"Homogeneous and inhomogeneous Jones matrices", S.Y. Lu and R.A. Chipman, J. Opt. Soc. Am. A/Vol. 11, No. 2 pp. 766 (1994)
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray or bool): Result.
"""
# Calculate the inhomogeneity parameter
eta = self.parent.parameters.inhomogeneity(out_number=out_number)
cond = eta < tol_default**2
# Reshape if neccessary
cond = reshape([cond],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = '{} is homogeneous:'.format(self.parent.name)
PrintParam(param=cond,
shape=self.parent.shape,
title='Homogeneous',
heading=heading,
verbose=verbose,
draw=draw)
return cond
[docs]
def is_retarder(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Determines if matrix is an homogeneous retarder. The condition is that the Jones matrix must be unitary ($$J^{\dagger}=J^{-1}$$).
References:
"Polarized light and the Mueller Matrix approach", J. J. Gil, pp 123.
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray or bool): Result.
"""
#Remove global_phase before checking if it is a polarizer
obj = self.parent.remove_global_phase(keep=True)
# Calculate the hermitian conjugate
H = obj.hermitian(keep=True)
# Check if it is the inverse matrix
H = H * obj
I = Jones_matrix()
I.vacuum(length=H.size)
dif = H - I
dif = dif.parameters.norm(out_number=out_number, shape=False)
cond = dif < tol_default
# Reshape if neccessary
cond = reshape([cond],
shape_like=shape_like,
shape_fun=shape,
obj=obj)
# Print the result if required
if verbose or draw:
heading = '{} is an homogeneous retarder:'.format(self.parent.name)
PrintParam(param=cond,
shape=obj.shape,
title='Retarder',
heading=heading,
verbose=verbose,
draw=draw)
return cond
[docs]
def is_diattenuator(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Determines if matrix is an homogeneous diattenuator. The condition is that the Jones matrix must be hermitian ($$J^{\dagger}=J$$).
References:
"Polarized light and the Mueller Matrix approach", J. J. Gil, pp 123.
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray or bool): Result.
"""
# Calculate the hermitian conjugate
H = self.parent.hermitian(keep=True)
# Check if it is the inverse matrix
dif = H - self.parent
dif = dif.parameters.norm(out_number=out_number, shape=False)
cond = dif < tol_default
# Reshape if neccessary
cond = reshape([cond],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = '{} is an homogeneous diattenuator:'.format(
self.parent.name)
PrintParam(param=cond,
shape=self.parent.shape,
title='Diattenuator',
heading=heading,
verbose=verbose,
draw=draw)
return cond
[docs]
def is_polarizer(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Determines if matrix is an homogeneous polarizer. In Jones formalism, there is no difference between homogeneous diattenuators and polarizers.
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray or bool): Result.
"""
#Remove global_phase before checking if it is a polarizer
obj = self.parent.remove_global_phase(keep=True)
# Calculate if it is a diattenuator
cond = obj.checks.is_diattenuator(out_number=True,
shape_like=None,
shape=None)
# Reshape if neccessary
cond = reshape([cond],
shape_like=shape_like,
shape_fun=shape,
obj=obj)
# Print the result if required
if verbose or draw:
heading = '{} is an homogeneous polarizer:'.format(
self.parent.name)
PrintParam(param=cond,
shape=obj.shape,
title='Polarizer',
heading=heading,
verbose=verbose,
draw=draw)
return cond
[docs]
def is_symmetric(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Determines if the object matrix is symmetric (i.e. $$J = J^T$$).
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray or bool): Result.
"""
# Calculate the matrix components
_, J01, J10, _ = self.parent.parameters.components(out_number=False,
shape=False)
# See if J01 is equal to J10
cond = np.abs(J01 - J10) < tol_default**2
# Reshape if neccessary
cond = reshape([cond],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = '{} is symmetric:'.format(self.parent.name)
PrintParam(param=cond,
shape=self.parent.shape,
title='Symmetric',
heading=heading,
verbose=verbose,
draw=draw)
return cond
[docs]
def is_conjugate_symmetric(self,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""
Determines if the object matrix is conjugate symmetric (i.e. $$J = J^{\dagger}$$).
Parameters:
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray or bool): Result.
"""
# Calculate the matrix components
J00, J01, J10, J11 = self.parent.parameters.components(
out_number=False, shape=False)
# See if J01 is equal to J10
cond1 = np.abs(J01 - np.conj(J10)) < tol_default**2
cond2 = np.abs(J00 - np.conj(J00)) < tol_default**2
cond3 = np.abs(J11 - np.conj(J11)) < tol_default**2
cond = cond1 * cond2 * cond3
# Reshape if neccessary
cond = reshape([cond],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = '{} is symmetric:'.format(self.parent.name)
PrintParam(param=cond,
shape=self.parent.shape,
title='Symmetric',
heading=heading,
verbose=verbose,
draw=draw)
return cond
[docs]
def is_eigenstate(self, S, out_number=True, shape_like=None, shape=None, verbose=False, draw=False):
"""
Determines if the vector S is an eigenstate of the object.
Parameters:
S (Stokes or Jones_vector): State to test.
out_number (bool): if True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): if True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
(numpy.ndarray or bool): Result.
"""
# Multiply
Sout = self.parent * S
# Check if S and Sout are proportional
prop = Sout.M[0,:] / S.M[0,:]
cond1 = np.abs(Sout.M[1,:] / S.M[1,:] - prop) < tol_default
if Sout.type == "Stokes":
cond2 = np.abs(Sout.M[2,:] / S.M[2,:] - prop) < tol_default
cond3 = np.abs(Sout.M[3,:] / S.M[3,:] - prop) < tol_default
cond = cond1 * cond2 * cond3
else:
cond = cond1
# Reshape if neccessary
cond = reshape([cond],
shape_like=shape_like,
shape_fun=shape,
obj=self.parent)
# Print the result if required
if verbose or draw:
heading = '{} is an eigenstate of {}:'.format(S.name, self.parent.name)
PrintParam(param=cond,
shape=self.parent.shape,
title='Eigenstate',
heading=heading,
verbose=verbose,
draw=draw)
return cond
######################################################################
# Checks
######################################################################
[docs]
class Analysis_Jones_Matrix(object):
"""Class for Jones matrix analysis.
Parameters:
self.parent (Jones_matrix): Parent object.
"""
def __init__(self, Jones_matrix):
self.parent = Jones_matrix
[docs]
def decompose_pure(self,
decomposition='RP',
all_info=False,
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False,
transmissions='ALL',
angles="ALL"):
"""Polar decomposition of a pure Mueller matrix in a retarder and a diattenuator.
References:
"Homogeneous and inhomogeneous Jones matrices", S.Y. Lu and R.A. Chipman, J. Opt. Soc. Am. A/Vol. 11, No. 2 pp. 766 (1994)
Parameters:
decomposition (string): string with the order of the elements: retarder (R) or diattenuator/polarizer (D or P).
all_info (bool): If True, the method returns the information regarding each condition separately. Default: False.
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
transmissions (string): Determines the type of transmission output, FIELD, INTENSITY or ALL. Default: ALL.
angles (string): Determines the type of angles output, CHARAC (characteristic angles), AZIMUTH (azimuth and ellipticity) or ALL. Default: ALL.
Returns:
Jr (Jones_matrix): Jones matrix object of the retarder.
Jd (Jones_matrix): Jones matrix object of the diattenuator.
dict_param (dictionary): Dictionary with the 9 parameters (7 independent) of both the retarder and the diattenuator (only if all_info = True).
"""
# Calculate the matrix Jh * J
Jh = self.parent.hermitian(keep=True)
J = Jh * self.parent
# Extract eigenvalues and eigenvectors
v1, v2, e1, e2 = J.parameters.eig(shape=False, out_number=False)
# Calculate the auxiliar eigenvectors
J00, J01, J10, J11 = self.parent.parameters.components(
shape=False, out_number=False)
e3 = np.array(
[J00 * e1[0, :] + J01 * e1[1, :], J10 * e1[0, :] + J11 * e1[1, :]])
e4 = np.array(
[J00 * e2[0, :] + J01 * e2[1, :], J10 * e2[0, :] + J11 * e2[1, :]])
n3 = np.linalg.norm(e3, axis=0)
n4 = np.linalg.norm(e4, axis=0)
# Norm fix
if not n3.all():
n3 = 1
elif not n4.all():
n4 = 1
# Normalize eigenvectors
e3 = e3 / n3
e4 = e4 / n4
# Calculate the auxiliar matrices
J1, J2, J3, J4 = create_Jones_matrices(('J1', 'J2', 'J3', 'J4'))
if decomposition[0] in ('r', 'R'):
J1.from_components(
(np.abs(e1[0, :])**2, e1[0, :] * np.conj(e1[1, :]),
e1[1, :] * np.conj(e1[0, :]), np.abs(e1[1, :])**2))
J2.from_components(
(np.abs(e2[0, :])**2, e2[0, :] * np.conj(e2[1, :]),
e2[1, :] * np.conj(e2[0, :]), np.abs(e2[1, :])**2))
else:
J1.from_components(
(np.abs(e3[0, :])**2, e3[0, :] * np.conj(e3[1, :]),
e3[1, :] * np.conj(e3[0, :]), np.abs(e3[1, :])**2))
J2.from_components(
(np.abs(e4[0, :])**2, e4[0, :] * np.conj(e4[1, :]),
e4[1, :] * np.conj(e4[0, :]), np.abs(e4[1, :])**2))
J3.from_components(
(e3[0, :] * np.conj(e1[0, :]), e3[0, :] * np.conj(e1[1, :]),
e3[1, :] * np.conj(e1[0, :]), e3[1, :] * np.conj(e1[1, :])))
J4.from_components(
(e4[0, :] * np.conj(e2[0, :]), e4[0, :] * np.conj(e2[1, :]),
e4[1, :] * np.conj(e2[0, :]), e4[1, :] * np.conj(e2[1, :])))
# Calculate the matrices of the retarder and the diattenuator
Jd = np.sqrt(v1) * J1 + np.sqrt(v2) * J2
Jr = J3 + J4
# Update shapes
Jr.shape, _ = select_shape(self.parent,
shape_fun=shape,
shape_like=shape_like)
Jd.shape, _ = (Jr.shape, Jr.ndim)
# Fix names
if change_names:
Jd.name = self.parent.name + ' Diattenuator'
Jr.name = self.parent.name + ' Retarder'
else:
Jd.name = self.parent.name
Jr.name = self.parent.name
# Calculate error
if decomposition[0] in ('r', 'R'):
J = Jr * Jd
else:
J = Jd * Jr
J = J - self.parent
error = J.parameters.norm(shape=shape, shape_like=shape_like)
# Print the result if required
if all_info or verbose or draw:
if verbose or draw:
print("\n------------------------------------------------------")
print('Polar decomposition of {} as M = {}.'.format(
self.parent.name, decomposition))
# Diattenuator
trans, ang = Jd.analysis.diattenuator(transmissions=transmissions,
angles=angles,
out_number=out_number,
verbose=verbose,
draw=draw)
# Retarder
R, ang2 = Jr.analysis.retarder(angles=angles,
out_number=out_number,
shape=shape,
shape_like=shape_like,
verbose=verbose,
draw=draw)
# Error
if verbose or draw:
heading = '{} decomposition mean square error:'.format(
self.parent.name)
PrintParam(param=[error],
shape=self.parent.shape,
title=['MSE'],
heading=heading,
verbose=verbose,
draw=draw)
# Extract info from matrices
if all_info:
parameters = {}
parameters['error'] = error
# Diattenuator
if transmissions.upper() == 'FIELD':
parameters['p1'], parameters['p2'] = trans
elif transmissions.upper() == 'INTENSITY':
parameters['Tmax'], parameters['Tmin'] = trans
else:
parameters['Tmax'], parameters['Tmin'], parameters[
'p1'], parameters['p2'] = trans
if angles.upper() == 'CHARAC':
parameters['alpha D'], parameters['delay D'] = ang
elif angles.upper() == 'ALL':
parameters['alpha D'], parameters['delay D'], parameters[
'azimuth D'], parameters['ellipticity D'] = ang
else:
parameters['azimuth D'], parameters['ellipticity D'] = ang
# Retarder
parameters['R'] = R
if angles.upper() == 'CHARAC':
parameters['alpha R'], parameters['delay R'] = ang2
elif angles.upper() == 'ALL':
parameters['alpha R'], parameters['delay R'], parameters[
'azimuth R'], parameters['ellipticity R'] = ang2
else:
parameters['azimuth R'], parameters['ellipticity R'] = ang2
# Return
if all_info:
return Jr, Jd, parameters
else:
return Jr, Jd
[docs]
def diattenuator(self,
transmissions='ALL',
angles="ALL",
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Analyzes the properties of the optical objects as a diattenuator.
Parameters:
transmissions (string): Determines the type of transmission output, FIELD, INTENSITY or ALL. Default: ALL.
angles (string): Determines the type of angles output, CHARAC (characteristic angles), AZIMUTH (azimuth and ellipticity) or ALL. Default: ALL.
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
trans (list): List including Tmax, Tmin, p1 and p2 (wich appear only if requested).
state (list): List with alpha, delay, azimuth and ellipticity of the transmission state (wich appear only if requested).
"""
# Calculate transmissions
trans = self.parent.parameters.transmissions(kind=transmissions,
out_number=out_number,
shape=shape,
shape_like=shape_like)
# Calculate the eigenstates
_, E1 = self.parent.parameters.eigenstates(shape=shape,
shape_like=shape_like)
E1.name = self.parent.name
# Calculate the parameters of the eigenstates
ang, title_ang = ([], [])
if angles in ('ALL', 'All', 'all', 'CHARAC', 'Charac', 'charac'):
alpha, delay = E1.parameters.charac_angles(out_number=out_number,
shape=shape,
shape_like=shape_like)
ang += [alpha, delay]
title_ang += ['Alpha', 'Delay']
if angles in ('ALL', 'All', 'all', 'AZIMUTH', 'Azimuth', 'azimuth'):
az, el = E1.parameters.azimuth_ellipticity(out_number=out_number,
shape=shape,
shape_like=shape_like)
ang += [az, el]
title_ang += ['Azimuth', 'Ellipticity angle']
# Print the result if required
if verbose or draw:
# Transform angles to degrees for representation
angles_rep = []
for a in ang:
angles_rep.append(a / degrees)
if transmissions in ('INTENSITY', 'Intensity', 'intensity'):
title_trans = ['Max. transmission', 'Min. transmission']
elif transmissions in ('FIELD', 'Field', 'field'):
title_trans = ['p1', 'p2']
else:
title_trans = [
'Max. transmission', 'Min. transmission', 'p1', 'p2'
]
print('\nAnalysis of {} as polarizer:\n'.format(self.parent.name))
heading = '- Transmissions of {} are:'.format(self.parent.name)
PrintParam(param=trans,
shape=self.parent.shape,
title=title_trans,
heading=heading,
verbose=verbose,
draw=draw)
heading = '- Angles of {} are:'.format(self.parent.name)
PrintParam(param=angles_rep,
shape=self.parent.shape,
title=title_ang,
heading=heading,
verbose=verbose,
draw=draw)
return trans, ang
[docs]
def polarizer(self,
transmissions='ALL',
angles="ALL",
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Analyzes the properties of the optical objects as a polarizer. In Jones formalism, this is the same as analyzing the element as a diattenuator.
Parameters:
transmissions (string): Determines the type of transmission output, FIELD, INTENSITY or ALL. Default: ALL.
angles (string): Determines the type of angles output, CHARAC (characteristic angles), AZIMUTH (azimuth and ellipticity) or ALL. Default: ALL.
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
trans (list): List including Tmax, Tmin, p1 and p2 (wich appear only if requested).
state (list): List with alpha, delay, azimuth and ellipticity of the transmission state (wich appear only if requested).
"""
return self.diattenuator(self,
transmissions='ALL',
angles="ALL",
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False)
[docs]
def retarder(self,
angles="ALL",
out_number=True,
shape_like=None,
shape=None,
verbose=False,
draw=False):
"""Analyzes the properties of the optical objects as a retarder.
Parameters:
angles (string): Determines the type of angles output, CHARAC (characteristic angles), AZIMUTH (azimuth and ellipticity) or ALL. Default: ALL.
out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True.
shape_like (numpy.ndarray or py_pol object): Use the shape of this object. Default: None.
shape (tuple or list): If no shape_like array is given, use this shape instead. Default: None.
verbose (bool): If True prints the parameter. Default: False.
draw (bool): If True and the object is a 1D or 2D, plot it. Default: False.
Returns:
trans (list): List including Tmax, Tmin, p1 and p2 (wich appear only if requested).
state (list): List with alpha, delay, azimuth and ellipticity of the transmission state (wich appear only if requested).
"""
# Calculate retardance
R = self.parent.parameters.retardance(out_number=out_number,
shape=shape,
shape_like=shape_like)
# Calculate the components
comp = self.parent.parameters.components(shape=shape,
shape_like=shape_like)
phase_01 = np.angle(comp[1])
phase_10 = np.angle(comp[2])
# Fix numerical error of arcsin.
arg = np.abs(comp[1]) / np.sin(R / 2)
cond = np.arcsin(arg) > 1
if np.any(~cond):
arg[~cond] = 1
# Calculate the angles
alpha = 0.5 * np.arcsin(arg)
gp = (phase_10 + phase_01 - np.pi)/ 2
delay = (phase_10 - phase_01) / 2
# Correct delayP
cond = (gp < -np.pi / 2) + (gp > np.pi / 2)
if np.any(cond):
delay[cond] = delay[cond] - np.pi
delay = put_in_limits(delay, 'delay')
# Correct alpha
# aux1 = comp[0] * np.exp(-1j * gp)
# aux2 = np.cos(alpha)**2 * np.exp(
# 1j * R / 2) + np.sin(alpha)**2 * np.exp(-1j * R / 2)
# aux1 = np.abs(aux1 - aux2)
# cond = (aux1 > tol_default) * (aux1 < np.pi / 2)
# if np.any(cond):
# alpha[cond] = np.pi / 2 - alpha[cond]
Jaux = Jones_matrix()
Jaux.retarder_charac_angles(R=R, alpha=np.pi / 2 - alpha, delay=delay)
dif = self.parent - Jaux
cond = dif.parameters.norm() < tol_default
if np.any(cond):
alpha[cond] = np.pi / 2 - alpha[cond]
# Calculate the parameters of the eigenstates
title_ang, ang = ([], [])
if angles in ('ALL', 'All', 'all', 'CHARAC', 'Charac', 'charac'):
ang += [alpha, delay]
title_ang += ['Alpha', 'Delay']
if angles in ('ALL', 'All', 'all', 'AZIMUTH', 'Azimuth', 'azimuth'):
az, el = charac_angles_2_azimuth_elipt(alpha, delay)
ang += [az, el]
title_ang += ['Azimuth', 'Ellipticity']
# Print the result if required
if verbose or draw:
# Transform angles to degrees for representation
angles_rep = []
for a in ang:
angles_rep.append(a / degrees)
print('\nAnalysis of {} as retarder:\n'.format(self.parent.name))
heading = '- Retardance of {} is:'.format(self.parent.name)
PrintParam(param=(R / degrees),
shape=self.parent.shape,
title=('Retardance'),
heading=heading,
verbose=verbose,
draw=draw)
heading = '- Angles of {} are:'.format(self.parent.name)
PrintParam(param=angles_rep,
shape=self.parent.shape,
title=title_ang,
heading=heading,
verbose=verbose,
draw=draw)
# Return
return R, ang