def get_eps(self, points): """ Compute the permittivity of the layer over a 'points' tuple containing a meshgrid in x, y defined by arrays of same shape. """ xmesh, ymesh = points if ymesh.shape != xmesh.shape: raise ValueError("xmesh and ymesh must have the same shape") eps_r = self.eps_b * bd.ones(xmesh.shape) # Slightly hacky way to include the periodicity a1 = self.lattice.a1 a2 = self.lattice.a2 a_p = min([np.linalg.norm(a1), np.linalg.norm(a2)]) nmax = np.int_( np.sqrt( np.square(np.max(abs(xmesh))) + np.square(np.max(abs(ymesh)))) / a_p) + 1 for shape in self.shapes: for n1 in range(-nmax, nmax + 1): for n2 in range(-nmax, nmax + 1): in_shape = shape.is_inside(xmesh + n1 * a1[0] + n2 * a2[0], ymesh + n1 * a1[1] + n2 * a2[1]) eps_r[in_shape] = utils.get_value(shape.eps) return eps_r
def run(self, kpoints=np.array([[0], [0]]), pol='te', numeig=10): """ Run the simulation. The computed eigen-frequencies are stored in :attr:`PlaneWaveExp.freqs`, and the corresponding eigenvectors - in :attr:`PlaneWaveExp.eigvecs`. Parameters ---------- kpoints : np.ndarray, optional Numpy array of shape (2, Nk) with the [kx, ky] coordinates of the k-vectors over which the simulation is run. pol : {'te', 'tm'}, optional Polarization of the modes. numeig : int, optional Number of eigen-frequencies to be stored (starting from lowest). """ self._kpoints = kpoints self.pol = pol.lower() # Change this if switching to a solver that allows for variable numeig self.numeig = numeig self._compute_ft() self._compute_eps_inv() freqs = [] self._eigvecs = [] for ik, k in enumerate(kpoints.T): # Construct the matrix for diagonalization if self.pol == 'te': mat = bd.dot(bd.transpose(k[:, bd.newaxis] + self.gvec), (k[:, bd.newaxis] + self.gvec)) mat = mat * self.eps_inv_mat elif self.pol == 'tm': Gk = bd.sqrt(bd.square(k[0] + self.gvec[0, :]) + \ bd.square(k[1] + self.gvec[1, :])) mat = bd.outer(Gk, Gk) mat = mat * self.eps_inv_mat else: raise ValueError("Polarization should be 'TE' or 'TM'") # Diagonalize using numpy.linalg.eigh() for now; should maybe switch # to scipy.sparse.linalg.eigsh() in the future # NB: we shift the matrix by np.eye to avoid problems at the zero- # frequency mode at Gamma (freq2, evecs) = bd.eigh(mat + bd.eye(mat.shape[0])) freq1 = bd.sqrt(bd.abs(freq2 - bd.ones(mat.shape[0])))/2/np.pi i_sort = bd.argsort(freq1)[0:self.numeig] freq = freq1[i_sort] evec = evecs[:, i_sort] freqs.append(freq) self._eigvecs.append(evec) # Store the eigenfrequencies taking the standard reduced frequency # convention for the units (2pi a/c) self._freqs = bd.array(freqs) self.mat = mat
def rad_modes(omega: float, g_array: np.ndarray, eps_array: np.ndarray, d_array: np.ndarray, pol: str='TE', clad: int=0): """ Function to compute the radiative modes of a multi-layer structure Input g_array : numpy array of wave vector amplitudes eps_array : numpy array of slab permittivities, starting with lower cladding and ending with upper cladding d_array : thicknesses of each layer omega : frequency of the radiative mode pol : polarization, 'te' or 'tm' clad : radiating into cladding index, 0 (lower) or 1 (upper) Output Xs, Ys : X, Y coefficients of the modes in every layer """ Xs, Ys = [], [] for ig, g in enumerate(g_array): g_val = max([g, 1e-10]) # Get the scattering and transfer matrices if pol.lower()=='te' and clad==0: S_mat, T_mat = S_T_matrices_TE(omega, g_val, eps_array[::-1], d_array[::-1]) elif pol.lower()=='te' and clad==1: S_mat, T_mat = S_T_matrices_TE(omega, g_val, eps_array, d_array) elif pol.lower()=='tm' and clad==0: S_mat, T_mat = S_T_matrices_TM(omega, g_val, eps_array[::-1], d_array[::-1]) elif pol.lower()=='tm' and clad==1: S_mat, T_mat = S_T_matrices_TM(omega, g_val, eps_array, d_array) # Compute the transfer matrix coefficients coeffs = [bd.array([0, 1])] coeffs.append(bd.dot(T_mat[0], bd.dot(S_mat[0], coeffs[0]))) for i, S in enumerate(S_mat[1:-1]): T2 = T_mat[i+1] T1 = T_mat[i] coeffs.append(bd.dot(T2, bd.dot(S, bd.dot(T1, coeffs[-1])))) coeffs.append(bd.dot(S_mat[-1], bd.dot(T_mat[-1], coeffs[-1]))) coeffs = bd.array(coeffs, dtype=bd.complex).transpose() # Normalize coeffs = coeffs / coeffs[1, -1] if pol=='te': c_ind = [0, -1] coeffs = coeffs/bd.sqrt(eps_array[c_ind[clad]])/omega # Assign correctly based on which cladding the modes radiate to if clad == 0: Xs.append(coeffs[0, ::-1].ravel()) Ys.append(coeffs[1, ::-1].ravel()) elif clad == 1: Xs.append(coeffs[1, :].ravel()) Ys.append(coeffs[0, :].ravel()) Xs = bd.array(Xs, dtype=bd.complex).transpose() Ys = bd.array(Ys, dtype=bd.complex).transpose() # Fix the dimension if g_array is an empty list if len(g_array)==0: Xs = bd.ones((eps_array.size, 1))*Xs Ys = bd.ones((eps_array.size, 1))*Ys """ (Xs, Ys) corresponds to the X, W coefficients for TE radiative modes in Andreani and Gerace PRB (2006), and to the Z, Y coefficients for TM modes Note that there's an error in the manuscript; within our definitions, the correct statement should be: X3 = 0 for states out-going in the lower cladding; normalize through W1; and W1 = 0 for states out-going in the upper cladding; normalize through X3. """ return (Xs, Ys)
def __init__(self, eps=1, x_cent=0, y_cent=0, f_as=np.array([0.]), f_bs=np.array([]), npts=100): """Create a shape defined by its Fourier coefficients in polar coordinates. Parameters ---------- eps : float Permittivity value x_cent : float x-coordinate of shape center y_cent : float y-coordinate of shape center f_as : Numpy array Fourier coefficients an (see Note) f_bs : Numpy array Fourier coefficients bn (see Note) npts : int Number of points in the polygonal discretization Note ---- We use the discrete Fourier expansion ``R(phi) = a0/2 + sum(an*cos(n*phi)) + sum(bn*sin(n*phi))`` The coefficients ``f_as`` are an array containing ``[a0, a1, ...]``, while ``f_bs`` define ``[b1, b2, ...]``. Note ---- This is a subclass of Poly because we discretize the shape into a polygon and use that to compute the fourier transform for the mode expansions. For intricate shapes, increase ``npts`` to make the discretization smoother. """ self.x_cent = x_cent self.y_cent = y_cent self.npts = npts phis = bd.linspace(0, 2 * np.pi, npts + 1) R_phi = f_as[0] / 2 * bd.ones(phis.shape) for (n, an) in enumerate(f_as[1:]): R_phi = R_phi + an * bd.cos((n + 1) * phis) for (n, bn) in enumerate(f_bs): R_phi = R_phi + bn * bd.sin((n + 1) * phis) if np.any(R_phi < 0): raise ValueError("Coefficients of FourierShape should be such " "that R(phi) is non-negative for all phi.") x_edges = R_phi * bd.cos(phis) y_edges = R_phi * bd.sin(phis) super().__init__(eps, x_edges, y_edges)