Source code for pyoptics.interfaces

#!/usr/bin/env python
#-*- coding:utf-8 -*-

"""Fresnel coefficients of a single planar interface between two media.

All functions take the angle of incidence ``phi_1`` (in radians, measured
from the surface normal) and the refractive indices ``n_1`` of the incident
and ``n_2`` of the second medium. Every argument may be a number or an array;
arrays broadcast against each other with the usual NumPy rules, so a sweep
over angles and wavelengths is a single call.

Conventions
-----------
Fields vary as exp(i(k.r - omega*t)): a wave travelling in +z is
exp(+i*k_z*z), and absorbing media have refractive indices n + i*kappa with
kappa > 0. The normal component of the transmitted wave vector,
k_0*n_2*cos(phi_2), is chosen with a non-negative imaginary part, so that
transmitted waves (evanescent ones under total internal reflection, or waves
in absorbing media) decay away from the interface.

The amplitude coefficients are ratios of the full electric field amplitudes
with the sign convention of Hecht, Optics, eqs. (4.42) - (4.45): at normal
incidence r_s = (n_1 - n_2)/(n_1 + n_2) and r_p = (n_2 - n_1)/(n_1 + n_2).
The p-wave reference directions are such that r_p = tan(phi_1 - phi_2)/
tan(phi_1 + phi_2) for dielectrics, which vanishes at Brewster's angle.

The energy coefficients R and T are ratios of the normal components of the
time averaged Poynting vectors. They are well defined for a lossless incident
medium (real ``n_1``) only; then R + T = 1 also for an absorbing second
medium, T being the power that enters it.

References
----------
M. Born and E. Wolf, "Principles of Optics", 7th ed., Cambridge University
Press (1999), section 1.5.

E. Hecht, "Optics", 5th ed., Pearson (2017), section 4.6.

A. Zangwill, "Modern Electrodynamics", Cambridge University Press (2013).

R. M. A. Azzam and N. M. Bashara, "Ellipsometry and Polarized Light",
North-Holland (1977).
"""

from math import pi

import numpy as np
import numpy.lib.scimath as scimath

__all__ = ["refracted_angle", "r_s", "r_p", "t_s", "t_p", "R_s", "R_p", "T_s",
           "T_p", "brewster_angle", "critical_angle"]


