Getting started with lumopt2: L-bend#
This example demonstrates using lumopt2 to optimize an L-bend waveguide coupler.
This example uses the closed curve parametrization approach, typically used for photonic integrated circuit applications, and demonstrates the use of a Python callable function for setup, and callbacks to customize the visualization.
For a more basic example demonstrating lumopt2 workflow, see the simple metalens example.
The Python script associated with this example is attached to the article.
Base geometry#
In this example, the base simulation is set up using a Python function, which defines the simulation region, optical ports, source settings, and monitor settings. This function is passed later to the project setup.
The function also sets up the geometry of the fixed input and output straight waveguides; however, the actual bend geometry to be optimized is defined by the ClosedCurve class, as explained later.
35def generate_base_sim(fdtd):
36 fdtd.addfdtd({"x min":fdtd_min_x-fdtd_buffer, "x max":fdtd_max_x+fdtd_buffer, "y min":fdtd_min_y-fdtd_buffer,
37 "y max":fdtd_max_y+fdtd_buffer, "z span":fdtd_span_z+2*fdtd_buffer, "index": n_bg,
38 "mesh accuracy": 3, "mesh refinement": "precise volume average"})
39
40 # Input waveguides (horizontal - extending beyond crossing region)
41 fdtd.addrect({"name": "wg_in", "index": n_wg, "x min": 2*fdtd_min_x, "x max":-bend_start, "y":0, "y span":wg_width, "z span": wg_height})
42 fdtd.addrect({"name": "wg_out", "index": n_wg, "y min": bend_start, "y max": 2*fdtd_max_y, "x":0, "x span":wg_width, "z span": wg_height})
43
44 # Ports
45 fdtd.addport({"name": "port_in"})
46 fdtd.set("injection axis","x")
47 fdtd.set({"x":-bend_start-dist_to_wall/2., "y":0, "y span":mode_width, "z span":mode_height, "direction":"Forward", "frequency dependent profile":False})
48
49 fdtd.addport({"name": "port_out"})
50 fdtd.set("injection axis","y")
51 fdtd.set({"y": bend_start+dist_to_wall/2., "x":0, "x span":mode_width, "z span":mode_height, "direction":"Backward", "frequency dependent profile":False})
52
53 # Source and monitor properties
54 fdtd.setglobalsource("wavelength start", wavelengths[0])
55 fdtd.setglobalsource("wavelength stop", wavelengths[-1])
56 fdtd.setglobalmonitor("frequency points", len(wavelengths))
57 fdtd.setglobalmonitor("use wavelength spacing", True)
58 fdtd.setnamed("FDTD::ports","override global monitor settings",False)
For the L-bend geometry, first construct a closed path as a list of Segment class instances.
Each Segment object is described by the (x, y) coordinates of the start point and the type of segment (linear for straight lines and cubic for a cubic polynomial).
The end point of each is segment is assumed to be the start point of the next; in the special case of the last segment in the list, the end point is the start of the first one.
The segments are connected ensuring both continuity of the curve and its first derivative to obtain a smooth 2D curve.
68path = [ (lmpt.Segment([ fdtd_min_x, wg_width/2], 'linear')), # Segment 1
69 (lmpt.Segment([-wg_width/2-bend_radius, wg_width/2], 'cubic')), # Segment 2 (outer sidewall, parametric)
70 (lmpt.Segment([-wg_width/2, wg_width/2+bend_radius], 'linear')), # Segment 3
71 (lmpt.Segment([-wg_width/2, fdtd_max_y], 'linear')), # Segment 4
72 (lmpt.Segment([ wg_width/2, fdtd_max_y], 'linear')), # Segment 5
73 (lmpt.Segment([ wg_width/2, wg_width/2+bend_radius], 'cubic')), # Segment 6 (inner sidewall, parametric)
74 (lmpt.Segment([-wg_width/2-bend_radius, -wg_width/2], 'linear')), # Segment 7
75 (lmpt.Segment([ fdtd_min_x, -wg_width/2], 'linear')), # Segment 8
76 ]
After defining the path, pass it to the ClosedCurve class to create the object, passing in along with other variables including the refractive index and thickness.
The optimization region must be passed to the ClosedCurve class as well since it is an input for the Parametrization class later on.
81optimization_region = lmpt.Box(x_min=fdtd_min_x, x_max=fdtd_max_x,
82 y_min=fdtd_min_y, y_max=fdtd_max_y,
83 z_min=-wg_height/2.0, z_max=wg_height/2.0,
84 mesh_size=mesh_size)
85
86# Create base geometry using ClosedCurve
87closed_curve = lmpt.ClosedCurve(path, optimization_region=optimization_region, index=n_wg, z_min=-wg_height/2.0, z_max= wg_height/2.0)
At this point, the geometry is set up as a fixed L-bend, and you can visualize it using ClosedCurve.plot() to ensure that the shape is as expected.
88closed_curve.plot() # Visualize the base geometry
Parametrization#
For parametrization of the L-bend, you can use the Parametrize class, which allows you to directly select the segment index to parametrize.
When creating this class, you specify a segment to parametrize, and a number of added vertices, which are allowed to move. In addition, you also specify the bounds and the direction of movement. Here the "normal" movement option restricts movement to the normal of the curve.
90## CLOSED CURVE - PARAMETRIZATION ##
91num_pts_per_curve = 2 # Number of control points to optimize for each of the two curved segments
92num_params = 2 * num_pts_per_curve # Total number of parameters
93
94# Each control point is allowed to slide along the local outward normal between
95# bounds[0] and bounds[1]. The asymmetric range gives the optimizer more room
96# to bow the silicon outward (positive direction) than to carve into it.
97bounds = (-200e-9, 400e-9)
98segments_to_parametrize = [lmpt.Parametrize(segment_index=2, num_added_vertices=num_pts_per_curve, bounds=bounds, movement='normal'), # Outer sidewall
99 lmpt.Parametrize(segment_index=6, num_added_vertices=num_pts_per_curve, bounds=bounds, movement='normal')] # Inner sidewall
After entering the settings, use the ClosedCurve.make_segments_parametric to finalize the parametric segments.
99closed_curve.make_segments_parametric(segments_to_parametrize)
After parametrization, you can see the added vertices using ClosedCurve.plot().
101closed_curve.plot() # Visualize the base geometry
Tip
In this example, the parametrization is set up such that each vertex can move independently of each other. You can also enforce symmetry by linking the movement of the vertices. For an example, please see the parametrization page.
Figure of merit#
For this example, the figure of merit is set up using the PortResults class, which takes in the name of a port object, metric, and target wavelengths.
In this example, we aim to maximize the transmission for the full O-band using a second order P-norm using PNorm(), with a target transmission of 1, which calculates the figure of merit based on \(1-\sqrt{\text{mean}((|T(\lambda)|-1)^2)}\).
109port_out = lmpt.PortResults('port_out', metric='transmission', wavelengths=wavelengths)
110l_bend_fom = lmpt.Fom(port_out, fct=lmpt.PNorm(p=2,target=1.0))
Project configuration#
The definition of the base geometry, parametrization, and figure of merit are passed to the lumopt2.core.project.Project class.
You can also include the lumopt2.core.fdtd_session.FdtdSession and lumopt2.utils.runner.LocalRunner classes if non-default settings needed. See the simple metalens example for more details.
118project = lmpt.Project(setup=generate_base_sim, parametrization=closed_curve, fom=l_bend_fom, fdtd_session=fdtd_session)
At this point, you can open the project to ensure the set up is correct by calling Project.visualize_fom().
The initial figure of merit value is also printed in the terminal.
119project.visualize_fom()
Optimizer#
For this example, the optimization is ran with the ScipyOptimizer class with the default L-BFGS-B method and a maximum of 10 iterations.
123optimizer = lmpt.ScipyOptimizer(method='L-BFGS-B', max_iter=10)
Visualization and logging#
To aid visualization and logging for this optimization, this example uses the built-in callback functions to plot the geometry, the figure of merit, the gradient norm, and a monitor result for each iteration of the optimization.
The visualizer is initialized using the GraphicalVisualizer class, with specific panels for each subplot.
128visualizer = lmpt.GraphicalVisualizer(
129 figsize=(7, 7),
130 layout=(2, 2),
131 panels=[ lmpt.FomPanel(),
132 lmpt.GradientNormPanel(),
133 lmpt.GeometryPanel(),
134 lmpt.MonitorPanel( monitor_name='FDTD::ports::port_out',
135 result_name='T',
136 operation='abs',
137 title='Output transmission',
138 ),
139 ],
140)
After specifying the visualizer, pass it as a callback function to the optimization object. In addition to the visualizer, a FileLogger is also included.
143optimization = lmpt.Optimization(
144 project=project,
145 optimizer=optimizer,
146 callbacks=[visualizer, lmpt.FileLogger()],
147)
Results#
The simulation is run by calling the optimization.run() method, with all previous settings combined into the Optimization project.
The final visualizer output is shown below.
After the simulation completes, export the final simulation file using the Project.save_project() method.
In this example, the final project file is saved as L_bend_optimization_final.fsp in the optimization directory. This project file can then be used for further analysis or exporting for fabrication.
153best_params, best_fom = result
154project.save_project("L_bend_optimization_final.fsp",params=best_params)
Tip
See the Lumerical Knowledge Base article Importing and exporting GDSII files for more information on exporting a GDS file from the final project.
Further resources#
After completing this example, further explore lumopt2 using the following pages.
Reference for key concepts in lumopt2 in further detail.
Full API reference for lumopt2, including all available classes and functions.