Skip to content

Commit

Permalink
Squashed commit of a number of enhancements added by Lumerical:
Browse files Browse the repository at this point in the history
 - Improved finite difference computation of partial derivatives of permittivity using FDTD Solutions mesh. Enables the usage of conformal mesh and support for more general geometries. Changed this to be the default method for computing derivatives. WARNING: This can increase computation time substantially in for 3D optimization and this feature can be turned off with the use_deps parameter.

 - Changed to using a mode source for the adjoint simulation instead of a custom source with H field only. The mode source injects only the desired fields, whereas the custom source created a lot of fields propagating in the reverse direction. The mode source is broadband and will support a broadband FOM

 - Changed to using the mode expansion functions from FDTD Solutions, This supports multiple frequencies and will support a broadband FOM.

 - Removed dependencies on some external Python modules for easier portability (pathlib Pygments sphinx_rtd_theme)

 - Some general cleanup and minor fixes.
  • Loading branch information
areid-van authored and crees-lumerical committed Feb 15, 2019
1 parent 81ab810 commit f10643b
Show file tree
Hide file tree
Showing 28 changed files with 739 additions and 2,931 deletions.
3 changes: 3 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ MIT License

Copyright (c) 2018 chriskeraly

Portions copyright (c) Lumerical Inc. 2019
See individual files for details.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
Expand Down
83 changes: 39 additions & 44 deletions examples/WDM_splitter/WDM_splitter.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,76 @@
""" Copyright chriskeraly
Copyright (c) 2019 Lumerical Inc. """

######## IMPORTS ########
# General purpose imports
import os
import numpy as np
from lumopt.optimization import Super_Optimization, Optimization
from lumopt.geometries.polygon import function_defined_Polygon
from lumopt.optimizers.generic_optimizers import ScipyOptimizers
import scipy as sp
from lumopt import CONFIG

from lumopt.figures_of_merit.modematch import ModeMatch
from lumopt.utilities.load_lumerical_scripts import load_from_lsf
import os
from lumopt import CONFIG
import scipy
from lumopt.geometries.polygon import FunctionDefinedPolygon
from lumopt.figures_of_merit.modematch import ModeMatch
from lumopt.optimizers.generic_optimizers import ScipyOptimizers
from lumopt.optimization import Optimization

######## DEFINE BASE SIMULATION ########

#Here I just use the same script for both simulations, but it's just to keep the example simple. You could use two
script_1550=load_from_lsf(os.path.join(CONFIG['root'],'examples/WDM_splitter/WDM_splitter_base_TE_1550.lsf'))
script_1310=load_from_lsf(os.path.join(CONFIG['root'],'examples/WDM_splitter/WDM_splitter_base_TE_1550.lsf')).replace('1550e-9','1310e-9')

# Use the same script for both simulations, but it's just to keep the example simple. You could use two.
script_1550 = load_from_lsf(os.path.join(CONFIG['root'], 'examples/WDM_splitter/WDM_splitter_base_TE_1550.lsf'))
script_1310 = load_from_lsf(os.path.join(CONFIG['root'], 'examples/WDM_splitter/WDM_splitter_base_TE_1550.lsf')).replace('1550e-9','1310e-9')

######## DEFINE OPTIMIZABLE GEOMETRY ########
separation=500e-9
size_x=10e-6
separation = 500.0e-9
size_x = 10.0e-6

def lower_coupler_arm(params,n_points=10):
points_x=np.concatenate(([0.5e-6],np.linspace(0.55e-6,size_x-0.55e-6,20),[size_x-0.5e-6]))
points_y=np.concatenate(([-separation/2],params-separation/2,params[::-1]-separation/2,[-separation/2]))
def lower_coupler_arm(params, n_points = 10):
points_x = np.concatenate(([0.5e-6], np.linspace(0.55e-6,size_x-0.55e-6,20), [size_x-0.5e-6]))
points_y = np.concatenate(([-separation/2], params-separation/2, params[::-1]-separation/2, [-separation/2]))
n_interpolation_points=100
polygon_points_x = np.linspace(min(points_x), max(points_x), n_interpolation_points)
interpolator = scipy.interpolate.interp1d(points_x, points_y, kind='cubic')
interpolator = sp.interpolate.interp1d(points_x, points_y, kind = 'cubic')
polygon_points_y = [max(min(point,0e-6),-separation/2-0.25e-6) for point in interpolator(polygon_points_x)]

