Skip to content

Utilities

vectorwaves.utils

Utility Functions

Miscellaneous helper functions for calculating optical properties, polarization ellipses, and basis transformations.

decompose_in_basis(E1, E2, u)

Decomposes a 2-component complex field (E1, E2) into an orthonormal polarization basis defined by the reference vector u.

Parameters:

Name Type Description Default
E1 ndarray

Complex arrays representing the field in the original basis.

required
E2 ndarray

Complex arrays representing the field in the original basis.

required
u tuple or ndarray

Reference vector (2,) defining the first new basis vector (in the same plane).

required

Returns:

Type Description
dict

Contains the new basis vectors and the projected field components: {'u_hat': array, 'v_hat': array, 'E_u': array, 'E_v': array}

Source code in src\vectorwaves\utils.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def decompose_in_basis(E1: np.ndarray, E2: np.ndarray, u: Union[Tuple[complex, complex], np.ndarray]) -> Dict[str, np.ndarray]:
    """
    Decomposes a 2-component complex field (E1, E2) into an orthonormal 
    polarization basis defined by the reference vector `u`.

    Parameters
    ----------
    E1, E2 : np.ndarray
        Complex arrays representing the field in the original basis.
    u : tuple or np.ndarray
        Reference vector (2,) defining the first new basis vector (in the same plane).

    Returns
    -------
    dict 
        Contains the new basis vectors and the projected field components:
        {'u_hat': array, 'v_hat': array, 'E_u': array, 'E_v': array}
    """
    u_arr = np.asarray(u, dtype=complex)
    norm = np.linalg.norm(u_arr)
    u_hat = u_arr / norm if norm > 0 else u_arr

    # Construct orthogonal vector v_hat
    v_hat = np.array([-np.conj(u_hat[1]), np.conj(u_hat[0])], dtype=complex)

    E_u = np.conj(u_hat[0]) * E1 + np.conj(u_hat[1]) * E2
    E_v = np.conj(v_hat[0]) * E1 + np.conj(v_hat[1]) * E2

    return {
        "E_u": E_u,
        "E_v": E_v,
        "u_hat": u_hat,
        "v_hat": v_hat,
    }

get_pol_ellipse_params(E1, E2)

Calculates the geometric properties of the polarization ellipse formed by two orthogonal electric field components.

Parameters:

Name Type Description Default
E1 ndarray

Complex electric field components of the same shape.

required
E2 ndarray

Complex electric field components of the same shape.

required

Returns:

Type Description
dict

Dictionary containing arrays of the same shape as inputs: - 'psi': Orientation angle in[-pi/2, pi/2] - 'chi': Ellipticity angle in [-pi/4, pi/4] - 'delta': Phase difference (phi_2 - phi_1) in range [-pi, pi] - 'a': Semi-major axis length (normalized to local intensity) - 'b': Semi-minor axis length (normalized to local intensity) - 'handedness': +1 for LCP/CCW, -1 for RCP/CW (based on S3 sign)

Source code in src\vectorwaves\utils.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def get_pol_ellipse_params(E1: np.ndarray, E2: np.ndarray) -> Dict[str, np.ndarray]:
    """
    Calculates the geometric properties of the polarization ellipse
    formed by two orthogonal electric field components.

    Parameters
    ----------
    E1, E2 : np.ndarray
        Complex electric field components of the same shape.

    Returns
    -------
    dict 
        Dictionary containing arrays of the same shape as inputs:
        - 'psi': Orientation angle in[-pi/2, pi/2]
        - 'chi': Ellipticity angle in [-pi/4, pi/4]
        - 'delta': Phase difference (phi_2 - phi_1) in range [-pi, pi]
        - 'a': Semi-major axis length (normalized to local intensity)
        - 'b': Semi-minor axis length (normalized to local intensity)
        - 'handedness': +1 for LCP/CCW, -1 for RCP/CW (based on S3 sign)
    """
    stokes = get_stokes_params(E1, E2, normalize=True)
    S0, s1, s2, s3 = stokes['S0'], stokes['s1'], stokes['s2'], stokes['s3']

    # 1. Orientation Angle (Psi)
    psi = 0.5 * np.arctan2(s2, s1)

    # 2. Ellipticity Angle (Chi)
    s3_norm = np.clip(s3, -1.0, 1.0)
    chi = 0.5 * np.arcsin(s3_norm)

    # 3. Phase Difference (Delta)
    delta = np.angle(E2 * np.conj(E1))

    # 4. Geometry for Plotting (Semi-axes)
    amp = np.sqrt(S0)
    a = amp * np.cos(chi)
    b = amp * np.abs(np.sin(chi))

    return {
        'psi': psi, 
        'chi': chi, 
        'delta': delta,
        'a': a, 
        'b': b,
        'handedness': np.sign(s3)
    }

get_stokes_params(E1, E2, normalize=False)

Computes Stokes parameters from two orthogonal electric field components.

For a beam propagating along the Z-axis, you can pass FieldResult.E[0] (Ex) and FieldResult.E[1] (Ey) to calculate the transverse Stokes parameters.

Parameters:

Name Type Description Default
E1 ndarray

Complex electric field components of the same shape.

required
E2 ndarray

Complex electric field components of the same shape.

required
normalize bool

If True, returns the normalized Stokes parameters (s1, s2, s3) where s_i = S_i / S0. Defaults to False.

False

Returns:

Type Description
dict
  • If normalize=True: {'S0': ..., 's1': ..., 's2': ..., 's3': ...}
  • If normalize=False: {'S0': ..., 'S1': ..., 'S2': ..., 'S3': ...}
Source code in src\vectorwaves\utils.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def get_stokes_params(E1: np.ndarray, E2: np.ndarray, normalize: bool = False) -> Dict[str, np.ndarray]:
    """
    Computes Stokes parameters from two orthogonal electric field components.

    For a beam propagating along the Z-axis, you can pass `FieldResult.E[0]` (Ex) 
    and `FieldResult.E[1]` (Ey) to calculate the transverse Stokes parameters.

    Parameters
    ----------
    E1, E2 : np.ndarray
        Complex electric field components of the same shape.
    normalize : bool, optional
        If True, returns the normalized Stokes parameters (s1, s2, s3) where 
        s_i = S_i / S0. Defaults to False.

    Returns
    -------
    dict 
        - If normalize=True:  {'S0': ..., 's1': ..., 's2': ..., 's3': ...}
        - If normalize=False: {'S0': ..., 'S1': ..., 'S2': ..., 'S3': ...}
    """    
    I1 = np.abs(E1)**2
    I2 = np.abs(E2)**2

    S0 = I1 + I2
    S1 = I1 - I2

    # Calculate cross term
    cross_term = E1 * np.conj(E2)
    S2 = 2 * np.real(cross_term)
    S3 = 2 * np.imag(cross_term)

    if normalize:
        denom = np.where(S0 == 0, 1.0, S0)
        return {'S0': S0, 's1': S1/denom, 's2': S2/denom, 's3': S3/denom}

    return {'S0': S0, 'S1': S1, 'S2': S2, 'S3': S3}