SDKFrameworksAnsysLumerical
Lumerical FDTD
Integrate OptixLog with Lumerical FDTD for 3D/2D nanophotonic simulations
Lumerical FDTD Integration
Lumerical FDTD is a high-performance 3D/2D Maxwell solver for nanophotonic device design. OptixLog helps you track simulations, compare designs, and collaborate with your team.
Two Integration Methods:
- lumapi — Script your simulations in Python (available now)
- Companion App — Sync from GUI with one click (coming soon)
lumapi Integration
Basic Example
import lumapi
import os
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
import numpy as np
from optixlog import Optixlog
def simulate_waveguide():
# Initialize OptixLog
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="FDTD_Simulations", create_if_not_exists=True)
# Simulation parameters
params = {
"waveguide_width": 0.5, # μm
"waveguide_height": 0.22, # μm
"wavelength_center": 1.55, # μm
"wavelength_span": 0.1, # μm
"mesh_accuracy": 3,
"simulation_time": 1000 # fs
}
run = project.run(
name="straight_waveguide",
config={
"tool": "Lumerical FDTD",
**params
}
)
with lumapi.FDTD() as fdtd:
# Create simulation region
fdtd.addfde()
fdtd.set("x", 0)
fdtd.set("y", 0)
fdtd.set("z", 0)
fdtd.set("x span", 10e-6)
fdtd.set("y span", 3e-6)
fdtd.set("z span", 2e-6)
fdtd.set("mesh accuracy", params["mesh_accuracy"])
fdtd.set("simulation time", params["simulation_time"] * 1e-15)
# Add waveguide
fdtd.addrect()
fdtd.set("name", "waveguide")
fdtd.set("x", 0)
fdtd.set("x span", 10e-6)
fdtd.set("y", 0)
fdtd.set("y span", params["waveguide_width"] * 1e-6)
fdtd.set("z", 0)
fdtd.set("z span", params["waveguide_height"] * 1e-6)
fdtd.set("material", "Si (Silicon) - Palik")
# Add mode source
fdtd.addmode()
fdtd.set("name", "source")
fdtd.set("injection axis", "x-axis")
fdtd.set("direction", "Forward")
fdtd.set("x", -4e-6)
fdtd.set("y", 0)
fdtd.set("y span", 2e-6)
fdtd.set("z", 0)
fdtd.set("z span", 1.5e-6)
fdtd.set("wavelength start", (params["wavelength_center"] - params["wavelength_span"]/2) * 1e-6)
fdtd.set("wavelength stop", (params["wavelength_center"] + params["wavelength_span"]/2) * 1e-6)
# Add transmission monitor
fdtd.addpower()
fdtd.set("name", "transmission")
fdtd.set("monitor type", "2D X-normal")
fdtd.set("x", 4e-6)
fdtd.set("y", 0)
fdtd.set("y span", 2e-6)
fdtd.set("z", 0)
fdtd.set("z span", 1.5e-6)
# Log pre-run info
run.log(step=0, phase="setup_complete")
# Run simulation
fdtd.run()
# Extract results
T = fdtd.getresult("transmission", "T")
wavelengths = T["lambda"].flatten() * 1e9 # Convert to nm
transmission = T["T"].flatten()
# Log metrics
run.log(step=1,
max_transmission=float(np.max(transmission)),
avg_transmission=float(np.mean(transmission)),
center_wavelength_nm=params["wavelength_center"] * 1000)
# Create and log plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(wavelengths, transmission, 'b-', linewidth=2)
ax.set_xlabel('Wavelength (nm)')
ax.set_ylabel('Transmission')
ax.set_title('Waveguide Transmission Spectrum')
ax.grid(True, alpha=0.3)
ax.set_ylim(0, 1.1)
run.log_matplotlib("transmission_spectrum", fig)
plt.close(fig)
run.log(step=2, simulation_completed=True)
print("✅ FDTD simulation complete!")
if __name__ == "__main__":
simulate_waveguide()Common FDTD Workflows
Ring Resonator Analysis
def simulate_ring_resonator():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="RingResonators", create_if_not_exists=True)
params = {
"ring_radius": 5.0, # μm
"waveguide_width": 0.5, # μm
"coupling_gap": 0.2, # μm
}
run = project.run(
name=f"ring_r{params['ring_radius']}_gap{params['coupling_gap']}",
config=params
)
with lumapi.FDTD() as fdtd:
fdtd.load("ring_resonator_template.fsp")
# Update parameters
fdtd.setnamed("ring", "inner radius", (params["ring_radius"] - params["waveguide_width"]/2) * 1e-6)
fdtd.setnamed("ring", "outer radius", (params["ring_radius"] + params["waveguide_width"]/2) * 1e-6)
fdtd.setnamed("bus", "y", (params["ring_radius"] + params["coupling_gap"] + params["waveguide_width"]/2) * 1e-6)
fdtd.run()
# Get through and drop transmission
T_through = fdtd.getresult("through", "T")
T_drop = fdtd.getresult("drop", "T")
wavelengths = T_through["lambda"].flatten() * 1e9
through = T_through["T"].flatten()
drop = T_drop["T"].flatten()
# Find resonances (transmission dips)
from scipy.signal import find_peaks
peaks, _ = find_peaks(-through, height=-0.5, distance=10)
resonance_wavelengths = wavelengths[peaks]
extinction_ratios = through[peaks]
# Log results
run.log(step=0,
num_resonances=len(peaks),
first_resonance_nm=float(resonance_wavelengths[0]) if len(peaks) > 0 else None,
max_extinction_dB=float(10 * np.log10(min(extinction_ratios))) if len(peaks) > 0 else None)
# Calculate FSR if multiple resonances
if len(resonance_wavelengths) >= 2:
fsr = np.diff(resonance_wavelengths)[0]
run.log(step=1, fsr_nm=float(fsr))
# Plot spectra
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
ax1.plot(wavelengths, through, 'b-', label='Through')
ax1.plot(wavelengths, drop, 'r-', label='Drop')
ax1.set_ylabel('Transmission')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.set_title('Ring Resonator Spectrum')
ax2.plot(wavelengths, 10*np.log10(through + 1e-10), 'b-', label='Through')
ax2.plot(wavelengths, 10*np.log10(drop + 1e-10), 'r-', label='Drop')
ax2.set_xlabel('Wavelength (nm)')
ax2.set_ylabel('Transmission (dB)')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
run.log_matplotlib("ring_spectrum", fig)
plt.close(fig)Field Profile Extraction
def extract_field_profiles():
with lumapi.FDTD() as fdtd:
fdtd.load("device.fsp")
fdtd.run()
# Get E-field data
E = fdtd.getresult("field_monitor", "E")
Ex = E["Ex"]
Ey = E["Ey"]
Ez = E["Ez"]
# Calculate intensity
intensity = np.abs(Ex)**2 + np.abs(Ey)**2 + np.abs(Ez)**2
# Get at center wavelength
wavelengths = E["lambda"].flatten()
center_idx = len(wavelengths) // 2
intensity_slice = intensity[:, :, 0, center_idx] # 2D slice
# Log field statistics
run.log(step=0,
max_intensity=float(np.max(intensity_slice)),
field_confinement=calculate_confinement(intensity_slice))
# Plot
fig, ax = plt.subplots(figsize=(10, 6))
im = ax.imshow(intensity_slice.T, cmap='hot', origin='lower')
ax.set_title(f'E-field Intensity at λ={wavelengths[center_idx]*1e9:.1f} nm')
plt.colorbar(im, ax=ax, label='|E|²')
run.log_matplotlib("field_profile", fig)
plt.close(fig)Parameter Sweeps
Wavelength Sweep
def wavelength_sweep():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="WavelengthSweeps", create_if_not_exists=True)
wavelengths = np.linspace(1.5, 1.6, 11) # μm
for wl in wavelengths:
run = project.run(
name=f"wl_{wl:.3f}um",
config={"wavelength": wl}
)
with lumapi.FDTD() as fdtd:
fdtd.load("device.fsp")
fdtd.setnamed("source", "wavelength start", wl * 1e-6)
fdtd.setnamed("source", "wavelength stop", wl * 1e-6)
fdtd.run()
T = fdtd.getresult("T_monitor", "T")
run.log(step=0, transmission=float(T["T"][0]))Geometry Sweep
def geometry_sweep():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="GeometrySweeps", create_if_not_exists=True)
widths = [0.4, 0.45, 0.5, 0.55, 0.6] # μm
gaps = [0.1, 0.15, 0.2, 0.25, 0.3] # μm
for width in widths:
for gap in gaps:
run = project.run(
name=f"w{width}_g{gap}",
config={"width": width, "gap": gap}
)
with lumapi.FDTD() as fdtd:
fdtd.load("coupler.fsp")
fdtd.setnamed("wg1", "y span", width * 1e-6)
fdtd.setnamed("wg2", "y span", width * 1e-6)
fdtd.setnamed("wg2", "y", (width + gap) * 1e-6)
fdtd.run()
coupling = fdtd.getresult("cross", "T")
run.log(step=0,
coupling_ratio=float(np.max(coupling["T"])),
width_um=width,
gap_um=gap)Companion App Integration (Planned)
Coming Soon — The OptixLog Companion App will enable GUI-based workflows without scripting.
How It Will Work
- Work normally in FDTD GUI — Design, mesh, simulate
- Save your project —
.fspfile saved to watched directory - Click "Sync" in Companion App — Results uploaded to OptixLog
Planned Configuration
# Companion app will detect FDTD projects
fdtd:
enabled: true
extract:
- monitors: ["transmission", "reflection", "field"]
- mesh_info: true
- simulation_time: true
file_pattern: "*.fsp"Data Extraction (Planned)
The companion app will automatically extract:
| Data Type | Extraction |
|---|---|
| Transmission spectra | From power monitors |
| Reflection spectra | From power monitors |
| Field profiles | From field monitors (optional) |
| Mesh statistics | Cell count, memory |
| Simulation time | Actual runtime |
| Convergence | Auto-shutoff status |
Tips for FDTD + OptixLog
1. Log Mesh Information
run.log(step=0,
mesh_cells_x=int(fdtd.getnamed("FDTD", "mesh cells x")),
mesh_cells_y=int(fdtd.getnamed("FDTD", "mesh cells y")),
mesh_cells_z=int(fdtd.getnamed("FDTD", "mesh cells z")),
memory_gb=float(fdtd.getnamed("FDTD", "memory")) / 1024)2. Track Convergence
# Check if auto-shutoff triggered
if fdtd.getnamed("FDTD", "simulation time") < fdtd.getnamed("FDTD", "stop time"):
run.log(step=1, early_shutoff=True,
final_time_fs=float(fdtd.getnamed("FDTD", "simulation time")) * 1e15)3. Save Lumerical Files to OptixLog
# Save and log the .fsp file
fdtd.save("simulation.fsp")
run.log_file("simulation_file", "simulation.fsp", "application/octet-stream")Next Steps
- MODE Integration — Waveguide mode analysis
- INTERCONNECT Integration — Circuit simulation
- Lumerical Overview — General lumapi setup