Skip to content
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

get_eigenmode_coefficients() frequency dependence #1305

Closed
smartalecH opened this issue Jul 29, 2020 · 5 comments · Fixed by #1306
Closed

get_eigenmode_coefficients() frequency dependence #1305

smartalecH opened this issue Jul 29, 2020 · 5 comments · Fixed by #1306
Labels

Comments

@smartalecH
Copy link
Collaborator

smartalecH commented Jul 29, 2020

It appears that any DFT monitor (in particular get_eigenmode_coefficients()) with some dependence on multiple field components does not reliably produce the same result for a given frequency array if said frequency array is shuffled. Here's an extreme example:

image

import meep as mp
import numpy as np
from matplotlib import pyplot as plt
mp.quiet(quietval=True)


# -------------- first run --------------- #

freqs = np.array([1/1.55,1/1.6])

cell = [16,8,0]
gauss_src = mp.GaussianSource(frequency=1/1.55,fwidth=0.2/1.55)
sources = [mp.EigenModeSource(gauss_src,
                     eig_resolution=128,
                     eig_band = 1,
                     #eig_parity=mp.EVEN_Y,
                     size=[0,4],
                     center=[-1,0])]
np.random.seed(1)
func = lambda x: float(np.random.rand(1,)*(11.4-2)+2)
#func = lambda x: 11
sim = mp.Simulation(cell_size=[6,4,0],
                    boundary_layers=[mp.PML(1.0)],
                    geometry=[mp.Block(center=mp.Vector3(),size=(4,0.5,1),material=mp.Medium(index=3.4)),
                    mp.Block(center=mp.Vector3(),size=(0.5,0.5,1),epsilon_func=func)],
                    sources=sources,
                    resolution=10)

mon = sim.add_mode_monitor(freqs,mp.ModeRegion(center=(1,0,0),size=(0,4,0)),yee_grid=True)
sim.run(until_after_sources=200)

dft = [sim.get_dft_array(mon,mp.Ez,0).flatten()[1],sim.get_dft_array(mon,mp.Ez,1).flatten()[1]]
coeffs = sim.get_eigenmode_coefficients(mon,[1]).alpha[:,:,0]
print("Round 1 dft fields")
print(dft)
print("Round 1 eig coeffs:")
print(coeffs)

# -------------- second run with flipped frequency array --------------- #

sim.reset_meep()
np.random.seed(1)
mon2 = sim.add_mode_monitor(np.flipud(freqs),mp.ModeRegion(center=(1,0,0),size=(0,4,0)),yee_grid=True)
sim.run(until_after_sources=200)

dft2 = [sim.get_dft_array(mon2,mp.Ez,0).flatten()[1],sim.get_dft_array(mon2,mp.Ez,1).flatten()[1]]
coeffs2 = sim.get_eigenmode_coefficients(mon2,[1]).alpha[:,:,0]
print("Round 2 dft fields")
print(dft2)
print("Round 2 eig coeffs:")
print(coeffs2)

print("DFT difference:", dft - np.flipud(dft2))
print("eig coeffs difference:", coeffs - np.fliplr(coeffs2))

The output is

Round 1 dft fields
[(-0.008871683979082452+0.02179173297687087j), (-0.0010350658692419093+0.0010121248148330516j)]
Round 1 eig coeffs:
[[ 4.60069925+2.8010177j  -0.13295154+1.97848851j]]
FloatProgress(value=0.0, description='0% done ', max=277.5)
Round 2 dft fields
[(-0.001035065864159373+0.0010121248222745756j), (-0.008871683991283226+0.02179173293283784j)]
Round 2 eig coeffs:
[[-0.1329515 +1.97848851j  4.60069874+2.80101795j]]
DFT difference: [ 1.22007734e-11+4.40330307e-11j -5.08253639e-12-7.44152396e-12j]
eig coeffs difference: [[ 5.12833504e-07-2.55536971e-07j -4.34463213e-08+2.89126012e-09j]]

Note the large difference in eigenmode coefficients: 1.39950082-2.47439812j, 0.59514822-0.85129899j.

Edit: I forgot to reseed the random generator for consistency between the two runs. Here's the actual error (still large): 5.12833504e-07-2.55536971e-07j -4.34463213e-08+2.89126012e-09j

@stevengj @oskooi

@oskooi
Copy link
Collaborator

oskooi commented Jul 29, 2020

