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 compute_ft(self, gvec): """ Compute the 2D Fourier transform of the layer permittivity. """ FT = bd.zeros(gvec.shape[1]) for shape in self.shapes: # Note: compute_ft() returns the FT of a function that is one # inside the shape and zero outside FT = FT + (shape.eps - self.eps_b) * shape.compute_ft(gvec) # Apply some final coefficients # Note the hacky way to set the zero element so as to work with # 'autograd' backend ind0 = bd.abs(gvec[0, :]) + bd.abs(gvec[1, :]) < 1e-10 FT = FT / self.lattice.ec_area FT = FT * (1 - ind0) + self.eps_avg * ind0 return FT
def normalization_coeff(omega, g, eps_array, d_array, chi_array, ABref, pol='TE'): """ Normalization of the guided modes (i.e. the A and B coeffs) """ assert len(d_array)==len(eps_array)-2, \ 'd_array should have length = num_layers' if chi_array is None: chi_array = chi(omega, g, eps_array) As = ABref[:, 0].ravel() Bs = ABref[:, 1].ravel() if pol == 'TM': term1 = (bd.abs(Bs[0])**2) * J_alpha(chi_array[0]-bd.conj(chi_array[0])) term2 = (bd.abs(As[-1])**2) * \ J_alpha(chi_array[-1]-bd.conj(chi_array[-1])) term3 = ( (bd.abs(As[1:-1])**2 + bd.abs(Bs[1:-1])**2) * \ I_alpha(chi_array[1:-1]-bd.conj(chi_array[1:-1]),d_array) + (bd.conj(As[1:-1]) * Bs[1:-1] + As[1:-1] * bd.conj(Bs[1:-1])) * I_alpha(-chi_array[1:-1]-bd.conj(chi_array[1:-1]),d_array) ) return term1 + term2 + bd.sum(term3) elif pol == 'TE': term1 = (bd.abs(chi_array[0])**2 + g**2) * \ (bd.abs(Bs[0])**2) * J_alpha(chi_array[0]-bd.conj(chi_array[0])) term2 = (bd.abs(chi_array[-1])**2 + g**2) * \ (bd.abs(As[-1])**2) * J_alpha(chi_array[-1]-bd.conj(chi_array[-1])) term3 = (bd.abs(chi_array[1:-1])**2 + g**2) * ( (bd.abs(As[1:-1])**2 + bd.abs(Bs[1:-1])**2) * \ I_alpha(chi_array[1:-1]-bd.conj(chi_array[1:-1]), d_array)) + \ (g**2 - bd.abs(chi_array[1:-1])**2) * ( (bd.conj(As[1:-1]) * Bs[1:-1] + As[1:-1] * bd.conj(Bs[1:-1])) * I_alpha(-chi_array[1:-1]-bd.conj(chi_array[1:-1]), d_array) ) return term1 + term2 + bd.sum(term3) else: raise Exception('Polarization should be TE or TM.')