[docs] def refracted_angle(phi_1, n_1, n_2): """Angle of the refracted wave from Snell's law. Parameters ---------- phi_1 : number or array Angle of incidence in radians. n_1, n_2 : numbers or arrays (Complex) refractive indices of the incident and the second medium. Returns ------- phi_2 : number or array Refracted angle in radians, solving n_1*sin(phi_1) = n_2*sin(phi_2). Real for propagating waves between lossless media, complex under total internal reflection or for absorbing media. Of the two solutions of Snell's law, the one with Im(n_2*cos(phi_2)) >= 0 is returned, i.e. the transmitted wave exp(i*n_2*k_0*cos(phi_2)*z) decays (exp(-i*omega*t) convention). """ phi_2 = scimath.arcsin(n_1*np.sin(phi_1)/n_2) # arcsin() picks the growing evanescent wave under total internal # reflection; pi - phi_2 is the other solution of Snell's law: growing = np.imag(n_2*np.cos(phi_2)) < 0 phi_2 = np.where(growing, pi - phi_2, phi_2)[()] return phi_2
def _normal_indices(phi_1, n_1, n_2): """Normal components q_j = n_j*cos(phi_j) of the wave vectors over k_0. q_2 = sqrt(n_2**2 - (n_1*sin(phi_1))**2) is computed directly instead of via the (possibly complex) refracted angle, on the branch Im(q_2) >= 0 chosen by refracted_angle(). It stays real for propagating waves between lossless media, so that the Fresnel coefficients are real there. """ phi_1, n_1, n_2 = np.asarray(phi_1), np.asarray(n_1), np.asarray(n_2) q_1 = n_1*np.cos(phi_1) q_2 = scimath.sqrt(n_2**2 - (n_1*np.sin(phi_1))**2) q_2 = np.where(np.imag(q_2) < 0, -q_2, q_2) return q_1, q_2, n_1, n_2
[docs] def r_s(phi_1, n_1, n_2): """Reflection coefficient (field amplitude) for s-polarized waves. Parameters ---------- phi_1 : number or array Angle of incidence in radians. n_1, n_2 : numbers or arrays (Complex) refractive indices of the incident and the second medium. Returns ------- r : number or array (n_1*cos(phi_1) - n_2*cos(phi_2))/(n_1*cos(phi_1) + n_2*cos(phi_2)). Real for propagating waves between lossless media, complex otherwise. """ q_1, q_2, _, _ = _normal_indices(phi_1, n_1, n_2) return ((q_1 - q_2)/(q_1 + q_2))[()]
[docs] def r_p(phi_1, n_1, n_2): """Reflection coefficient (field amplitude) for p-polarized waves. Parameters ---------- phi_1 : number or array Angle of incidence in radians. n_1, n_2 : numbers or arrays (Complex) refractive indices of the incident and the second medium. Returns ------- r : number or array (n_2*cos(phi_1) - n_1*cos(phi_2))/(n_2*cos(phi_1) + n_1*cos(phi_2)). Real for propagating waves between lossless media, complex otherwise. """ q_1, q_2, n_1, n_2 = _normal_indices(phi_1, n_1, n_2) # multiplied by n_1*n_2 to avoid cos(phi_2): a, b = n_2**2*q_1, n_1**2*q_2 return ((a - b)/(a + b))[()]
[docs] def t_s(phi_1, n_1, n_2): """Transmission coefficient (field amplitude) for s-polarized waves. Parameters ---------- phi_1 : number or array Angle of incidence in radians. n_1, n_2 : numbers or arrays (Complex) refractive indices of the incident and the second medium. Returns ------- t : number or array 2*n_1*cos(phi_1)/(n_1*cos(phi_1) + n_2*cos(phi_2)). Note t_s = 1 + r_s. """ q_1, q_2, _, _ = _normal_indices(phi_1, n_1, n_2) return (2*q_1/(q_1 + q_2))[()]
[docs] def t_p(phi_1, n_1, n_2): """Transmission coefficient (field amplitude) for p-polarized waves. Parameters ---------- phi_1 : number or array Angle of incidence in radians. n_1, n_2 : numbers or arrays (Complex) refractive indices of the incident and the second medium. Returns ------- t : number or array 2*n_1*cos(phi_1)/(n_2*cos(phi_1) + n_1*cos(phi_2)). Note n_2*t_p = n_1*(1 + r_p). """ q_1, q_2, n_1, n_2 = _normal_indices(phi_1, n_1, n_2) return (2*n_1*n_2*q_1/(n_2**2*q_1 + n_1**2*q_2))[()]
[docs] def R_s(phi_1, n_1, n_2): """Reflectance (power reflection coefficient) for s-polarized waves. Parameters ---------- phi_1 : number or array Angle of incidence in radians. n_1, n_2 : numbers or arrays (Complex) refractive indices of the incident and the second medium. Returns ------- R : number or array ``|r_s|**2``. """ return np.abs(r_s(phi_1, n_1, n_2))**2
[docs] def R_p(phi_1, n_1, n_2): """Reflectance (power reflection coefficient) for p-polarized waves. Parameters ---------- phi_1 : number or array Angle of incidence in radians. n_1, n_2 : numbers or arrays (Complex) refractive indices of the incident and the second medium. Returns ------- R : number or array ``|r_p|**2``. """ return np.abs(r_p(phi_1, n_1, n_2))**2
[docs] def T_s(phi_1, n_1, n_2): """Transmittance (power transmission coefficient) for s-polarized waves. Ratio of the normal components of the time averaged Poynting vectors of the transmitted and the incident wave. T_s = 0 under total internal reflection, and R_s + T_s = 1 for a lossless incident medium. Parameters ---------- phi_1 : number or array Angle of incidence in radians. n_1, n_2 : numbers or arrays Refractive indices of the incident (real) and the second (complex) medium. Returns ------- T : number or array Re(n_2*cos(phi_2))/Re(n_1*cos(phi_1))*|t_s|**2. """ q_1, q_2, n_1, n_2 = _normal_indices(phi_1, n_1, n_2) t = 2*q_1/(q_1 + q_2) return (np.real(q_2)/np.real(q_1)*np.abs(t)**2)[()]
[docs] def T_p(phi_1, n_1, n_2): """Transmittance (power transmission coefficient) for p-polarized waves. Ratio of the normal components of the time averaged Poynting vectors of the transmitted and the incident wave. T_p = 0 under total internal reflection, and R_p + T_p = 1 for a lossless incident medium. Parameters ---------- phi_1 : number or array Angle of incidence in radians. n_1, n_2 : numbers or arrays Refractive indices of the incident (real) and the second (complex) medium. Returns ------- T : number or array Re(conj(n_2)*cos(phi_2))/Re(conj(n_1)*cos(phi_1))*|t_p|**2. """ q_1, q_2, n_1, n_2 = _normal_indices(phi_1, n_1, n_2) t = 2*n_1*n_2*q_1/(n_2**2*q_1 + n_1**2*q_2) # conj(n_j)*cos(phi_j) = conj(n_j)*q_j/n_j: eta_1 = np.real(np.conj(n_1)*q_1/n_1) eta_2 = np.real(np.conj(n_2)*q_2/n_2) return (eta_2/eta_1*np.abs(t)**2)[()]
[docs] def brewster_angle(n_1, n_2): """Brewster's angle, at which the p-wave reflectance vanishes. Parameters ---------- n_1, n_2 : numbers or arrays Real refractive indices of the incident and the second medium. Returns ------- phi_B : number or array arctan(n_2/n_1) in radians. Brewster's angle exists for both directions of incidence; going from the denser medium, it lies below the critical angle. """ return np.arctan2(n_2, n_1)
[docs] def critical_angle(n_1, n_2): """Critical angle for total internal reflection. Parameters ---------- n_1, n_2 : numbers or arrays Real refractive indices of the incident and the second medium. Returns ------- phi_c : number or array arcsin(n_2/n_1) in radians where n_1 >= n_2, NaN where n_1 < n_2 (no total internal reflection when going into a denser medium). """ ratio = np.asarray(n_2/np.asarray(n_1, dtype=float), dtype=float) with np.errstate(invalid="ignore"): phi_c = np.where(ratio <= 1, np.arcsin(np.minimum(ratio, 1)), np.nan) return phi_c[()]