Simulations in vipdopt

This notebook serves as an interactive guide to creating simulations in vipdopt to interface directly with the Lumerical FDTD solver. First run the below code to do the necessary setup and imports.

[2]:
# imports
from pathlib import Path
import sys

import numpy as np

np.set_printoptions(threshold=100)

# Get vipdopt directory path from Notebook
parent_dir = str(Path().resolve().parents[2])

# Add to sys.path
sys.path.insert(0, parent_dir)

# Imports from vipdopt
from vipdopt.simulation import LumericalSimulation, LumericalSimObject, Monitor, Source, Import, LumericalFDTD

Creating a New Simulation

To create a simulation we must first instantiate a LumericalSimulation object. You can also load a simulation from an appropiately formatted JSON file.

[3]:
sim = LumericalSimulation()

loaded_sim = LumericalSimulation(source='simulation_example.json')

Simulation Objects

Once a simulation object has been created, it can be populated with objects. Simulation objects require three things:

  1. A name

  2. An LumericalSimObjectType, that is, the type of object to create in Lumerical (e.g. power, profile, dipole)

  3. Properties, the values that define the object

The properties can be provided upon creation of the object, or manipulated after the fact.

[ ]:
# Here we create an object through our simulation
props = {
    'x' : 0.0,
    'y' : 0.4e-6,
    'monitor type' : 'linear x',
}

power = sim.new_object('power', 'power', properties=props)

# You can also create a standalone object and add it to your simulation

source_1 = LumericalSimObject('source_1', 'dipole')
sim.add_object(source_1)

print(sim.objects)

Simulation objects can be edited with the update method, which works just like that of the builtin dict in Python. Simulation objects can also be edited using dictionary-like access to individual properties.

[ ]:
# Using the update method
source_props = {
    'theta': 90,
}

source_1.update(x=0, y=0)
source_1.update(**source_props)  # You can also unpack a dictionary

# The simulation's update_object method works in a similar fashion
sim.update_object('source_1', phi=0)

source_1
[ ]:
# Using dictionary access to change parameters and add new ones
source_1['theta'] = 45
source_1['z'] = 0

source_1

Simulation Object Subclasses

Because the different object types have unique properties and data from simulations, there are also subclasses of LumericalSimObject that better cater to their specific needs. These include:

  • Monitor (which is further split into the Profile and Power classes)

  • Source (which is further split into DipoleSource, GaussianSource, and TFSFSource)

  • Import

Monitor Class

The Monitor class has additional methods and attributes for accessing data from a previously ran simulation. In Lumerical, profile and power monitors measure specific data:

  • E field

  • H field

  • Poynting vector

  • transmission

  • power

  • source power

When a Monitor is first created, these values are all set to None.

[ ]:
mon = Monitor('mon', 'power')

print(mon.e)  # will be None by default

Once a simulation is run, files containing each monitor’s data can be generated. Monitor objects will dynamically draw data from these files as needed, and repalce its data with placeholder values (None) when no longer needed, so as to save memory.

[ ]:
# Suppose simulation 'sim' was run and saved data to 'sim.fsp'
# The data from mon1 would be saved to 'sim_mon1.npz'

mon.set_source('sim_mon.npz')  # Link our monitor to it's data file

print(mon.e)  # No longer None

# Say we wanted the average absolute value of the electric field
value = np.abs(mon.e).mean()

# We no longer need the field in memory so we can clear it out; `value` remains the same
mon.reset()

print(f'The mean value of |E| is: {value}')

Source Class

The Source class is made to represent the various light sources in a simulation. The main addition to the Source class is that it is hashable, so it can be used as a key in dictionaries.

The Source class is currently required for creating figures of merit (see more here)

[ ]:
src1 = Source('src1', 'gaussian')
src2 = Source('src2', 'dipole')

# A dictionary that maps Sources to their type
source_to_type = {src: src.obj_type for src in [src1, src2]}
print(source_to_type)

Import Class

The Import class mirrors the import primitive in Lumerical. It is used to create a 3D geometry. If you wish to use vipdopt for optimization purposes, you will need to include at least one Import in your simulation, as it is responsible for creating the design space for a device.

