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

Python implementation for medium evaluations #862

Merged
merged 8 commits into from May 14, 2019

Conversation

smartalecH
Copy link
Collaborator

@smartalecH smartalecH commented May 7, 2019

The python portion of #475 that is used to evaluate material dispersion profiles at a given frequency/wavelength. Useful for making sure your profile really is what you expect. Also great for doing several MPB simulations across a band.

Here is a simple test script that pulls the profiles of Ag, Au, Si, and Cu and compares them to their original sources (stored in the attached h5 file):

import numpy as np
import meep as mp
from meep.materials import Ag, Au, Cu, Si
from matplotlib import pyplot as plt
import h5py


dataNames     = ['Ag','Au','Cu','Si']
testMaterials = [Ag,Au,Cu,Si]
hf = h5py.File('materialData.hdf5', 'r')

plt.figure(figsize=(4,8))

for iter in range(len(testMaterials)):
    # Pull Meep data
    M = testMaterials[iter]
    f0 = M.valid_freq_range[0]
    f1 = M.valid_freq_range[1]
    wavelength = np.linspace(1/f0,1/f1,1000)
    freq = 1/wavelength
    eps  = M.get_eps(freq)
    n = [np.real(np.sqrt(num.x)) for num in eps]
    k = [np.imag(np.sqrt(num.x)) for num in eps]

    # Pull refractiveindex.info data
    ref = hf.get(dataNames[iter])
    wavelength_ref = ref[:,0]
    n_ref = ref[:,1]
    if M !=Si:
        k_ref = ref[:,2]

    linewidth = 2
    # Set up plot
    plt.subplot(421 + 2*iter)
    plt.plot(wavelength_ref,n_ref,color='Red',linewidth=linewidth,label='ref')
    plt.plot(wavelength,n,'--',color='Blue',linewidth=linewidth,label='meep')
    plt.title("{} (real)".format(dataNames[iter]))
    plt.legend()
    plt.xlabel('Wavelength ($\mu$m)')
    plt.grid(True)
    plt.ylabel('n')

    if M !=Si:
        plt.subplot(421 + 2*iter+1)
        plt.plot(wavelength_ref,k_ref,color='Red',linewidth=linewidth,label='ref')
        plt.plot(wavelength,k,'--',color='Blue',linewidth=linewidth,label='meep')
        plt.title("{} (imag)".format(dataNames[iter]))
        plt.legend()
        plt.grid(True)
        plt.xlabel('Wavelength ($\mu$m)')
        plt.ylabel('k')
    

plt.tight_layout()
plt.savefig('materialEval.png')
plt.show()

materialEval

materialData.hdf5.zip

python/geom.py Outdated Show resolved Hide resolved
python/geom.py Outdated Show resolved Hide resolved
@smartalecH
Copy link
Collaborator Author

smartalecH commented May 7, 2019

Thanks for the suggestions! The function epsilon() now outputs a list of matrix objects corresponding to the dielectric tensor at that particular frequency.

Here is a test case showing both the ordinary and extraordinary axes for lithium niobate and barium borate:

hf = h5py.File('materialData_ani.hdf5', 'r')

dataNames = ["BaB2","LiNO"]
testMaterials = [BaB2O4,LiNbO3]
names = ["BaB2O4","LiNbO3"]
plt.figure(figsize=(4,4))
for iter in range(len(testMaterials)):
    # Pull Meep data
    M = testMaterials[iter]
    f0 = M.valid_freq_range[0]
    f1 = M.valid_freq_range[1]
    wavelength = np.linspace(1/f0,1/f1,1000)
    freq = 1/wavelength
    eps  = M.epsilon(freq.tolist())
    ne = [np.real(np.sqrt(num.c3.z)) for num in eps]
    no = [np.real(np.sqrt(num.c1.x)) for num in eps]

    # Pull refractiveindex.info data
    ref = hf.get(dataNames[iter] + '_e')
    wavelength_ref = ref[:,0]
    ne_ref = ref[:,1]
    ref = hf.get(dataNames[iter] + '_o')
    wavelength_ref = ref[:,0]
    no_ref = ref[:,1]

    linewidth = 2
    # Set up plot
    plt.subplot(221 + 2*iter)
    plt.plot(wavelength_ref,ne_ref,color='Red',linewidth=linewidth,label='ref')
    plt.plot(wavelength,ne,'--',color='Blue',linewidth=linewidth,label='meep')
    plt.title("{}".format(dataNames[iter]))
    plt.legend()
    plt.xlabel('Wavelength ($\mu$m)')
    plt.grid(True)
    plt.ylabel('n$_e$')


    plt.subplot(221 + 2*iter+1)
    plt.plot(wavelength_ref,no_ref,color='Red',linewidth=linewidth,label='ref')
    plt.plot(wavelength,no,'--',color='Blue',linewidth=linewidth,label='meep')
    plt.title("{}".format(dataNames[iter]))
    plt.legend()
    plt.grid(True)
    plt.xlabel('Wavelength ($\mu$m)')
    plt.ylabel('n$_o$')
    

