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:
A name
An
LumericalSimObjectType, that is, the type of object to create in Lumerical (e.g. power, profile, dipole)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 theProfileandPowerclasses)Source(which is further split intoDipoleSource,GaussianSource, andTFSFSource)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 LumericalSaving 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:
Load into the fdtd and run (only runs the currently loaded simulation)
Add it as a job
fdtd.addjob()and usefdtd.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