Import comes with two additional methods, set_nk2 and get_nk2 which mirrors Lumerical’s import_nk2 script command. These just set and retrieve refractive index values (n and k) over a volume (defined by x, y, z). These methods are mainly used by the LumericalOptimization to step a design and import those changes into Lumerical, and can mainly be ignored.

Simulation Configuration Files

Above, we showed how to create a simulation in a Python script by adding objects one by one. This process can become rather cumbersome for more complex simulations. Therefore, we provide a simpler way for creating simulations: a simulation configuration file.

A simulation configuration file is a JSON formatted file that contains all of the simulation objects. Every simulation object has the following format when serialized for JSON:

"name": "<object name>",
"obj_type": "<object type>",
"properties": {
    "prop1": "<prop1_value>",
    "prop2": "<prop2_value>",
}

The simulation file has an entry "objects" with a list containing each simulation object. There can also be an entry called "info" to provide additional information about a simulation, such as a name or the savefile path.

For an example, see simulation_example.json

[5]:
sim_file = 'simulation_example.json'

sim = LumericalSimulation(sim_file)

# Or alteratively,
sim = LumericalSimulation()
sim.load(sim_file)

print(sim)
{
    "objects": {
        "FDTD": {
            "name": "FDTD",
            "obj_type": "fdtd",
            "info": {
                "name": ""
            },
            "properties": {
                "dimension": "3D",
                "x span": 3.06e-06,
                "y span": 3.06e-06,
                "z max": 2.8049999999999994e-06,
                "z min": -2.2949999999999996e-06,
                "simulation time": 6.000000000000001e-13,
                "index": 1.5
            }
        },
        "forward_src_x": {
            "name": "forward_src_x",
            "obj_type": "gaussian",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "forward_src_x",
                "angle theta": 0.0,
                "angle phi": 0,
                "polarization angle": 0,
                "direction": "Backward",
                "x span": 6.12e-06,
                "injection axis": "z-axis",
                "y span": 6.12e-06,
                "z": 2.5499999999999997e-06,
                "x": 0.0,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07,
                "waist radius w0": 1e-06,
                "distance from waist": -5.099999999999998e-07
            }
        },
        "forward_src_y": {
            "name": "forward_src_y",
            "obj_type": "gaussian",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "forward_src_y",
                "angle theta": 0.0,
                "angle phi": 0,
                "polarization angle": 90,
                "direction": "Backward",
                "x span": 6.12e-06,
                "injection axis": "z-axis",
                "y span": 6.12e-06,
                "z": 2.5499999999999997e-06,
                "x": 0.0,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07,
                "waist radius w0": 1e-06,
                "distance from waist": -5.099999999999998e-07
            }
        },
        "adj_src_0x": {
            "name": "adj_src_0x",
            "obj_type": "dipole",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "adj_src_0x",
                "x": 5.1e-07,
                "y": 5.1e-07,
                "z": -1.5299999999999998e-06,
                "theta": 90,
                "phi": 0,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07
            }
        },
        "adj_src_0y": {
            "name": "adj_src_0y",
            "obj_type": "dipole",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "adj_src_0y",
                "x": 5.1e-07,
                "y": 5.1e-07,
                "z": -1.5299999999999998e-06,
                "theta": 90,
                "phi": 90,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07
            }
        },
        "adj_src_1x": {
            "name": "adj_src_1x",
            "obj_type": "dipole",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "adj_src_1x",
                "x": -5.1e-07,
                "y": 5.1e-07,
                "z": -1.5299999999999998e-06,
                "theta": 90,
                "phi": 0,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07
            }
        },
        "adj_src_1y": {
            "name": "adj_src_1y",
            "obj_type": "dipole",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "adj_src_1y",
                "x": -5.1e-07,
                "y": 5.1e-07,
                "z": -1.5299999999999998e-06,
                "theta": 90,
                "phi": 90,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07
            }
        },
        "adj_src_2x": {
            "name": "adj_src_2x",
            "obj_type": "dipole",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "adj_src_2x",
                "x": -5.1e-07,
                "y": -5.1e-07,
                "z": -1.5299999999999998e-06,
                "theta": 90,
                "phi": 0,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07
            }
        },
        "adj_src_2y": {
            "name": "adj_src_2y",
            "obj_type": "dipole",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "adj_src_2y",
                "x": -5.1e-07,
                "y": -5.1e-07,
                "z": -1.5299999999999998e-06,
                "theta": 90,
                "phi": 90,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07
            }
        },
        "adj_src_3x": {
            "name": "adj_src_3x",
            "obj_type": "dipole",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "adj_src_3x",
                "x": 5.1e-07,
                "y": -5.1e-07,
                "z": -1.5299999999999998e-06,
                "theta": 90,
                "phi": 0,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07
            }
        },
        "adj_src_3y": {
            "name": "adj_src_3y",
            "obj_type": "dipole",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "adj_src_3y",
                "x": 5.1e-07,
                "y": -5.1e-07,
                "z": -1.5299999999999998e-06,
                "theta": 90,
                "phi": 90,
                "wavelength start": 3.75e-07,
                "wavelength stop": 7.249999999999999e-07
            }
        },
        "focal_monitor_0": {
            "name": "focal_monitor_0",
            "obj_type": "power",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "focal_monitor_0",
                "monitor type": "point",
                "x": 5.1e-07,
                "y": 5.1e-07,
                "z": -1.5299999999999998e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "use source limits": 1,
                "frequency points": 60
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        },
        "focal_monitor_1": {
            "name": "focal_monitor_1",
            "obj_type": "power",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "focal_monitor_1",
                "monitor type": "point",
                "x": -5.1e-07,
                "y": 5.1e-07,
                "z": -1.5299999999999998e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "use source limits": 1,
                "frequency points": 60
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        },
        "focal_monitor_2": {
            "name": "focal_monitor_2",
            "obj_type": "power",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "focal_monitor_2",
                "monitor type": "point",
                "x": -5.1e-07,
                "y": -5.1e-07,
                "z": -1.5299999999999998e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "use source limits": 1,
                "frequency points": 60
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        },
        "focal_monitor_3": {
            "name": "focal_monitor_3",
            "obj_type": "power",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "focal_monitor_3",
                "monitor type": "point",
                "x": 5.1e-07,
                "y": -5.1e-07,
                "z": -1.5299999999999998e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "use source limits": 1,
                "frequency points": 60
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        },
        "transmission_monitor_0": {
            "name": "transmission_monitor_0",
            "obj_type": "power",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "transmission_monitor_0",
                "monitor type": "2D Z-normal",
                "y": 5.1e-07,
                "y span": 1.02e-06,
                "z": -1.5299999999999998e-06,
                "x": 5.1e-07,
                "x span": 1.02e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "use source limits": 1,
                "frequency points": 60
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        },
        "transmission_monitor_1": {
            "name": "transmission_monitor_1",
            "obj_type": "power",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "transmission_monitor_1",
                "monitor type": "2D Z-normal",
                "y": 5.1e-07,
                "y span": 1.02e-06,
                "z": -1.5299999999999998e-06,
                "x": -5.1e-07,
                "x span": 1.02e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "use source limits": 1,
                "frequency points": 60
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        },
        "transmission_monitor_2": {
            "name": "transmission_monitor_2",
            "obj_type": "power",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "transmission_monitor_2",
                "monitor type": "2D Z-normal",
                "y": -5.1e-07,
                "y span": 1.02e-06,
                "z": -1.5299999999999998e-06,
                "x": -5.1e-07,
                "x span": 1.02e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "use source limits": 1,
                "frequency points": 60
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        },
        "transmission_monitor_3": {
            "name": "transmission_monitor_3",
            "obj_type": "power",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "transmission_monitor_3",
                "monitor type": "2D Z-normal",
                "y": -5.1e-07,
                "y span": 1.02e-06,
                "z": -1.5299999999999998e-06,
                "x": 5.1e-07,
                "x span": 1.02e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "use source limits": 1,
                "frequency points": 60
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        },
        "transmission_focal_monitor_": {
            "name": "transmission_focal_monitor_",
            "obj_type": "power",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "transmission_focal_monitor_",
                "monitor type": "2D Z-normal",
                "y": 0,
                "y span": 2.04e-06,
                "z": -1.5299999999999998e-06,
                "x": 0,
                "x span": 2.04e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "use source limits": 1,
                "frequency points": 60
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        },
        "PEC_screen": {
            "name": "PEC_screen",
            "obj_type": "rect",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "PEC_screen",
                "x": 0,
                "x span": 3.5904e-06,
                "y": 0,
                "y span": 3.5904e-06,
                "z min": 2.193e-06,
                "z max": 2.346e-06,
                "material": "PEC (Perfect Electrical Conductor)"
            }
        },
        "source_aperture": {
            "name": "source_aperture",
            "obj_type": "rect",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "source_aperture",
                "x": 0,
                "x span": 2.04e-06,
                "y": 0,
                "y span": 2.04e-06,
                "z min": 2.193e-06,
                "z max": 2.346e-06,
                "index": 1.5
            }
        },
        "design_import": {
            "name": "design_import",
            "obj_type": "import",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "design_import",
                "x span": 2.04e-06,
                "y span": 2.04e-06,
                "z min": 0.0,
                "z max": 2.04e-06
            },
            "n": null,
            "x": [
                1.0
            ],
            "y": [
                1.0
            ],
            "z": [
                1.0
            ]
        },
        "design_mesh": {
            "name": "design_mesh",
            "obj_type": "mesh",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "design_mesh",
                "x": 0,
                "x span": 3.06e-06,
                "y": 0,
                "y span": 3.06e-06,
                "z min": -5e-07,
                "z max": 2.54e-06,
                "dx": 5.0999999999999993e-08,
                "dy": 5.0999999999999993e-08,
                "dz": 5.0999999999999993e-08
            }
        },
        "design_index_monitor": {
            "name": "design_index_monitor",
            "obj_type": "index",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "design_index_monitor",
                "x span": 2.04e-06,
                "monitor type": "3D",
                "y span": 2.04e-06,
                "z min": 0.0,
                "z max": 2.04e-06,
                "spatial interpolation": "nearest mesh cell"
            }
        },
        "design_efield_monitor": {
            "name": "design_efield_monitor",
            "obj_type": "profile",
            "info": {
                "name": ""
            },
            "properties": {
                "name": "design_efield_monitor",
                "x span": 2.04e-06,
                "monitor type": "3D",
                "y span": 2.04e-06,
                "z min": 0.0,
                "z max": 2.04e-06,
                "override global monitor settings": 1,
                "use wavelength spacing": 1,
                "frequency points": 60,
                "output Hx": 0,
                "output Hy": 0,
                "output Hz": 0
            },
            "src": null,
            "_tshape": null,
            "_fshape": null,
            "_e": null,
            "_h": null,
            "_p": null,
            "_t": null,
            "_sp": null,
            "_power": null,
            "_sync": false
        }
    }
}

