Module layerlumos.layerlumos

Expand source code
import numpy as np
from scipy.constants import c
from .utils import load_material, interpolate_material
import numpy as np

def stackrt(n, d, f, theta=np.array([0])):
    """
    Calculates the reflection and transmission coefficients for a multilayer stack
    at different frequencies and incidence angles.

    Parameters:
    - n (numpy.ndarray): The refractive indices of the layers for each frequency.
                         Shape should be (Nfreq, Nlayers), where Nfreq is the number of
                         frequencies and Nlayers is the number of layers.
    - d (numpy.ndarray): The thicknesses of the layers. Shape should be (Nlayers,).
    - f (numpy.ndarray): The frequencies at which to calculate the coefficients.
                         Shape should be (Nfreq,).
    - theta (float or numpy.ndarray): The incidence angle(s) in degrees. Can be a single value or an array of angles.

    Returns:
    - R_TE (numpy.ndarray): Reflectance for TE polarization. Shape is (Nfreq,).
    - T_TE (numpy.ndarray): Transmittance for TE polarization. Shape is (Nfreq,).
    - R_TM (numpy.ndarray): Reflectance for TM polarization. Shape is (Nfreq,).
    - T_TM (numpy.ndarray): Transmittance for TM polarization. Shape is (Nfreq,).
    """
    c = 3e8  # Speed of light in vacuum
    wvl = c / f  # Convert frequency to wavelength
    theta_radians = np.radians(theta)  # Convert incidence angle to radians

    # Initialize arrays for both amplitude and intensity coefficients
    r_TE, r_TM, t_TE, t_TM = np.zeros((len(f), len(theta_radians)), dtype=np.complex128), np.zeros((len(f), len(theta_radians)), dtype=np.complex128), np.zeros((len(f), len(theta_radians)), dtype=np.complex128), np.zeros((len(f), len(theta_radians)), dtype=np.complex128)
    R_TE, T_TE, R_TM, T_TM = np.zeros((len(f), len(theta_radians))), np.zeros((len(f), len(theta_radians))), np.zeros((len(f), len(theta_radians))), np.zeros((len(f), len(theta_radians)))

    for i, lambda_i in enumerate(wvl):
        for angle_idx, theta_i in enumerate(theta_radians):
            M_TE, M_TM = np.eye(2, dtype=np.complex128), np.eye(2, dtype=np.complex128)
            # Snell's law to calculate angle in each layer
            sin_theta_i = np.sin(theta_i)
            n_0 = n[i, 0]
            sin_theta_layers = n_0 * sin_theta_i / n[i, :]
            cos_theta_layers = np.sqrt(1 - sin_theta_layers**2)
            
            for j in range(len(d) - 1):
                n_j, n_next = n[i, j], n[i, j+1]
                d_j = d[j+1]
                cos_theta_j, cos_theta_next = cos_theta_layers[j], cos_theta_layers[j+1]

                # Interface calculations for TE and TM polarization with angle consideration
                r_jk_TE = (n_j * cos_theta_j - n_next * cos_theta_next) / (n_j * cos_theta_j + n_next * cos_theta_next)
                t_jk_TE = 2 * n_j * cos_theta_j / (n_j * cos_theta_j + n_next * cos_theta_next)
                r_jk_TM = (n_next * cos_theta_j - n_j * cos_theta_next) / (n_next * cos_theta_j + n_j * cos_theta_next)
                t_jk_TM = 2 * n_j * cos_theta_j / (n_next * cos_theta_j + n_j * cos_theta_next)
                
                M_jk_TE = np.array([[1/t_jk_TE, r_jk_TE/t_jk_TE], [r_jk_TE/t_jk_TE, 1/t_jk_TE]], dtype=np.complex128)
                M_jk_TM = np.array([[1/t_jk_TM, r_jk_TM/t_jk_TM], [r_jk_TM/t_jk_TM, 1/t_jk_TM]], dtype=np.complex128)
                
                # Adjusting phase change calculation for angle
                delta = 2 * np.pi * n_next * d_j * cos_theta_j / lambda_i
                P = np.array([[np.exp(-1j * delta.item()), 0], [0, np.exp(1j * delta.item())]], dtype=np.complex128)
                
                M_TE = np.dot(M_TE, np.dot(M_jk_TE, P))
                M_TM = np.dot(M_TM, np.dot(M_jk_TM, P))

            r_TE[i, angle_idx], t_TE[i, angle_idx] = M_TE[1, 0] / M_TE[0, 0], 1 / M_TE[0, 0]
            r_TM[i, angle_idx], t_TM[i, angle_idx] = M_TM[1, 0] / M_TM[0, 0], 1 / M_TM[0, 0]
            R_TE[i, angle_idx], T_TE[i, angle_idx] = np.abs(r_TE[i, angle_idx])**2, np.abs(t_TE[i, angle_idx])**2 * np.real(n[i, -1] / n_0)
            R_TM[i, angle_idx], T_TM[i, angle_idx] = np.abs(r_TM[i, angle_idx])**2, np.abs(t_TM[i, angle_idx])**2 * np.real(n[i, -1] / n_0)
    return R_TE, T_TE, R_TM, T_TM

