Source code for pytheas.periodic2D.femmodel

# -*- coding: utf-8 -*-
# Author: Benjamin Vial
# License: MIT


import os
import numpy as np
import scipy as sc
from ..tools import femio
from ..basefem import *


[docs]class Periodic2D(BaseFEM): """A class for a finite element model of a 2D mono-periodic medium. The model consist of a single unit cell with quasi-periodic boundary conditions in the :math:`x` direction enclosed with perfectly matched layers (PMLs) in the :math:`y` direction to truncate the semi infinite media. From top to bottom: - PML top - superstrate (incident medium) - layer 2 - design layer: this is the layer containing the periodic pattern, can be continuous or discrete - layer 1 - substrate - PML bottom Parameters ---------- analysis : str, default "direct" Analysis type: either "direct" (plane wave) or "modal" (spectral problem) pola : str, default "TE" Polarization case: either "TE" (E along z) or "TM" (H along z) A : float, default 1 Incident plane wave amplitude lambda0 : float, default 1 Incident plane wave wavelength in free space lambda_mesh : float, default 1 Wavelength to use for meshing theta_deg : float, default 0 Incident plane wave angle (in degrees). Light comes from the top (travels along -y if normal incidence, ``theta_deg=0`` is set) d : float, default 0.8 Periodicity h_sup : float, default 1 Thickness superstrate h_sub : float, default 1 Thickness substrate h_layer1 : float, default 0.1 Thickness layer 1 h_layer2 : float, default 0.1 Thickness layer 2 h_des : float, default 1 Thickness layer design h_pmltop : float, default 1 Thickness pml top h_pmlbot : float, default 1 Thickness pml bot a_pml : float, default 1 PMLs complex y-stretching parameter, real part b_pml : float, default 1 PMLs complex y-stretching parameter, imaginary part eps_sup : complex, default (1 - 0 * 1j) Permittivity superstrate eps_sub : complex, default (1 - 0 * 1j) Permittivity substrate eps_layer1 : complex, default (1 - 0 * 1j) Permittivity layer 1 eps_layer2 : complex, default (1 - 0 * 1j) Permittivity layer 2 eps_des : complex, default (1 - 0 * 1j) Permittivity layer design eps_incl : complex, default (1 - 0 * 1j) Permittivity inclusion """ def __init__( self, analysis="direct", pola="TE", A=1, lambda0=1, lambda_mesh=1, theta_deg=0, d=0.8, h_sup=1, h_sub=1, h_layer1=0.1, h_layer2=0.1, h_des=1.0, h_pmltop=1.0, h_pmlbot=1.0, a_pml=1, b_pml=1, eps_sup=1 - 0 * 1j, eps_sub=1 - 0 * 1j, eps_layer1=1 - 0 * 1j, eps_layer2=1 - 0 * 1j, eps_des=1 - 0 * 1j, eps_incl=1 - 0 * 1j, mu_incl=1 - 0 * 1j, mu_des=1 - 0 * 1j, ): super().__init__() self.dir_path = get_file_path(__file__) self.analysis = analysis self.pola = pola self.A = A self.lambda0 = lambda0 self.lambda_mesh = lambda_mesh self.theta_deg = theta_deg # opto-geometric parameters ------------------------------------------- self.d = d self.h_sup = h_sup self.h_sub = h_sub self.h_layer1 = h_layer1 self.h_layer2 = h_layer2 self.h_des = h_des self.h_pmltop = h_pmltop self.h_pmlbot = h_pmlbot self.a_pml = a_pml self.b_pml = b_pml self.eps_sup = eps_sup self.eps_sub = eps_sub self.eps_layer1 = eps_layer1 self.eps_layer2 = eps_layer2 self.eps_des = eps_des self.eps_incl = eps_incl self.mu_incl = mu_incl self.mu_des = mu_des self.dom_des = 4000 self.nb_incl = 1 self.aniso = False # if not isinstance(self.eps_layer1, complex): # raise TypeError("bar must be a complex number") # postprocessing ------------------------------------------------- # int: number of diffraction orders # for postprocessing diffraction efficiencies # N_d_order = 0 # int: number of x integration points # for postprocessing diffraction efficiencies self.npt_integ = 1000 # int: number of y slices points # for postprocessing diffraction efficiencies self.nb_slice = 20 # flt: such that `scan_dist = min(h_sup, h_sub)/scan_dist_ratio` self.scan_dist_ratio = 5 self.delta_x, self.delta_y = 0, 0 self.nper = 3 # modal analysis ------------------------------------------------- # int: number of eigenvalues searched for in modal analysis self.neig = 6 # flt: wavelength around which to search eigenvalues self.lambda0search = 1 # topology optimization ------------------------ self.adjoint = False self.r_tar = 1 + 0j self.t_tar = 0 + 0j @property def scan_dist(self): return min(self.h_sub, self.h_sup) / self.scan_dist_ratio @property def ycut_sub_min(self): return -self.h_sub + self.scan_dist @property def ycut_sub_max(self): return -self.scan_dist @property def ycut_sup_min(self): return self.h_layer1 + self.h_layer2 + self.h_des + self.scan_dist @property def ycut_sup_max(self): return self.h_layer1 + self.h_layer2 + self.h_des + self.h_sup - self.scan_dist @property def domX_L(self): return -self.d / 2 @property def domX_R(self): return self.d / 2 @property def domY_B(self): return -self.h_sub + self.delta_y # - self.h_pmlbot @property def domY_T(self): # + self.h_pmltop return self.h_layer1 + self.h_layer2 + self.h_des + self.h_sup - self.delta_y @property def corners_des(self): return -self.d / 2, +self.d / 2, self.h_layer1, self.h_layer1 + self.h_des @property def theta(self): return np.pi / 180.0 * (self.theta_deg) @property def omega0(self): return 2.0 * np.pi * self.cel / self.lambda0 @property def N_d_order(self): a = self.d / self.lambda0 b = np.sin(self.theta) WRA = np.zeros((4)) WRA[0] = a * (np.sqrt(self.eps_sup.real) + b) WRA[1] = a * (np.sqrt(self.eps_sub.real) + b) WRA[2] = a * (np.sqrt(self.eps_sup.real) - b) WRA[3] = a * (np.sqrt(self.eps_sub.real) - b) return int(max(abs(WRA))) # def _make_param_dict(self): # param_dict = super()._make_param_dict() # param_dict["aniso"] = int(self.aniso) # return param_dict
[docs] def compute_solution(self): """Compute the solution of the FEM problem using getdp""" res_list = ["helmoltz_scalar", "helmoltz_scalar_modal"] super().compute_solution(res_list=res_list)
[docs] def postpro_absorption(self): """ Compute the absorption coefficient Returns ------- Q : float Absorption coefficient """ self._print_progress("Postprocessing absorption") self.postprocess("postop_absorption") Q = femio.load_table(self.tmppath("Q.txt")).real return Q
def _postpro_fields_cuts(self): """ Compute the field cuts in substrate and superstarte Returns ------- u_diff_t : array-like Transmitted field cuts u_diff_r : array-like Reflected field cuts """ path_sub = self.tmppath("sub_field_cuts.out") path_sup = self.tmppath("sup_field_cuts.out") if os.path.isfile(path_sub): os.remove(path_sub) if os.path.isfile(path_sup): os.remove(path_sup) self.postprocess("postop_fields_cuts") u_diff_t = femio.load_table(path_sub) u_diff_r = femio.load_table(path_sup) return u_diff_t, u_diff_r
[docs] def get_field_map(self, name): """Retrieve a field map. Parameters ---------- name : str Choose between "u" (scattered field), "u_tot" (total field) Returns ------- array A 2D complex array of shape (`Nix`, `Niy`) """ field = femio.load_table(self.tmppath(name + ".txt")) field = np.flipud(field.reshape((self.Niy, self.Nix))).T return field
[docs] def diffraction_efficiencies(self, cplx_effs=False, orders=False): """Postprocess diffraction efficiencies. Parameters ---------- cplx_effs : bool If `True`, return complex coefficients (amplitude reflection and transmission). If `False`, return real coefficients (power reflection and transmission) orders : bool If `True`, computes the transmission and reflection for all the propagating diffraction orders. If `False`, returns the sum of all the propagating diffraction orders. Returns ------- dict A dictionary containing the diffraction efficiencies. """ self._print_progress("Processing diffraction efficiencies") if self.pola == "TE": nu = 1 else: nu = 1 / self.eps_sub order_shift = 0 No_ordre = np.linspace( -self.N_d_order + order_shift, self.N_d_order + order_shift, 2 * self.N_d_order + 1, ) x_slice = sc.linspace(-self.d / 2, self.d / 2, self.npt_integ) y_slice_t = sc.linspace(self.ycut_sub_min, self.ycut_sub_max, self.nb_slice) y_slice_r = sc.linspace(self.ycut_sup_min, self.ycut_sup_max, self.nb_slice) k_sub = 2 * np.pi * sc.sqrt(self.eps_sub) / self.lambda0 k_sup = 2 * np.pi * sc.sqrt(self.eps_sup) / self.lambda0 alpha_sup = -k_sup * sc.sin(self.theta) beta_sup = sc.sqrt(k_sup ** 2 - alpha_sup ** 2) # beta_sub = sc.sqrt(k_sub ** 2 - alpha_sup ** 2) s_t = sc.zeros((1, (2 * self.N_d_order + 1)), complex)[0, :] s_r = sc.zeros((1, (2 * self.N_d_order + 1)), complex)[0, :] Aeff_t = sc.zeros((self.nb_slice, 2 * self.N_d_order + 1), complex) Aeff_r = sc.zeros((self.nb_slice, 2 * self.N_d_order + 1), complex) field_diff_t, field_diff_r = self._postpro_fields_cuts() field_diff_t = np.transpose( field_diff_t.reshape(self.npt_integ, self.nb_slice, order="F") ) field_diff_r = np.transpose( field_diff_r.reshape(self.npt_integ, self.nb_slice, order="F") ) alphat_t = alpha_sup + 2 * np.pi / (self.d) * No_ordre alphat_r = alpha_sup + 2 * np.pi / (self.d) * No_ordre betat_sup = np.conj(sc.sqrt(k_sup ** 2 - alphat_r ** 2)) betat_sub = np.conj(sc.sqrt(k_sub ** 2 - alphat_t ** 2)) for m1 in range(0, self.nb_slice): slice_t = field_diff_t[m1, :] slice_r = field_diff_r[m1, :] for k in range(0, 2 * self.N_d_order + 1): expalpha_t = sc.exp(-1j * alphat_t[k] * x_slice) expalpha_r = sc.exp(-1j * alphat_r[k] * x_slice) s_t[k] = sc.trapz(slice_t * expalpha_t, x=x_slice) / self.d s_r[k] = sc.trapz(slice_r * expalpha_r, x=x_slice) / self.d Aeff_t[m1, :] = s_t * sc.exp(-1j * betat_sub * (y_slice_t[m1])) Aeff_r[m1, :] = s_r * sc.exp( 1j * betat_sup * (y_slice_r[m1] - (self.ycut_sup_min - self.scan_dist)) ) # Aeff_r = -np.conj(Aeff_r) Beff_t = (np.abs(Aeff_t)) ** 2 * betat_sub / beta_sup * nu Beff_r = (np.abs(Aeff_r)) ** 2 * betat_sup / beta_sup # print(Aeff_r) # print(Aeff_t) rcplx = np.mean(Aeff_r, axis=0) tcplx = np.mean(Aeff_t, axis=0) Rorders = np.mean(Beff_r.real, axis=0) Torders = np.mean(Beff_t.real, axis=0) R = np.sum(Rorders, axis=0) T = np.sum(Torders, axis=0) Q = self.postpro_absorption() B = T + R + Q # energy balance if self.python_verbose: print(" Energy balance") print( " R = ", "%0.6f" % R, " (std slice2slice =", "%0.6e" % sc.std(np.sum(Aeff_r.real, axis=1)), ")", ) print( " T = ", "%0.6f" % T, " (std slice2slice =", "%0.6e" % sc.std(np.sum(Aeff_t.real, axis=1)), ")", ) print(" Q = ", "%0.6f" % Q) print(" ------------------------") print(" Total = ", "%0.6f" % (B)) if cplx_effs: R, T = rcplx, tcplx else: if orders: R, T = Rorders, Torders eff = dict() eff["R"] = R eff["T"] = T eff["Q"] = Q eff["B"] = B return eff
def pseudo_periodize(self, qt): k0 = 2 * np.pi / self.lambda0 coef = np.exp(-1j * k0 * np.sin(self.theta) * self.d) qtper = qt for i in range(self.nper - 1): qtper = np.row_stack((qtper, qt * coef ** (i + 1))) return qtper def periodize(self, qt): qtper = qt for _ in range(self.nper - 1): qtper = np.row_stack((qtper, qt)) return qtper def plot_field_and_pattern( self, fig, ax, field, pattern, cmap_div, cmap_mat, cbar=True, vmin=None, vmax=None, ): self._print_progress("Plotting field map") # x = np.linspace( # self.nper * self.domX_L, self.nper * self.domX_R, self.nper * self.Nix # ) # y = np.linspace(self.domY_B, self.domY_T, self.Niy) # xx, yy = np.meshgrid(x, y) extent = ( self.nper * self.domX_L, self.nper * self.domX_R, self.domY_B, self.domY_T, ) im1 = ax.imshow( field.T, interpolation="bilinear", cmap=cmap_div, vmin=vmin, vmax=vmax, extent=extent, ) if cbar: fig.colorbar(im1, fraction=0.046, pad=0.04) ax.imshow( pattern.T, interpolation="None", cmap=cmap_mat, alpha=0.4, extent=( self.nper * self.domX_L, self.nper * self.domX_R, self.h_layer1, self.h_layer1 + self.h_des, ), ) ax.imshow( field.T, alpha=0.0, interpolation="bilinear", cmap=cmap_div, vmin=vmin, vmax=vmax, extent=( self.nper * self.domX_L, self.nper * self.domX_R, self.domY_B, self.domY_T, ), ) ax.set_ylim((self.domY_B, self.domY_T)) def plot_pattern_per(self, fig, ax, pattern, nper, cmap): im1 = ax.imshow( pattern.T, cmap=cmap, extent=( -self.nper * self.d / 2, self.nper * self.d / 2, self.h_layer1, self.h_layer1 + self.h_des, ), ) fig.colorbar(im1, fraction=0.046, pad=0.04) def plot_fieldv_streamlines(self, ax, vx, vy, cmap): self._print_progress("Plotting vector field streamlines") x = np.linspace( self.nper * self.domX_L, self.nper * self.domX_R, self.nper * self.Nix ) y = np.linspace(self.domY_B, self.domY_T, self.Niy) vlognorm = (np.sqrt(np.abs(vx) ** 2 + np.abs(vy) ** 2)).T Ndens = 1 density = [Ndens * self.nper, Ndens * self.Niy / self.Nix] lw = 3 * vlognorm / vlognorm.max() ax.streamplot( x, y, vx.T, vy.T, color="k", linewidth=lw, cmap=cmap, density=density, arrowstyle="Fancy", ) ax.set_ylim((self.domY_B, self.domY_T))