polygon_points_up = [(x, y) for x, y in zip(polygon_points_x, polygon_points_y)]
polygon_points_down = [(x, y-0.5e-6) for x, y in zip(polygon_points_x, polygon_points_y)]
polygon_points = np.array(polygon_points_up[::-1] + polygon_points_down)
return polygon_points#[::-1]
return polygon_points

def upper_coupler_arm(params,n_points=10):
points_x=np.concatenate(([0.5e-6],np.linspace(0.55e-6,size_x-0.55e-6,20),[size_x-0.5e-6]))
points_y=np.concatenate(([separation/2],-params+separation/2,-params[::-1]+separation/2,[separation/2]))
def upper_coupler_arm(params, n_points = 10):
points_x = np.concatenate(([0.5e-6], np.linspace(0.55e-6,size_x-0.55e-6,20), [size_x-0.5e-6]))
points_y = np.concatenate(([separation/2], -params+separation/2,-params[::-1]+separation/2, [separation/2]))
n_interpolation_points=100
polygon_points_x = np.linspace(min(points_x), max(points_x), n_interpolation_points)
interpolator = scipy.interpolate.interp1d(points_x, points_y, kind='cubic')
interpolator = sp.interpolate.interp1d(points_x, points_y, kind = 'cubic')
polygon_points_y = [max(min(point,separation/2+0.5e-6),-0e-6) for point in interpolator(polygon_points_x)]

polygon_points_up = [(x, y) for x, y in zip(polygon_points_x, polygon_points_y)]
polygon_points_down = [(x, y+0.5e-6) for x, y in zip(polygon_points_x, polygon_points_y)]
polygon_points = np.array(polygon_points_up + polygon_points_down[::-1])
return polygon_points

bounds = [(-0.25e-6, 0.25e-6)]*10

#final value from splitter_opt_2D.py optimization
initial_params=np.linspace(0,0.24e-6,10)
#initial_params=np.linspace(-0.25e-6,0.25e-6,10)
geometry_1550_lower = function_defined_Polygon(func=lower_coupler_arm,initial_params=initial_params,eps_out=1.44 ** 2, eps_in=2.8 ** 2,bounds=bounds,depth=220e-9,edge_precision=5)
geometry_1550_upper = function_defined_Polygon(func=upper_coupler_arm,initial_params=initial_params,eps_out=1.44 ** 2, eps_in=2.8 ** 2,bounds=bounds,depth=220e-9,edge_precision=5)
geometry_1550=geometry_1550_lower*geometry_1550_upper
geometry_1310_lower = function_defined_Polygon(func=lower_coupler_arm,initial_params=initial_params,eps_out=1.44 ** 2, eps_in=2.8 ** 2,bounds=bounds,depth=220e-9,edge_precision=5)
geometry_1310_upper = function_defined_Polygon(func=upper_coupler_arm,initial_params=initial_params,eps_out=1.44 ** 2, eps_in=2.8 ** 2,bounds=bounds,depth=220e-9,edge_precision=5)
geometry_1310=geometry_1310_lower*geometry_1310_upper
initial_params = np.linspace(0,0.24e-6,10)
geometry_1550_lower = FunctionDefinedPolygon(func = lower_coupler_arm, initial_params = initial_params, bounds = bounds, z = 0.0, depth = 220e-9, eps_out = 1.44 ** 2, eps_in = 2.8 ** 2, edge_precision = 5, dx = 0.01e-9)
geometry_1550_upper = FunctionDefinedPolygon(func = upper_coupler_arm, initial_params = initial_params, bounds = bounds, z = 0.0, depth = 220e-9, eps_out = 1.44 ** 2, eps_in = 2.8 ** 2, edge_precision = 5, dx = 0.01e-9)
geometry_1550 = geometry_1550_lower * geometry_1550_upper
geometry_1310_lower = FunctionDefinedPolygon(func = lower_coupler_arm, initial_params = initial_params, bounds = bounds, z = 0.0, depth = 220e-9, eps_out = 1.44 ** 2, eps_in = 2.8 ** 2, edge_precision = 5, dx = 0.01e-9)
geometry_1310_upper = FunctionDefinedPolygon(func = upper_coupler_arm, initial_params = initial_params, bounds = bounds, z = 0.0, depth = 220e-9, eps_out = 1.44 ** 2, eps_in = 2.8 ** 2, edge_precision = 5, dx = 0.01e-9)
geometry_1310 = geometry_1310_lower * geometry_1310_upper

######## DEFINE FIGURE OF MERIT ########
# Although we are optimizing for the same thing, two separate fom objects must be create
# Although we are optimizing for the same thing, two separate fom objects must be created.

fom_1550=ModeMatch(modeorder=2,wavelength=1550e-9,monitor_name='fom_1550')
fom_1310=ModeMatch(modeorder=2,wavelength=1310e-9,monitor_name='fom_1310')
fom_1550 = ModeMatch(monitor_name = 'fom_1550', wavelengths = 1550e-9, mode_number = 2, direction = 'Forward')
fom_1310 = ModeMatch(monitor_name = 'fom_1310', wavelengths = 1310e-9, mode_number = 2, direction = 'Forward')

######## DEFINE OPTIMIZATION ALGORITHM ########
#For the optimizer, they should all be set the same, but different objects. Eventually this will be improved
optimizer_1550=ScipyOptimizers(max_iter=40)
optimizer_1310=ScipyOptimizers(max_iter=40)
optimizer_1550 = ScipyOptimizers(max_iter = 40)
optimizer_1310 = ScipyOptimizers(max_iter = 40)

######## PUT EVERYTHING TOGETHER ########
opt_1550=Optimization(base_script=script_1550,fom=fom_1550,geometry=geometry_1550,optimizer=optimizer_1550)
opt_1310=Optimization(base_script=script_1310,fom=fom_1310,geometry=geometry_1310,optimizer=optimizer_1310)

opt=opt_1550+opt_1310
opt_1550 = Optimization(base_script = script_1550, fom = fom_1550, geometry = geometry_1550, optimizer = optimizer_1550)
opt_1310 = Optimization(base_script = script_1310, fom = fom_1310, geometry = geometry_1310, optimizer = optimizer_1310)
opt = opt_1550 + opt_1310

######## RUN THE OPTIMIZER ########
opt.run()
62 changes: 30 additions & 32 deletions examples/Ysplitter/robust_coupler.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
""" Copyright chriskeraly
Copyright (c) 2019 Lumerical Inc. """

######## IMPORTS ########
# General purpose imports
import os
import numpy as np
from lumopt.optimization import Super_Optimization, Optimization
from lumopt.geometries.polygon import function_defined_Polygon
from lumopt.optimizers.generic_optimizers import ScipyOptimizers
import scipy as sp
from lumopt import CONFIG

from lumopt.figures_of_merit.modematch import ModeMatch
from lumopt.utilities.load_lumerical_scripts import load_from_lsf
import os
from lumopt import CONFIG
import scipy
from lumopt.geometries.polygon import FunctionDefinedPolygon
from lumopt.figures_of_merit.modematch import ModeMatch
from lumopt.optimizers.generic_optimizers import ScipyOptimizers
from lumopt.optimization import Optimization

######## DEFINE BASE SIMULATION ########

#Here I just use the same script for both simulations, but it's just to keep the example simple. You could use two
script_1=load_from_lsf(os.path.join(CONFIG['root'],'examples/Ysplitter/splitter_base_TE_modematch.lsf'))
script_2=load_from_lsf(os.path.join(CONFIG['root'],'examples/Ysplitter/splitter_base_TE_modematch_25nmoffset.lsf'))