def stackrt0(n, d, f):
    """
    Calculates the reflection and transmission coefficients for a multilayer stack
    at different frequencies under normal incidence.

    Parameters:
    - n (numpy.ndarray): The refractive indices of the layers for each frequency.
                         Shape should be (Nfreq, Nlayers), where Nfreq is the number of
                         frequencies and Nlayers is the number of layers.
    - d (numpy.ndarray): The thicknesses of the layers. Shape should be (Nlayers,).
    - f (numpy.ndarray): The frequencies at which to calculate the coefficients.
                         Shape should be (Nfreq,).

    Returns:
    - R_TE (numpy.ndarray): Reflectance for TE polarization. Shape is (Nfreq,).
    - T_TE (numpy.ndarray): Transmittance for TE polarization. Shape is (Nfreq,).
    - R_TM (numpy.ndarray): Reflectance for TM polarization. Shape is (Nfreq,).
    - T_TM (numpy.ndarray): Transmittance for TM polarization. Shape is (Nfreq,).
    """
    wvl = c / f  # Convert frequency to wavelength
    # Initialize arrays for both amplitude and intensity coefficients
    r_TE, r_TM, t_TE, t_TM = np.zeros_like(f, dtype=np.complex128), np.zeros_like(f, dtype=np.complex128), np.zeros_like(f, dtype=np.complex128), np.zeros_like(f, dtype=np.complex128)
    R_TE, T_TE, R_TM, T_TM = np.zeros_like(f), np.zeros_like(f), np.zeros_like(f), np.zeros_like(f)

    for i, freq in enumerate(f):  # Iterate over each frequency
        lambda_i = wvl[i]
        M_TE, M_TM = np.eye(2, dtype=np.complex128), np.eye(2, dtype=np.complex128)
        for j in range(0, len(n[i, :]) - 1):
            n_j = n[i, j]
            n_next = n[i, j+1]
            d_next = d[j+1]

            # Interface calculations for TE and TM polarization
            r_jk_TE = (n_j - n_next) / (n_j + n_next)
            t_jk_TE = 2 * n_j / (n_j + n_next)
            M_jk_TE = np.array([[1/t_jk_TE, r_jk_TE/t_jk_TE], [r_jk_TE/t_jk_TE, 1/t_jk_TE]], dtype=np.complex128)
            r_jk_TM = (n_next - n_j) / (n_next + n_j)
            t_jk_TM = 2 * n_j / (n_next + n_j)
            M_jk_TM = np.array([[1/t_jk_TM, r_jk_TM/t_jk_TM], [r_jk_TM/t_jk_TM, 1/t_jk_TM]], dtype=np.complex128)
            
            delta = 2 * np.pi * n_next * d_next / lambda_i
            P = np.array([[np.exp(-1j * delta.item()), 0], [0, np.exp(1j * delta.item())]], dtype=np.complex128)
            M_TE = np.dot(M_TE, np.dot(M_jk_TE, P))
            M_TM = np.dot(M_TM, np.dot(M_jk_TM, P))

        r_TE[i], t_TE[i] = M_TE[1, 0] / M_TE[0, 0], 1 / M_TE[0, 0]
        r_TM[i], t_TM[i] = M_TM[1, 0] / M_TM[0, 0], 1 / M_TM[0, 0]
        R_TE[i], T_TE[i] = np.abs(r_TE[i])**2, np.abs(t_TE[i])**2 * np.real(n[i, -1] / n[i, 0])
        R_TM[i], T_TM[i] = np.abs(r_TM[i])**2, np.abs(t_TM[i])**2 * np.real(n[i, -1] / n[i, 0])

    return R_TE, T_TE, R_TM, T_TM