Indeed, the results from get_eigenmode_coefficients seem to depend on the order of the values of the input frequency array. This can be demonstrated using a slightly simpler example involving a straight waveguide where the transmitted flux is computed at two frequencies using two different methods: (1) add_flux (Poynting flux) vs. (2) get_eigenmode_coefficients (mode decomposition).

import meep as mp
import numpy as np

resolution = 20 # pixels/um                                                                                                                          

sxy = 16  # size of cell in X/Y directions                                                                                                           
cell = mp.Vector3(sxy,sxy,0)

dpml = 1.0
pml_layers = [mp.PML(dpml)]

w = 1    # width of waveguide                                                                                                                        
geometry = [mp.Block(size=mp.Vector3(mp.inf,w,mp.inf),
                     center=mp.Vector3(),
                     material=mp.Medium(epsilon=12))]

fcen = 0.15  # pulse center frequency                                                                                                                
df = 0.1     # pulse width (in frequency)                                                                                                            
sources = [mp.EigenModeSource(src=mp.GaussianSource(fcen,fwidth=df),
                              eig_band=1,
                              eig_parity=mp.EVEN_Y+mp.ODD_Z,
                              center=mp.Vector3(-0.5*sxy+dpml,0,0),
                              size=mp.Vector3(0,sxy-2*dpml,0))]

symmetries = [mp.Mirror(direction=mp.Y)]

sim = mp.Simulation(cell_size=cell,
                    boundary_layers=pml_layers,
                    geometry=geometry,
                    sources=sources,
                    symmetries=symmetries,
                    resolution=resolution)

fr = mp.FluxRegion(center=mp.Vector3(0.5*sxy-dpml,0,0),
                   size=mp.Vector3(0,sxy-2*dpml,0))

freqs = np.array([0.15,0.15+0.3*df])
freqs = np.flipud(freqs)

tran = sim.add_flux(freqs,fr)

sim.run(until_after_sources=100)

coeffs = sim.get_eigenmode_coefficients(tran, [1], eig_parity=mp.EVEN_Y+mp.ODD_Z)

power = np.power(np.abs(coeffs.alpha[0,:,0]),2)
flux = mp.get_fluxes(tran)

for j in range(len(freqs)):
    print("flux:, {} (freq), {} (flux), {} (mode coeffs.), {} (error)".format(freqs[j],flux[j],power[j],abs(flux[j]-power[j])/flux[j]))

The output from two separate runs involving the same two values for the input freqs array but ordered differently reveals the discrepancy.

1. freqs = [ 0.15, 0.18 ]

flux:, 0.15 (freq), 100.05231502391385 (flux), 100.05248536311117 (mode coeffs.), 1.7025013092841147e-06 (error)
flux:, 0.18 (freq), 4.103821940566634 (flux), 4.103216232942241 (mode coeffs.), 0.00014759598081134467 (error)

2. freqs = [ 0.18, 0.15]

flux:, 0.18 (freq), 4.103821940566634 (flux), 4.103216234272091 (mode coeffs.), 0.0001475956567597588 (error)
flux:, 0.15 (freq), 100.05231502391385 (flux), 100.0524853653827 (mode coeffs.), 1.7025240126054779e-06 (error)

Note that the Poynting flux values (labeled flux in the output) are identical in the two runs. Only the mode decomposition values (labeled mode coeffs.) are different (in the ninth digit after the decimal).

@oskooi oskooi added the bug label Jul 29, 2020
@oskooi
Copy link
Collaborator

oskooi commented Jul 29, 2020

I think this behavior may be related to #1233. One workaround is to simply reduce the eig_tolerance from its default of 1e-12 to e.g. 1e-20 which will force the MPB eigensolver to reduce the discrepancy in the mode coefficients at the same frequency in the two runs with different frequency orderings.

@smartalecH
Copy link
Collaborator Author

Note that decreasing the tolerance as described tends to cause MPB to "oscillate" around a particular set of iterations, as described before the patch in #1233 was implemented.

The kpoint finding routine needs more investigating.

@smartalecH
Copy link
Collaborator Author

It's also important to note that the raw DFT fields pulled using get_dft_array() also depend on the order of the freqs (the error is above machine precision).

@stevengj
Copy link
Collaborator

I think we probably just need to ensure that the mode solver is completely deterministic — this way, any errors (e.g. from MPB's tolerance) will cancel when you do the finite-difference calculation. For now, that is as simple as calling srand() before this loop. In the longer run, calling srand is problematic because it changes global state — every subsequent call to rand will be affected. We can fix this by using the Mersenne twister RNG that we ship with Meep, which gives us more control over its state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants