-
Notifications
You must be signed in to change notification settings - Fork 637
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
eigenmodes of diffracted planewaves #1316
Conversation
class DiffractedPlanewave(object): | ||
def __init__(self, | ||
g=None, | ||
axis=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eventually, we'll have the EigenmodeSource etcetera choose a reasonable default if axis=None
when the source is added.
(You'll generally want axis
to lie in the plane of the source, I think. In 2d you'll also want it to be in the xy plane, i.e. it should be along the source line.)
@@ -98,6 +98,32 @@ def py_v3_to_vec(dims, iterable, is_cylindrical=False): | |||
else: | |||
raise ValueError("Invalid dimensions in Volume: {}".format(dims)) | |||
|
|||
class DiffractedPlanewave(object): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: the current plan is to optionally pass this for the band_num
parameter to eigenmode sources etcetera.
da9db99
to
332c75d
Compare
The basic framework now seems to be working although there are several issues that need to be resolved. As an initial demonstration/test, the mode coefficients of the diffracted orders of the 2d binary phase grating (from Tutorial/Mode Decomposition/Transmittance Spectra for Planewave at Normal Incidence) computed using a import meep as mp
import math
import cmath
import numpy as np
resolution = 50 # pixels/μm
dpml = 1.0 # PML thickness
dsub = 3.0 # substrate thickness
dpad = 3.0 # length of padding between grating and PML
gp = 10.0 # grating period
gh = 0.5 # grating height
gdc = 0.5 # grating duty cycle
sx = dpml+dsub+gh+dpad+dpml
sy = gp
cell_size = mp.Vector3(sx,sy,0)
pml_layers = [mp.PML(thickness=dpml,direction=mp.X)]
wvl = 0.5 # center wavelength
fcen = 1/wvl # center frequency
df = 0.05*fcen # frequency width
ng = 1.5
glass = mp.Medium(index=ng)
# rotation angle of incident planewave; counter clockwise (CCW) about Z axis, 0 degrees along +X axis
theta_in = math.radians(0)
# k (in source medium) with correct length (plane of incidence: XY)
k = mp.Vector3(fcen*ng).rotate(mp.Vector3(z=1), theta_in)
symmetries = []
eig_parity = mp.ODD_Z
if theta_in == 0:
k = mp.Vector3(0,0,0)
symmetries = [mp.Mirror(mp.Y)]
eig_parity += mp.EVEN_Y
def pw_amp(k,x0):
def _pw_amp(x):
return cmath.exp(1j*2*math.pi*k.dot(x+x0))
return _pw_amp
src_pt = mp.Vector3(-0.5*sx+dpml+0.3*dsub,0,0)
sources = [mp.Source(mp.GaussianSource(fcen,fwidth=df),
component=mp.Ez,
center=src_pt,
size=mp.Vector3(0,sy,0),
amp_func=pw_amp(k,src_pt))]
sim = mp.Simulation(resolution=resolution,
cell_size=cell_size,
boundary_layers=pml_layers,
k_point=k,
default_material=glass,
sources=sources,
symmetries=symmetries)
tran_pt = mp.Vector3(0.5*sx-dpml-0.5*dpad,0,0)
tran_flux = sim.add_flux(fcen, 0, 1, mp.FluxRegion(center=tran_pt, size=mp.Vector3(0,sy,0)))
sim.run(until_after_sources=50)
input_flux = mp.get_fluxes(tran_flux)
sim.reset_meep()
geometry = [mp.Block(material=glass,
size=mp.Vector3(dpml+dsub,mp.inf,mp.inf),
center=mp.Vector3(-0.5*sx+0.5*(dpml+dsub),0,0)),
mp.Block(material=glass,
size=mp.Vector3(gh,gdc*gp,mp.inf),
center=mp.Vector3(-0.5*sx+dpml+dsub+0.5*gh,0,0))]
sim = mp.Simulation(resolution=resolution,
cell_size=cell_size,
boundary_layers=pml_layers,
geometry=geometry,
k_point=k,
sources=sources,
symmetries=symmetries)
tran_flux = sim.add_mode_monitor(fcen, 0, 1, mp.FluxRegion(center=tran_pt, size=mp.Vector3(0,sy,0)))
sim.run(until_after_sources=200)
## when using "bands" as a list:
## bands=[1] is 0th diffraction order, bands[2] is 1st diffraction order, ...
## when using "bands" as a DiffractedPlanewave:
## (0,0,0) is 0th diffraction order, (0,1,0) is 1st diffraction order, ...
diff_order = 1
res = sim.get_eigenmode_coefficients(tran_flux, [diff_order+1], eig_parity=eig_parity)
t_ref = res.alpha
res = sim.get_eigenmode_coefficients(tran_flux, mp.DiffractedPlanewave((0,diff_order,0),mp.Vector3(1,1,0),1,0))
t_dp = res.alpha
print("tran:, {}, {:.8f} (eigensolver), {:.8f} (planewave)".format(diff_order,
0.5*abs(t_ref[0,0,0])**2/input_flux[0],
abs(t_dp[0,0,0])**2/input_flux[0])) first diffraction order (
third diffraction order (
fifth diffraction order (
Issues that need to be fixed:
|
The following is a 3d test based on extending the 2d binary-grating tutorial example. The test involves computing the mode coefficients of all transmitted diffraction orders based on individual calls to # binary grating on a semi-infinite glass substrate
# structure is 3d extrusion of tutorial example
import math
import cmath
import numpy as np
import meep as mp
resolution = 50 # pixels/μm
dpml = 1.0 # PML thickness
dsub = 3.0 # substrate length
dpad = 3.0 # padding length between grating and pml
gp = 2.0 # grating period
gh = 0.5 # grating height
gdc = 0.5 # grating duty cycle
sx = dpml+dsub+gh+dpad+dpml
sy = gp
sz = gp
cell_size = mp.Vector3(sx,sy,sz)
pml_layers = [mp.PML(thickness=dpml,direction=mp.X)]
wvl = 0.5 # wavelength
fcen = 1/wvl # frequency
ng = 1.5
glass = mp.Medium(index=ng)
# angle of incident planewave; CCW about Z axis, 0 degrees along +X axis
theta_in = math.radians(0)
# k with correct length (plane of incidence: XY)
k = mp.Vector3(math.cos(theta_in),math.sin(theta_in)).scale(fcen)
## disabled: causes field instability
# symmetries = [mp.Mirror(mp.Z, phase=-1)]
symmetries = []
if theta_in == 0:
k = mp.Vector3()
symmetries.append(mp.Mirror(mp.Y))
def pw_amp(k,x0):
def _pw_amp(x):
return cmath.exp(1j*2*math.pi*ng*k.dot(x+x0))
return _pw_amp
src_pt = mp.Vector3(-0.5*sx+dpml+0.5*dsub)
sources = [mp.Source(mp.GaussianSource(fcen, fwidth=0.2*fcen),
component=mp.Ez,
center=src_pt,
size=mp.Vector3(0,sy,sz),
amp_func=pw_amp(k,src_pt))]
sim = mp.Simulation(resolution=resolution,
cell_size=cell_size,
boundary_layers=pml_layers,
k_point=k,
default_material=glass,
sources=sources,
symmetries=symmetries)
flux_mon = sim.add_flux(fcen, 0, 1,
mp.FluxRegion(center=mp.Vector3(0.5*sx-dpml-0.5*dpad), size=mp.Vector3(0,sy,sz)))
sim.run(until_after_sources=10)
input_flux = mp.get_fluxes(flux_mon)
sim.reset_meep()
geometry = [mp.Block(material=glass,
size=mp.Vector3(dpml+dsub,mp.inf,mp.inf),
center=mp.Vector3(-0.5*sx+0.5*(dpml+dsub))),
mp.Block(material=glass,
size=mp.Vector3(gh,gdc*gp,gdc*gp),
center=mp.Vector3(-0.5*sx+dpml+dsub+0.5*gh))]
sim = mp.Simulation(resolution=resolution,
cell_size=cell_size,
boundary_layers=pml_layers,
geometry=geometry,
k_point=k,
sources=sources,
symmetries=symmetries)
mode_mon = sim.add_flux(fcen, 0, 1,
mp.FluxRegion(center=mp.Vector3(0.5*sx-dpml-0.5*dpad), size=mp.Vector3(0,sy,sz)))
sim.run(until_after_sources=100)
m_flux = 0
for ny in range(-4,5):
for nz in range(-4,5):
res = sim.get_eigenmode_coefficients(mode_mon,
mp.DiffractedPlanewave((0,ny,nz),mp.Vector3(1.2,1.5,0),1,0),
eig_parity=mp.EVEN_Y+mp.ODD_Z)
tran = abs(res.alpha[0,0,0])**2/input_flux[0]
print("tran:, {}, {}, {}".format(ny,nz,tran))
m_flux += tran
flux = mp.get_fluxes(mode_mon)
t_flux = t_flux[0]/input_flux[0]
err = abs(m_flux_t_flux)/t_flux
print("flux:, {} (mode coeffs.), {} (flux), {} (error)".format(0.5*m_flux,t_flux,err)) The results show that the relative error is converging to zero with increasing resolution. resolution = 30
resolution = 40
|
@oskooi, If I'm reading the diffs and the current code correctly, it looks like you didn't add the new |
Diffracted planewaves are not mirror symmetric. As explained in the documentation, you have to use |
Output for the third diffraction order:
Note that the kx can be computed analytically (which agrees with the output for
|
src/mpb.cpp
Outdated
scalar_complex s = {real(dp->get_s()),imag(dp->get_s())}; | ||
scalar_complex p = {real(dp->get_p()),imag(dp->get_p())}; | ||
|
||
master_printf("diffracted planewave: k = %g, %g, %g for omega = %g\n", k[0],k[1],k[2], frequency); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It needs to first compute the correct k vector from the frequency and get_g()
.
src/mpb.cpp
Outdated
update_maxwell_data_k(mdata, k, G[0], G[1], G[2]); | ||
maxwell_set_planewave(mdata, H, band_num, dp->get_g(), s, p, dp->get_axis()); | ||
|
||
eigvals[band_num - 1] = frequency; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or if match_frequency is false then it needs to compute the frequency
from k
and get_g()
.
The following code computes the correct "k" (which is actually equivalent to the dominant planewave) from the frequency and if (match_frequency) {
double k2sum = 0;
LOOP_OVER_DIRECTIONS(v.dim, dd) {
int m = dp->get_g()[dd - X];
if ((float(eig_vol.in_direction(dd)) == float(v.in_direction(dd))) &&
(boundaries[High][dd] == Periodic && boundaries[Low][dd] == Periodic)) {
k[dd - X] += m/v.in_direction(dd);
k2sum += k[dd - X]*k[dd - X];
}
}
vec cen = eig_vol.center();
double nn = sqrt(real(get_eps(cen, frequency)) * real(get_mu(cen, frequency)));
LOOP_OVER_DIRECTIONS(v.dim, dd) {
if ((float(eig_vol.in_direction(dd)) != float(v.in_direction(dd))) &&
(boundaries[High][dd] == Periodic && boundaries[Low][dd] == Periodic)) {
k[dd - X] = sqrt(frequency*frequency*nn*nn - k2sum);
}
}
} While it produces the correct result for "k" as shown in the output below on the line
The dominant planewave is reported as Is the code computing an incorrect value for "k"? |
… header for non-MPB builds
Working now: the dominant planewave computed directly in There is one major limitation in the implementation: because the diffracted planewaves are not symmetric (as noted above), first diffraction order (
third diffraction order (
third diffraction order (
|
Any ideas how to fix the following SWIG-related error from Travis (serial, Python2.7, no MPB)?
|
…sect_ray_with_segment argument
…st or DiffractedPlanewave object
…e range as separate type
This is ready to be merged. I added a new test ( note: in 3d, the results depend somewhat on the choice of |
Notes: For the 3d case, things seem fine — the basic conceptual issue here is that in 3d, different diffraction orders have different planes of incidence (not just the xy plane!), and so the definition of s and p will depend on the choice of axis. Normally you would want the axis to lie within the mode-monitor plane, however, I think — it would be good to make this the default. The issue with symmetries seems likely to be unrelated to this PR, so probably it should be debugged separately. |
I think I know why Lines 658 to 660 in 859d219
Omitting symmetries in the mode monitor when symmetries are specified for the cell volume in the case of a normally incident planewave with Lines 603 to 604 in 859d219
Lines 614 to 615 in 859d219
We obviously do not want to modify Unfortunately, there does not seem to be a simple workaround. |
* eigenmodes of diffracted planewaves in 3d * overload get_eigenmode_coefficients_and_kpoints with diffractedplanewave object in place of bands array * SWIG typemap for axis member of class diffractedplanewave * only set kperp when computing diffracted orders and fix get_eigenmode header for non-MPB builds * add missing header for get_eigenmode_coefficients for non-MPB build * typo * SWIG typemap for diffraction order input parameter g * rename parameter s to spol to resolve name conflict with Scheme intersect_ray_with_segment argument * move constructor for diffractedplanewave from mpb.cpp to sources.cpp * bug fix in k2sum and add Python test * remove unicode character from Python test * bands parameter of get_eigenmode_coefficients can only be either a list or DiffractedPlanewave object * use exception handling for isinstance since Python2 does not recognize range as separate type * eigenvalue is square if eigenfrequency (bug fix for group velocity) * remove output for debugging
Follows the outline described by @stevengj in #291:comment.
Documentation, tutorial,
and testwill likely be added in a separate PR once the API is working.