Functions

def stackrt(n, d, f, theta=array([0]))

Calculates the reflection and transmission coefficients for a multilayer stack at different frequencies and incidence angles.

Parameters: - n (numpy.ndarray): The refractive indices of the layers for each frequency. Shape should be (Nfreq, Nlayers), where Nfreq is the number of frequencies and Nlayers is the number of layers. - d (numpy.ndarray): The thicknesses of the layers. Shape should be (Nlayers,). - f (numpy.ndarray): The frequencies at which to calculate the coefficients. Shape should be (Nfreq,). - theta (float or numpy.ndarray): The incidence angle(s) in degrees. Can be a single value or an array of angles.

Returns: - R_TE (numpy.ndarray): Reflectance for TE polarization. Shape is (Nfreq,). - T_TE (numpy.ndarray): Transmittance for TE polarization. Shape is (Nfreq,). - R_TM (numpy.ndarray): Reflectance for TM polarization. Shape is (Nfreq,). - T_TM (numpy.ndarray): Transmittance for TM polarization. Shape is (Nfreq,).

Expand source code
def stackrt(n, d, f, theta=np.array([0])):
    """
    Calculates the reflection and transmission coefficients for a multilayer stack
    at different frequencies and incidence angles.

    Parameters:
    - n (numpy.ndarray): The refractive indices of the layers for each frequency.
                         Shape should be (Nfreq, Nlayers), where Nfreq is the number of
                         frequencies and Nlayers is the number of layers.
    - d (numpy.ndarray): The thicknesses of the layers. Shape should be (Nlayers,).
    - f (numpy.ndarray): The frequencies at which to calculate the coefficients.
                         Shape should be (Nfreq,).
    - theta (float or numpy.ndarray): The incidence angle(s) in degrees. Can be a single value or an array of angles.

    Returns:
    - R_TE (numpy.ndarray): Reflectance for TE polarization. Shape is (Nfreq,).
    - T_TE (numpy.ndarray): Transmittance for TE polarization. Shape is (Nfreq,).
    - R_TM (numpy.ndarray): Reflectance for TM polarization. Shape is (Nfreq,).
    - T_TM (numpy.ndarray): Transmittance for TM polarization. Shape is (Nfreq,).
    """
    c = 3e8  # Speed of light in vacuum
    wvl = c / f  # Convert frequency to wavelength
    theta_radians = np.radians(theta)  # Convert incidence angle to radians

    # Initialize arrays for both amplitude and intensity coefficients
    r_TE, r_TM, t_TE, t_TM = np.zeros((len(f), len(theta_radians)), dtype=np.complex128), np.zeros((len(f), len(theta_radians)), dtype=np.complex128), np.zeros((len(f), len(theta_radians)), dtype=np.complex128), np.zeros((len(f), len(theta_radians)), dtype=np.complex128)
    R_TE, T_TE, R_TM, T_TM = np.zeros((len(f), len(theta_radians))), np.zeros((len(f), len(theta_radians))), np.zeros((len(f), len(theta_radians))), np.zeros((len(f), len(theta_radians)))

    for i, lambda_i in enumerate(wvl):
        for angle_idx, theta_i in enumerate(theta_radians):
            M_TE, M_TM = np.eye(2, dtype=np.complex128), np.eye(2, dtype=np.complex128)
            # Snell's law to calculate angle in each layer
            sin_theta_i = np.sin(theta_i)
            n_0 = n[i, 0]
            sin_theta_layers = n_0 * sin_theta_i / n[i, :]
            cos_theta_layers = np.sqrt(1 - sin_theta_layers**2)
            
            for j in range(len(d) - 1):
                n_j, n_next = n[i, j], n[i, j+1]
                d_j = d[j+1]
                cos_theta_j, cos_theta_next = cos_theta_layers[j], cos_theta_layers[j+1]

                # Interface calculations for TE and TM polarization with angle consideration
                r_jk_TE = (n_j * cos_theta_j - n_next * cos_theta_next) / (n_j * cos_theta_j + n_next * cos_theta_next)
                t_jk_TE = 2 * n_j * cos_theta_j / (n_j * cos_theta_j + n_next * cos_theta_next)
                r_jk_TM = (n_next * cos_theta_j - n_j * cos_theta_next) / (n_next * cos_theta_j + n_j * cos_theta_next)
                t_jk_TM = 2 * n_j * cos_theta_j / (n_next * cos_theta_j + n_j * cos_theta_next)
                
                M_jk_TE = np.array([[1/t_jk_TE, r_jk_TE/t_jk_TE], [r_jk_TE/t_jk_TE, 1/t_jk_TE]], dtype=np.complex128)
                M_jk_TM = np.array([[1/t_jk_TM, r_jk_TM/t_jk_TM], [r_jk_TM/t_jk_TM, 1/t_jk_TM]], dtype=np.complex128)
                
                # Adjusting phase change calculation for angle
                delta = 2 * np.pi * n_next * d_j * cos_theta_j / lambda_i
                P = np.array([[np.exp(-1j * delta.item()), 0], [0, np.exp(1j * delta.item())]], dtype=np.complex128)
                
                M_TE = np.dot(M_TE, np.dot(M_jk_TE, P))
                M_TM = np.dot(M_TM, np.dot(M_jk_TM, P))

            r_TE[i, angle_idx], t_TE[i, angle_idx] = M_TE[1, 0] / M_TE[0, 0], 1 / M_TE[0, 0]
            r_TM[i, angle_idx], t_TM[i, angle_idx] = M_TM[1, 0] / M_TM[0, 0], 1 / M_TM[0, 0]
            R_TE[i, angle_idx], T_TE[i, angle_idx] = np.abs(r_TE[i, angle_idx])**2, np.abs(t_TE[i, angle_idx])**2 * np.real(n[i, -1] / n_0)
            R_TM[i, angle_idx], T_TM[i, angle_idx] = np.abs(r_TM[i, angle_idx])**2, np.abs(t_TM[i, angle_idx])**2 * np.real(n[i, -1] / n_0)
    return R_TE, T_TE, R_TM, T_TM
