Getting started with lumopt2: L-bend#

This example demonstrates using lumopt2 to optimize an L-bend waveguide coupler.

L-bend project schematic

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
L-bend 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
L-bend 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()
L-bend initial project

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.

L-bend final visualizer output

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.

lumopt2 user guide

Reference for key concepts in lumopt2 in further detail.

Introduction to photonic inverse design with lumopt2
lumopt2 API reference

Full API reference for lumopt2, including all available classes and functions.

lumopt2