# Use the same script for both simulations, but it's just to keep the example simple. You could use two.
script_1 = load_from_lsf(os.path.join(CONFIG['root'], 'examples/Ysplitter/splitter_base_TE_modematch.lsf'))
script_2 = load_from_lsf(os.path.join(CONFIG['root'], 'examples/Ysplitter/splitter_base_TE_modematch_25nmoffset.lsf'))

######## DEFINE OPTIMIZABLE GEOMETRY ########

## Here the two splitters just have a 25nm offset from each other, so that the result is robust
def taper_splitter_1(params,n_points=10):
points_x=np.concatenate(([-1.01e-6],np.linspace(-1.1e-6,0.9e-6,10),[1.01e-6]))
points_y=np.concatenate(([0.25e-6],params,[0.6e-6]))
points_x = np.concatenate(([-1.01e-6],np.linspace(-1.1e-6,0.9e-6,10),[1.01e-6]))
points_y = np.concatenate(([0.25e-6],params,[0.6e-6]))
n_interpolation_points=100
polygon_points_x = np.linspace(min(points_x), max(points_x), n_interpolation_points)
interpolator = scipy.interpolate.interp1d(points_x, points_y, kind='cubic')
interpolator = sp.interpolate.interp1d(points_x, points_y, kind='cubic')
polygon_points_y = [max(min(point,1e-6),-1e-6) for point in interpolator(polygon_points_x)]

polygon_points_up = [(x, y) for x, y in zip(polygon_points_x, polygon_points_y)]
Expand All @@ -36,11 +38,11 @@ def taper_splitter_1(params,n_points=10):

dx=25e-9
def taper_splitter_2(params,n_points=10):
points_x=np.concatenate(([-1.01e-6],np.linspace(-1.1e-6,0.9e-6,10),[1.01e-6]))
points_y=np.concatenate(([0.25e-6+dx],params+dx,[0.6e-6+dx]))
points_x = np.concatenate(([-1.01e-6],np.linspace(-1.1e-6,0.9e-6,10),[1.01e-6]))
points_y = np.concatenate(([0.25e-6+dx],params+dx,[0.6e-6+dx]))
n_interpolation_points=100
polygon_points_x = np.linspace(min(points_x), max(points_x), n_interpolation_points)
interpolator = scipy.interpolate.interp1d(points_x, points_y, kind='cubic')
interpolator = sp.interpolate.interp1d(points_x, points_y, kind='cubic')
polygon_points_y = [max(min(point,1e-6),-1e-6) for point in interpolator(polygon_points_x)]

polygon_points_up = [(x, y) for x, y in zip(polygon_points_x, polygon_points_y)]
Expand All @@ -49,30 +51,26 @@ def taper_splitter_2(params,n_points=10):
return polygon_points

bounds = [(0.2e-6, 1e-6)]*10

#final value from splitter_opt_2D.py optimization
initial_params=np.array([2.44788514e-07, 2.65915795e-07, 2.68748023e-07, 4.42233947e-07, 6.61232152e-07, 6.47561406e-07, 6.91473099e-07, 6.17511522e-07, 6.70669074e-07, 5.86141086e-07])

geometry_1 = function_defined_Polygon(func=taper_splitter_1,initial_params=initial_params,eps_out=1.44 ** 2, eps_in=2.8 ** 2,bounds=bounds,depth=220e-9,edge_precision=5)
geometry_2 = function_defined_Polygon(func=taper_splitter_2,initial_params=initial_params,eps_out=1.44 ** 2, eps_in=2.8 ** 2,bounds=bounds,depth=220e-9,edge_precision=5)

# final value from splitter_opt_2D.py optimization
initial_params = np.array([2.44788514e-07, 2.65915795e-07, 2.68748023e-07, 4.42233947e-07, 6.61232152e-07, 6.47561406e-07, 6.91473099e-07, 6.17511522e-07, 6.70669074e-07, 5.86141086e-07])
geometry_1 = FunctionDefinedPolygon(func = taper_splitter_1, initial_params = initial_params, bounds = bounds, z = 0.0, depth = 220e-9, eps_out = 1.44 ** 2, eps_in = 2.8 ** 2, edge_precision = 5, dx = 0.01e-9)
geometry_2 = FunctionDefinedPolygon(func = taper_splitter_2, initial_params = initial_params, bounds = bounds, z = 0.0, depth = 220e-9, eps_out = 1.44 ** 2, eps_in = 2.8 ** 2, edge_precision = 5, dx = 0.01e-9)

