Source code for opticalmaterialspy._material_base

import numpy as np
from scipy import constants as spc
from scipy import interpolate as spi
import abc
from functools import wraps


class _Material(metaclass=abc.ABCMeta):
    '''
    Abstract material class that can calculate many
    material properties based on the permittivity.

    Args:
        wlMinNm (float): The minimum wavelength [nm].
        wlMaxNm (float): The maximum wavelength [nm].
    '''
    def __init__(self, wlMinNm=300., wlMaxNm=5000.):
        self._wlMin = wlMinNm
        self._wlMax = wlMaxNm

        self._wls = np.linspace(self._wlMin, self._wlMax, 100, False)
        self._wlStep = (self._wlMax-self._wlMin)/100.

        self.nDer1Func = None
        self.nDer2Func = None

    def _convertWavelengthUnitsNm(self, wavelength):
        if wavelength is not False and wavelength is not None:
            if np.all(wavelength <= self._wlMax * 1.e-9):
                wlFactor = 1.e9
            elif np.all(wavelength <= self._wlMax * 1.e-3):
                wlFactor = 1.e3
            else:
                wlFactor = 1.
            wl = wavelength * wlFactor
            assert(np.all(wl >= self._wlMin) and np.all(wl <= self._wlMax)), \
                'Wavelength not in range %.1f nm to %.1f nm.' % (self._wlMin, self._wlMax)
        else:
            wl = wavelength
        return wl

    def convertWavelengthUnitsNm(func):
        @wraps(func)
        def _convertWavelengthUnitsNmWrapper(self, wavelength):
            wl = self._convertWavelengthUnitsNm(wavelength)
            return func(self, wl)
        return _convertWavelengthUnitsNmWrapper

    @abc.abstractproperty
    def _eps(self, wavelength=None):
        '''
        The permittivty of the desired material.

        Each new material needs this method implemented.

        Args:
            wavelength (float, list, None): The wavelength the permittivty
                will be evaluated at.

        Returns:
            float, list: The permittivty at the target wavelength.
        '''
        return NotImplementedError

    @convertWavelengthUnitsNm
    def eps(self, wavelength=None):
        '''
        The permittivty of the desired material.

        Args:
            wavelength (float, list, None): The wavelength the permittivty
                will be evaluated at.

        Returns:
            float, list: The permittivty at the target wavelength.
        '''
        return self._eps(wavelength)

    def _nDer1(self, wavelength):
        if not self.nDer1Func:
            nDer1 = np.gradient(self.n(self._wls), self._wlStep) # [1/nm]
            self.nDer1Func = spi.interp1d(self._wls, nDer1)
        return self.nDer1Func(wavelength) * 1.e9  # [1/nm] -> [1/m]

    def _nDer2(self, wavelength):
        if not self.nDer2Func:
            nDer2 = np.gradient(self._nDer1(self._wls), self._wlStep)  # [1/(nm)^2]
            self.nDer2Func = spi.interp1d(self._wls, nDer2)
        return self.nDer2Func(wavelength) * 1.e9  # [1/(nm)^2] -> [1/m]

    def n(self, wavelength=None):
        '''
        The refractive index of the desired material.

        Args:
            wavelength (float, list, None): The wavelength the refractive index
                will be evaluated at.

        Returns:
            float, list: The refractive index at the target wavelength.
        '''
        return self.eps(wavelength)**0.5

    # dn/dlambda
    def nDer1(self, wavelength):
        '''
        The first derivative of the refractive index with respect to
        wavelength.

        Args:
            wavelength (float, list, None): The wavelength(s) the derivative
                will be evaluated at.

        Returns:
            float, list: The refractive index at the target wavelength(s).
        '''
        return self._nDer1(wavelength)

    # d^2n/dlambda^2
    def nDer2(self, wavelength):
        '''
        The second derivative of the refractive index with respect to
        wavelength.

        Args:
            wavelength (float, list, None): The wavelength(s) the derivative
                will be evaluated at.

        Returns:
            float, list: The refractive index at the target wavelength(s).
        '''
        return self._nDer2(wavelength)

    # n_g: group velocity index
    @convertWavelengthUnitsNm
    def ng(self, wavelength):
        '''
        The group index with respect to wavelength.

        Args:
            wavelength (float, list, None): The wavelength(s) the group
                index will be evaluated at.

        Returns:
            float, list: The group index at the target wavelength(s).
        '''
        return self.n(wavelength) - (wavelength*1.e-9)*self.nDer1(wavelength)

    # Group velocity (v_g)
    def vg(self, wavelength):
        '''
        The group velocities with respect to wavelength.

        Args:
            wavelength (float, list, None): The wavelength(s) the group
                velocities will be evaluated at.

        Returns:
            float, list: The group velocities at the target wavelength(s).
        '''
        return spc.c / self.ng(wavelength)

    # Group velocity dispersion (GVD)
    # beta_2 = lambda^3/(2 pi c^2) * d^2n/dlambda^2
    @convertWavelengthUnitsNm
    def gvd(self, wavelength):
        '''
        The group velocity dispersion (GVD) with respect to wavelength.

        Args:
            wavelength (float, list, None): The wavelength(s) the GVD will
                be evaluated at.

        Returns:
            float, list: The GVD at the target wavelength(s).
        '''
        g = (wavelength*1.e-9)**3./(2.*spc.pi*spc.c**2.) * self.nDer2(wavelength)
        return g

    # beta(w) = beta0 + beta1(w-w0) + 1/2 beta2(w-w0)^2 + ...
    @convertWavelengthUnitsNm
    def beta0(self, wavelength):
        '''
        The propagation constant with respect to wavelength.

        Args:
            wavelength (float, list, None): The wavelength(s) the
                propagation constant will be evaluated at.

        Returns:
            float, list: The propagation constant at the target wavelength(s).
        '''
        return 2.*spc.pi*self.n(wavelength)/(wavelength*1.e-9)

    def beta1(self, wavelength):
        '''
        The derivative of the propagation constant with respect to wavelength.

        Args:
            wavelength (float, list, None): The wavelength(s) the
                propagation constant will be evaluated at.

        Returns:
            float, list: The propagation constant at the target wavelength(s).
        '''
        return 1./self.vg(wavelength)

    def beta2(self, wavelength):
        '''
        The second derivative of the propagation constant with respect to wavelength.

        Args:
            wavelength (float, list, None): The wavelength(s) the
                propagation constant will be evaluated at.

        Returns:
            float, list: The propagation constant at the target wavelength(s).
        '''
        return self.gvd(wavelength)

    def z0(self, wavelength):
        '''
        The wave impedance assuming the material is dielectric (not
        lossy or magnetic).

        Args:
            wavelength (float, list, None): The wavelength(s) the
                propagation constant will be evaluated at.

        Returns:
            float, list: The impedance of the material.

        '''
        return 120*np.pi / self.n(wavelength)

    @staticmethod
    def _cauchy_equation(wavelength, coefficients):
        '''
        Helpful function to evaluate Cauchy equations.

        Args:
            wavelength (float, list, None): The wavelength(s) the
                Cauchy equation will be evaluated at.
            coefficients (list): A list of the coefficients of
                the Cauchy equation.

        Returns:
            float, list: The refractive index at the target wavelength(s).
        '''
        n = 0.
        for i, c in enumerate(coefficients):
            exponent  = 2*i
            n += c / wavelength**exponent
        return n

class _MaterialAni(metaclass=abc.ABCMeta):
    def __init__(self):
        pass

    @abc.abstractproperty
    def xx(self):
        pass

    @abc.abstractproperty
    def yy(self):
        pass

    @abc.abstractproperty
    def zz(self):
        pass

    @property
    def xy(self):
        return None

    @property
    def xz(self):
        return None

    @property
    def yx(self):
        return None

    @property
    def yz(self):
        return None

    @property
    def zx(self):
        return None

    @property
    def zy(self):
        return None

    def n3(self, wl):
        return [self.xx.n(wl), self.yy.n(wl), self.zz.n(wl)]

    def n_xyz(self, wl):
        return self.n3(wl)

    def n5(self, wl):
        return [self.xx.n(wl), self.xy.n(wl), self.yx.n(wl),
                self.yy.n(wl), self.zz.n(wl)]