Source code for elli.solver2x2

# Encoding: utf-8
import numpy as np
from numpy.lib.scimath import arcsin, sqrt

from .solver import Solver
from .result import Result


[docs]class Solver2x2(Solver): ''' Solver class to evaluate Experiment objects. Simple but fast 2x2 transfer matrix method. Cannot handle anisotropy or anything fancy, thus Jonas and Mueller matrices cannot be calculated (respective functions return None). '''
[docs] def list_snell(self, n_list): angles = arcsin(n_list[0] * np.sin(np.deg2rad(self.theta_i)) / n_list) angles[0] = np.where(np.invert(Solver2x2.is_forward_angle(n_list[0], angles[0])), np.pi - angles[0], angles[0]) angles[-1] = np.where(np.invert(Solver2x2.is_forward_angle(n_list[-1], angles[-1])), np.pi - angles[-1], angles[-1]) return angles
[docs] def calculate(self) -> Result: """Calculates the transfer matrix for the given material stack""" if len(self.permittivity_profile) > 2: d, eps = list(zip(*self.permittivity_profile[1:-1])) d_list = np.array(d) n_list = sqrt(np.vstack([self.permittivity_profile[0][1][:, 0, 0], np.array(eps)[..., 0, 0], self.permittivity_profile[-1][1][:, 0, 0]])) else: d_list = np.array([]) n_list = sqrt(np.vstack([self.permittivity_profile[0][1][:, 0, 0], self.permittivity_profile[-1][1][:, 0, 0]])) num_layers = n_list.shape[0] th_list = self.list_snell(n_list) kz_list = 2 * np.pi * n_list * np.cos(th_list) / self.lbda delta = kz_list[1:-1] * (d_list if n_list.ndim == 1 else d_list[:, None]) esum = 'ij...,jk...->ik...' ones = np.repeat(1, n_list.shape[1]) if n_list.ndim > 1 else 1 rs, rp, ts, tp = Solver2x2.fresnel(n_list[0], n_list[1], th_list[0], th_list[1]) Ms = np.array([[ones, rs], [rs, ones]], dtype=complex) / ts Mp = np.array([[ones, rp], [rp, ones]], dtype=complex) / tp for i in range(1, num_layers-1): rs, rp, ts, tp = Solver2x2.fresnel(n_list[i], n_list[i+1], th_list[i], th_list[i+1]) em = np.exp(-1j * delta[i-1]) ep = np.exp(1j * delta[i-1]) Ms = np.einsum(esum, Ms, np.array([[em, rs * em], [rs * ep, ep]], dtype=complex) / ts) Mp = np.einsum(esum, Mp, np.array([[em, rp * em], [rp * ep, ep]], dtype=complex) / tp) rtots = Ms[1, 0] / Ms[0, 0] ttots = 1 / Ms[0, 0] rtotp = Mp[1, 0] / Mp[0, 0] ttotp = 1 / Mp[0, 0] zeros = np.repeat(0, n_list.shape[1]) if n_list.ndim > 1 else 1 jones_matrix_r = np.moveaxis(np.array([[rtotp, zeros], [zeros, rtots]]), 2, 0) jones_matrix_t = np.moveaxis(np.array([[ttotp, zeros], [zeros, ttots]]), 2, 0) # TODO: Test if p and s correction formulas are needed. power_correction = (((n_list[-1] * np.cos(th_list[-1])).real) / (n_list[0] * np.cos(th_list[0])).real) return Result(self.experiment, jones_matrix_r, jones_matrix_t, power_correction)
[docs] @staticmethod def fresnel(n_i, n_t, th_i, th_t): """Calculate fresnel coefficients at the interface of two materials n_i: Refractive index of the material of the incident wave n_t: Refractive index of the material of the transmitted wave thi_i: Incident angle of the incident wave thi_t: Refracted angle of the transmitted wave returns: r_s: s-polarized reflection coefficient r_p: p-polarized reflection coefficient t_s: s-polarized transmission coefficient t_p: p-polarized transmission coefficient """ cos_i = np.cos(th_i) cos_t = np.cos(th_t) r_s = (n_i * cos_i - n_t * cos_t) / (n_i * cos_i + n_t * cos_t) r_p = (n_t * cos_i - n_i * cos_t) / (n_t * cos_i + n_i * cos_t) t_s = 2 * n_i * cos_i / (n_i * cos_i + n_t * cos_t) t_p = 2 * n_i * cos_i / (n_t * cos_i + n_i * cos_t) return r_s, r_p, t_s, t_p
[docs] @staticmethod def is_forward_angle(n, theta): ncostheta = n * np.cos(theta) return np.where(abs(ncostheta.imag) > 1e-10, ncostheta.imag > 0, ncostheta.real > 0)