######## DEFINE FIGURE OF MERIT ########
# Although we are optimizing for the same thing, two separate fom objects must be create

fom_1=ModeMatch(modeorder=3)
fom_2=ModeMatch(modeorder=3)
fom_1 = ModeMatch(monitor_name = 'fom', wavelengths = 1550e-9, mode_number = 3, direction = 'Forward')
fom_2 = ModeMatch(monitor_name = 'fom', wavelengths = 1550e-9, mode_number = 3, direction = 'Forward')

######## DEFINE OPTIMIZATION ALGORITHM ########
#For the optimizer, they should all be set the same, but different objects. Eventually this will be improved
optimizer_1=ScipyOptimizers(max_iter=40)
optimizer_2=ScipyOptimizers(max_iter=40)
optimizer_1 = ScipyOptimizers(max_iter = 40)
optimizer_2 = ScipyOptimizers(max_iter = 40)

######## PUT EVERYTHING TOGETHER ########
opt_1=Optimization(base_script=script_1,fom=fom_1,geometry=geometry_1,optimizer=optimizer_1)
opt_2=Optimization(base_script=script_2,fom=fom_2,geometry=geometry_2,optimizer=optimizer_2)
opt_1 = Optimization(base_script = script_1, fom = fom_1, geometry = geometry_1, optimizer = optimizer_1)
opt_2 = Optimization(base_script = script_2, fom = fom_2, geometry = geometry_2, optimizer = optimizer_2)

opt=opt_1+opt_2
opt = opt_1 + opt_2

######## RUN THE OPTIMIZER ########
opt.run()
65 changes: 30 additions & 35 deletions examples/Ysplitter/splitter_opt_2D.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,63 @@
""" Copyright chriskeraly
Copyright (c) 2019 Lumerical Inc. """

######## IMPORTS ########
# General purpose imports
import numpy as np
import os
import scipy
import numpy as np
import scipy as sp
from lumopt import CONFIG

# Optimization specific imports
from lumopt.utilities.load_lumerical_scripts import load_from_lsf
from lumopt.geometries.polygon import function_defined_Polygon
from lumopt.geometries.polygon import FunctionDefinedPolygon
from lumopt.figures_of_merit.modematch import ModeMatch
from lumopt.optimization import Optimization
from lumopt.optimizers.generic_optimizers import ScipyOptimizers
from lumopt.optimization import Optimization

######## DEFINE BASE SIMULATION ########

script = load_from_lsf(os.path.join(CONFIG['root'], 'examples/Ysplitter/splitter_base_TE_modematch.lsf'))
base_script_file_name = os.path.join(CONFIG['root'], 'examples/Ysplitter/splitter_base_TE_modematch.lsf')
base_script = load_from_lsf(base_script_file_name)

######## DEFINE OPTIMIZABLE GEOMETRY ########
# The class FunctionDefinedPolygon needs a parameterized Polygon (with points ordered
# in a counter-clockwise direction). Here the geometry is defined by 10 parameters defining
# the knots of a spline, and the resulting Polygon has 200 edges, making it quite smooth.

# The class function_defined_Polygon needs a parameterized Polygon (with points ordered
# in a counter-clockwise direction. Here the geometry is defined by 10 parameters defining
# the knots of a spline, and the resulting Polygon has 200 edges, making it quite smooth

