def mat_te_tm(eps_array, d_array, eps_inv_mat, indmode1, oms1, As1, Bs1, chis1, indmode2, oms2, As2, Bs2, chis2, qp): """ Matrix block for TE-TM mode coupling """ # Index matrix selecting the participating modes indmat = np.ix_(indmode1, indmode2) # Number of layers Nl = eps_array.size # Build the matrix layer by layer mat = bd.zeros((indmode1.size, indmode2.size)) # Contributions from layers for il in range(0, Nl): mat = mat + 1j * eps_inv_mat[il][indmat] * \ eps_array[il] * chis2[il, :][bd.newaxis, :] * ( -bd.outer(bd.conj(As1[il, :]), As2[il, :]) * IJ_layer(il, Nl, chis2[il, :] - bd.conj(chis1[il, :][:, bd.newaxis]), d_array) +bd.outer(bd.conj(Bs1[il, :]), Bs2[il, :]) * IJ_layer(il, Nl, -chis2[il, :] + bd.conj(chis1[il, :][:, bd.newaxis]), d_array) +bd.outer(bd.conj(As1[il, :]), Bs2[il, :]) * IJ_layer(il, Nl, -chis2[il, :] - bd.conj(chis1[il, :][:, bd.newaxis]), d_array) -bd.outer(bd.conj(Bs1[il, :]), As2[il, :]) * IJ_layer(il, Nl, chis2[il, :] + bd.conj(chis1[il, :][:, bd.newaxis]), d_array) ) # Final pre-factor mat = mat * (oms1**2)[:, bd.newaxis] * qp[indmat] return mat
def mat_tm_tm(eps_array, d_array, eps_inv_mat, gk, indmode1, oms1, As1, Bs1, chis1, indmode2, oms2, As2, Bs2, chis2, pp): """ Matrix block for TM-TM mode coupling """ # Index matrix selecting the participating modes indmat = np.ix_(indmode1, indmode2) # Number of layers Nl = eps_array.size # Build the matrix layer by layer mat = bd.zeros((indmode1.size, indmode2.size)) for il in range(0, Nl): mat = mat + eps_inv_mat[il][indmat]*( (pp[indmat] * bd.outer(bd.conj(chis1[il, :]), chis2[il, :]) + \ bd.outer(gk[indmode1], gk[indmode2])) * ( bd.outer(bd.conj(As1[il, :]), As2[il, :]) * IJ_layer(il, Nl, chis2[il, :] - bd.conj(chis1[il, :][:, bd.newaxis]), d_array) + bd.outer(bd.conj(Bs1[il, :]), Bs2[il, :]) * IJ_layer(il, Nl, -chis2[il, :] + bd.conj(chis1[il, :][:, bd.newaxis]), d_array)) - \ (pp[indmat] * bd.outer(bd.conj(chis1[il, :]), chis2[il, :]) - \ bd.outer(gk[indmode1], gk[indmode2])) * ( bd.outer(bd.conj(As1[il, :]), Bs2[il, :]) * IJ_layer(il, Nl, -chis2[il, :] - bd.conj(chis1[il, :][:, bd.newaxis]), d_array) + bd.outer(bd.conj(Bs1[il, :]), As2[il, :]) * IJ_layer(il, Nl, chis2[il, :] + bd.conj(chis1[il, :][:, bd.newaxis]), d_array)) ) return mat
def S_T_matrices_TE(omega, g, eps_array, d_array): """ Function to get a list of S and T matrices for D22 calculation """ assert len(d_array)==len(eps_array)-2, \ 'd_array should have length = num_layers' chi_array = chi(omega, g, eps_array) S11 = (chi_array[:-1] + chi_array[1:]) S12 = -chi_array[:-1] + chi_array[1:] S22 = S11 S21 = S12 S_matrices = 0.5 / chi_array[1:].reshape(-1,1,1) * \ bd.array([[S11,S12],[S21,S22]]).transpose([2,0,1]) T11 = bd.exp(1j*chi_array[1:-1]*d_array/2) T22 = bd.exp(-1j*chi_array[1:-1]*d_array/2) T_matrices = bd.array([[T11,bd.zeros(T11.shape)], [bd.zeros(T11.shape),T22]]).transpose([2,0,1]) return S_matrices, T_matrices
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 compute_ft(self, gvec): """Compute Fourier transform of the polygon The polygon is assumed to take a value of 1 inside and a value of 0 outside. The Fourier transform calculation follows that of Lee, IEEE TAP (1984). Parameters ---------- gvec : np.ndarray of shape (2, Ng) g-vectors at which FT is evaluated """ (gx, gy) = self._parse_ft_gvec(gvec) (xj, yj) = self.x_edges, self.y_edges npts = xj.shape[0] ng = gx.shape[0] # Note: the paper uses +1j*g*x convention for FT while we use # -1j*g*x everywhere in legume gx = -gx[:, bd.newaxis] gy = -gy[:, bd.newaxis] xj = xj[bd.newaxis, :] yj = yj[bd.newaxis, :] ft = bd.zeros((ng), dtype=bd.complex); aj = (bd.roll(xj, -1, axis=1) - xj + 1e-10) / \ (bd.roll(yj, -1, axis=1) - yj + 1e-20) bj = xj - aj * yj # We first handle the Gx = 0 case ind_gx0 = np.abs(gx[:, 0]) < 1e-10 ind_gx = ~ind_gx0 if np.sum(ind_gx0) > 0: # And first the Gy = 0 case ind_gy0 = np.abs(gy[:, 0]) < 1e-10 if np.sum(ind_gy0*ind_gx0) > 0: ft = ind_gx0*ind_gy0*bd.sum(xj * bd.roll(yj, -1, axis=1)-\ yj * bd.roll(xj, -1, axis=1))/2 # Remove the Gx = 0, Gy = 0 component ind_gx0[ind_gy0] = False # Compute the remaining Gx = 0 components a2j = 1 / aj b2j = yj - a2j * xj bgtemp = gy * b2j agtemp1 = bd.dot(gx, xj) + bd.dot(gy, a2j * xj) agtemp2 = bd.dot(gx, bd.roll(xj, -1, axis=1)) + \ bd.dot(gy, a2j * bd.roll(xj, -1, axis=1)) denom = gy * (gx + bd.dot(gy, a2j)) ftemp = bd.sum(bd.exp(1j*bgtemp) * (bd.exp(1j*agtemp2) - \ bd.exp(1j*agtemp1)) * \ denom / (bd.square(denom) + 1e-50) , axis=1) ft = bd.where(ind_gx0, ftemp, ft) # Finally compute the general case for Gx != 0 if np.sum(ind_gx) > 0: bgtemp = bd.dot(gx, bj) agtemp1 = bd.dot(gy, yj) + bd.dot(gx, aj * yj) agtemp2 = bd.dot(gy, bd.roll(yj, -1, axis=1)) + \ bd.dot(gx, aj * bd.roll(yj, -1, axis=1)) denom = gx * (gy + bd.dot(gx, aj)) ftemp = -bd.sum(bd.exp(1j*bgtemp) * (bd.exp(1j * agtemp2) - \ bd.exp(1j * agtemp1)) * \ denom / (bd.square(denom) + 1e-50) , axis=1) ft = bd.where(ind_gx, ftemp, ft) return ft
def ft_field_xy(self, field, kind, mind): """ Compute the 'H', 'D' or 'E' field Fourier components in the xy-plane. Parameters ---------- field : {'H', 'D', 'E'} The field to be computed. kind : int The field of the mode at `PlaneWaveExp.kpoints[:, kind]` is computed. mind : int The field of the `mind` mode at that kpoint is computed. Note ---- The function outputs 1D arrays with the same size as `PlaneWaveExp.gvec[0, :]` corresponding to the G-vectors in that array. Returns ------- fi_x : np.ndarray The Fourier transform of the x-component of the specified field. fi_y : np.ndarray The Fourier transform of the y-component of the specified field. fi_z : np.ndarray The Fourier transform of the z-component of the specified field. """ evec = self.eigvecs[kind][:, mind] omega = self.freqs[kind][mind]*2*np.pi k = self.kpoints[:, kind] # G + k vectors gkx = self.gvec[0, :] + k[0] + 1e-10 gky = self.gvec[1, :] + k[1] gnorm = bd.sqrt(bd.square(gkx) + bd.square(gky)) # Unit vectors in the propagation direction px = gkx / gnorm py = gky / gnorm # Unit vectors in-plane orthogonal to the propagation direction qx = py qy = -px if field.lower()=='h': if self.pol == 'te': Hx_ft = bd.zeros(gnorm.shape) Hy_ft = bd.zeros(gnorm.shape) Hz_ft = evec elif self.pol == 'tm': Hx_ft = evec * qx Hy_ft = evec * qy Hz_ft = bd.zeros(gnorm.shape) return (Hx_ft, Hy_ft, Hz_ft) elif field.lower()=='d' or field.lower()=='e': if self.pol == 'te': Dx_ft = 1j / omega * evec * qx Dy_ft = 1j / omega * evec * qy Dz_ft = bd.zeros(gnorm.shape) elif self.pol == 'tm': Dx_ft = bd.zeros(gnorm.shape) Dy_ft = bd.zeros(gnorm.shape) Dz_ft = 1j / omega * evec if field.lower()=='d': return (Dx_ft, Dy_ft, Dz_ft) else: # Get E-field by convolving FT(1/eps) with FT(D) Ex_ft = bd.dot(self.eps_inv_mat, Dx_ft) Ey_ft = bd.dot(self.eps_inv_mat, Dy_ft) Ez_ft = bd.dot(self.eps_inv_mat, Dz_ft) return (Ex_ft, Ey_ft, Ez_ft)