Connecting to Lumerical FDTD

So far, all we’ve done is create a Python representation of a simulation, but to actually use it with Lumerical, we would typically use their provided API (lumapi). In vipdopt, a class LumericalFDTD is provided which effectively serves as a wrapper around lumapi functionality. It is responsible for the following:

  • Importing LumericalSimulation’s into Lumerical

  • Saving and running simulation jobs

  • Retrieving data from completed simulations

  • Managing resources to be used in running jobs

[6]:
# Creating the FDTD hook

fdtd = LumericalFDTD()
sim = LumericalSimulation('simulation_example.json')
sim_file = 'sim.fsp'  # Where Lumerical will save simulation data

fdtd.connect()  # This starts a Lumerical session

fdtd.load(path=None, sim=sim)  # Load simulation into Lumerical
fdtd.save(sim_file)  # Lumerical must have a file on the disk before running a job

There are two ways to run a simulation:

  1. Load into the fdtd and run (only runs the currently loaded simulation)

  2. Add it as a job fdtd.addjob() and use fdtd.runjobs()

[4]:
# Option 1
fdtd.run()
[7]:
# Option 2

fdtd.addjob(sim_file)  # Add the simulation file to the job queue
fdtd.runjobs()
[8]:
# Create individual data files for each Monitor
fdtd.reformat_monitor_data([sim])

fdtd.close()  # End Lumerical session