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.

36def generate_base_sim(fdtd):
37    fdtd.addfdtd({"x min":fdtd_min_x-fdtd_buffer, "x max":fdtd_max_x+fdtd_buffer, "y min":fdtd_min_y-fdtd_buffer,
38                "y max":fdtd_max_y+fdtd_buffer, "z span":fdtd_span_z+2*fdtd_buffer, "index": n_bg,
39                "mesh accuracy": 3, "mesh refinement": "precise volume average"})
40
41    # Input waveguides (horizontal - extending beyond crossing region)
42    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})
43    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})
44
45    # Ports
46    fdtd.addport({"name": "port_in"})
47    fdtd.set("injection axis","x")
48    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})
49
50    fdtd.addport({"name": "port_out"})
51    fdtd.set("injection axis","y")
52    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})
53
54    # Source and monitor properties
55    fdtd.setglobalsource("wavelength start", wavelengths[0])
56    fdtd.setglobalsource("wavelength stop", wavelengths[-1])
57    fdtd.setglobalmonitor("frequency points", len(wavelengths))
58    fdtd.setglobalmonitor("use wavelength spacing", True)
59    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.

69path = [ (lmpt.Segment([ fdtd_min_x,              wg_width/2],             'linear')),  # Segment 1
70         (lmpt.Segment([-wg_width/2-bend_radius,  wg_width/2],             'cubic')),   # Segment 2 (outer sidewall, parametric)
71         (lmpt.Segment([-wg_width/2,              wg_width/2+bend_radius], 'linear')),  # Segment 3
72         (lmpt.Segment([-wg_width/2,              fdtd_max_y],             'linear')),  # Segment 4
73         (lmpt.Segment([ wg_width/2,              fdtd_max_y],             'linear')),  # Segment 5
74         (lmpt.Segment([ wg_width/2,              wg_width/2+bend_radius], 'cubic')),   # Segment 6 (inner sidewall, parametric)
75         (lmpt.Segment([-wg_width/2-bend_radius, -wg_width/2],             'linear')),  # Segment 7
76         (lmpt.Segment([ fdtd_min_x,             -wg_width/2],             'linear')),  # Segment 8
77       ]

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.

82optimization_region = lmpt.Box(x_min=fdtd_min_x, x_max=fdtd_max_x,
83                               y_min=fdtd_min_y, y_max=fdtd_max_y,
84                               z_min=-wg_height/2.0, z_max=wg_height/2.0,
85                               mesh_size=mesh_size)
86
87# Create base geometry using ClosedCurve
88closed_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.

89closed_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.

 91## CLOSED CURVE - PARAMETRIZATION ##
 92num_pts_per_curve = 2                      # Number of control points to optimize for each of the two curved segments
 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.
 97# Here, positive movement of vertex means an expansion of the waveguide, and negative movement means a contraction of the waveguide.
 98bounds = (-200e-9, 400e-9)
 99segments_to_parametrize = [lmpt.Parametrize(segment_index=2, num_added_vertices=num_pts_per_curve, bounds=bounds, movement='normal'),  # Outer sidewall
100                           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.

Tip

Set the movement to both to instead allow the vertices to move in Cartesian directions. See Parametrize for more information.

101closed_curve.make_segments_parametric(segments_to_parametrize)

After parametrization, you can see the added vertices using ClosedCurve.plot().

103closed_curve.plot() # Visualize the geometry with parametric segments and control points
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)}\).

111port_out = lmpt.PortResults('port_out', metric='transmission', wavelengths=wavelengths)
112l_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. This example uses the default GPU local runner, which uses the first GPU resource enabled in the FDTD Resource Configuration. To use the CPU, pass in a LocalRunner class with the argument "CPU". See the simple metalens example for more details. You can also include the lumopt2.core.fdtd_session.FdtdSession and lumopt2.utils.runner.LocalRunner classes if non-default settings needed.

120project = lmpt.Project(setup=generate_base_sim,
121                       parametrization=closed_curve,
122                       fom=l_bend_fom,
123                       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. Press enter to continue with the optimization after confirming the set up and values on screen.

124project.visualize_fom()

Tip

Validation functions in lumopt2 typically prompt you to press Enter to continue, giving you a chance to check the results before continuing.

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.

128optimizer = 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.

133visualizer = lmpt.GraphicalVisualizer(
134    figsize=(7, 7),
135    layout=(2, 2),
136    panels=[ lmpt.FomPanel(),
137             lmpt.GradientNormPanel(),
138             lmpt.GeometryPanel(),
139             lmpt.MonitorPanel( monitor_name='FDTD::ports::port_out',
140                                result_name='T',
141                                operation='abs',
142                           title='Output transmission',
143        ),
144    ],
145)

After specifying the visualizer, pass it as a callback function to the optimization object. In addition to the visualizer, a FileLogger is also included.

148optimization = lmpt.Optimization(
149    project=project,
150    optimizer=optimizer,
151    callbacks=[visualizer, lmpt.FileLogger()],
152)

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.

158best_params, best_fom = result
159project.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