# -*- coding: utf-8 -*-
# Author: Benjamin Vial
# License: MIT
"""
Finite Element model of 2D media
"""
import numpy as np
from ..tools import femio
from ..basefem import *
[docs]class Scatt2D(BaseFEM):
"""A class for a finite element model of a 2D medium"""
def __init__(self):
super().__init__()
self.dir_path = get_file_path(__file__)
#: str: analysys type (either "direct" or "modal")
self.analysis = "direct"
#: str: polarisation of the incident plane wave
#: (either "TE" or "TM")
self.pola = "TE"
self.nb_incl = 1
# Incident plane wave parameters
#: flt: incident plane wave amplitude
self.A = 1.0
#: flt: incident plane wave wavelength in free space
self.lambda0 = 1.0
#: flt: wavelength to use for meshing
self.lambda_mesh = 1.0
#: flt : incident plane wave angle (in degrees).
#: Light comes from the top
#: (travels along -y if normal incidence, `theta_deg=0` is set)
self.theta_deg = 0.0
#: line source position
self.ls_flag = False
self.xs = 0
self.ys = 0
#: beam?
self.beam_flag = False
self.waist = 1.5
# opto-geometric parameters -------------------------------------------
self.h_pml = 2.0 #: flt: thickness pml
self.hx_des = 2.0 #: flt: x - thickness scattering box (design)
self.hy_des = 2.0 #: flt: y - thickness scattering box
self.a_pml = 1 #: flt: PMLs parameter, real part
self.b_pml = 1 #: flt: PMLs parameter, imaginary part
self.eps_host = 1 #: flt: permittivity host
self.eps_sub = 1 #: flt: permittivity substrate
self.eps_des = 1 #: flt: permittivity scattering box
self.eps_incl = 1 #: flt: permittivity inclusion
self.dom_des = 5 #: design domain number (check .geo/.pro files)
# postprocessing -------------------------------------------------
#: coords of point for PostProcessing
self.xpp, self.ypp = 0, 0
#: int: number of theta points for computing the angular dependance
#: of the modal coupling coefficients
self.Ni_theta = 45
self.M_fs = 3
self.Nibox_x = 500 #: int: number of x interpolation points on the design box
self.Nibox_y = 500 #: int: number of y interpolation points on the design box
#: int: number of x interpolation points for near to far field calculations
self.Nin2f_x = 500
#: int: number of y interpolation points for near to far field calculations
self.Nin2f_y = 500
self.rat_n2f = 0.95
self.inclusion_filename_ = "inclusion0.geo"
#: int: number of y slices points
#: for postprocessing diffraction efficiencies
self.nb_slice = 20
#: flt: such that `scan_dist = min(h_sup, hsub)/scan_dist_ratio`
self.scan_dist_ratio = 5
self.space2pml_L, self.space2pml_R = 1.0, 1.0
self.space2pml_T, self.space2pml_B = 1.0, 1.0
#: int: number of x points for postprocessing field maps
self.Nix = 100
self.Niy = 100
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.target_flag = False
self.r_target = 0.01
self.x_target = 0
self.y_target = 0
@property
def corners_des(self):
return -self.hx_des / 2, +self.hx_des / 2, -self.hy_des / 2, +self.hy_des / 2
@property
def domX_L(self):
return -self.hx_des / 2 - self.space2pml_L
@property
def domX_R(self):
return self.hx_des / 2 + self.space2pml_R
@property
def domY_B(self):
return -self.hy_des / 2 - self.space2pml_B
@property
def domY_T(self):
return self.hy_des / 2 + self.space2pml_T
@property
def Xn2f_L(self):
return self.domX_L * self.rat_n2f
@property
def Xn2f_R(self):
return self.domX_R * self.rat_n2f
@property
def Yn2f_B(self):
return self.domY_B * self.rat_n2f
@property
def Yn2f_T(self):
return self.domY_T * self.rat_n2f
@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
def _make_param_dict(self):
param_dict = super()._make_param_dict()
param_dict["ls_flag"] = int(self.ls_flag)
param_dict["beam_flag"] = int(self.beam_flag)
param_dict["target_flag"] = int(self.target_flag)
return param_dict
def postpro_fields_box(self):
path_T = self.tmppath("field_box_T.out")
path_L = self.tmppath("field_box_L.out")
path_R = self.tmppath("field_box_R.out")
path_B = self.tmppath("field_box_B.out")
self.postprocess("postop_fields_box")
u_T = femio.load_timetable(path_T)
u_R = femio.load_timetable(path_R)
u_B = femio.load_timetable(path_B)
u_L = femio.load_timetable(path_L)
if self.analysis == "modal":
u_T = u_T.reshape((self.Nibox_x, self.neig))
u_B = u_B.reshape((self.Nibox_x, self.neig))
u_L = u_L.reshape((self.Nibox_y, self.neig))
u_R = u_R.reshape((self.Nibox_y, self.neig))
return u_T, u_B, u_L, u_R
def postpro_fields_n2f(self):
self.postprocess("postop_fields_n2f")
u_out, vx_out, vy_out = {}, {}, {}
for i in ["T", "L", "R", "B"]:
u = femio.load_timetable(self.tmppath("field_n2f_{0}.out".format(i)))
vx = femio.load_timetable(
self.tmppath("field_dual_x_n2f_{0}.out".format(i))
)
vy = femio.load_timetable(
self.tmppath("field_dual_y_n2f_{0}.out".format(i))
)
if self.analysis == "modal":
if (i == "T") or (i == "B"):
n = self.Nin2f_x
else:
n = self.Nin2f_y
u = u.reshape((n, self.neig))
vx = vx.reshape((n, self.neig))
vy = vy.reshape((n, self.neig))
u_out[i] = u
vx_out[i] = vx
vy_out[i] = vy
return u_out, vx_out, vy_out
def normalized_scs(self, ff, theta):
xi = np.linspace(self.Xn2f_L, self.Xn2f_R, self.Nin2f_x)
yi = np.linspace(self.Yn2f_B, self.Yn2f_T, self.Nin2f_y)
nu0 = np.sqrt(self.mu0 / self.epsilon0)
k0 = 2 * np.pi / self.lambda0
x = {"T": xi, "L": self.Xn2f_L, "R": self.Xn2f_R, "B": xi}
y = {"T": self.Yn2f_T, "L": yi, "R": yi, "B": self.Yn2f_B}
nx = {"T": 0, "L": -1, "R": 1, "B": 0}
ny = {"T": 1, "L": 0, "R": 0, "B": -1}
Ez, Hx, Hy = ff
Itheta = []
for t in theta:
s = np.sin(t)
c = -np.cos(t)
I = 0
for loc in ["T", "L", "R", "B"]:
if (loc == "T") or (loc == "B"):
l = xi
else:
l = yi
expo = np.exp(-1j * k0 * (x[loc] * s + y[loc] * c))
J = (
nu0 * (nx[loc] * Hy[loc] - ny[loc] * Hx[loc])
- (ny[loc] * c + nx[loc] * s) * Ez[loc]
) * expo
I += np.trapz(J, l)
Itheta.append(I)
return np.abs(Itheta) ** 2 / (8 * np.pi)
def get_field_map(self, name):
field = femio.load_table(self.tmppath(name))
return np.flipud(field.reshape((self.Niy, self.Nix)))
def get_field_point(self):
self.postprocess("postop_field_on_point")
u_tot = femio.load_table(self.tmppath("u_tot_point.txt"))
u_i = femio.load_table(self.tmppath("u_i_point.txt"))
u = femio.load_table(self.tmppath("u_point.txt"))
return u, u_tot, u_i
def postpro_coupling_angle(self):
self._print_progress("Angular sweep for coupling coeffs")
self.postprocess("postop_coupling_coeffs_angle")
filename = self.tmppath("coupling_coeffs.txt")
tmp = femio.load_timetable(filename)
return tmp.reshape((self.Ni_theta, self.neig))
def postpro_fourrier_coefs_angle(self):
self._print_progress("Fourrier coefficients for coupling")
self.postprocess("postop_coupling_coeffs_fourrier_series")
filename = self.tmppath("coupling_coeffs_fs.txt")
tmp = femio.load_timetable(filename)
return tmp.reshape((2 * self.M_fs + 1, self.neig))
def postpro_modal_coupling(self):
self._print_progress("QNMs coupling coeffs")
self.postprocess("postop_mode_coupling")
filename = self.tmppath("mode_coupling.txt")
tmp = femio.load_timetable(filename)
return tmp # .reshape((self.Ni_theta, self.neig))
def postpro_modal_coupling_int(self):
self._print_progress("QNMs coupling coeffs integrand")
self.postprocess("postop_mode_coupling_int")
mode = femio.load_timetable(self.tmppath("mode_coupling_int.txt"))
u1 = np.zeros((self.Nix, self.Niy, self.neig), dtype=complex)
u = mode.reshape((self.Niy, self.Nix, self.neig))
for imode in range(self.neig):
u1[:, :, imode] = np.flipud(u[:, :, imode]).T
return u1
def get_deq_deps(self):
if self.pola is "TE":
return self._get_qty("dEq_deps.txt")
else:
return self._get_qty("dEq_deps_x.txt"), self._get_qty("dEq_deps_y.txt")