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

Approximate material grid smoothing (β=∞) #1951

Draft
wants to merge 34 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5fd8cf6
subpixel averaging for boundaries of variable-material objects
stevengj Oct 10, 2020
d67b1f2
rm obsolete references to meepgeom::is_file
stevengj Oct 14, 2020
6a9f7dd
bugfix
stevengj Oct 20, 2020
bf5296c
undo spurious change
stevengj Jun 22, 2021
a480ffd
add proper kernels for sphere caps
smartalecH Feb 16, 2022
5c38aa7
clean up comments
smartalecH Feb 16, 2022
bb07eaa
checkpoint
smartalecH Feb 18, 2022
92499af
merge in previous updates from subpixel_varmat
smartalecH Feb 18, 2022
1652a62
minor tweaks to kill errors
smartalecH Feb 19, 2022
ee071de
enable smoothing
smartalecH Feb 21, 2022
b544afd
get gradients working both with and without averaging
smartalecH Feb 23, 2022
5f59014
adjust FD step
smartalecH Feb 26, 2022
8bfbb77
fix boundary case when spatial gradient doesn't exist
smartalecH Mar 2, 2022
1880b0d
refactor
smartalecH Mar 8, 2022
fa35e16
add duals
smartalecH Mar 9, 2022
3aa3c09
fix interface artifacts and get gradients working
smartalecH Mar 10, 2022
b1fd007
Merge branch 'master' of https://github.com/NanoComp/meep into inf_sm…
smartalecH Mar 10, 2022
5c07d78
minor formatting
smartalecH Mar 21, 2022
25f9437
update makefile
smartalecH Apr 7, 2022
b69a5d1
fix bug in fill fraction
smartalecH May 18, 2022
69265e9
rebase
smartalecH May 25, 2022
7a558fd
fix matgrid test bug
smartalecH May 25, 2022
9ac153e
fix damping test failure
smartalecH May 25, 2022
3349aee
revamp matgrid test and add comments
smartalecH May 26, 2022
35ec6d8
update tests and add comments to code
smartalecH May 29, 2022
fabd531
approximate material-grid smoothing
stevengj Jul 29, 2022
9793cf5
reformat
stevengj Jul 29, 2022
c88ca4a
rebase
smartalecH Aug 9, 2022
83fd9ef
add function to update m number
smartalecH Aug 12, 2022
2dff39e
update change m number too
smartalecH Aug 12, 2022
2230adb
precommit cleanup
smartalecH Aug 12, 2022
33ea63a
precommit cleanup
smartalecH Aug 12, 2022
1d78417
incorporate m changes
smartalecH Aug 13, 2022
d3f5ef9
add threshold check for normal vector to mitigate floating point sens…
smartalecH Aug 18, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions libpympb/pympb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,12 +376,9 @@ int mode_solver::mean_epsilon(symmetric_matrix *meps, symmetric_matrix *meps_inv

bool o1_is_var = o1 && meep_geom::is_variable(o1->material);
bool o2_is_var = o2 && meep_geom::is_variable(o2->material);
bool default_is_var_or_file =
meep_geom::is_variable(default_material) || meep_geom::is_file(default_material);
bool default_is_var = meep_geom::is_variable(default_material);

if (o1_is_var || o2_is_var ||
(default_is_var_or_file &&
(!o1 || !o2 || meep_geom::is_file(o1->material) || meep_geom::is_file(o2->material)))) {
if (o1_is_var || o2_is_var || (default_is_var && (!o1 || !o2))) {
return 0; /* arbitrary material functions are non-analyzable */
}

Expand Down
18 changes: 18 additions & 0 deletions python/adjoint/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import meep as mp
from scipy import special
from scipy import signal
import skfmm
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it necessary to include this additional module just for its signed distance function (rather than write our own or find a similar function in e.g. SciPy)? skfmm does not seem to be widely used which could make installing it a challenge for some users building from source.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

could make installing it a challenge for some users building from source.

From their docs:

pip install scikit-fmm

What's challenging about this particular package vs numpy, scipy, etc.?

It's convenient for testing, but we can remove it if it really is a challenge to install.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I ran into some package conflicts during the pip install. In general, it is useful to keep the number of package dependencies, particularly smaller ones, to a minimum.

from scipy import interpolate

def _proper_pad(x,n):
'''
Expand Down Expand Up @@ -854,3 +856,19 @@ def gray_indicator(x):
density-based topology optimization. Archive of Applied Mechanics, 86(1-2), 189-218.
'''
return npa.mean(4 * x.flatten() * (1 - x.flatten())) * 100

def make_sdf(data):
'''
Assume the input, data, is the desired output shape
(i.e. 1d, 2d, or 3d) and that it's values are between
0 and 1.
'''
# create signed distance function
sd = skfmm.distance(data- 0.5 , dx = 1)

# interpolate zero-levelset onto 0.5-levelset
x = [np.min(sd.flatten()), 0, np.max(sd.flatten())]
y = [0, 0.5, 1]
f = interpolate.interp1d(x, y, kind='linear')

return f(sd)
2 changes: 1 addition & 1 deletion python/adjoint/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
_Z_AXIS = 1

# default finite difference step size when calculating Aᵤ
FD_DEFAULT = 1e-3
FD_DEFAULT = 1e-6

class DesignRegion(object):
def __init__(
Expand Down
4 changes: 1 addition & 3 deletions python/meep.i
Original file line number Diff line number Diff line change
Expand Up @@ -1504,8 +1504,6 @@ void _get_gradient(PyObject *grad, double scalegrad,
%ignore material_type_equal;
%ignore is_variable;
%ignore is_variable;
%ignore is_file;
%ignore is_file;
%ignore is_medium;
%ignore is_medium;
%ignore is_metal;
Expand Down Expand Up @@ -1986,7 +1984,7 @@ meep_geom::geom_epsilon* _set_materials(meep::structure * s,
geps = existing_geps;
} else {
geps = meep_geom::make_geom_epsilon(s, &gobj_list, center, _ensure_periodicity, _default_material,
extra_materials);
extra_materials, use_anisotropic_averaging);
}
if (set_materials) {
meep_geom::set_materials_from_geom_epsilon(s, geps, use_anisotropic_averaging, tol,
Expand Down
1 change: 1 addition & 0 deletions python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ numpy
parameterized
pytest
scipy
scikit-fmm
192 changes: 87 additions & 105 deletions python/tests/test_material_grid.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
'''
Checks that a material grid works with symmetries, and that the
new subpixel smoothing feature is accurate.

TODO:
Create a 3D test that works well with the new smoothing
'''

import meep as mp
from meep import mpb
try:
import meep.adjoint as mpa
except:
import adjoint as mpa
import numpy as np
from scipy.ndimage import gaussian_filter
import unittest

rad = 0.301943
k_point = mp.Vector3(0.3892,0.1597,0)
Si = mp.Medium(index=3.5)
cell_size = mp.Vector3(1,1,0)

def compute_transmittance(matgrid_symmetry=False):
resolution = 25
Expand All @@ -24,13 +36,16 @@ def compute_transmittance(matgrid_symmetry=False):
rng = np.random.RandomState(2069588)

w = rng.rand(Nx,Ny)
# for subpixel smoothing, the underlying design grid must be smoothly varying
w = mpa.tanh_projection(rng.rand(Nx,Ny),1e5,0.5)
w = mpa.conic_filter(w,0.25,matgrid_size.x,matgrid_size.y,matgrid_resolution)
weights = 0.5 * (w + np.fliplr(w)) if not matgrid_symmetry else w

matgrid = mp.MaterialGrid(mp.Vector3(Nx,Ny),
mp.air,
mp.Medium(index=3.5),
weights=weights,
do_averaging=False,
beta=np.inf,
grid_type='U_MEAN')

geometry = [mp.Block(center=mp.Vector3(),
Expand Down Expand Up @@ -76,20 +91,13 @@ def compute_transmittance(matgrid_symmetry=False):

return tran


def compute_resonant_mode_2d(res,default_mat=False):
cell_size = mp.Vector3(1,1,0)

rad = 0.301943

def compute_resonant_mode_2d(res,radius=rad,default_mat=False,cylinder=False,cylinder_matgrid=True,do_averaging=True):
fcen = 0.3
df = 0.2*fcen
sources = [mp.Source(mp.GaussianSource(fcen,fwidth=df),
component=mp.Hz,
center=mp.Vector3(-0.1057,0.2094,0))]

k_point = mp.Vector3(0.3892,0.1597,0)

matgrid_size = mp.Vector3(1,1,0)
matgrid_resolution = 1200

Expand All @@ -100,24 +108,37 @@ def compute_resonant_mode_2d(res,default_mat=False):
x = np.linspace(-0.5*matgrid_size.x,0.5*matgrid_size.x,Nx)
y = np.linspace(-0.5*matgrid_size.y,0.5*matgrid_size.y,Ny)
xv, yv = np.meshgrid(x,y)
weights = np.sqrt(np.square(xv) + np.square(yv)) < rad
filtered_weights = gaussian_filter(weights,
sigma=3.0,
output=np.double)

weights = mpa.make_sdf(np.sqrt(np.square(xv) + np.square(yv)) < radius)
if cylinder:
weights = 0.0*weights+1.0

matgrid = mp.MaterialGrid(mp.Vector3(Nx,Ny),
mp.air,
mp.Medium(index=3.5),
weights=filtered_weights,
do_averaging=True,
beta=1000,
Si,
weights=weights,
beta=np.inf,
eta=0.5)

geometry = [mp.Block(center=mp.Vector3(),

# Use a cylinder object, not a block object
if cylinder:
# within the cylinder, use a (uniform) material grid
if cylinder_matgrid:
geometry = [mp.Cylinder(center=mp.Vector3(),
radius=radius,
material=matgrid)]
# within the cylinder, just use a normal medium
else:
geometry = [mp.Cylinder(center=mp.Vector3(),
radius=radius,
material=Si)]
# use a block object
else:
geometry = [mp.Block(center=mp.Vector3(),
size=mp.Vector3(matgrid_size.x,matgrid_size.y,0),
material=matgrid)]

sim = mp.Simulation(resolution=res,
eps_averaging=do_averaging,
cell_size=cell_size,
default_material=matgrid if default_mat else mp.Medium(),
geometry=geometry if not default_mat else [],
Expand All @@ -126,7 +147,7 @@ def compute_resonant_mode_2d(res,default_mat=False):

h = mp.Harminv(mp.Hz, mp.Vector3(0.3718,-0.2076), fcen, df)
sim.run(mp.after_sources(h),
until_after_sources=200)
until_after_sources=300)

try:
for m in h.modes:
Expand All @@ -137,109 +158,70 @@ def compute_resonant_mode_2d(res,default_mat=False):

return freq

def compute_resonant_mode_mpb(resolution=512):
geometry = [mp.Cylinder(rad, material=Si)]
geometry_lattice = mp.Lattice(cell_size)

def compute_resonant_mode_3d(use_matgrid=True):
resolution = 25

wvl = 1.27
fcen = 1/wvl
df = 0.02*fcen

nSi = 3.45
Si = mp.Medium(index=nSi)
nSiO2 = 1.45
SiO2 = mp.Medium(index=nSiO2)

s = 1.0
cell_size = mp.Vector3(s,s,s)

rad = 0.34 # radius of sphere

if use_matgrid:
matgrid_resolution = 2*resolution
N = int(s*matgrid_resolution)
coord = np.linspace(-0.5*s,0.5*s,N)
xv, yv, zv = np.meshgrid(coord,coord,coord)

weights = np.sqrt(np.square(xv) + np.square(yv) + np.square(zv)) < rad
filtered_weights = gaussian_filter(weights,
sigma=4/resolution,
output=np.double)

matgrid = mp.MaterialGrid(mp.Vector3(N,N,N),
SiO2,
Si,
weights=filtered_weights,
do_averaging=True,
beta=1000,
eta=0.5)

geometry = [mp.Block(center=mp.Vector3(),
size=cell_size,
material=matgrid)]
else:
geometry = [mp.Sphere(center=mp.Vector3(),
radius=rad,
material=Si)]

sources = [mp.Source(src=mp.GaussianSource(fcen,fwidth=df),
size=mp.Vector3(),
center=mp.Vector3(0.13,0.25,0.06),
component=mp.Ez)]
ms = mpb.ModeSolver(num_bands=1,
k_points=[mp.cartesian_to_reciprocal(k_point, geometry_lattice)],
geometry=geometry,
geometry_lattice=geometry_lattice,
stevengj marked this conversation as resolved.
Show resolved Hide resolved
resolution=resolution)

k_point = mp.Vector3(0.23,-0.17,0.35)

sim = mp.Simulation(resolution=resolution,
cell_size=cell_size,
sources=sources,
default_material=SiO2,
k_point=k_point,
geometry=geometry)

h = mp.Harminv(mp.Ez, mp.Vector3(-0.2684,0.1185,0.0187), fcen, df)

sim.run(mp.after_sources(h),
until_after_sources=200)

try:
for m in h.modes:
print("harminv:, {}, {}, {}".format(resolution,m.freq,m.Q))
freq = h.modes[0].freq
except:
raise RuntimeError("No resonant modes found.")

return freq

ms.run_te()
return ms.freqs[0]

class TestMaterialGrid(unittest.TestCase):

def test_subpixel_smoothing(self):
# "exact" frequency computed using MaterialGrid at resolution = 300
freq_ref = 0.29826813873225283

res = [25, 50]
res = 25

def subpixel_test_matrix(radius,do_averaging):
freq_cylinder = compute_resonant_mode_2d(res, radius, default_mat=False, cylinder=True, cylinder_matgrid=False, do_averaging=do_averaging)
freq_matgrid = compute_resonant_mode_2d(res, radius, default_mat=False, cylinder=False, do_averaging=do_averaging)
freq_matgrid_cylinder = compute_resonant_mode_2d(res, radius, default_mat=False, cylinder=True, cylinder_matgrid=True, do_averaging=do_averaging)
return [freq_cylinder,freq_matgrid,freq_matgrid_cylinder]

# when smoothing is off, all three tests should be identical to machine precision
no_smoothing = subpixel_test_matrix(rad,False)
self.assertEqual(no_smoothing[0], no_smoothing[1])
self.assertEqual(no_smoothing[1], no_smoothing[2])

# when we slightly perturb the radius, the results should be the same as before.
no_smoothing_perturbed = subpixel_test_matrix(rad+0.01/res,False)
self.assertEqual(no_smoothing, no_smoothing_perturbed)

# when smoothing is on, the simple material results should be different from the matgrid results
smoothing = subpixel_test_matrix(rad,True)
self.assertNotEqual(smoothing[0], smoothing[1])
self.assertAlmostEqual(smoothing[1], smoothing[2], 3)

# when we slighty perturb the radius, the results should all be different from before.
smoothing_perturbed = subpixel_test_matrix(rad+0.01/res,True)
self.assertNotEqual(smoothing, smoothing_perturbed)

# "exact" frequency computed using MPB
freq_ref = compute_resonant_mode_mpb()
print(freq_ref)
res = [50, 100]
freq_matgrid = []
for r in res:
freq_matgrid.append(compute_resonant_mode_2d(r))
freq_matgrid.append(compute_resonant_mode_2d(r,cylinder=False))
# verify that the frequency of the resonant mode is
# approximately equal to the reference value
self.assertAlmostEqual(freq_ref, freq_matgrid[-1], 2)
print("results: ",freq_ref,freq_matgrid)

# verify that the relative error is decreasing with increasing resolution
# and is better than linear convergence because of subpixel smoothing
self.assertLess(abs(freq_matgrid[1]-freq_ref)*(res[1]/res[0]),
abs(freq_matgrid[0]-freq_ref))

freq_matgrid_default_mat = compute_resonant_mode_2d(res[0], True)
# ensure that a material grid as a default material works
freq_matgrid_default_mat = compute_resonant_mode_2d(res[0], rad, True)
self.assertAlmostEqual(freq_matgrid[0], freq_matgrid_default_mat)


def test_matgrid_3d(self):
freq_matgrid = compute_resonant_mode_3d(True)
freq_geomobj = compute_resonant_mode_3d(False)
self.assertAlmostEqual(freq_matgrid, freq_geomobj, places=2)


def test_symmetry(self):
tran_nosym = compute_transmittance(False)
tran_sym = compute_transmittance(True)
Expand Down
8 changes: 6 additions & 2 deletions python/typemap_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,12 @@ static int pymaterial_grid_to_material_grid(PyObject *po, material_data *md) {
if (!PyArray_ISCARRAY(pao)) {
meep::abort("Numpy array weights must be C-style contiguous.");
}
md->weights = new double[PyArray_SIZE(pao)];
memcpy(md->weights, (double *)PyArray_DATA(pao), PyArray_SIZE(pao) * sizeof(double));
md->weights.clear();
for (size_t i=0;i<PyArray_SIZE(pao);i++){
md->weights.push_back(((double *)PyArray_DATA(pao))[i]);
}
//md->weights = new double[PyArray_SIZE(pao)];
//memcpy(md->weights, (double *)PyArray_DATA(pao), PyArray_SIZE(pao) * sizeof(double));

// if needed, combine sus structs to main object
PyObject *py_e_sus_m1 = PyObject_GetAttrString(po_medium1, "E_susceptibilities");
Expand Down
6 changes: 3 additions & 3 deletions src/material_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ void material_data::copy_from(const material_data &from) {
}

grid_size = from.grid_size;
if (from.weights) {
if (from.weights.size() != 0) {
size_t N = from.grid_size.x * from.grid_size.y * from.grid_size.z;
weights = new double[N];
std::copy_n(from.weights, N, weights);
weights.clear();
weights.insert(weights.end(), from.weights.begin(), from.weights.end());
}

medium_1 = from.medium_1;
Expand Down
Loading