plt.tight_layout()
plt.savefig('materialEval_ani.png')

plt.show()

materialEval_ani

The previous simulations also still work.

I still haven't refactored the function into _get_epsmu(tensor, susceptibilities, conductivity) quite yet. Where would you like that function to reside?

Also, the Matrix class seems to have trouble accessing its elements with the bracket notation, even with the __getitem__ overload. That's why I resorted to the odd, num.c3.z notation. The class is also missing copy overloads. We may want to look into that.

materialData_ani.zip

@stevengj
Copy link
Collaborator

stevengj commented May 9, 2019

Here is some anisotropic data on liquid crystal, for example: https://www.tandfonline.com/doi/full/10.1080/15421400600655816?scroll=top&needAccess=true&

@stevengj
Copy link
Collaborator

stevengj commented May 9, 2019

For a test, just comparing the output to two hard-coded values should be sufficient.

@stevengj
Copy link
Collaborator

stevengj commented May 9, 2019

To handle scalars and numpy arrays, you could do something like:

if hasattr(freq, "__iter__"):
    freqs = freq
else:
    freqs = [freq]

then, at the end, you can do something like

if hasattr(freq, "__iter__"):
    return result
else:
    return result[1]

You probably also want to make the result an Nx3x3 numpy array rather than a Python list.

@smartalecH
Copy link
Collaborator Author

I just pushed an update that refactored the method to use a common _get_epsmu() function. I vectorized the computation using numpy arrays (I'm not sure there's actually any gains here).

The function can take either a scalar, list, or numpy array. It computes everything and outputs a numpy array with size (N,3,3) where N is the number of frequency points passed.

I added a test script that tests Si, LiNO3, and Au. I'm still looking for a decent material to test nondiagonal components.

I also added one more Lorentzian to the Au and Ag entries to match the cited publication.

python/geom.py Outdated Show resolved Hide resolved
python/geom.py Outdated Show resolved Hide resolved
python/geom.py Outdated Show resolved Hide resolved
@smartalecH
Copy link
Collaborator Author

My latest commit leverages broadcasting to streamline the computation.

I've also modified the Matrix constructor to allow the user to either specify three column vectors (as in the current implementation) or to specify a diagonal vector and an off-diagonal vector. If we decide to keep this, we should modify the docs too.

@oskooi
Copy link
Collaborator

oskooi commented May 10, 2019

Note that you need to add the test to python/Makefile.am; otherwise it is not part of the test suite and will not run during make check.

@@ -258,12 +258,17 @@
Ag_frq4 = 9.083*eV_um_scale # 0.137 um
Ag_gam4 = 0.916*eV_um_scale
Ag_sig4 = Ag_f4*Ag_plasma_frq**2/Ag_frq4**2
Ag_f5 = 5.646
Ag_frq5 = 20.29*eV_um_scale
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This additional Lorentzian term corresponds to a resonance at a wavelength of 0.061 μm (or 61 nm) that is so far outside the optical range that it should not affect results but it will increase the computational expense due to the additional storage and time stepping of auxiliary fields. (This is why it was originally left out.) Is it really necessary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but it seems to be a pretty broad resonance because even though it's so far away, it does alter the shape of the resonances between 200 nm and 300 nm. After that, however, it's negligible.

Here's a comparison:

copareResonance

The 5 resonance implementation matches refractiveindex.info's raw data points.

@@ -291,12 +296,17 @@
Au_frq4 = 4.304*eV_um_scale # 0.288 um
Au_gam4 = 2.494*eV_um_scale
Au_sig4 = Au_f4*Au_plasma_frq**2/Au_frq4**2
Au_f5 = 4.384
Au_frq5 = 13.32*eV_um_scale
Copy link
Collaborator

@oskooi oskooi May 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Lorentzian resonance is at 0.093 μm (or 93 nm). Similar to the additional Ag term; is it really necessary or should we just reduce the tolerance of the test slightly (i.e., matching the results to 4 decimal places rather than 6)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before. Let me know if you want me to remove it.

python/geom.py Outdated Show resolved Hide resolved
@stevengj stevengj merged commit 0355db6 into NanoComp:master May 14, 2019
bencbartlett pushed a commit to bencbartlett/meep that referenced this pull request Sep 9, 2021
* Python implementation for medium evaluations

* Enabled full Chi1 tensor, small naming conventions

* refactored method with test and updated materials

* refactored matrix init and streamlined computation with broadcasting

* squeeze output for scalar freq inputs

* Add test to makefile

* complex hermitian Matrix init

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

Successfully merging this pull request may close these issues.

None yet

3 participants