Source code for py_pol.stokes

# !/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
# -------------------------------------
"""
Stokes objects describe light polarization states in the Mueller-Stokes formalism.

**Class fields**
    * **M**: 2xN array containing all the Stokes vectors.
    * **global_phase**: Global phase of the light state.
    * **name**: Name of the object for print purposes.
    * **shape**: Shape desired for the outputs.
    * **size**: Number of stores Stokes vectors.
    * **type**: Type of the object ('Jones_vector'). This is used for determining the object class as using isinstance may throw unexpected results in .ipynb files.
    * **parameters**: Object of class *Parameters_Jones_vector*.
    * **checks**: Object of class *Checks_Jones_vector*.


**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 Stokes vectors directly from the 4 elements $S_0$, $S_1$, $S_2$, $S_3$.
    * **from_matrix**: Creates Stokes vectors from an external 4 x shape numpy array.
    * **from_list**: Creates a Jones_vector object directly from a list of 4 or 4x1 numpy arrays.
    * **from_Jones**: Creates Stokes vectors from a Jones_vector object.
    * **linear_light**: Creates Stokes vectors for pure linear polarizer light.
    * **circular_light**: Creates Stokes vectors for pure circular polarizer light.
    * **elliptical_light** Creates Stokes vectors for polarizer elliptical light.
    * **general_charac_angles** Creates Stokes vectors given by their characteristic angles.
    * **general_azimuth_ellipticity** Creates Stokes vectors given by their azimuth and ellipticity.


**Manipulation methods**
    * **simplify**:  Simplifies the Stokes vectors in several ways.
    * **rotate**: Rotates the Stokes vectors.
    * **sum**: Calculates the summatory of the Stokes vectors in the object.
    * **reciprocal**: Calculates the Stokes vectors that propagates backwards.
    * **orthogonal**: Calculates the orthogonal Stokes vectors.
    * **normalize**: Normalize the electric field to be normalized in electric field amplitude or intensity.
    * **rotate_to_azimuth**: Rotates the Stokes vectors to have a certain azimuth.
    * **remove_global_phase**: Calculates the global phase of the electric field (respect to the X component) and removes it.
    * **add_global_phase**: Adds a global phase to the Stokes vectors.
    * **set_global_phase**: Sets the global phase of the Stokes vectors.
    * **set_depolarization**: Sets the degree of depolarization.
    * **add_depolarization**: Increases the degree of depolarization.


**Drawing methods**
    * **draw_ellipse**:  Draws the polarization ellipse of the Stokes vectors.
    * **draw_poincare**: Draws the Stokes vectors in the Poincare sphere.


**Parameters subclass methods**
    * **matrix**:  Gets a numpy array with the Stokes vectors.
    * **components**: Calculates the electric field components of the Stokes vectors.
    * **amplitudes**: Calculates the electric field amplitudes of the Stokes vectors.
    * **intensity**: Calculates the intensity of the Stokes vectors.
    * **irradiance**: Calculates the irradiance of the Stokes vectors.
    * **alpha**: Calculates the ratio between electric field amplitudes ($E_x$/$E_y$).
    * **delay / delta**: Calculates the delay (phase shift) between Ex and Ey components of the electric field.
    * **charac_angles**: Calculates both alpha and delay, the characteristic angles of the Stokes vectors.
    * **azimuth**: Calculates azimuth, that is, the orientation angle of the major axis.
    * **ellipticity_angle**: Calculates the ellipticity angle.
    * **azimuth_ellipticity**: Calculates both azimuth and ellipticity angles.
    * **ellipse_axes**: Calculates the length of major and minor axis (a,b).
    * **ellipticity_param**: Calculates the ellipticity parameter, b/a.
    * **eccentricity**: Calculates the eccentricity, the complementary of the ellipticity parameter.
    * **global_phase**: Calculates the global phase of the Stokes vectors (respect to the X component of the electric field).
    * **degree_polarization**: Calculates the degree of polarization of the Stokes vectors.
    * **degree_depolarization**: Calculates the degree of depolarization of the Stokes vectors.
    * **degree_linear_polarization**: Calculates the degree of linear polarization of the Stokes vectors.
    * **degree_circular_polarization**: Calculates the degree of circular polarization of the Stokes vectors.
    * **norm**: Calculates the norm of the Stokes vectors.
    * **polarized_unpolarized**: Divides the Stokes vector in Sp+Su, where Sp is fully-polarized and Su fully-unpolarized.

    * **get_all**: Returns a dictionary with all the parameters of Stokes vectors.



**Checks subclass methods**
    * **is_physical**: Checks if the Stokes vectors are physically realizable.
    * **is_linear**: Checks if the Stokes vectors are lienarly polarized.
    * **is_circular**: Checks if the Stokes vectors are circularly polarized.
    * **is_right_handed**: Checks if the Stokes vectors rotation direction are right handed.
    * **is_left_handed**: Checks if the Stokes vectors rotation direction are left handed.
    * **is_polarized**: Checks if the Stokes vectors are at least partially polarized.
    * **is_totally_polarized**: Checks if the Stokes vectors are totally polarized.
    * **is_depolarized**: Checks if the Stokes vectors are at least partially depolarized.
    * **is_totally_depolarized**: Checks if the Stokes vectors are totally depolarized.

    * **get_all**: Returns a dictionary with all the checks of Stokes vectors.


**Analysis subclass methods**
    * **filter_physical_conditions**: Forces the Stokes vectors to be physically realizable.
    * **density**: Calculates the density of states around the Poincaré sphere.
    * **existence**: Checks the existence of states around the Poincaré sphere.
"""

import warnings
from functools import wraps

import numpy as np
from numpy import arctan2, array, cos, matrix, pi, sin, sqrt
from scipy import optimize
from copy import deepcopy

from . import degrees, eps, num_decimals, number_types, eta
from .py_pol import Py_pol
from .drawings import *
from .jones_vector import Jones_vector
from .utils import (azimuth_elipt_2_charac_angles, put_in_limits, repair_name,
                    rotation_matrix_Mueller, prepare_variables, reshape,
                    PrintParam, take_shape, select_shape, PrintMatrices,
                    combine_indices, merge_indices, multitake,
                    fit_distribution, azel_2_xyz)

warnings.filterwarnings('ignore')

stokes_0 = np.zeros((4, 1), dtype=float)
tol_default = eps
N_print_list = 10
print_list_spaces = 3
change_names = True
unknown_phase = False
default_phase = 0

# TODO: función para luz eliptica (a, b, angulo, polarización)? No sabía hacer

# # define Python user-defined exceptions
# class Error(Exception):
#     """Base class for other exceptions"""
#     pass
#
#
# class CustomError(Error):
#     """Raised when a custom error is produced"""
#     pass

################################################################################
# Functions
################################################################################