def stackrt0(n, d, f)

Calculates the reflection and transmission coefficients for a multilayer stack at different frequencies under normal incidence.

Parameters: - n (numpy.ndarray): The refractive indices of the layers for each frequency. Shape should be (Nfreq, Nlayers), where Nfreq is the number of frequencies and Nlayers is the number of layers. - d (numpy.ndarray): The thicknesses of the layers. Shape should be (Nlayers,). - f (numpy.ndarray): The frequencies at which to calculate the coefficients. Shape should be (Nfreq,).

Returns: - R_TE (numpy.ndarray): Reflectance for TE polarization. Shape is (Nfreq,). - T_TE (numpy.ndarray): Transmittance for TE polarization. Shape is (Nfreq,). - R_TM (numpy.ndarray): Reflectance for TM polarization. Shape is (Nfreq,). - T_TM (numpy.ndarray): Transmittance for TM polarization. Shape is (Nfreq,).

Expand source code
def stackrt0(n, d, f):
    """
    Calculates the reflection and transmission coefficients for a multilayer stack
    at different frequencies under normal incidence.

    Parameters:
    - n (numpy.ndarray): The refractive indices of the layers for each frequency.
                         Shape should be (Nfreq, Nlayers), where Nfreq is the number of
                         frequencies and Nlayers is the number of layers.
    - d (numpy.ndarray): The thicknesses of the layers. Shape should be (Nlayers,).
    - f (numpy.ndarray): The frequencies at which to calculate the coefficients.
                         Shape should be (Nfreq,).

    Returns:
    - R_TE (numpy.ndarray): Reflectance for TE polarization. Shape is (Nfreq,).
    - T_TE (numpy.ndarray): Transmittance for TE polarization. Shape is (Nfreq,).
    - R_TM (numpy.ndarray): Reflectance for TM polarization. Shape is (Nfreq,).
    - T_TM (numpy.ndarray): Transmittance for TM polarization. Shape is (Nfreq,).
    """
    wvl = c / f  # Convert frequency to wavelength
    # Initialize arrays for both amplitude and intensity coefficients
    r_TE, r_TM, t_TE, t_TM = np.zeros_like(f, dtype=np.complex128), np.zeros_like(f, dtype=np.complex128), np.zeros_like(f, dtype=np.complex128), np.zeros_like(f, dtype=np.complex128)
    R_TE, T_TE, R_TM, T_TM = np.zeros_like(f), np.zeros_like(f), np.zeros_like(f), np.zeros_like(f)

    for i, freq in enumerate(f):  # Iterate over each frequency
        lambda_i = wvl[i]
        M_TE, M_TM = np.eye(2, dtype=np.complex128), np.eye(2, dtype=np.complex128)
        for j in range(0, len(n[i, :]) - 1):
            n_j = n[i, j]
            n_next = n[i, j+1]
            d_next = d[j+1]

            # Interface calculations for TE and TM polarization
            r_jk_TE = (n_j - n_next) / (n_j + n_next)
            t_jk_TE = 2 * n_j / (n_j + n_next)
            M_jk_TE = np.array([[1/t_jk_TE, r_jk_TE/t_jk_TE], [r_jk_TE/t_jk_TE, 1/t_jk_TE]], dtype=np.complex128)
            r_jk_TM = (n_next - n_j) / (n_next + n_j)
            t_jk_TM = 2 * n_j / (n_next + n_j)
            M_jk_TM = np.array([[1/t_jk_TM, r_jk_TM/t_jk_TM], [r_jk_TM/t_jk_TM, 1/t_jk_TM]], dtype=np.complex128)
            
            delta = 2 * np.pi * n_next * d_next / lambda_i
            P = np.array([[np.exp(-1j * delta.item()), 0], [0, np.exp(1j * delta.item())]], dtype=np.complex128)
            M_TE = np.dot(M_TE, np.dot(M_jk_TE, P))
            M_TM = np.dot(M_TM, np.dot(M_jk_TM, P))

        r_TE[i], t_TE[i] = M_TE[1, 0] / M_TE[0, 0], 1 / M_TE[0, 0]
        r_TM[i], t_TM[i] = M_TM[1, 0] / M_TM[0, 0], 1 / M_TM[0, 0]
        R_TE[i], T_TE[i] = np.abs(r_TE[i])**2, np.abs(t_TE[i])**2 * np.real(n[i, -1] / n[i, 0])
        R_TM[i], T_TM[i] = np.abs(r_TM[i])**2, np.abs(t_TM[i])**2 * np.real(n[i, -1] / n[i, 0])

    return R_TE, T_TE, R_TM, T_TM