def taper_splitter(params=np.linspace(0.25e-6, 0.6e-6, 10)):
'''Just a taper where the paramaters are the y coordinates of the nodes of a cubic spline'''
points_x=np.concatenate(([-1.01e-6],np.linspace(-1e-6,1e-6,10),[1.01e-6]))
points_y=np.concatenate(([0.25e-6],params,[0.6e-6]))
n_interpolation_points=100
def taper_splitter(params = np.linspace(0.25e-6, 0.6e-6, 10)):
''' Defines a taper where the paramaters are the y coordinates of the nodes of a cubic spline. '''
points_x = np.concatenate(([-1.01e-6], np.linspace(-1e-6,1e-6,10), [1.01e-6]))
points_y = np.concatenate(([0.25e-6], params, [0.6e-6]))
n_interpolation_points = 100
polygon_points_x = np.linspace(min(points_x), max(points_x), n_interpolation_points)
interpolator = scipy.interpolate.interp1d(points_x, points_y, kind='cubic')
interpolator = sp.interpolate.interp1d(points_x, points_y, kind = 'cubic')
polygon_points_y = interpolator(polygon_points_x)
polygon_points_y = np.maximum(0.2e-6, (np.minimum(1e-6, polygon_points_y)))
polygon_points_up = [(x, y) for x, y in zip(polygon_points_x, polygon_points_y)]
polygon_points_down = [(x, -y) for x, y in zip(polygon_points_x, polygon_points_y)]
polygon_points = np.array(polygon_points_up[::-1] + polygon_points_down)
return polygon_points

# The geometry will pass on the bounds and initial parameters to the optimizer
# The geometry will pass on the bounds and initial parameters to the optimizer.
bounds = [(0.2e-6, 1e-6)]*10
inital_params=np.linspace(0.25e-6, 0.6e-6, 10)

geometry = function_defined_Polygon(func=taper_splitter, initial_params=inital_params,
eps_out=1.44 ** 2, eps_in=2.8 ** 2, bounds=bounds,
depth=220e-9,
edge_precision=5)
# We must define the permittivities of the material making the optimizable
# geometry and of that surrounding it. Since this is a 2D simulation, the depth has no importance.
# edge_precision defines the discretization of the edges forming the optimizable polygon. It should be set such
# that there are at least a few points per mesh cell. An effective index of 2.8 is user to simulate a 2D slab of
# 220 nm thick
inital_params = np.linspace(0.25e-6, 0.6e-6, 10)
# The permittivity of the material making the optimizable geometry and the permittivity of the material surrounding
# it must be defined. Since this is a 2D simulation, the depth has no importance. The edge precision defines the
# discretization of the edges forming the optimizable polygon. It should be set such there are at least a few points
# per mesh cell. An effective index of 2.8 is user to simulate a 2D slab of 220 nm thickness.
geometry = FunctionDefinedPolygon(func = taper_splitter, initial_params = inital_params, bounds = bounds, z = 0.0, depth = 220e-9, eps_out = 1.44 ** 2, eps_in = 2.8 ** 2, edge_precision = 5, dx = 0.01e-9)

######## DEFINE FIGURE OF MERIT ########

fom = ModeMatch(modeorder=3,monitor_name='fom',wavelength=1550e-9)
# The base simulation script defines a field monitor named 'fom' at the point where we want to
# modematch to the 3rd order mode (fundamental TE mode)
# The base simulation script defines a field monitor named 'fom' at the point where we want to modematch to the 3rd mode (fundamental TE mode).
fom = ModeMatch(monitor_name = 'fom', wavelengths = 1550e-9, mode_number = 3, direction = 'Forward')

######## DEFINE OPTIMIZATION ALGORITHM ########
optimizer = ScipyOptimizers(max_iter=30,method='L-BFGS-B',scaling_factor=1e6)
# This will run Scipy's implementation of the L-BFGS-B algoithm for at least 40 iterations. Since the variables are on the
# order of 1e-6, we scale them up to be on the order of 1
# order of 1e-6, thery are scale up to be on the order of 1.
optimizer = ScipyOptimizers(max_iter = 30, method = 'L-BFGS-B', scaling_factor = 1e6)

######## PUT EVERYTHING TOGETHER ########
opt = Optimization(base_script=script, fom=fom, geometry=geometry, optimizer=optimizer)
opt = Optimization(base_script = base_script, fom = fom, geometry = geometry, optimizer = optimizer)

######## RUN THE OPTIMIZER ########
opt.run()
Expand Down
Loading

0 comments on commit f10643b

Please sign in to comment.