[docs] def create_Stokes(name='S', N=1, out_object=True): """Function that creates several Stokes objects at the same time from a list of names or a number. Parameters: names (str, list or tuple): name of vector for string representation. If list or tuple, it also represents the number of objects to be created. Default: 'S'. N (int): Number of elements to be created. This parameter is overrided if name is a list or tuple. Defeult: 1. out_object (bool): If True and the result is a list of length 1, return a Stokes object instead. Default: True. Returns: S (Stokes or list): List of Stokes vectors """ S = [] if isinstance(name, list) or isinstance(name, tuple): for n in name: S.append(Stokes(n)) else: for _ in range(N): S.append(Stokes(name)) if len(S) == 1 and out_object: S = S[0] return S
[docs] def set_printoptions(N_list=None, list_spaces=None): """Function that modifies the global print options parameters. TODO: Single global function for all modules. 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 Stokes(Py_pol): """Class for Stokes vectors Parameters: M (float or numpy.ndarray): 4xN array containing all the Stokes vectors. name (string): Name of the object for print purposes. shape (tuple or list): Shape desired for the outputs. size (int): Number of stored Stokes vectors. ndim (int): Number of dimensions for representation purposes. global_phase (float or numpy.ndarray): Global phase of the Stokes vector. If it is a numpy.ndarray, it must have the same number of elements than self.size. It is used for addition and substraction. type (string): Type of the object ('Stokes'). This is used for determining the object class as using isinstance may throw unexpected results in .ipynb files. Attributes: self.parameters (class): Class containing the measurable parameters of Stokes vectors. self.checks (class): Class containing the methods that check something about the Stokes vectors. self.analysis (class): Class containing the methods to analyze the Stokes vectors. """ __array_priority__ = 15000 def __init__(self, name='S'): """Triggers during the Stokes inicialization. Parameters: name (string): Name of the object for representation purposes. Default: 'S'. Returns: (Stokes): """ super().__init__(name=name, _class="Stokes") self._global_phase = None self.parameters = Parameters_Stokes_vector(self) self.analysis = Analysis_Stokes(self) self.checks = Check_Stokes(self) def __add__(self, other): """Adds two fields represented by their Stokes or Stokes vectors. Parameters: other (Stokes or Jones_vector): 2nd field to add. Returns: (Stokes): Result. """ M3 = Stokes() if other.type == 'Stokes': # Easy case, incoherent sum if self.global_phase is None or other.global_phase is None or np.all( self.global_phase == other.global_phase): M3.from_matrix(self.M + other.M) elif np.all(self.global_phase == other.global_phase): M3.from_matrix(self.M + other.M, global_phase=self.global_phase) # More complicated case, coherent sum else: # Divide Stokes vectors in polarized and unpolarized counterparts s_pol, s_unpol = self.parameters.polarized_unpolarized() o_pol, o_unpol = other.parameters.polarized_unpolarized() # Create the Stokes vectors corresponding to the polarized parts and add them s = Jones_vector(self.name) s.from_Stokes(s_pol) o = Jones_vector(other.name) o.from_Stokes(o_pol) m3 = s + o # Reconvert to Stokes and add the unpolarized parts M3.from_Jones(m3) M3.M = M3.M + s_unpol.M + o_unpol.M elif other.type == 'Jones_vector': # If self has unknown global phase, make the sum incoherent if self.global_phase is None: S = Stokes(other.name) S.from_Jones(other) M3.from_matrix(self.M + S.M) # Coherent sum else: # Divide Stokes vectors in polarized and unpolarized counterparts s_pol, s_unpol = self.parameters.polarized_unpolarized() # Create the Stokes vectors corresponding to the polarized parts and add them s = Jones_vector(self.name) s.from_Stokes(s_pol) m3 = s + other # Reconvert to Stokes and add the unpolarized parts M3.from_Jones(m3) M3.M = M3.M + s_unpol.M else: raise ValueError( 'other is {} instead of Stokes or Jones_vector.'.format( type(other))) # Fix name and update common variables M3.shape = take_shape((self, other)) if change_names: M3.name = self.name + " + " + other.name return M3 def __sub__(self, other): """Substracts two fields represented by their Stokes or Jones vectors. Parameters: other (Stokes or Jones_vector): 2nd field to substract. Returns: (Stokes): Result. """ M3 = self + ((-1) * other) if change_names: M3.name = self.name + " - " + other.name return M3 def __mul__(self, other): """Multiplies a Stokes vector by a number. If the number is complex or real negative, the absolute value is used and the global phase is updated acordingly. Parameters: other (float, complex or numpy.ndarray): number to multiply. Returns: (Stokes): Result. """ M3 = self.copy() # If we have a number, name can be updated if isinstance(other, number_types) and change_names: M3.name = str(other) + " * " + self.name # Calculate components S0, S1, S2, S3 = self.parameters.components(shape=False) # Save the Number of elements, and then flatten if isinstance(other, np.ndarray): N = other.size other2 = other.flatten() else: N = 1 other2 = other # Check that the multiplication can be performed if N == self.size or self.size == 1 or N == 1: # Calculate the absolute value and complex phase of the number mod, phase = (np.abs(other2), np.angle(other2)) # Create the object M3.from_components((S0 * mod, S1 * mod, S2 * mod, S3 * mod), global_phase=self.global_phase) M3.add_global_phase(phase) if isinstance(other, np.ndarray): M3.shape = take_shape((self, other.shape)) elif isinstance(other, number_types): M3.shape = self.shape else: raise ValueError( 'The number of elements in other ({}) and self {} ({}) is not the same' .format(N, self.name, self.size)) return M3 def __rmul__(self, other): """Multiplies a Stokes vector by a number. If the number is complex or real negative, the absolute value is used and the global phase is updated acordingly. Parameters: other (float, complex or numpy.ndarray): number to multiply. Returns: (Stokes): Result. """ M3 = self * other if isinstance(other, number_types) and change_names: M3.name = str(other) + " * " + self.name return M3 def __truediv__(self, other): """Divides a Stokes vector by a number. If the number is complex or real negative, the absolute value is used and the global phase is updated acordingly. Parameters: other (float or numpy.ndarray): Divisor. Returns: (Stokes): Result. """ M3 = self * (other**(-1)) if isinstance(other, number_types) and change_names: M3.name = self.name + " / " + str(other) return M3 def __repr__(self): """ Represents the Stokes vector with print(). """ # Extract the components S0, S1, S2, S3 = self.parameters.components() # If the object is empty, say it if self.size == 0 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: 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 = "{} S0 = {}".format(self.name, S0) l1_name = " " * len(self.name) + " S1 = {}".format(S1) l2_name = " " * len(self.name) + " S2 = {}".format(S2) l3_name = " " * len(self.name) + " S3 = {}".format(S3) # Print higher dimensionality as pure arrays else: l0_name = "{} S0 = \n{}".format(self.name, S0) l1_name = "{} S1 = \n{}".format(self.name, S1) l2_name = "{} S2 = \n{}".format(self.name, S2) l3_name = "{} S3 = \n{}".format(self.name, S3) 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: S = Stokes(self.name + '_picked') else: S = Stokes(self.name) # If the indices are 1D, act upon the matrix directly cond = (isinstance(index, (int, slice)) and self.ndim > 1) or (isinstance(index, np.ndarray) and index.ndim == 1 and self.ndim > 1) if cond: S.from_matrix(self.M[:, index]) # Add global phase if self.global_phase is not None: if self.global_phase.size == 1: S.global_phase = self.global_phase else: S.global_phase = self.global_phase[index] # If not, act upon the components else: S0, S1, S2, S3 = self.parameters.components(out_number=False) M = np.array([S0[index], S1[index], S2[index], S3[index]]) S.from_matrix(M) # Add global phase if self.global_phase is not None: if self.global_phase.size == 1: S.global_phase = self.global_phase else: phase = self.parameters.global_phase(out_number=False) S.set_global_phase(phase[index]) return S def __setitem__(self, index, data): """ Implements object inclusion from indices. """ # Check that data is a correct pypol object if data.type == 'Jones_vector': data2 = Stokes(data.name) data2.from_Jones(data) elif data.type == 'Stokes': data2 = data else: raise ValueError( 'data is type {} instead of Jones_vector or Stokes.'.format( data.type)) # Expand phase if required if self.global_phase is None: self.global_phase = np.zeros(self.size) * np.nan if data2.global_phase is None: data2.global_phase = np.nan # If the indices are 1D, act upon the matrix directly if isinstance(index, int) and self.ndim > 1: self.M[:, index] = np.squeeze(data2.M) # Add global phase self.global_phase[index] = data2.global_phase elif isinstance(index, slice) and self.ndim > 1: if data2.size == 1: if index.step is None: step = 1 else: step = index.step N = int((index.stop - index.start) / step) data3 = data2.stretch(length=N, keep=True) else: data3 = data2 self.M[:, index] = np.squeeze(data3.M) # Add global phase self.global_phase[index] = data3.global_phase elif isinstance(index, np.ndarray) and index.ndim == 1 and self.ndim > 1: self.M[:, index] = data2.M # Add global phase self.global_phase[index] = data2.global_phase # If not, act upon the components else: # Extract phase and components S0, S1, S2, S3 = self.parameters.components(out_number=False) phase = self.parameters.global_phase(out_number=False) S0_new, S1_new, S2_new, S3_new = data2.parameters.components( out_number=False) phase_new = data2.parameters.global_phase(out_number=False) # Set the new values S0[index] = np.squeeze(S0_new) S1[index] = np.squeeze(S1_new) S2[index] = np.squeeze(S2_new) S3[index] = np.squeeze(S3_new) phase[index] = np.squeeze(phase_new) # Update the object self.from_components((S0, S1, S2, S3), global_phase=phase) def __eq__(self, other): """ Implements equality operation. """ try: # Calculate the difference object if other.type == 'Jones_vector': S = Stokes() S.from_Jones(other) j3 = self - other elif other.type == 'Stokes': j3 = self - other else: return False # Compare matrices norm = j3.parameters.norm() cond1 = norm < tol_default # Compare phases if j3.global_phase is None: # Check if one of the original phases was different than None if other.type == 'Jones_vector': cond2 = (self.global_phase is None) * \ (S.global_phase is None) elif other.type == 'Stokes': cond2 = (self.global_phase is None) * \ (other.global_phase is None) else: print(j3, j3.global_phase) cond2 = j3.global_phase == 0 # Merge conditions cond = cond1 * cond2 new_shape = take_shape((self, other)) if new_shape is not None: cond = np.reshape(cond, new_shape) return cond except: return False ################ ## PROPERTIES ################ @property def global_phase(self): return self._global_phase @global_phase.setter def global_phase(self, value): if value is None or isinstance(value, number_types): self._global_phase = value elif value.size == 1: self._global_phase = np.ones(self.size) * value elif value.size != self.size: raise ValueError( "Global phase has diffrent size ({}) than object ({})".format( value.size, self.size)) else: self._global_phase = value ##################### ## MANIPULATION #####################
[docs] def sum(self, axis=None, keep=False, change_name=change_names): """Calculates the sum of Stokes vectors stored in the object. Parameters: axis (int, list or tuple): Axes along which the sum is performed. If None, all vectors are summed. Default: None. keep (bool): If True, the original element is not updated. Default: False. change_name (bool): If True, changes the object name adding Sum of at the beggining of the name. Default: True. Returns: Modified object. """ new_obj, S = create_Stokes(N=2) # Simple case if axis is not None: N_axis = np.array(axis).size if axis is None or self.ndim <= 1 or self.ndim == N_axis: gp = self.parameters.global_phase(out_number=False, shape=False) # Sum all elements for ind in range(0, self.size): S.from_matrix(self.M[:, ind]) S.set_global_phase(gp[ind]) new_obj = new_obj + S # Complicated case else: # Calculate maximum axis if isinstance(axis, int): m = axis + 1 else: axis = np.array(axis) m = np.max(axis) + 1 # Check that the axes are correct if m >= self.ndim + 1: raise ValueError( 'Axis {} greater than the number of dimensions of {}, which is {}' .format(m, self.name, self.ndim)) # Calculate shapes, sizes and indices if isinstance(axis, int): shape_removed = self.shape[axis] else: shape_removed = np.array(self.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(self.shape, axis) N_matrix = np.prod(shape_matrix) ind_matrix = combine_indices( np.unravel_index(np.array(range(N_matrix)), shape_matrix)) shape_final = [4] + list(shape_matrix) axes_aux = np.array(range(1, self.ndim + 1)) shape_orig = [4] + list(self.shape) # Prealocate memory M_orig = np.reshape(self.M, shape_orig) phase_orig = self.parameters.global_phase(out_number=False) M = np.zeros(shape_final) phase = np.zeros(shape_final) # Make the for loop of the matrix to be calculated for indM in range(N_matrix): # Prepare the Stokes vector to sum indices = merge_indices(ind_matrix[indM], ind_removed[0], axis) new_obj.from_matrix(multitake(M_orig, indices, axes_aux)) new_obj.set_global_phase(phase_orig[tuple(indices)]) # Make the summation loop for indR in range(1, N_removed): indices = merge_indices(ind_matrix[indM], ind_removed[indR], axis) S.from_matrix(multitake(M_orig, indices, axes_aux)) S.set_global_phase(phase_orig[tuple(indices)]) new_obj = new_obj + S # Store the result ind_aux = tuple([0] + list(ind_matrix[indM])) M[ind_aux] = new_obj.M[0, 0] ind_aux = tuple([1] + list(ind_matrix[indM])) M[ind_aux] = new_obj.M[1, 0] ind_aux = tuple([2] + list(ind_matrix[indM])) M[ind_aux] = new_obj.M[2, 0] ind_aux = tuple([3] + list(ind_matrix[indM])) M[ind_aux] = new_obj.M[3, 0] ind_aux = tuple(ind_matrix[indM]) phase = new_obj.parameters.global_phase() # Create the object and return it new_obj.from_matrix(M) new_obj.set_global_phase(phase) if change_names: new_obj.name = 'Sum of ' + self.name else: new_obj.name = self.name # Act differently if we want to keep self intact if ~keep: self = new_obj return new_obj
# @_actualize_
[docs] def rotate(self, angle=0, keep=False, change_name=change_names): """Rotates a jones vector a certain angle. M_rotated = rotation_matrix_Jones(-angle) * self.M 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: (Stokes): Rotated object. """ # 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 array of rotation matrices Mrot = rotation_matrix_Mueller(-angle) # The 1-D case is much simpler. Differenciate it. if new_obj.size <= 1 and angle.size == 1: S = np.squeeze(Mrot) @ new_obj.M else: # Move axes of the variables to allow multiplication Mrot = np.moveaxis(Mrot, 2, 0) S = np.moveaxis(new_obj.M, 1, 0) S = np.expand_dims(S, 2) # Multiply S = Mrot @ S # Reshape again to accomodate to our way of representing elements S = np.moveaxis(np.squeeze(S), 0, 1) # Update new_obj.from_matrix(S, global_phase=self.global_phase) # Update name if required if change_name and angle.size == 1: if angle[0] != 0: new_obj.name = new_obj.name + \ " @ {:1.2f} deg".format(angle[0] / degrees) new_obj.name = repair_name(new_obj.name) new_obj.shape, _ = select_shape(new_obj, new_shape) # Return return new_obj
# @_actualize_
[docs] def set_depolarization(self, degree_pol, degree_depol=None, ratio=np.ones(3), keep=True, change_name=change_names): """Function that reduces de polarization degree of a Stokes vector among the three last components. Parameters: degree_pol (float) [0, 1]: Polarization degree. degree_depol (float) [0, 1]: Depolarization degree. Overrides degree_pol if different than None. Default: None. ratio (np.array): Ratio between the three components of the depolarization degree. When different from [1, 1, 1]. Default: [1, 1, 1]. keep (bool): If True, the original element is not updated. Default: True. change_name (bool): If True, changes the object name adding Normalized of at the end of the name. Default: True. Returns: (Stokes): Result. """ # Act differently if we want to keep self intact if keep: new_obj = self.copy() else: new_obj = self # Transform to polarization degree if required if degree_depol is not None: degree_pol = np.sqrt(1 - degree_depol**2) # Prepare variables (degree_pol), new_shape = prepare_variables(vars=[degree_pol], expand=[True], length=1, give_shape=True) # Extract the components and the current polarization degree S0, S1, S2, S3 = new_obj.parameters.components(shape=False) degree_pol_prov = np.sqrt( np.abs(ratio[0])**2 * S1**2 + np.abs(ratio[1])**2 * S2**2 + np.abs(ratio[2])**2 * S3**2) / S0 # Calculate the proportionallity coefficients c1 = degree_pol * np.abs(ratio[0]) / degree_pol_prov c2 = degree_pol * np.abs(ratio[1]) / degree_pol_prov c3 = degree_pol * np.abs(ratio[2]) / degree_pol_prov # Calculate the object new_obj.from_components((S0, S1 * c1, S2 * c2, S3 * c3)) # End operations self.shape, _ = select_shape(self, shape_var=new_shape) if change_names: new_obj.name = new_obj.name + ' depolarized' return new_obj
[docs] def add_depolarization(self, degree_pol, degree_depol=None, ratio=np.ones(3), keep=True, change_name=change_names): """Function that reduces de polarization degree of a Stokes vector among the three last components. Parameters: degree_pol (float) [0, 1]: Polarization degree. degree_depol (float) [0, 1]: Depolarization degree. Overrides degree_pol if different than None. Default: None. ratio (np.array): Ratio between the three components of the depolarization degree. When different from [1, 1, 1]. Default: [1, 1, 1]. keep (bool): If True, the original element is not updated. Default: True. change_name (bool): If True, changes the object name adding Normalized of at the end of the name. Default: True. Returns: (Stokes): Result. """ # Act differently if we want to keep self intact if keep: new_obj = self.copy() else: new_obj = self # Transform to polarization degree if required if degree_depol is not None: degree_pol = np.sqrt(1 - degree_depol**2) # Prepare variables (degree_pol), new_shape = prepare_variables(vars=[degree_pol], expand=[True], length=1, give_shape=True) # Calculate the new polarization degree degree_pol_old = new_obj.parameters.degree_polarization(shape=False) degree_pol = degree_pol + degree_pol_old degree_pol[degree_pol < 0] = 0 degree_pol[degree_pol > 1] = 1 # Set the new polarization degree new_obj.set_depolarization(degree_pol=degree_pol) # End operations self.shape, _ = select_shape(self, shape_var=new_shape) if change_names: new_obj.name = new_obj.name + ' depolarized' return new_obj
[docs] def normalize(self, keep=False, change_name=change_names): """Function that normalizes the Stokes vectors to have Intensity = 1. Parameters: keep (bool): If True, the original element is not updated. Default: False. change_name (bool): If True, changes the object name adding Normalized of at the end of the name. Default: True. Returns: (Stokes): Result. """ # Act differently if we want to keep self intact if keep: new_obj = self.copy() else: new_obj = self # Calculate the normalized components S0, S1, S2, S3 = new_obj.parameters.components(out_number=False) S1, S2, S3 = (S1 / S0, S2 / S0, S3 / S0) # Avoid dividing by 0 cond = S0 == 0 S1[cond] = 0 S2[cond] = 0 S3[cond] = 0 new_obj.from_components((1, S1, S2, S3)) # End operations if change_names: new_obj.name = new_obj.name + ' normalized' return new_obj
[docs] def add_global_phase(self, phase=0, unknown_as_zero=unknown_phase, keep=False): """Function that adds a phase to the Stokes object. Parameters: phase (float or np.ndarray): Phase to be added to the Stokes vectors. Default: 0. unknown_as_zero (bool): If True, takes unknown phase as zero. Default: False. keep (bool): If True, self is not updated. Default: False. Returns: (Stokes): Recalculated Stokes object. """ # Act differently if we want to keep self intact if keep: new_obj = self.copy() else: new_obj = self # Prepare variables phase, new_obj, new_shape = prepare_variables([phase], expand=[True], obj=new_obj, give_shape=True) phase = phase.flatten() # Add the phase if self.global_phase is None: if unknown_as_zero: self.global_phase = phase else: self.global_phase = self.global_phase + phase # End new_obj.shape, _ = select_shape(new_obj, new_shape) return new_obj
[docs] def set_global_phase(self, phase=0, keep=False, length=1, shape_like=None, shape=None): """Function that sets the phase to the Stokes object. Parameters: phase (float or np.ndarray): Phase to be added to the Stokes vectors. Default: 0. keep (bool): If True, self is not updated. Default: False. length (int): If amplitude and azimuth are not specified, it is created a 4 x length array of jones vectors. 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: (Stokes): Recalculated Stokes object. """ # Act differently if we want to keep self intact if keep: new_obj = self.copy() else: new_obj = self # If None or Nan, skip some steps if phase is None or np.all(np.isnan(phase)): self.phase = None else: # Prepare variables phase, new_obj, new_shape = prepare_variables([phase], expand=[True], obj=new_obj, give_shape=True, length=length) phase = phase.flatten() new_obj.shape, _ = select_shape(new_obj, shape_var=new_shape, shape_fun=shape, shape_like=shape_like) self.global_phase = phase # End return new_obj
[docs] def remove_global_phase(self, keep=False): """Function that removes the phase to the Stokes object. Parameters: keep (bool): If True, self is not updated. Default: False. Returns: (Stokes): Recalculated Stokes object. """ # Act differently if we want to keep self intact if keep: new_obj = self.copy() else: new_obj = self # Set the phase self.global_phase = default_phase # End return new_obj
[docs] def rotate_to_azimuth(self, azimuth=0, keep=False): """Function that rotates the Stokes vectors to have a certain azimuth. Parameters: azimuth (string or np.ndarray): Azimuth of the Stokes vectors. 'X', 'Y', '-X' and '-Y' are the same as 0, 90, 180 and 270 degrees respectively. Default: 0. keep (bool): If True, self is not updated. Default: False. Returns: (Stokes): Normalized Stokes vectors. """ # Translate the valid strings if isinstance(azimuth, str): if azimuth in ('X', 'x'): azimuth = 0 elif azimuth in ('Y', 'y'): azimuth = 90 * degrees elif azimuth in ('-X', '-x'): azimuth = 180 * degrees elif azimuth in ('-Y', '-y'): azimuth = 270 * degrees # Act differently if we want to keep self intact if keep: new_obj = self.copy() else: new_obj = self # Prepare variables azimuth, new_obj = prepare_variables([azimuth], expand=[True], obj=new_obj) # Calculate the new vector I = new_obj.parameters.intensity() el = new_obj.parameters.ellipticity_angle() new_obj.general_azimuth_ellipticity(azimuth=azimuth, ellipticity=el, intensity=I) # Return return new_obj
[docs] def rotate_Poincare(self, x_angle=0, y_angle=0, z_angle=0, rot_vector=None, angle=None, keep=False): """Function to rotate the states in the Poincare sphere around X, Y and Z axes (in that order) if no rotation vector is specified. Rotation angle sign is defined using the right hand rule. Args: x_angle (int): Angle around axis X. Default: 0. y_angle (int): Angle around axis Y. Default: 0. z_angle (int): Angle around axis Z. Default: 0. rot_vector (iterable): 3xN iterable with the rotation vector. Default: None. angle (iterable): rotation angles around the rotation vector. Default: None. keep (bool): If True, self is not updated. Default: False. Returns: (Stokes): Rotated Stokes vectors. """ # Act differently if we want to keep self intact if keep: new_obj = self.copy() else: new_obj = self # First option: rotation around X,Y,Z axes if rot_vector is None or angle is None: new_obj.rotate_Poincare(angle=x_angle, rot_vector=[1, 0, 0]) new_obj.rotate_Poincare(angle=y_angle, rot_vector=[0, 1, 0]) new_obj.rotate_Poincare(angle=z_angle, rot_vector=[0, 0, 1]) # Defined rotation vector else: # Prepare variables try: ux, uy, uz = rot_vector sum = np.sqrt(ux**2 + uy**2 + uz**2) ux = ux / sum uy = uy / sum uz = uz / sum except: raise ValueError("rot_vector must be an iterable with 3 elements in the first dimension") S0, S1, S2, S3 = self.parameters.components(out_number=False, shape=False) (angle, S1, S2, S3, ux, uy, uz), new_shape = prepare_variables( vars=[angle, S1, S2, S3, ux, uy, uz], expand=[True] * 7, length=1, give_shape=True) angle = angle.flatten() one = 1 if S0.size == 1 else np.ones_like(S0) # Rotate S_array = np.array([S1, S2, S3]).T[:, :, np.newaxis] sinA = np.sin(angle) cosA = np.cos(angle) rot = np.array([ [cosA + (one - cosA)*ux**2, ux*uy * (one - cosA) - uz*sinA, ux*uz * (one - cosA) + uy*sinA], [ux*uy * (one - cosA) + uz*sinA, cosA + (one - cosA)*uy**2, uy*uz * (one - cosA) - ux*sinA], [ux*uz * (one - cosA) - uy*sinA, uz*uy * (one - cosA) + ux*sinA, cosA + (one - cosA)*uz**2], ]) rot = np.moveaxis(rot, -1, 0) S_array = rot @ S_array # Recreate S new_obj.from_components([S0, S_array[:, 0, 0], S_array[:, 1, 0], S_array[:, 2, 0]]) # End new_obj.shape, _ = select_shape(new_obj, new_shape) return new_obj
[docs] def rotate_Poincare_orig(self, x_angle=0, y_angle=0, z_angle=0, angles=None, keep=False): """Function to rotate the states in the Poincare sphere around X, Y and Z axes (in that order). Rotation angle sign is defined using the right hand rule. Args: x_angle (int): Angle around axis X. Default: 0. y_angle (int): Angle around axis Y. Default: 0. z_angle (int): Angle around axis Z. Default: 0. angles (iterable): 3xN iterable which overrides x_angle, y_angle and z_angle. Default: None. keep (bool): If True, self is not updated. Default: False. Returns: (Stokes): Rotated Stokes vectors. """ # Use angles in the same variable if allowed if angles is not None: try: x_angle, y_angle, z_angle = angles except: raise ValueError("Angles must be an iterable with 3 elements in the first dimension") # Act differently if we want to keep self intact if keep: new_obj = self.copy() else: new_obj = self # Prepare variables S0, S1, S2, S3 = self.parameters.components(out_number=False, shape=False) (x_angle, y_angle, z_angle, S1, S2, S3), new_shape = prepare_variables( vars=[x_angle, y_angle, z_angle, S1, S2, S3], expand=[True] * 6, length=1, give_shape=True) x_angle = x_angle.flatten() y_angle = y_angle.flatten() z_angle = z_angle.flatten() zero = 0 if S0.size == 1 else np.zeros_like(S0) one = 1 if S0.size == 1 else np.ones_like(S0) # Rotate X S_array = np.array([S1, S2, S3]).T[:, :, np.newaxis] # S_array = np.array([S1, S2, S3]) # if S0.size > 1: # S_array = S_array.T[:, :, np.newaxis] sinA = np.sin(x_angle) cosA = np.cos(x_angle) rot = np.array([[one, zero, zero], [zero, cosA, -sinA], [zero, sinA, cosA]]) rot = np.moveaxis(rot, -1, 0) # print(type(S0), S0) # print("S_array", S_array.shape) # print("rot", rot.shape) S_array = rot @ S_array # Rotate Y sinA = np.sin(y_angle) cosA = np.cos(y_angle) rot = np.array([[cosA, zero, sinA], [zero, one, zero], [-sinA, zero, cosA]]) rot = np.moveaxis(rot, -1, 0) S_array = rot @ S_array # Rotate Z sinA = np.sin(z_angle) cosA = np.cos(z_angle) rot = np.array([[cosA, -sinA, zero], [sinA, cosA, zero], [zero, zero, one]]) rot = np.moveaxis(rot, -1, 0) S_array = rot @ S_array # Recreate S # print(S_array.shape) # print(S_array[0,:]) new_obj.from_components([S0, S_array[:, 0, 0], S_array[:, 1, 0], S_array[:, 2, 0]]) # End new_obj.shape, _ = select_shape(new_obj, new_shape) return new_obj
########################################################################### # Creation ###########################################################################
[docs] def from_components(self, components, degree_pol=1, degree_depol=None, global_phase=default_phase, length=1, shape_like=None, shape=None): """Creates Stokes vectors directly from the 4 elements [s0, s1, s2, s3] Parameters: components (tuple): A 4 element tuple containing the 4 components of the Stokes vectors (S0, S1, S2, S3). degree_pol (float) [0, 1]: Polarization degree. Default: 1 degree_depol (float) [0, 1]: Depolarization degree. Overrides degree_pol if different than None. Default: None. global_phase (numpy.ndarray): Adds a global phase to the Stokes object. Default: 0. length (int): If amplitude and azimuth are not specified, it is created a 4 x length array of stokes vectors. 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: (Stokes): Created object. """ # Transform to polarization degree if required if degree_depol is not None: degree_pol = np.sqrt(1 - degree_depol**2) # Prepare variables S0, S1, S2, S3 = components (S0, S1, S2, S3, degree_pol), new_shape = prepare_variables( vars=[S0, S1, S2, S3, degree_pol], expand=[True, True, True, True, False], length=length, give_shape=True) # Store # print("From_matrix", S0.shape, (degree_pol * S1).shape, (degree_pol * S2).shape, (degree_pol * S3).shape) if S0.size == 1: self.M = np.array( [S0, degree_pol * S1, degree_pol * S2, degree_pol * S3], dtype=float) else: self.M = np.array( [S0, degree_pol * S1, degree_pol * S2, degree_pol * S3]) # print(self.M.shape) # self.size = S0.size self.shape, _ = select_shape(self, shape_var=new_shape, shape_fun=shape, shape_like=shape_like) self.set_global_phase(global_phase) return self
[docs] def from_matrix(self, M, degree_pol=1, degree_depol=None, global_phase=default_phase, shape_like=None, shape=None): """Creates a Stokes object from an external array. Parameters: M (numpy.ndarray): New matrix. At least one dimension must be of size 4. degree_pol (float) [0, 1]: Polarization degree. Default: 1 degree_depol (float) [0, 1]: Depolarization degree. Overrides degree_pol if different than None. Default: None. global_phase (numpy.ndarray): Adds a global phase to the Stokes object. Default: 0. 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: (Stokes): Created object. """ # Check if the matrix is of the correct Size M = np.array(M) s = M.size # Case 1D if M.ndim == 1: if M.size % 4 == 0: M = np.reshape(M, (4, int(M.size / 4))) new_shape = None else: raise ValueError( 'M must have a number of elements multiple off 4.') # Case 2D elif M.ndim == 2: s = M.shape if s[0] != 4: if s[1] == 4: M = M.transpose() new_shape = [s[0]] else: raise ValueError( 'The Stokes vectors must be a 4xN or Nx4 array. Current shape is {}' .format(s)) else: new_shape = [s[1]] # Case 3+D else: sh = np.array(M.shape) if (sh == 4).any: # Store the shape of the desired outputs ind = np.argmin(~(sh == 4)) # Store info M = np.array([ np.take(M, 0, axis=ind).flatten(), np.take(M, 1, axis=ind).flatten(), np.take(M, 2, axis=ind).flatten(), np.take(M, 3, axis=ind).flatten() ]) new_shape = np.delete(sh, ind) else: raise ValueError( 'The matrix must have one axis with exactly 4 elements') self.M = M # self.size = M.size / 4 # End operations self.shape, _ = select_shape(self, shape_var=new_shape, shape_fun=shape, shape_like=shape_like) # Add global phase and depolarization self.set_global_phase(global_phase) self.set_depolarization(degree_pol=degree_pol, degree_depol=degree_depol) return self
[docs] def from_distribution(self, Ex, Ey, ind_d=-1, method='direct', N_periods=1, shape_like=None, shape=None): """Determine the Stokes vectors from a temporal or spatial electric field distribution [(Ex(t), Ey(t)]. Parameters: Ex (numpy.ndarray or float): X component of the electric field. Ey (numpy.ndarray or float): Y component of the electric field. ind_d (int): Index of the spatial or temporal dimension. Default: -1. method (string): Method for calculating the field amplitude and delay: DIRECT or FIT. Default: direct. N_periods (float): Number of periods in the representation data. It is used by the fit algorithm (real case only) for calculating the frequency. If the value is not exact, convergency may decrease. 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_vector): Created object. """ # Measure sizes, dims, etc Ex = np.array(Ex) Ey = np.array(Ey) dimsX, dimsY = (Ex.ndim, Ey.ndim) dims = max(dimsX, dimsY) shapeX, shapeY = (np.array(Ex.shape), np.array(Ey.shape)) Nx, Ny = (Ex.size, Ey.size) new_shape = None # Check that the data is compatible if Nx != Ny and dimsX != 1 and dimsY != 1: raise ValueError( 'Ex and Ey components must have the same number of elements') elif Nx != Ny and dimsX == 1 and dimsY == 1: raise ValueError( 'Ex and Ey components must have the same number of elements') elif dimsX == 1 and not (shapeY[ind_d] == Nx): raise ValueError( 'Number of elements of X and Y components does not match') elif dimsY == 1 and not (shapeX[ind_d] == Ny): raise ValueError( 'Number of elements of X and Y components does not match') elif shapeX[ind_d] != shapeY[ind_d]: raise ValueError( 'Temporal dimension of X and Y components does not match') # Check if the variables are real or complex complex_X = Ex.dtype == np.dtype('complex128') complex_Y = Ey.dtype == np.dtype('complex128') if not complex_X == complex_Y: raise ValueError( 'Both Ex and Ey must be in the same representation type: real or complex' ) # Prepare the data as a 2D arrays if dimsX != 1: Nt = shapeX[ind_d] NelemX = int(Nx / Nt) if complex_X: ex = np.zeros((Nt, NelemX), dtype=complex) else: ex = np.zeros((Nt, NelemX)) for ind in range(Nt): ex[ind, :] = np.take(Ex, ind, axis=ind_d).flatten() new_shape = np.delete(shapeX, ind_d) else: Nt = Nx NelemX = 1 ex = np.reshape(Ex, (Nt, 1)) if dimsY != 1: NelemY = int(Ny / Nt) if complex_X: ey = np.zeros((Nt, NelemY), dtype=complex) else: ey = np.zeros((Nt, NelemY)) for ind in range(Nt): ey[ind, :] = np.take(Ey, ind, axis=ind_d).flatten() if dimsY > dimsX: new_shape = np.delete(shapeY, ind_d) else: ey = np.reshape(Ey, (Nt, 1)) NelemY = 1 # Calculate if complex_X: # Amplitude here is easy to extract if method in ('fit', 'FIT', 'Fit'): # Use the mean for polarized amplitude Ex = np.abs(ex).mean(axis=0) Ey = np.abs(ey).mean(axis=0) # Use variance for unpolarized intensity dIx = np.abs(ex).var(axis=0) dIy = np.abs(ey).var(axis=0) # Fit the phase phaseX = np.unwrap(np.angle(ex), axis=0) phaseY = np.unwrap(np.angle(ey), axis=0) x = np.arange(ex.shape[0]) _, phaseX = np.polyfit(x=x, y=phaseX, deg=1) _, phaseY = np.polyfit(x=x, y=phaseY, deg=1) # Calculate the components S0 = Ex**2 + Ey**2 + (dIx + dIy) / 2 S1 = Ex**2 - Ey**2 S2 = 2 * Ex * Ey * np.cos(phaseY - phaseX) S3 = -2 * Ex * Ey * np.sin(phaseY - phaseX) else: # Use the average values S0 = np.mean(np.abs(ex)**2, axis=0).real + \ np.mean(np.abs(ey)**2, axis=0).real S1 = np.mean(np.abs(ex)**2, axis=0).real - \ np.mean(np.abs(ey)**2, axis=0).real S2 = 2 * np.mean(ex * ey.conjugate(), axis=0).real S3 = 2 * np.mean(ex * ey.conjugate(), axis=0).imag phaseX = np.angle(ex[0, :]) else: # Real evolution according to cos(k*x) or cos(w*t). Start by calculating the direct values Ex = np.abs(ex).max(axis=0) Ey = np.abs(ey).max(axis=0) phaseX = np.arccos(ex[0, :] / Ex) cond = ex[1, :] > ex[0, :] phaseX[cond] = np.pi * 2 - phaseX[cond] phaseY = np.arccos(ey[0, :] / Ey) cond = ey[1, :] > ey[0, :] phaseY[cond] = np.pi * 2 - phaseY[cond] # If using fit, use those values as first guess if method in ('fit', 'FIT', 'Fit'): # Prepare variables for X fit s = ex.shape[1] zero, one = (np.zeros(s), np.ones(s)) cte = 2 * np.pi * N_periods / ex.shape[0] par0 = np.concatenate((phaseX, one * cte, Ex)) min_limit = np.concatenate((zero, zero, zero)) max_limit = np.concatenate((2 * np.pi * one, np.pi * one, Ex)) # X fit result = optimize.least_squares(fit_distribution, par0, args=(ex, 0), bounds=(min_limit, max_limit)) phaseX = result.x[:s] Ex = result.x[2 * s:] dIx = np.reshape(result.fun, ex.shape).var(axis=0) # prepare variables for Y fit par0 = np.concatenate((phaseY, one * cte, Ey)) max_limit = np.concatenate((2 * np.pi * one, np.pi * one, Ey)) # Y fit result = optimize.least_squares(fit_distribution, par0, args=(ey, 0), bounds=(min_limit, max_limit)) phaseY = result.x[:s] Ey = result.x[2 * s:] dIy = np.reshape(result.fun, ey.shape).var(axis=0) else: # Without fit, it is impossible to asses the unpolarized part dIx, dIy = (0, 0) # Calculate the components S0 = Ex**2 + Ey**2 + (dIx + dIy) / 2 S1 = Ex**2 - Ey**2 S2 = 2 * Ex * Ey * np.cos(phaseY - phaseX) S3 = -2 * Ex * Ey * np.sin(phaseY - phaseX) # Create the object self.from_components((S0, S1, S2, S3), global_phase=phaseX) self.shape, _ = select_shape(self, shape_var=new_shape, shape_fun=shape, shape_like=shape_like) return self
[docs] def from_Jones(self, E, degree_pol=1, degree_depol=None, length=1, shape_like=None, shape=None): """Creates a Stokes object from a Jones vectors object or a matrix corresponding to a Jones vectors. .. math:: s_0 = abs(E_x)^2 + abs(E_y)^2 .. math:: s_1 = (abs(E_x)^2 - abs(E_y)^2) p_1 .. math:: s_2 = 2 real(E_x E_y^*) p_1 .. math:: s_3 = -2 imag(E_x E_y^*) p_2 Parameters: E (Jones_vector object): Jones vectors. degree_pol (float or numpy.ndarray): Degree of polarization of the new Stokes vector. Default: 1. 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: (Stokes): Created object. """ # Transform to polarization degree if required if degree_depol is not None: degree_pol = np.sqrt(1 - degree_depol**2) # Prepare variables (degree_pol), new_shape = prepare_variables(vars=[degree_pol], expand=[False], length=max(length, E.size), give_shape=True) # Calculate electric field components Ex, Ey = E.parameters.components(shape=False) global_phase = E.parameters.global_phase(shape=False) # Calculate the Stokes vector S0 = E.parameters.intensity(shape=False) S1 = degree_pol * (np.abs(Ex)**2 - np.abs(Ey)**2) S2 = 2 * degree_pol * np.real(Ex * np.conj(Ey)) S3 = -2 * degree_pol * np.imag(Ex * np.conj(Ey)) self.from_components((S0, S1, S2, S3), global_phase=global_phase) # Select final shape self.shape, _ = select_shape(self, shape_var=new_shape, shape_like=E) self.shape, _ = select_shape(self, shape_var=E.shape, shape_fun=shape, shape_like=shape_like) # Return return self
# @_actualize_ # def from_distribution(self, E, is_normalized=False): # """Creates Stokes vectors from a [Ex(t), Ey(t)] electric field. # # Parameters: # E (numpy.array): [Ex(t), Ey(t)] # is_normalized (bool): If True intensity is normalized # # Returns: # S (4x1 numpy.matrix): Stokes vector (I, Q, U, V). # """ # # Ex, Ey = E[:, 0], E[:, 1] # # S = np.matrix(np.array([[0.0], [0.0], [0.0], [0.0]])) # S[0] = (np.conjugate(Ex) * Ex + np.conjugate(Ey) * Ey).mean().real # S[1] = (np.conjugate(Ex) * Ex - np.conjugate(Ey) * Ey).mean().real # S[2] = 2 * (Ex * np.conjugate(Ey)).mean().real # # S[2] = (Ex * np.conjugate(Ey)+Ey*np.conjugate(Ex)).mean().real # S[3] = -2 * (Ex * np.conjugate(Ey)).mean().imag # # S[3] = (1j*(Ex * np.conjugate(Ey)-Ey*np.conjugate(Ex)).mean()).real # # if is_normalized: # S = S / S[0] # # self.from_matrix(S) # # # @_actualize_ # def from_distribution_deprecated(self, E, is_normalized=False): # """Creates Stokes vectors from a [Ex(t), Ey(t)] electric field. # # Parameters: # E (numpy.array): [Ex(t), Ey(t)] # is_normalized (bool): If True intensity is normalized # # Returns: # S (4x1 numpy.matrix): Stokes vector (I, Q, U, V). # """ # # Ex, Ey = E[0], E[1] # # if is_normalized is True: # intensity = np.sqrt(np.conjugate(Ex) * Ex + np.conjugate(Ey) * Ey) # Ex = Ex / intensity # Ey = Ey / intensity # # S = np.matrix(np.array([[0.0], [0.0], [0.0], [0.0]])) # S[0] = (np.conjugate(Ex) * Ex + np.conjugate(Ey) * Ey).mean.real # S[1] = (np.conjugate(Ex) * Ex - np.conjugate(Ey) * Ey).real # S[2] = 2 * (Ex * np.conjugate(Ey)).real # S[3] = 2 * (Ex * np.conjugate(Ey)).imag # self.from_matrix(S) # # @_actualize_ # def to_Jones(self): # """Function that converts Stokes light states to Jones states. # # Returns: # j (Jones_vector object): Stokes state.""" # j = Jones_vector(self.name) # j.from_Stokes(self) # return j
[docs] def linear_light(self, intensity=1, azimuth=0, amplitude=None, degree_pol=1, degree_depol=None, global_phase=default_phase, length=1, shape_like=None, shape=None): """Creates a Stokes vector of linear polarizer light. Parameters: intensity (numpy.array or float): Array of intensity. Default: 1. azimuth (numpy.array or float): Array of azimuths. Default: 0. amplitude (numpy.array or float): Array of electric field amplitude. Overrides intensity if it is different than None. Default: None. degree_pol (numpy.array or float): Array of polarization degree. Default: 1. degree_depol (float) [0, 1]: Depolarization degree. Overrides degree_pol if different than None. Default: None. global_phase (float or numpy.ndarray): Adds a global phase to the Stokes object. Default: default_phase. 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: (Stokes): Created object. """ # Transform to polarization degree if required if degree_depol is not None: degree_pol = np.sqrt(1 - degree_depol**2) # Use amplitude if specified if amplitude is not None: intensity = amplitude**2 # Prepare variables (intensity, azimuth, degree_pol), new_shape = prepare_variables( vars=[intensity, azimuth, degree_pol], expand=[True, False, False], length=length, give_shape=True) # Calculate the components S0 = intensity S1 = intensity * degree_pol * cos(2 * azimuth) S2 = intensity * degree_pol * sin(2 * azimuth) S3 = np.zeros_like(intensity) # Calculate the vector self.from_components((S0, S1, S2, S3), global_phase=global_phase) # Store self.shape, _ = select_shape(self, shape_var=new_shape, shape_fun=shape, shape_like=shape_like) return self
# @_actualize_
[docs] def circular_light(self, kind='d', intensity=1, amplitude=None, degree_pol=1, degree_depol=None, global_phase=default_phase, length=1, shape_like=None, shape=None): """Creates Stokes vectors for pure circular polarizer light. Parameters: kind (str): 'd','r' - right, dextro. 'l', 'i' - left, levo. intensity (numpy.array or float): Array of intensity. Default: 1. amplitude (numpy.array or float): Array of electric field amplitude. Overrides inetnsity if it is different than None. Default: None. degree_pol (numpy.array or float): Array of polarization degree. Default: 1. degree_depol (float) [0, 1]: Depolarization degree. Overrides degree_pol if different than None. Default: None. global_phase (float or numpy.ndarray): Adds a global phase to the Stokes object. Default: default_phase. 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: (Stokes): Created object. """ # Use the correct sign if kind in 'drDR': # derecha, right sign = 1 elif kind in 'ilIL': # izquierda, left sign = -1 else: raise ValueError('kind is {} instead of I, L, D or R'.format(kind)) # Transform to polarization degree if required if degree_depol is not None: degree_pol = np.sqrt(1 - degree_depol**2) # Use amplitude if specified if amplitude is not None: intensity = amplitude**2 # Prepare variables (intensity, degree_pol), new_shape = prepare_variables( vars=[intensity, degree_pol], expand=[True, False], length=length, give_shape=True) # Calculate the vector self.from_components((intensity, 0, 0, sign * degree_pol * intensity), global_phase=global_phase) # Store self.shape, _ = select_shape(self, shape_var=new_shape, shape_fun=shape, shape_like=shape_like) return self
# @_actualize_
[docs] def elliptical_light(self, a=1, b=1, kind='r', azimuth=0, degree_pol=1, degree_depol=None, global_phase=default_phase, length=1, shape_like=None, shape=None): """Stokes object of the most general light calculated from the polarization ellipse parameters. Parameters: a (numpy.array or float): Array of electric amplitude of x axis. Default: 1. b (numpy.array or float): Array of electric amplitude of y axis. Default: 1. kind (str): 'd','r' - right, dextro. 'l', 'i' - left, levo. azimuth (numpy.array or float): Angle of the a axis respect to the x axis. Default: 0. degree_pol (float) [0, 1]: Polarization degree. Default: 1 degree_depol (float) [0, 1]: Depolarization degree. Overrides degree_pol if different than None. Default: None. global_phase (float or numpy.ndarray): Adds a global phase to the Stokes object. Default: default_phase. 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: (Stokes): Created object. """ # Transform to polarization degree if required if degree_depol is not None: degree_pol = np.sqrt(1 - degree_depol**2) # Calculate it as Stokes vectors (much easier) J = Jones_vector() J.elliptical_light(a=a, b=b, kind=kind, azimuth=azimuth, global_phase=global_phase, length=length, shape_like=shape_like, shape=shape) # Transform it to Stokes self.from_Jones(J) # Depolarize self.set_depolarization(degree_pol=degree_pol) return self
# @_actualize_
[docs] def general_charac_angles(self, alpha=0, delay=0, intensity=1, amplitude=None, degree_pol=1, degree_depol=None, global_phase=default_phase, length=1, shape_like=None, shape=None): """Creates Stokes vectors given by their characteristic angles. References: J.J. Gil, R. Ossikovsky "Polarized light and the Mueller Matrix approach", CRC Press (2016),pp 137. Parameters: alpha (float): [0, pi]: tan(alpha) is the ratio between field amplitudes of X and Y components. Default: 0 delay (float): [0, 2*pi]: phase difference between X and Y field components. Default: 0 intensity (numpy.array or float): Array of intensity. Default: 1. amplitude (numpy.array or float): Array of electric field amplitude. Overrides intensity if it is different than None. Default: None. degree_pol (numpy.array or float): Array of polarization degree. Default: 1. degree_depol (float) [0, 1]: Depolarization degree. Overrides degree_pol if different than None. Default: None. global_phase (float or numpy.ndarray): Adds a global phase to the Stokes object. Default: default_phase. 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: S (4x1 numpy.matrix): Stokes vector. """ # Transform to polarization degree if required if degree_depol is not None: degree_pol = np.sqrt(1 - degree_depol**2) # Use amplitude if specified if amplitude is not None: intensity = amplitude**2 # Prepare variables (intensity, alpha, delay, degree_pol, global_phase), new_shape = prepare_variables( vars=[intensity, alpha, delay, degree_pol, global_phase], expand=[False, False, False, False, False], length=length, give_shape=True) # Restrict possible values alpha = put_in_limits(alpha, 'alpha') delay = put_in_limits(delay, 'delay') # Calculate the components S0 = intensity S1 = intensity * degree_pol * cos(2 * alpha) S2 = intensity * degree_pol * sin(2 * alpha) * cos(delay) S3 = intensity * degree_pol * sin(2 * alpha) * sin(delay) # Nan cases are totally depolarized cond = np.isnan(S3) if np.any(cond): S1[cond] = 0 S2[cond] = 0 S3[cond] = 0 # From components self.from_components((S0, S1, S2, S3), global_phase=global_phase, length=length, shape=False) # Store self.shape, _ = select_shape(self, shape_var=new_shape, shape_fun=shape, shape_like=shape_like) return self
# @_actualize_
[docs] def general_azimuth_ellipticity(self, azimuth=0, ellipticity=0, intensity=1, amplitude=None, degree_pol=1, degree_depol=None, global_phase=default_phase, length=1, shape_like=None, shape=None): """Creates Stokes vectors given by their azimuth and ellipticity. References: J.J. Gil, R. Ossikovsky "Polarized light and the Mueller Matrix approach", CRC Press (2016), pp 137. Parameters: azimuth (float): [0, pi]: azimuth. Default: 0 ellipticity (float): [-pi/4, pi/4]: ellipticity. Default: 0 intensity (numpy.array or float): Array of intensity. Default: 1. amplitude (numpy.array or float): Array of electric field amplitude. Overrides intensity if it is different than None. Default: None. degree_pol (numpy.array or float): Array of polarization degree. Default: 1. degree_depol (float) [0, 1]: Depolarization degree. Overrides degree_pol if different than None. Default: None. global_phase (float or numpy.ndarray): Adds a global phase to the Stokes object. Default: default_phase. 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: S (4x1 numpy.matrix): Stokes vector. """ # Transform to polarization degree if required if degree_depol is not None: degree_pol = np.sqrt(1 - degree_depol**2) # Use amplitude if specified if amplitude is not None: intensity = amplitude**2 # Prepare variables (intensity, azimuth, ellipticity, degree_pol, global_phase), new_shape = prepare_variables( vars=[intensity, azimuth, ellipticity, degree_pol, global_phase], expand=[True, False, False, False, False], length=length, give_shape=True) # Restrict possible values azimuth = put_in_limits(azimuth, 'azimuth') ellipticity = put_in_limits(ellipticity, 'ellipticity') # Calculate the components S0 = intensity S1 = intensity * degree_pol * cos(2 * azimuth) * cos(2 * ellipticity) S2 = intensity * degree_pol * sin(2 * azimuth) * cos(2 * ellipticity) S3 = intensity * degree_pol * sin(2 * ellipticity) # Separate totally depolarization from circular polarization cond = np.isnan(azimuth) + np.isnan(ellipticity) S1[cond] = 0 S2[cond] = 0 cond = np.isnan(ellipticity) + ( np.isnan(azimuth) * (np.abs(ellipticity) < np.pi / 4 - tol_default)) S3[cond] = 0 # From components self.from_components((S0, S1, S2, S3), global_phase=global_phase, length=length, shape=False) # Store self.shape, _ = select_shape(self, shape_var=new_shape, shape_fun=shape, shape_like=shape_like) return self
####################################################################### # Draw #######################################################################
[docs] def draw_poincare(self, *args, **kwargs): """"Draws stokes vector. Args: S (Jones_vector or Stokes): Object to be represented. fig (plotly.graph_objects.Figure): Figure to plot the data. Default: None figsize (2-element iterable): Figure size. Default: (6,6) draw_axes (bool): If True, it draws the three axes of the Poincare space. Default: True draw_guides (bool): If True, it draws the circles of S1, S2 and S3 = 0. Default: True kind (str): Choose between 'scatter', 'line', 'scatterline' or 'surf' to represent the data. If surf, the object must be 2D, 3D or 4D. Else, it must be 0D, 1D, 2D or 3D. Default: 'scatter'. depol (bool): If True, the depolarization is taking into account for scatter and line plots shrinking the radius below 1. Default: False. param (str, np.ndarray or None): If str, parameter to use as information for the color. Must be a method of Parameters class which returns a single variable. If np.ndarray, must have the same shape as S. Default: None. subplots (bool): If True, it tries to use the first two dimensions as rows and columns. If method == 'surf', this is mandatory. Default: False. in_degrees (bool): If True, transforms the parameters to degrees. Default: False. log (bool): If True, it calculates it in logarithmic scale. Default: False. hover (str): Choose between 'components' (S1, S2, S3) or 'angles' (azimuth, ellipticity). Default: 'components'. colormap (str): Colormap of the plots. Default: 'Blackbody'. show_fig (bool): If True, the figure is inmediately plotted. Default: False. Returns: fig (go.Figure): Plotly figure. """ fig = draw_poincare(self, *args, **kwargs) return fig
[docs] def draw_ellipse(self, *args, **kwargs): """Draws polarization ellipse of Stokes vector. Args: E (Jones_vector or Stokes): Light object. N_angles (int): Number of angles to plot the ellipses. Default: 91. filename (str): name of filename to save the figure. figsize (tuple): A tuple of length 2 containing the figure size. Default: (8,8). limit (float): limit for drawing. If empty, it is obtained from amplitudes. draw_arrow (bool): If True, draws an arrow containing the turning sense of the polarization. Does not work with linear polarization vectors. Default: True. depol_central (bool): If True, draws a central circle containing the unpolarized field amplitude. Default: False. depol_contour (bool): If True, draws a line enveloping the polarization ellipse in order to plot the depolarization. Default: False. depol_prob (bool): If True, plots the probability distribution of the electric field. Default: False. subplots (string, tuple or None): If AS_SHAPE, divides the figure in several subplots as the shape of the py_pol object. If INDIVIDUAL, each vector is represented in its own subaxis, trying to use a square grid. If tuple, divides the figure in that same number of subplots. If None, all ellipses are plot in the same axes. Default: None. N_prob (int): Number of points in each dimension for probability distributions. Default: 256. contour_levels (float, np.ndarray, tuple or list): Contains the contour levels (normalized to 1). Default: 0.9. cmap (str or color object): Default colormap for probability distributions. Default: hot. Returns: ax (handle): handle to axis. fig (handle): handle to figure. """ ax, fig = draw_ellipse(self, *args, **kwargs) return ax, fig
# ax, fig = draw_ellipse_stokes(self, kind, limit, has_line, filename) # return ax, fig # def pseudoinverse(self, returns_matrix=True, keep=True): # """Calculates the pseudoinverse of the Stokes vector. # TODO # # Parameters: # keep (bool): if True, the original element is not updated. Default: False. # returns_matrix (bool): if True returns a matrix, else returns an instance to object. Default: True. # # Returns: # (numpy.matrix or Mueller object): 4x4 matrix. # """ # # Calculate pseudoinverse # S = np.matrix(self.M) # Sinv = (S.T * S).I * S.T # # # Caluclate inverse # if keep: # S2 = Stokes(self.name + '_inv') # S2.from_matrix(Sinv) # else: # self.from_matrix(Sinv) # if returns_matrix: # return Sinv # else: # if keep: # return S2 # else: # return self
[docs] class Parameters_Stokes_vector(object): """Class for Stokes vector Parameters Parameters: Stokes_vector (Stokes_vector): Stokes Vector Attributes: self.parent (Stokes_vector): parent object. """ def __init__(self, Stokes): self.parent = Stokes def __repr__(self): """Prints all the 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 Stokes vectors. Parameters: 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. """ dict_params = {} dict_params['intensity'] = self.intensity(verbose=verbose, draw=draw) dict_params['E0x'], dict_params['E0y'], dict_params[ 'E0u'] = self.amplitudes(verbose=verbose, draw=draw, give_unpol=True) dict_params['global_phase'] = self.global_phase(verbose=verbose, draw=draw) dict_params['degree_depol'] = self.degree_depolarization( verbose=verbose, draw=draw) dict_params['degree_pol'] = self.degree_polarization(verbose=verbose, draw=draw) dict_params['degree_linear_pol'] = self.degree_linear_polarization( verbose=verbose, draw=draw) dict_params['degree_circular_pol'] = self.degree_circular_polarization( verbose=verbose, draw=draw) dict_params['alpha'] = self.alpha(verbose=verbose, draw=draw) dict_params['delay'] = self.delay(verbose=verbose, draw=draw) dict_params['ellipticity_param'] = self.ellipticity_param( verbose=verbose, draw=draw) dict_params['ellipticity_angle'] = self.ellipticity_angle( verbose=verbose, draw=draw) dict_params['azimuth'] = self.azimuth(verbose=verbose, draw=draw) dict_params['eccentricity'] = self.eccentricity(verbose=verbose, draw=draw) dict_params['S_p'], dict_params['S_u'] = self.polarized_unpolarized( verbose=verbose) dict_params['norm'] = self.norm(verbose=verbose, draw=draw) return dict_params
[docs] def matrix(self, shape=None, shape_like=None): """Returns the numpy array of the Stokes 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. Returns: (float or numpy.ndarray) 2xN 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([4] + list(shape)) M = np.reshape(self.parent.M, shape) else: M = self.parent.M return M
[docs] def global_phase(self, give_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Extracts the global phase of the Stokes vector. Parameters: give_nan (bool): If False, NaN values are transformed into 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ gp = self.parent.global_phase # If the phase is None (unknown), give nans or zeros if gp is None: if self.parent.shape is None: gp = np.array([0]) else: gp = np.zeros(self.parent.size) if give_nan: gp = gp * np.nan # If the result is a number and the user asks for it, return a float if out_number and gp.size == 1: gp = gp[0] # Calculate Ez and reshape if required gp = reshape([gp], 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=(gp / degrees), shape=self.parent.shape, title=("Global phase (deg)"), heading=heading, verbose=verbose, draw=draw) return gp
[docs] def components(self, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the $S_0$, $S_1$, $S_2$ and $S_3$ components of the Stokes vector. Parameters: out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: S0 (float or numpy.ndarray): Array of total intensity. S1 (float or numpy.ndarray): Array of linearly horizontal or vertical polarized intensity. S2 (float or numpy.ndarray): Array of linearly 45º or 135º polarized intensity. S3 (float or numpy.ndarray): Array of circularly polarized intensity. """ # Calculate the components S0 = self.parent.M[0, :] S1 = self.parent.M[1, :] S2 = self.parent.M[2, :] S3 = self.parent.M[3, :] # If the result is a number and the user asks for it, return a float if out_number and S0.size == 1: (S0, S1, S2, S3) = (S0[0], S1[0], S2[0], S3[0]) # Calculate Ez and reshape if required S0, S1, S2, S3 = reshape([S0, S1, S2, S3], shape_like=shape_like, shape_fun=shape, obj=self.parent) # Print the result if required if verbose or draw: heading = 'The intensity components of {} are (a.u.):'.format( self.parent.name) PrintParam(param=(S0, S1, S2, S3), shape=self.parent.shape, title=("S0: (a.u.)", "S1: (a.u.)", "S2: (a.u.)", "S3: (a.u.)"), heading=heading, verbose=verbose, draw=draw) # Return return S0, S1, S2, S3
[docs] def intensity(self, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """ Calculates the intensity of the Stokes vector ($S_0$). References: Handbook of Optics vol 2. 22.16 (eq.2) Parameters: out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ # Calculate intensity S0 = self.parent.M[0, :] # If the result is a number and the user asks for it, return a float if out_number and S0.size == 1: S0 = S0[0] # Calculate Ez and reshape if required S0 = reshape([S0], shape_like=shape_like, shape_fun=shape, obj=self.parent) # Print the result if required if verbose or draw: heading = 'The intensity of {} is (a.u.):'.format(self.parent.name) PrintParam(param=(S0), shape=self.parent.shape, title=("Intensity: (a.u.)"), heading=heading, verbose=verbose, draw=draw) return S0
[docs] def irradiance(self, n=1, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """ Calculates the irradiance of the Stokes vector ($S_0$). References: Handbook of Optics vol 2. 22.16 (eq.2) Parameters: n (float): Refractive index. Default: 1. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ # Calculate the irradiance Irrad = (n / (2 * eta)) * self.norm( out_number=out_number, shape_like=shape_like, shape=shape) # Print the result if required if verbose or draw: heading = 'The irradiance of {} is (W/m^2):'.format( self.parent.name) PrintParam(param=(Irrad), shape=self.parent.shape, title=("Irradiance: (W/m^2)"), heading=heading, verbose=verbose, draw=draw) return Irrad
[docs] def degree_polarization(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the degree of polarization (DP) of the Stokes vectors. $DP=\frac{\sqrt{S_{1}^{2}+S_{2}^{2}+S_{3}^{2}}}{S_{0}}$ References: Handbook of Optics vol 2. 22.16 (eq.3) Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result """ # Calculate the components S0, S1, S2, S3 = self.components(shape_like=shape_like, shape=shape, out_number=out_number) # Calculate the polarization degree DOP = np.zeros_like(S0) cond = S0 != 0 DOP[cond] = np.sqrt(S1[cond]**2 + S2[cond]**2 + S3[cond]**2) / S0[cond] if use_nan and np.any(~cond): DOP[~cond] = np.nan # Print the result if required if verbose or draw: heading = 'The degree of polarization of {} is:'.format( self.parent.name) PrintParam(param=(DOP), shape=self.parent.shape, title=("Degree of polarization"), heading=heading, verbose=verbose, draw=draw) # Return return DOP
[docs] def degree_depolarization(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the degree of depolarization (DD) of the Stokes vectors. Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result """ # Calculate the components DOP = self.degree_polarization(use_nan=use_nan, shape_like=shape_like, shape=shape, out_number=out_number) # Calculate the depolarization degree DD = np.sqrt(1 - DOP**2) # Print the result if required if verbose or draw: heading = 'The degree of depolarization of {} is:'.format( self.parent.name) PrintParam(param=(DD), shape=self.parent.shape, title=("Degree of depolarization"), heading=heading, verbose=verbose, draw=draw) # Return return DD
[docs] def degree_linear_polarization(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the degree of linear polarization (DLP) of the Stokes vectors. $DLP=\frac{\sqrt{S_{1}^{2}+S_{2}^{2}{S_{0}}$ References: Handbook of Optics vol 2. 22.16 (eq.4) Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result """ # Calculate the components S0, S1, S2, _ = self.components(shape_like=shape_like, shape=shape, out_number=out_number) # Calculate the polarization degree DOP = np.zeros_like(S0) cond = S0 != 0 DOP[cond] = np.sqrt(S1[cond]**2 + S2[cond]**2) / S0[cond] if use_nan and np.any(~cond): DOP[~cond] = np.nan # Print the result if required if verbose or draw: heading = 'The degree of linear polarization of {} is:'.format( self.parent.name) PrintParam(param=(DOP), shape=self.parent.shape, title=("Degree of linear polarization"), heading=heading, verbose=verbose, draw=draw) # Return return DOP
[docs] def degree_circular_polarization(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the degree of circular polarization (DCP) of the Stokes vectors. $DCP=\frac{S_{3}}{S_{0}}$ References: Handbook of Optics vol 2. 22.16 (eq.5) Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result """ # Calculate the components S0, _, _, S3 = self.components(shape_like=shape_like, shape=shape, out_number=out_number) # Calculate the polarization degree DOP = np.zeros_like(S0) cond = S0 != 0 DOP[cond] = np.abs(S3[cond]) / S0[cond] if use_nan and np.any(~cond): DOP[~cond] = np.nan # Print the result if required if verbose or draw: heading = 'The degree of circular polarization of {} is:'.format( self.parent.name) PrintParam(param=(DOP), shape=self.parent.shape, title=("Degree of circular polarization"), heading=heading, verbose=verbose, draw=draw) # Return return DOP
[docs] def alpha(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the ratio angle between electric field amplitudes. .. math:: arcsin(E_y/Ex). Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ # Calculate the components of the fully polarized part of S Sp, _ = self.polarized_unpolarized(shape_like=shape_like, shape=shape) S0, S1, _, _ = Sp.parameters.components(shape_like=shape_like, shape=shape, out_number=False) # Preallocate memory alpha = np.zeros_like(S0) if use_nan: alpha = alpha * np.nan # Calculate if intensity is enough cond = np.abs(S0) > tol_default**2 alpha[cond] = 0.5 * np.arccos(S1[cond] / S0[cond]) # If the result is a number and the user asks for it, return a float if out_number and alpha.size == 1: alpha = alpha[0] # Print the result if required if verbose or draw: heading = 'The alpha of {} is (deg):'.format(self.parent.name) PrintParam(param=(alpha / degrees), shape=self.parent.shape, title=("Alpha: (deg)"), heading=heading, verbose=verbose, draw=draw) return alpha
[docs] def delay(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Phase shift between $E_x$ and $E_y$ electric field components. .. math:: \delta_2 - \delta_1. Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ # Calculate the components S0, _, S2, S3 = self.components(shape_like=shape_like, shape=shape, out_number=False) # Preallocate memory delta = np.zeros_like(S0) if use_nan: delta = delta * np.nan # Calculate if intensity is enough cond = np.abs(S0) > tol_default**2 delta[cond] = np.arctan2(S3[cond], S2[cond]) # Make sure the values are in the correct range delta = put_in_limits(delta, 'delta', out_number=out_number) # Print the result if required if verbose or draw: heading = 'The delay of {} is (deg):'.format(self.parent.name) PrintParam(param=(delta / degrees), shape=self.parent.shape, title=("Delay: (deg)"), heading=heading, verbose=verbose, draw=draw) return delta
[docs] def delta(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Phase shift between $E_x$ and $E_y$ electric field components. This is the same as *delay*. .. math:: \delta_2 - \delta_1. Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ return self.delay(use_nan=use_nan, out_number=out_number, shape_like=shape_like, shape=shape, verbose=verbose, draw=draw)
[docs] def charac_angles(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the characteristic angles of the Stokes object. Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: alpha (float): [0, pi/2]: tan(alpha) is the ratio angle between amplitudes of the electric field. delay (float): [0, 2*pi]: phase difference between both components of the electric field. """ alpha = self.alpha(use_nan=use_nan, out_number=out_number, shape_like=shape_like, shape=shape, verbose=verbose, draw=draw) delta = self.delta(use_nan=use_nan, out_number=out_number, shape_like=shape_like, shape=shape, verbose=verbose, draw=draw) return alpha, delta
[docs] def ellipticity_param(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the ellipticity parameter: the ratio between the minor and major polarization ellipse axes. It takes only into account the fully polarized part of the Stokes vector. It goes from 0 for linearly polarized light to 1 for circulary polarized light. Positive sign means left-handed and negative values right-handed rotation direction. References: Handbook of Optics vol 2. 22.16 (eq.7) Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ # Calculate the components of the fully polarized part of S Sp, _ = self.polarized_unpolarized(shape_like=shape_like, shape=shape) S0, S1, S2, S3 = Sp.parameters.components(shape_like=shape_like, shape=shape, out_number=False) # Preallocate memory el = np.zeros_like(S0) if use_nan: el = el * np.nan # Act if we have some polarization cond = (S1 != 0) + (S2 != 0) + (S3 != 0) el[cond] = S3[cond] / (S0[cond] + np.sqrt(S1[cond]**2 + S2[cond]**2)) # Just in case remove nans if not use_nan: el[np.isnan(el)] = 0 # If the result is a number and the user asks for it, return a float if out_number and el.size == 1: el = el[0] # Print the result if required if verbose or draw: heading = 'The ellipticity parameter of {} is:'.format( self.parent.name) PrintParam(param=(el), shape=self.parent.shape, title=("Ellipticity parameter"), heading=heading, verbose=verbose, draw=draw) return el
[docs] def ellipticity_angle(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the ratio angle between the major and minor axis length. It goes from 0 degrees for linearly polarized light to 45 degrees for circular polarized light. Positive sign means left-handed and negative values right-handed rotation direction. References: J.J. Gil, R. Ossikovsky "Polarized light and the Mueller Matrix approach", CRC Press (2016) pp 13. Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ # Calculate the ellipticity parameter el = self.ellipticity_param(use_nan=use_nan, shape_like=shape_like, shape=shape, out_number=out_number) # Transform to angle el = np.arctan(el) # Print the result if required if verbose or draw: heading = 'The ellipticity angle of {} is (deg):'.format( self.parent.name) PrintParam(param=(el / degrees), shape=self.parent.shape, title=("Ellipticity angle: (deg)"), heading=heading, verbose=verbose, draw=draw) return el
[docs] def azimuth(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the rotation angle of the polarization elipse major axis. If S is not fully polarized, azimuth is computed on the fully polarized part of S. Azimuth ranges from 0 to 180 degres (not included this last value). References: Handbook of Optics vol 2. 22.16 (eq.8) Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ # Calculate the components of the fully polarized part of S Sp, _ = self.polarized_unpolarized(shape_like=shape_like, shape=shape) S0, S1, S2, S3 = Sp.parameters.components(shape_like=shape_like, shape=shape, out_number=False) # Preallocate memory azimuth = np.zeros_like(S0) if use_nan: azimuth = azimuth * np.nan # Take some cases apart cond1 = np.abs(S1) < tol_default**2 cond2 = S2 > tol_default**2 azimuth[cond1 * cond2] = np.pi / 4 cond2 = S2 < -tol_default**2 azimuth[cond1 * cond2] = -np.pi / 4 # General case azimuth[~cond1] = 0.5 * np.arctan2(S2[~cond1], S1[~cond1]) # Just in case remove nans if not use_nan: azimuth[np.isnan(azimuth)] = 0 # If the result is a number and the user asks for it, return a float if out_number and azimuth.size == 1: azimuth = azimuth[0] # Make sure the values are in the correct range azimuth = put_in_limits(azimuth, 'azimuth') # Print the result if required if verbose or draw: heading = 'The azimuth of {} is (deg):'.format(self.parent.name) PrintParam(param=(azimuth / degrees), shape=self.parent.shape, title=("Azimuth: (deg)"), heading=heading, verbose=verbose, draw=draw) return azimuth
[docs] def azimuth_ellipticity(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates both the azimuth and ellipticity of the Stokes object. Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: azimuth (float or numpy.ndarray): [0, pi): Azimuth. ellipticity (float or numpy.ndarray): [-pi/4, pi/4]: Ellipticity angle. """ azimuth = self.azimuth(use_nan=use_nan, out_number=out_number, shape_like=shape_like, shape=shape, verbose=verbose, draw=draw) ellipticity = self.ellipticity_angle(use_nan=use_nan, out_number=out_number, shape_like=shape_like, shape=shape, verbose=verbose, draw=draw) return azimuth, ellipticity
[docs] def eccentricity(self, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """The eccentricity of the polarization ellipse (0 for circular polarization, 1 for linear). References: Handbook of Optics vol 2. 22.16 (eq.9) Parameters: use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to 0. Default: True. out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: (float or numpy.ndarray): Result. """ # Calculate the ellipticity parameter e = self.ellipticity_param(use_nan=use_nan, out_number=out_number, shape_like=shape_like, shape=shape) # Now we can calculate the eccentricity e2 = np.sqrt(1 - e**2) # Print the result if required if verbose or draw: heading = 'The eccentricity of {} is:'.format(self.parent.name) PrintParam(param=(e2), shape=self.parent.shape, title=("Eccentricity"), heading=heading, verbose=verbose, draw=draw) return e2
[docs] def ellipse_axes(self, out_number=True, sort=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the length of major and minor axis (a,b) of the polarization ellipse. This is a wrapper around the Parameters_Jones_Vector.ellipse_axes function. References: D. Golstein "Polarized light" 2nd ed Marcel Dekker (2003), 3.4 eq.3-30a and 3-30b Parameters: out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. sort (bool): If True, it sorts a and b to be the major and minor axis respectively. Default: True. TODO: Check why this is neccessary. 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: a (numpy.ndarray or float): Major axis b (numpy.ndarray or float): Minor axis """ # Go to Jones, it is much easier J = Jones_vector() J.from_Stokes(self.parent) a, b = J.parameters.ellipse_axes(out_number=out_number, sort=sort, shape_like=shape_like, shape=shape, verbose=verbose, draw=draw) return a, b
[docs] def polarized_unpolarized(self, shape_like=None, shape=None, verbose=False): """Divides the Stokes vector in two, one totally polarized and the other totally unpolarized. References: Handbook of Optics vol 2. 22.16 (eq.6) Parameters: 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. verbose (bool): If True prints the parameter. Default: False. Returns: Sp (Stokes): Totally polarized Stokes object. Su (Stokes): Totally unpolarized Stokes object. """ # Calculate the degree of polarization and the components DOP = self.degree_polarization(shape=shape, shape_like=shape_like) S0, S1, S2, S3 = self.components(shape=shape, shape_like=shape_like) # Create the Stokes objects Sp, Su = create_Stokes(('Polarized ' + self.parent.name, 'Unpolarized ' + self.parent.name)) Sp.from_components( (DOP * S0, S1, S2, S3), global_phase=self.global_phase(shape=shape, shape_like=shape_like)) Su.from_components(((1 - DOP) * S0, 0, 0, 0)) # Print the result if required if verbose: print(Sp, Su) return Sp, Su
[docs] def amplitudes(self, give_Ez=False, give_unpol=False, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the $E_x$ and $E_y$ field amplitudes of the polarized part of the Stokes vectos. It may also calculate $E_z$ and the field amplitude of the unpolarized part. Parameters: give_Ez (bool): If True, it returns the z component of the electric field (all values will be 0). Default: False. give_unpol (bool): If True, it returns the unpolarized component of the electric field. 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: E0x (numpy.ndarray or float): Array of electric field amplitude along x axis. E0y (numpy.ndarray or float): Array of electric field amplitude along y axis. E0z (numpy.array, optional): Array of electric field amplitude along z axis. """ # Separate the polarized and the unpolarized parts Sp, Su = self.polarized_unpolarized() # Extract the important parameters S0, S1, _, _ = Sp.parameters.components(shape_like=shape_like, shape=shape, out_number=out_number) # Calculate the amplitudes E0x = np.sqrt((S0 + S1) / 2) E0y = np.sqrt((S0 - S1) / 2) if give_Ez: E0z = np.zeros_like(E0x) if give_unpol: Iu = Su.parameters.intensity(shape_like=shape_like, shape=shape, out_number=out_number) E0u = np.sqrt(Iu) # Print the result if required if verbose or draw: heading = 'The elctric field amplitudes of {} are (V/m):'.format( self.parent.name) if give_Ez and give_unpol: PrintParam(param=(E0x, E0y, E0z, E0u), shape=self.parent.shape, title=('Ex (V/m)', 'Ey (V/m)', 'Ez (V/m)', 'Eu (V/m)'), heading=heading, verbose=verbose, draw=draw) elif give_Ez: PrintParam(param=(E0x, E0y, E0z), shape=self.parent.shape, title=('Ex (V/m)', 'Ey (V/m)', 'Ez (V/m)'), heading=heading, verbose=verbose, draw=draw) elif give_unpol: PrintParam(param=(E0x, E0y, E0u), shape=self.parent.shape, title=('Ex (V/m)', 'Ey (V/m)', 'Eu (V/m)'), heading=heading, verbose=verbose, draw=draw) else: PrintParam(param=(E0x, E0y), shape=self.parent.shape, title=('Ex (V/m)', 'Ey (V/m)'), heading=heading, verbose=verbose, draw=draw) # Return if give_Ez and give_unpol: return E0x, E0y, E0z, E0u elif give_Ez: return E0x, E0y, E0z elif give_unpol: return E0x, E0y, E0u else: return E0x, E0y
[docs] def norm(self, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates the algebraic norm of the Stokes vectors. Parameters: out_number (bool): If True and the result is a 1x1 array, return a number instead. Default: True. 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. 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: S0 (float or numpy.ndarray): Array of total intensity. """ # Calculate the norm norm = np.linalg.norm(self.parent.M, axis=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 (a.u.):'.format(self.parent.name) PrintParam(param=norm, shape=self.parent.shape, title='Vector norm (a.u.)', heading=heading, verbose=verbose, draw=draw) return norm
[docs] def density(self, *args, **kwargs): """Maintained here just for compatibility. Look for Stokes.analysis.density instead. """ print( "DEPERCATION WARNING: Please use Stokes.analysis.density instead.") return self.parent.analysis.density(*args, **kwargs)
[docs] class Analysis_Stokes(object): """Class for Analysis of Stokes vetors. Parameters: stokes_vector (Stokes): Stokes vector Attributes: self.parent (Stokes) """ def __init__(self, parent): self.parent = parent
[docs] def filter_physical_conditions(self, tol=tol_default, keep=False): """Function that filters experimental errors by forcing the Stokes vector to fulfill the conditions necessary for a vector to be real light. Parameters: tol (float): Tolerance in equalities. Default: tol_default = 1e-6. keep (bool): If True, the object is updated to the filtered result. If false, a new fresh copy is created. Default: False. Returns: S (Stokes): Filtered Stokes vector. """ # Act differently if we want to keep self intact if keep: new_obj = self.parent.copy() else: new_obj = self.parent # Check if the Stokes vector is physically realizable cond, dict_param = new_obj.checks.is_physical(out_number=False, tol=tol, give_all=True) # Act only if it isn't if np.any(cond): S0, S1, S2, S3 = new_obj.parameters.components(out_number=False) # Fix the first and second conditions S0 = np.abs(S0) S1, S2, S3 = (np.real(S1), np.real(S2), np.real(S3)) # Fix the third condition PD = np.sqrt(S1**2 + S2**2 + S3**2) / S0 cond2 = PD > 1 + tol if np.any(cond2): S1[cond2] = S1[cond2] / PD[cond2] S2[cond2] = S2[cond2] / PD[cond2] S3[cond2] = S3[cond2] / PD[cond2] # Reconstruct the object new_obj.from_components((S0, S1, S2, S3)) return new_obj
[docs] def density(self, radius=0.1, sampling=1 * degrees, draw=False, return_coor=False, return_fig=False, **kwargs): """Calculates the density of elements in the Poincare sphere. Unlike many other parameters methods, the return object is always a 2D object covering the whole Poincare Sphere. Parameters: radius (number): Unit radius to compute density. Must be positive and lower than 1. Default: 0.1. sampling (number): Resolution in azimuth and ellipticity. Default: 1 degree. draw (bool): If True, plot it. Default: False. return_coor (bool): If True, it returns the x, y and z coordinates of the Poincare sphere. Default: False. return_fig (bool): If True, the figure object will be returned. Default: False. **kwargs: If draw is True, these arguments will be passed to draw_poincare method. Returns: density (np.ndarray): Result. Units: points / srad. """ # Calculate spherical coordinates az = np.linspace(0, 179.99 * degrees, int(np.ceil(179.99 * degrees / sampling) + 1)) el = np.linspace(-45 * degrees, 45 * degrees, int(np.ceil(90 * degrees / sampling) + 1)) az, el = np.meshgrid(az, el) x, y, z = azel_2_xyz(az, el) # Prepare the vector obj = self.parent.flatten(keep=True) obj.set_depolarization(0) obj.normalize() # Strecth arrays shape = [obj.size] + list((x.shape)) xb = np.broadcast_to(x, shape) yb = np.broadcast_to(y, shape) zb = np.broadcast_to(z, shape) coor = np.stack((xb, yb, zb)) M = np.broadcast_to(obj.M[1:, :, np.newaxis, np.newaxis], [3] + shape) # Calculate distances dist = np.sqrt(np.sum((M - coor)**2, axis=0)) # Calculate density dens = dist < radius dens = np.sum(dens, axis=0) / (dens * np.pi * np.sqrt(1 - radius**2)) # Draw if draw: S = Stokes("Density").from_components((1, x, y, z)) fig = S.draw_poincare(kind="surf", param=dens, **kwargs) fig.show() # Returns ret = [dens] if return_coor: ret = ret + [x, y, z] if return_fig: ret.append(fig) return ret
[docs] def existence(self, radius=0.1, sampling=1 * degrees, draw=False, return_coor=False, return_fig=False, **kwargs): """Calculates the density of elements in the Poincare sphere. Unlike many other parameters methods, the return object is always a 2D object covering the whole Poincare Sphere. Parameters: radius (number): Unit radius to compute density. Must be positive and lower than 1. Default: 0.1. sampling (number): Resolution in azimuth and ellipticity. Default: 1 degree. draw (bool): If True, plot it. Default: False. return_coor (bool): If True, it returns the x, y and z coordinates of the Poincare sphere. Default: False. return_fig (bool): If True, the figure object will be returned. Default: False. **kwargs: If draw is True, these arguments will be passed to draw_poincare method. Returns: existence (np.ndarray of bools): Result. fig (plotly.graph_objects.Figure): Figure. Returned only if return_fig is True. """ # Calculate density density, x, y, z = self.density(radius=radius, sampling=sampling, draw=False, return_coor=True, return_fig=False) existence = np.array(density >= 1, dtype=float) # Draw if draw: S = Stokes("Existence").from_components((1, x, y, z)) fig = S.draw_poincare(kind="surf", param=existence, **kwargs) fig.show() # Returns ret = [existence] if return_coor: ret = ret + [x, y, z] if return_fig: ret.append(fig) return ret
[docs] class Check_Stokes(object): """Class for Check of Stokes vectors. Parameters: stokes_vector (Stokes): Stokes vector Attributes: self.parent (Stokes) """ def __init__(self, parent): self.parent = parent def __repr__(self): """Prints all the checks""" self.get_all(verbose=True, draw=True) return ''
[docs] def get_all(self, verbose=False, draw=False): """Creates a dictionary with all the checks of Stokes vectors. Parameters: 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. """ dict_params = {} dict_params['is_physical'], dict_params[ 'dict_is_physical'] = self.is_physical(verbose=verbose, draw=draw, give_all=True) dict_params['is_linear'] = self.is_linear(verbose=verbose, draw=draw) dict_params['is_circular'] = self.is_circular(verbose=verbose, draw=draw) dict_params['is_right_handed'] = self.is_right_handed(verbose=verbose, draw=draw) dict_params['is_left_handed'] = self.is_left_handed(verbose=verbose, draw=draw) dict_params['is_polarized'] = self.is_polarized(verbose=verbose, draw=draw) dict_params['is_totally_polarized'] = self.is_totally_polarized( verbose=verbose, draw=draw) dict_params['is_depolarized'] = self.is_depolarized(verbose=verbose, draw=draw) dict_params['is_totally_depolarized'] = self.is_totally_depolarized( verbose=verbose, draw=draw) return dict_params
[docs] def is_physical(self, tol=tol_default, give_all=False, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Tests the conditions of physical realizability: * Condition 1: All components must be real. * Condition 2: S0 must be positive. * Condition 3: The square sum of S1, S2 and S3 must be equal or lower than S0. References: Handbook of Optics vol 2. 22.34 "Polarized light and the Mueller Matrix approach", J. J. Gil, pp 187. Parameters: tol (float): Tolerance in equality conditions. Default: tol_default = 1e-6. give_all (bool): If True, the function will return the individual conditions. 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: cond (bool): Is real or not. ind (dictionary, optional): dictionary with condition, True/False, distance """ # Calculate the three Conditions S0, S1, S2, S3 = self.parent.parameters.components(out_number=False) cond1_0, cond1_1, cond1_2, cond1_3 = (np.isreal(S0), np.isreal(S1), np.isreal(S2), np.isreal(S3)) cond2 = S0 >= -tol DOP = self.parent.parameters.degree_polarization(out_number=False) cond3 = DOP >= 1 + tol # Calculate Ez and reshape if required cond1_0, cond1_1, cond1_2, cond1_3, cond2, cond3 = reshape( [cond1_0, cond1_1, cond1_2, cond1_3, cond2, cond3], shape_like=shape_like, shape_fun=shape, obj=self.parent) # Calculate final conditions cond1 = cond1_0 * cond1_1 * cond1_2 * cond1_3 cond = cond1 * cond2 * cond3 # Create the dictionary with all the conditions if desired if give_all: dict = {} dict['cond1'] = cond1 dict['cond1_0'] = cond1_0 dict['cond1_1'] = cond1_1 dict['cond1_2'] = cond1_2 dict['cond1_3'] = cond1_3 dict['cond2'] = cond2 dict['S0'] = S0 dict['cond3'] = cond3 # Print the result if required if verbose or draw: if give_all: heading = '{} is physically realizable:'.format( self.parent.name) PrintParam(param=(cond1_0, cond1_1, cond1_2, cond1_3, cond1, cond2, cond3, cond), shape=self.parent.shape, title=("Physicall (cond. 1, S0)", "Physicall (cond. 1, S1)", "Physicall (cond. 1, S2)", "Physicall (cond. 1, S3)", "Physicall (cond. 1)", "Physicall (cond. 2)", "Physicall (cond. 3)", "Physicall"), heading=heading, verbose=verbose, draw=draw) else: heading = '{} is physically realizable:'.format( self.parent.name) PrintParam(param=(cond), shape=self.parent.shape, title=("Physicall"), heading=heading, verbose=verbose, draw=draw) # Return if give_all: return cond, dict else: return cond
[docs] def is_linear(self, tol=tol_default, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates if the Stokes vectors are linearly polarized. Parameters: tol (float): Tolerance. Default: tol_default = 1e-6. use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to False. Default: True. 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): Array of the condition. """ # Preallocate memory if use_nan: cond = np.zeros(max(self.parent.size, 1)) # No bool to allow nans else: cond = np.zeros(max(self.parent.size, 1), dtype=bool) # If the Stokes vector is totally depolarized, polarization is not defined cond1 = self.is_totally_depolarized(shape=False, out_number=False) if use_nan: cond[cond1] = np.nan # Calculate the relative degree of linear polarization DP = self.parent.parameters.degree_polarization(shape=False, out_number=False) DLP = self.parent.parameters.degree_linear_polarization( shape=False, out_number=False) cond[~cond1] = DLP[~cond1] / DP[~cond1] > 1 - tol # Give a number if required if out_number and cond.size == 1: cond = cond[0] # Reshape if required cond = reshape([cond], shape_like=shape_like, shape_fun=shape, obj=self.parent) # Print the result if required if verbose or draw: heading = '{} is linearly polarized:'.format(self.parent.name) PrintParam(param=(cond), shape=self.parent.shape, title=('Linearly polarized'), heading=heading, verbose=verbose, draw=draw) # Return return cond
[docs] def is_circular(self, tol=tol_default, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates if the Stokes vectors are circularly polarized. Parameters: tol (float): Tolerance. Default: tol_default: 1e-6. use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to False. Default: True. 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): Array of the condition. """ # Preallocate memory if use_nan: cond = np.zeros(max(self.parent.size, 1)) # No bool to allow nans else: cond = np.zeros(max(self.parent.size, 1), dtype=bool) # If the Stokes vector is totally depolarized, polarization is not defined cond1 = self.is_totally_depolarized(shape=False, out_number=False) if use_nan: cond[cond1] = np.nan # Calculate the relative degree of linear polarization DP = self.parent.parameters.degree_polarization(shape=False, out_number=False) DCP = self.parent.parameters.degree_circular_polarization( shape=False, out_number=False) cond[~cond1] = DCP[~cond1] / DP[~cond1] > 1 - tol # Give a number if required if out_number and cond.size == 1: cond = cond[0] # Reshape if required cond = reshape([cond], shape_like=shape_like, shape_fun=shape, obj=self.parent) # Print the result if required if verbose or draw: heading = '{} is circcularly polarized:'.format(self.parent.name) PrintParam(param=(cond), shape=self.parent.shape, title=('Circularly polarized'), heading=heading, verbose=verbose, draw=draw) # Return return cond
[docs] def is_right_handed(self, tol=tol_default, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates if the polarization rotation direction is right-handed. Parameters: tol (float): Tolerance. Default: tol_default: 1e-6. use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to False. Default: True. 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): Array of the condition. """ # Preallocate memory if use_nan: cond = np.zeros(max(self.parent.size, 1)) # No bool to allow nans else: cond = np.zeros(max(self.parent.size, 1), dtype=bool) # If the Stokes vector is linearly polarized or totally depolarized, right and left handed is not defined cond1 = self.is_linear(shape=False, out_number=False, use_nan=False) * \ self.is_totally_depolarized( shape=False, out_number=False, tol=tol) if use_nan: cond[cond1] = np.nan # If delay is between 0 and pi (not included), it is right handed delay = self.parent.parameters.delay(shape=False, out_number=False, use_nan=False) cond2 = ~cond1 * (delay > 0) * (delay < np.pi) cond[cond2] = True # Give a number if required if out_number and cond.size == 1: cond = cond[0] # Reshape if required cond = reshape([cond], shape_like=shape_like, shape_fun=shape, obj=self.parent) # Print the result if required if verbose or draw: heading = '{} is right handed:'.format(self.parent.name) PrintParam(param=(cond), shape=self.parent.shape, title=('Right handed'), heading=heading, verbose=verbose, draw=draw) # Return return cond
[docs] def is_left_handed(self, tol=tol_default, use_nan=True, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates if the polarization rotation direction is left-handed. Parameters: tol (float): Tolerance. Default: tol_default: 1e-6. use_nan (bool): If True, unknown values are set to np.nan, otherwise they are set to False. Default: True. 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): Array of the condition. """ # Preallocate memory if use_nan: cond = np.zeros(max(self.parent.size, 1)) # No bool to allow nans else: cond = np.zeros(max(self.parent.size, 1), dtype=bool) # If the Stokes vector is linearly polarized or totally depolarized, right and left handed is not defined cond1 = self.is_linear(shape=False, out_number=False, use_nan=False) * \ self.is_totally_depolarized( shape=False, out_number=False, tol=tol) if use_nan: cond[cond1] = np.nan # If delay is between 0 and pi (not included), it is right handed delay = self.parent.parameters.delay(shape=False, out_number=False) cond2 = ~cond1 * (delay > np.pi) * (delay < 2 * np.pi) cond[cond2] = True # Give a number if required if out_number and cond.size == 1: cond = cond[0] # Reshape if required cond = reshape([cond], shape_like=shape_like, shape_fun=shape, obj=self.parent) # Print the result if required if verbose or draw: heading = '{} is left handed:'.format(self.parent.name) PrintParam(param=(cond), shape=self.parent.shape, title=('Left handed'), heading=heading, verbose=verbose, draw=draw) # Return return cond
[docs] def is_polarized(self, tol=tol_default, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates if the Stokes vectors are at least partially polarized. Parameters: tol (float): Tolerance. Default: tol_default = 1e-6. 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): Array of the condition. """ # Calculate the polarization degree PD = self.parent.parameters.degree_polarization(shape=shape, shape_like=shape_like, out_number=out_number) cond = PD > tol # Print the result if required if verbose or draw: heading = '{} is polarized:'.format(self.parent.name) PrintParam(param=(cond), shape=self.parent.shape, title=('Polarized'), heading=heading, verbose=verbose, draw=draw) # Return return cond
[docs] def is_totally_polarized(self, tol=tol_default, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates if the Stokes vectors are totally polarized. Parameters: tol (float): Tolerance. Default: tol_default: 1e-6. 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): Array of the condition. """ # Calculate the polarization degree PD = self.parent.parameters.degree_polarization(shape=shape, shape_like=shape_like, out_number=out_number) cond = PD > 1 - tol # Print the result if required if verbose or draw: heading = '{} is totally polarized:'.format(self.parent.name) PrintParam(param=(cond), shape=self.parent.shape, title=('Totally polarized'), heading=heading, verbose=verbose, draw=draw) # Return return cond
[docs] def is_depolarized(self, tol=tol_default, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates if the Stokes vectors are at least partially depolarized. Parameters: tol (float): Tolerance. Default: tol_default: 1e-6. 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): Array of the condition. """ # Calculate the polarization degree DD = self.parent.parameters.degree_depolarization( shape=shape, shape_like=shape_like, out_number=out_number) cond = DD > tol # Print the result if required if verbose or draw: heading = '{} is depolarized:'.format(self.parent.name) PrintParam(param=(cond), shape=self.parent.shape, title=('Depolarized'), heading=heading, verbose=verbose, draw=draw) # Return return cond
[docs] def is_totally_depolarized(self, tol=tol_default, out_number=True, shape_like=None, shape=None, verbose=False, draw=False): """Calculates if the Stokes vectors are totally depolarized. Parameters: tol (float): Tolerance. Default: tol_default: 1e-6. 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): Array of the condition. """ # Calculate the polarization degree DD = self.parent.parameters.degree_depolarization( shape=shape, shape_like=shape_like, out_number=out_number) cond = DD > 1 - tol # Print the result if required if verbose or draw: heading = '{} is totally depolarized:'.format(self.parent.name) PrintParam(param=(cond), shape=self.parent.shape, title=('Totally depolarized'), heading=heading, verbose=verbose, draw=draw) # Return return cond