Source code for cemd_metasurf.my_metasurface
import numpy as np
from .polarizability import mie_functions
from .depolarization_gf.depolarization_gf import DGreenFunction
from .reflec_transm.reflec_transm import ReTr
[docs]class Metasurface(DGreenFunction, ReTr):
"""
Class for defining the metasurface
"a" and "b" are the lengths of the lattice vectors ("a" along the x-axis) and "th" is the angle between the lattice vectors.
For example the lattice vector "hat(v_1)" and "hat(v_2)" would be:
"hat(v_1) = a hat(x)",
"hat(v_2) = b (cos(th)*hat(x) + sin(th)*hat(y))".
Note that "norm(hat(v_1)) = a" and "norm(hat(v_2)) = b".
"x", "y" and "z" defined the position of the particles in the unit cell.
"eps_b" is the backgroud permittivity and the permittivity of the layred substrate (if present).
"d_layer" defined the with of the layered substrate (if present).
:param a: Length lattice vector along the x-axis
:type a: float
:param b: Length lattice vector along the other axis (defined by the :param th)
:type b: float
:param x: Position in the x-axis of the particles that composed the unit cell.
:type x: float or numpy.ndarray
:param y: Position in the y-axis of the particles that composed the unit cell.
:type y: float or numpy.ndarray
:param z: Position in the z-axis of the particles that composed the unit cell.
:type z: float or numpy.ndarray
:param th: Angle between lattice vectors.
:type th: float
:param eps_b: Permittivity of the media.
:type esp_b: float (and numpy.ndarray in the future)
:param d_layer: It will defined the width of the layers of the substrate.
:type d_later: float (and numpy.ndarray in the future)
"""
def __init__(self,a=400,b=400,x=0,y=0,z=0,th=np.pi/2,eps_b = 1, d_layer = 0):
if isinstance(x,int) or isinstance(x,float):
x = np.float64(x)
y = np.float64(y)
z = np.float64(z)
self.__num_part = 1
elif len(x) == 1:
x = np.float64(x[0])
y = np.float64(y[0])
z = np.float64(z[0])
self.__num_part = 1
else:
self.__num_part = np.max(x.shape)
if y.shape != x.shape or y.shape != z.shape or z.shape != y.shape:
raise ValueError("Size mismatch between x, y and z")
self.__a = a
self.__b = b
self.__x = x
self.__y = y
self.__z = z
self.__th = th
self.__eps_b = eps_b
self.__d_layer = d_layer
self.particles = [ParticleMie(a/4,eps = 3.5**2)]*self.__num_part
self.gb_kxky = None
self.k = None
self.kx = None
self.ky = None
self.array_k_gb = None
self.array_k_rt = None
self.array_k_rt_complex = None
self.array_k_rt_pol = None
[docs] def set_lattice(self,a,b,th):
"""
Set the properties of the lattice (lattice vectors and the angle between them).
:param a: Length lattice vector along the x-axis
:type a: float
:param b: Length lattice vector along the other axis (defined by the :param th)
:type b: float
:param th: Angle between lattice vectors.
:type th: float
"""
self.__a = a
self.__b = b
self.__th = th
[docs] def get_lattice(self):
"""
Return lattice parameters.
:return: tuple with the lattice parameters a, b and th.
"""
return self.__a, self.__b, self.__th
[docs] def set_unit_cell(self,x,y,z):
"""
Set the position of the particles in the unit cell.
:param x: Position in the x-axis of the particles that composed the unit cell.
:type x: float or numpy.ndarray
:param y: Position in the y-axis of the particles that composed the unit cell.
:type y: float or numpy.ndarray
:param z: Position in the z-axis of the particles that composed the unit cell.
:type z: float or numpy.ndarray
"""
if isinstance(x,int) or isinstance(x,float):
x = np.float64(x)
y = np.float64(y)
z = np.float64(z)
self.__num_part = 1
elif len(x) == 1:
x = np.float64(x[0])
y = np.float64(y[0])
z = np.float64(z[0])
self.__num_part = 1
else:
self.__num_part = np.max(x.shape)
if y.shape != x.shape or y.shape != z.shape or z.shape != y.shape:
raise ValueError("Size mismatch between x, y and z")
self.__x = x
self.__y = y
self.__z = z
[docs] def get_unit_cell(self):
"""
Return unit cell configuration.
:return: tuple with the position of the particles in the unit cell (x, y and z)
"""
return self.__x, self.__y, self.__z
[docs] def set_bloch(self,my_bloch):
"""
Set the actual Bloch wavevector configuration (it takes the last entry of my_bloch).
"k" is the wavevector in the medium where the metasurface is placed (k = 2pi/lambda*sqrt(eps_b[0]))
By the moment, "eps_b" is a number, but it will be generized in the future for supporting substrates.
"kx" and "ky" are the Bloch wavevector (periodicity along the metasurface plane).
:param my_bloch: The object with the information of the wavevectos
:type my_bloch: classes.BlochWavevector
"""
k, kx, ky = my_bloch.get_bloch()
self.k, self.kx, self.ky = k[-1], kx[-1], ky[-1]
[docs] def get_bloch(self):
"""
Return the actual Bloch wave configuration.
:return: tuple with the Bloch wave configuration (k, kx and ky)
"""
return self.k, self.kx, self.ky
[docs] def set_particles(self,my_particle):
"""
Set all particle to have the properties given by "my_particle".
:param my_particle: The object with the information of the particle.
:type my_particle: classes.ParticleMie (or other particle)
"""
self.particles = [my_particle]*self.__num_part
[docs] def set_particle_i(self,my_particle,i):
"""
Set the particle "i" to have the properties given by "my_particle".
:param my_particle: The object with the information of the particle.
:type my_particle: classes.ParticleMie (or other particle).
:param i: Index of the particle that will be set.
:type i: Int
"""
if i > len(self.particles) or i < 0:
raise ValueError("i bigger than the number of particles (or negative)")
else:
self.particles[i] = my_particle
[docs] def set_alpha(self):
"""
Set the polarizability to the one defined in self.particles at the inner wavevector (self.k).
"""
alp = np.zeros((self.__num_part*6,self.__num_part*6), dtype = "complex_")
i = 0
for particle in self.particles:
particle.set_alpha(self.k,self.__eps_b)
alp[i*6:i*6+6] = particle.alp
i = i+1
self.alp_uc = alp
[docs] def set_alpha_user_defined(self,alp):
"""
Set the polarizability to a given specific matrix.
At the moment is not really used ("set_alpha" is the one used for setting the polarizability
for calculating r and t), but in the future I would like to give more flexibility to the code
and, for example, being able of easyly set quirality (modify the off-diagonal matrices of alp).
:param alp: Polarizability of the system.
:type alp: numpy.ndarray
"""
if alp.shape != (self.__num_part*6, self.__num_part*6):
raise ValueError("the dimension of alp is not the appropieted")
self.alp_uc = alp
[docs]class BlochWavevector:
"""
Class to handle the Bloch wavevectors
"k" is the wavevector (in the medium).
"kx" and "ky" are the Bloch wavevector (periodicity along unit cells).
:param k: Wavevector in the medium.
:type k: float or numpy.ndarray
:param kx: Bloch wavevector (Floquet periodicity) along the x-axis.
:type kx: float or numpy.ndarray
:param ky: Bloch wavevector (Floquet periodicity) along the y-axis.
:type ky: float or numpy.ndarray
"""
def __init__(self,k,kx,ky):
if (isinstance(k,int) or isinstance(k,float)):
self.__k = np.array([k])
self.__kx = np.array([kx])
self.__ky = np.array([ky])
else:
self.__k = k.reshape(-1,)
self.__kx = kx.reshape(-1,)
self.__ky = ky.reshape(-1,)
[docs] def get_bloch(self):
"""
Get the wavevectors.
:return: tuple with the Bloch wavevectors (k, kx and ky)
"""
return self.__k, self.__kx, self.__ky
[docs] def get_bloch_i(self,i):
"""
Get the wavevector at index "i"
:param i: Index of the particle that will be set.
:type i: Int
:return: tuple with the Bloch wavevectors (k, kx and ky)
"""
if i < self.__k.shape[0]:
return self.__k[i], self.__kx[i], self.__ky[i]
else:
return print("i bigger than the dimension of the wavevectors")
[docs]class ParticleMie:
"""
Class to set the properties of the particle to a Mie particle with a permittivity
given by a Drude-Lorentz model (at the moment with one single resonance).
:param r_p: Radius of the particles.
:type r_p: Float
:param ei: Permittivity at high frecuencies (avobe resonances).
:type ei: Float
:param wp: Plasma frequency.
:type wp: Float (I will generalize it to an array setting several resonances)
:param wr: Resonant frequency.
:type wr: Float (I will generalize it to an array setting several resonances)
:param gr: Width of the resonance.
:type gr: Float (I will generalize it to an array setting several resonances)
"""
def __init__(self,r_p=1,eps=1,wp=0, wr=0, gr=0):
self.ei = eps
self.wp = wp
self.wr = wr
self.gr = gr
self.r_p = r_p
"""
Set the polatizability of the particle.
:param k: Wavevector (in the media).
:type k: Float
:param eps_b: Permittivity of the media.
:type eps_b: Float
"""
def set_alpha(self,k, eps_b=1):
k0 = k/np.sqrt(eps_b)
w = k0*3e8
eps = self.ei + self.wp**2/(self.wr**2 - w**2 - 1j*w*self.gr)
alp = mie_functions.get_alpha_mie(k0,self.r_p,np.sqrt(eps),np.sqrt(eps_b))
self.alp = alp
class ParticleUserDefined:
"""
Class to set the properties of the particle to tabulated data.
It will develop in future releases.
"""
def __init__(self,k_array,alp_e,alp_m,alp_chiral = None):
if k_array.size != k_array.shape[0]:
ValueError("k_array must be a one dimensional array")
self.k_array = k_array
self.alp_e = alp_e
self.alp_m = alp_m
"""
Set the polatizability of the particle at k.
"""
def set_alpha(self,k, eps_b=1):
size_k = self.k_array.size
alp_e = self.alp_e.reshape(size_k,-1)
alp_m = self.alp_m.reshape(size_k,-1)
if alp_e.shape[1] != alp_m.shape[1]:
ValueError("alp_e and alp_m must have the same dimensionality")
alp = np.zeros((6,6), dtype = 'complex_')
if alp_e.shape[1] == 1:
alpha_e = np.interp(k,self.k_array,alp_e)
alpha_m = np.interp(k,self.k_array,alp_m)
id3 = np.eye(3)
alp[0:3,0:3] = id3*alpha_e
alp[3:6,3:6] = id3*alpha_m
elif alp_e.shape[1] == 3:
alp[0,0] = np.interp(k,self.k_array,alp_e[:,0])
alp[1,1] = np.interp(k,self.k_array,alp_e[:,1])
alp[2,2] = np.interp(k,self.k_array,alp_e[:,2])
alp[3,3] = np.interp(k,self.k_array,alp_m[:,0])
alp[4,4] = np.interp(k,self.k_array,alp_m[:,1])
alp[5,5] = np.interp(k,self.k_array,alp_m[:,2])
else:
print("not implemented")
if type(self.alp_chiral) != type(None):
print("not implemented")