def adjoint_linear_Hz(simulation, b_aj, averaging=False, solver=DEFAULT_SOLVER, matrix_format=DEFAULT_MATRIX_FORMAT): # Compute the adjoint field for a linear problem # Note: the correct definition requires simulating with the transpose matrix A.T EPSILON_0_ = EPSILON_0*simulation.L0 MU_0_ = MU_0*simulation.L0 omega = simulation.omega (Nx, Ny) = (simulation.Nx, simulation.Ny) M = Nx*Ny A = simulation.A hz = solver_direct(A.T, b_aj, solver=solver) (Dyb, Dxb, Dxf, Dyf) = unpack_derivs(simulation.derivs) (Dyb_T, Dxb_T, Dxf_T, Dyf_T) = (Dyb.T, Dxb.T, Dxf.T, Dyf.T) if averaging: vector_eps_x = grid_average(EPSILON_0_*simulation.eps_r, 'x').reshape((-1,)) vector_eps_y = grid_average(EPSILON_0_*simulation.eps_r, 'y').reshape((-1,)) else: vector_eps_x = EPSILON_0_*simulation.eps_r.reshape((-1,)) vector_eps_y = EPSILON_0_*simulation.eps_r.reshape((-1,)) T_eps_x_inv = sp.spdiags(1/vector_eps_x, 0, M, M, format=matrix_format) T_eps_y_inv = sp.spdiags(1/vector_eps_y, 0, M, M, format=matrix_format) # Note: to get the correct gradient in the end, we must use Dxf, Dyf here ex = 1/1j/omega * T_eps_y_inv.dot(Dyf_T).dot(hz).T ey = -1/1j/omega * T_eps_x_inv.dot(Dxf_T).dot(hz).T Ex = ex.reshape((Nx, Ny)) Ey = ey.reshape((Nx, Ny)) return (Ex, Ey)
def flux_probe(self, direction_normal, center, width, nl=False): # computes the total flux across the plane (line in 2D) defined by direction_normal, center, width # first extract the slice of the permittivity if direction_normal == "x": inds_x = [center[0], center[0]+1] inds_y = [int(center[1]-width/2), int(center[1]+width/2)] elif direction_normal == "y": inds_x = [int(center[0]-width/2), int(center[0]+width/2)] inds_y = [center[1], center[1]+1] else: raise ValueError("The value of direction_normal is neither x nor y!") if self.pol == 'Ez': if nl: field_val_Ez = self.fields_nl['Ez'] field_val_Hy = self.fields_nl['Hy'] field_val_Hx = self.fields_nl['Hx'] else: field_val_Ez = self.fields['Ez'] field_val_Hy = self.fields['Hy'] field_val_Hx = self.fields['Hx'] Ez_x = grid_average(field_val_Ez[inds_x[0]:inds_x[1]+1, inds_y[0]:inds_y[1]+1], 'x')[:-1,:-1] Ez_y = grid_average(field_val_Ez[inds_x[0]:inds_x[1]+1, inds_y[0]:inds_y[1]+1], 'y')[:-1,:-1] # NOTE: Last part drops the extra rows/cols used for grid_average if direction_normal == "x": Sx = -1/2*np.real(Ez_x*np.conj(field_val_Hy[inds_x[0]:inds_x[1], inds_y[0]:inds_y[1]])) return self.dl*np.sum(Sx) elif direction_normal == "y": Sy = 1/2*np.real(Ez_y*np.conj(field_val_Hx[inds_x[0]:inds_x[1], inds_y[0]:inds_y[1]])) return self.dl*np.sum(Sy) elif self.pol == 'Hz': if nl: field_val_Hz = self.fields_nl['Hz'] field_val_Ey = self.fields_nl['Ey'] field_val_Ex = self.fields_nl['Ex'] else: field_val_Hz = self.fields['Hz'] field_val_Ey = self.fields['Ey'] field_val_Ex = self.fields['Ex'] Hz_x = grid_average(field_val_Hz[inds_x[0]:inds_x[1]+1, inds_y[0]:inds_y[1]+1], 'x')[:-1, :-1] Hz_y = grid_average(field_val_Hz[inds_x[0]:inds_x[1]+1, inds_y[0]:inds_y[1]+1], 'y')[:-1, :-1] # NOTE: Last part drops the extra rows/cols used for grid_average if direction_normal == "x": Sx = 1/2*np.real(field_val_Ey[inds_x[0]:inds_x[1], inds_y[0]:inds_y[1]]*np.conj(Hz_x)) return self.dl*np.sum(Sx) elif direction_normal == "y": Sy = -1/2*np.real(field_val_Ex[inds_x[0]:inds_x[1], inds_y[0]:inds_y[1]]*np.conj(Hz_y)) return self.dl*np.sum(Sy)
def solve_fields(self, include_nl=False, timing=False, averaging=False, matrix_format=DEFAULT_MATRIX_FORMAT): # performs direct solve for A given source EPSILON_0_ = EPSILON_0 * self.L0 MU_0_ = MU_0 * self.L0 if include_nl == False: eps_tot = self.eps_r X = solver_direct(self.A, self.src * 1j * self.omega, timing=timing) else: eps_tot = self.eps_r + self.eps_nl X = solver_direct(self.A + self.Anl, self.src * 1j * self.omega, timing=timing) (Nx, Ny) = self.src.shape M = Nx * Ny (Dyb, Dxb, Dxf, Dyf) = unpack_derivs(self.derivs) if self.pol == 'Hz': if averaging: eps_x = grid_average(EPSILON_0_ * (eps_tot), 'x') vector_eps_x = eps_x.reshape((-1, )) eps_y = grid_average(EPSILON_0_ * (eps_tot), 'y') vector_eps_y = eps_y.reshape((-1, )) else: vector_eps_x = EPSILON_0_ * (eps_tot).reshape((-1, )) vector_eps_y = EPSILON_0_ * (eps_tot).reshape((-1, )) T_eps_x_inv = sp.spdiags(1 / vector_eps_x, 0, M, M, format=matrix_format) T_eps_y_inv = sp.spdiags(1 / vector_eps_y, 0, M, M, format=matrix_format) ex = 1 / 1j / self.omega * T_eps_y_inv.dot(Dyb).dot(X) ey = -1 / 1j / self.omega * T_eps_x_inv.dot(Dxb).dot(X) Ex = ex.reshape((Nx, Ny)) Ey = ey.reshape((Nx, Ny)) Hz = X.reshape((Nx, Ny)) if include_nl == False: self.fields['Ex'] = Ex self.fields['Ey'] = Ey self.fields['Hz'] = Hz return (Ex, Ey, Hz) elif self.pol == 'Ez': hx = -1 / 1j / self.omega / MU_0_ * Dyb.dot(X) hy = 1 / 1j / self.omega / MU_0_ * Dxb.dot(X) Hx = hx.reshape((Nx, Ny)) Hy = hy.reshape((Nx, Ny)) Ez = X.reshape((Nx, Ny)) if include_nl == False: self.fields['Hx'] = Hx self.fields['Hy'] = Hy self.fields['Ez'] = Ez return (Hx, Hy, Ez) else: raise ValueError('Invalid polarization: {}'.format(str(self.pol)))
def grad_linear_Ey(optimization, dJ, Hz, args, averaging=False): EPSILON_0_ = EPSILON_0 * optimization.simulation.L0 MU_0_ = MU_0 * optimization.simulation.L0 omega = optimization.simulation.omega Dxb = optimization.simulation.derivs['Dxf'] Dyb = optimization.simulation.derivs['Dyf'] eps_tot = optimization.simulation.eps_r M = eps_tot.size if averaging: eps_x = grid_average(EPSILON_0_ * (eps_tot), 'x') vector_eps_x = eps_x.reshape((-1, )) eps_y = grid_average(EPSILON_0_ * (eps_tot), 'y') vector_eps_y = eps_y.reshape((-1, )) else: vector_eps_x = EPSILON_0_ * (eps_tot).reshape((-1, )) vector_eps_y = EPSILON_0_ * (eps_tot).reshape((-1, )) T_eps_x_inv = sp.spdiags(1 / vector_eps_x, 0, M, M, format=DEFAULT_MATRIX_FORMAT) T_eps_y_inv = sp.spdiags(1 / vector_eps_y, 0, M, M, format=DEFAULT_MATRIX_FORMAT) # get fields Hz_vec = np.reshape(Hz, (-1, )) Ex_vec = 1 / 1j / omega * T_eps_y_inv.dot(Dyb).dot(Hz_vec) Ey_vec = -1 / 1j / omega * T_eps_x_inv.dot(Dxb).dot(Hz_vec) partial = -dJ(*args) partial_vec = partial.reshape((-1, )) b_aj_vec = -1 / 1j / omega * T_eps_x_inv.dot(Dxb.T).dot(partial_vec) b_aj = b_aj_vec.reshape(Hz.shape) (Ex_aj, Ey_aj) = adjoint_linear_Hz(optimization.simulation, b_aj, averaging=averaging) Ex_aj_vec = np.reshape(Ex_aj, (-1, )).T Ey_aj_vec = np.reshape(Ey_aj, (-1, )).T # set up filtering bits rho = optimization.simulation.rho rho_t = rho2rhot(rho, optimization.W) rho_b = rhot2rhob(rho_t, eta=optimization.eta, beta=optimization.beta) eps_mat = (optimization.eps_m - 1) filt_mat = drhot_drho(optimization.W) proj_mat = drhob_drhot(rho_t, eta=optimization.eta, beta=optimization.beta) if averaging: design_region_x = grid_average(optimization.design_region, 'x') dAdeps_x = design_region_x * omega**2 * EPSILON_0_ design_region_y = grid_average(optimization.design_region, 'y') dAdeps_y = design_region_y * omega**2 * EPSILON_0_ dAdeps_vec_x = np.reshape(dAdeps_x, (-1, )) dAdeps_vec_y = np.reshape(dAdeps_y, (-1, )) dfdrho_x = eps_mat * filt_mat.multiply( Ex_vec * proj_mat * dAdeps_vec_x) dfdrho_y = eps_mat * filt_mat.multiply( Ey_vec * proj_mat * dAdeps_vec_x) sensitivity_vec = dfdrho_x.dot(Ex_aj_vec) + dfdrho_y.dot(Ey_aj_vec) else: dAdeps = optimization.design_region * omega**2 * EPSILON_0_ # Note: physical constants go here if need be! dAdeps_vec = np.reshape(dAdeps, (-1, )) dfdrho_x = eps_mat * filt_mat.multiply(Ex_vec * proj_mat * dAdeps_vec) dfdrho_y = eps_mat * filt_mat.multiply(Ey_vec * proj_mat * dAdeps_vec) sensitivity_vec = dfdrho_x.dot(Ex_aj_vec) + dfdrho_y.dot(Ey_aj_vec) return 1 * np.real(np.reshape(sensitivity_vec, rho.shape))