SDKFrameworksAnsysLumerical
Lumerical INTERCONNECT
Integrate OptixLog with Lumerical INTERCONNECT for photonic circuit simulation
Lumerical INTERCONNECT Integration
Lumerical INTERCONNECT is a photonic integrated circuit (PIC) simulator that models optical, electrical, and thermal behavior. OptixLog helps track circuit designs, compare architectures, and manage system-level optimizations.
Two Integration Methods:
- lumapi — Script your simulations in Python (available now)
- Companion App — Sync from GUI with one click (coming soon)
lumapi Integration
Basic Circuit Simulation
import lumapi
import os
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
import numpy as np
from optixlog import Optixlog
def simulate_mzi():
"""Simulate a Mach-Zehnder Interferometer."""
# Initialize OptixLog
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="INTERCONNECT_Circuits", create_if_not_exists=True)
# Parameters
params = {
"arm_length_diff": 50.0, # μm path difference
"coupler_ratio": 0.5, # 50/50 splitter
"wavelength_start": 1.5, # μm
"wavelength_stop": 1.6, # μm
"num_points": 1001
}
run = project.run(
name=f"mzi_dL{params['arm_length_diff']}um",
config={
"tool": "Lumerical INTERCONNECT",
"circuit": "MZI",
**params
}
)
with lumapi.INTERCONNECT() as ic:
# Create optical input
ic.addcwlaser("laser")
ic.set("frequency", 193.1e12) # ~1550 nm
ic.set("power", 1e-3) # 1 mW
# Add ONA (Optical Network Analyzer) for frequency sweep
ic.addona("ONA_1")
ic.set("analysis type", "scattering data")
ic.set("input parameter", "start and stop")
ic.set("start frequency", 3e8 / (params["wavelength_stop"] * 1e-6))
ic.set("stop frequency", 3e8 / (params["wavelength_start"] * 1e-6))
ic.set("number of points", params["num_points"])
# Add Y-branch splitter
ic.addelement("Y Branch")
ic.set("name", "splitter")
# Add waveguides for MZI arms
ic.addwaveguide("arm1")
ic.set("length", 100e-6)
ic.addwaveguide("arm2")
ic.set("length", (100 + params["arm_length_diff"]) * 1e-6)
# Add Y-branch combiner
ic.addelement("Y Branch")
ic.set("name", "combiner")
# Connect elements
ic.connect("ONA_1", "output", "splitter", "port 1")
ic.connect("splitter", "port 2", "arm1", "port 1")
ic.connect("splitter", "port 3", "arm2", "port 1")
ic.connect("arm1", "port 2", "combiner", "port 2")
ic.connect("arm2", "port 2", "combiner", "port 3")
ic.connect("combiner", "port 1", "ONA_1", "input")
# Run simulation
ic.run()
# Get results
result = ic.getresult("ONA_1", "input 1/mode 1/transmission")
wavelengths = 3e8 / result["frequency"] * 1e9 # Convert to nm
transmission = np.abs(result["transmission"])**2
# Find extinction ratio
max_T = np.max(transmission)
min_T = np.min(transmission)
extinction_ratio_dB = 10 * np.log10(max_T / min_T)
# Find FSR
from scipy.signal import find_peaks
peaks, _ = find_peaks(transmission, distance=50)
if len(peaks) >= 2:
fsr = np.abs(wavelengths[peaks[0]] - wavelengths[peaks[1]])
else:
fsr = None
# Log metrics
run.log(step=0,
max_transmission=float(max_T),
min_transmission=float(min_T),
extinction_ratio_dB=float(extinction_ratio_dB),
fsr_nm=float(fsr) if fsr else None)
# Plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(wavelengths, 10 * np.log10(transmission + 1e-10), 'b-')
ax.set_xlabel('Wavelength (nm)')
ax.set_ylabel('Transmission (dB)')
ax.set_title(f'MZI Response (ΔL = {params["arm_length_diff"]} μm)')
ax.grid(True, alpha=0.3)
run.log_matplotlib("mzi_response", fig)
plt.close(fig)
run.log(step=1, simulation_complete=True)
print("✅ INTERCONNECT simulation complete!")
if __name__ == "__main__":
simulate_mzi()Common INTERCONNECT Workflows
Ring Resonator Circuit
def simulate_ring_filter():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="RingFilters", create_if_not_exists=True)
params = {
"ring_radius": 10.0, # μm
"coupling_coeff": 0.1, # Power coupling
"round_trip_loss": 0.01 # dB
}
run = project.run(
name=f"ring_r{params['ring_radius']}um",
config=params
)
with lumapi.INTERCONNECT() as ic:
# Load ring resonator template
ic.load("ring_filter.icp")
# Set parameters
ic.setnamed("ring", "radius", params["ring_radius"] * 1e-6)
ic.setnamed("coupler", "coupling coefficient", params["coupling_coeff"])
ic.setnamed("ring_wg", "loss", params["round_trip_loss"])
ic.run()
# Get through and drop responses
through = ic.getresult("ONA", "input 1/mode 1/transmission")
drop = ic.getresult("ONA", "input 1/mode 1/reflection")
wavelengths = 3e8 / through["frequency"] * 1e9
T_through = np.abs(through["transmission"])**2
T_drop = np.abs(drop["reflection"])**2 if "reflection" in drop else np.zeros_like(T_through)
# Find resonances
from scipy.signal import find_peaks
dips, _ = find_peaks(-T_through, height=-0.5)
run.log(step=0,
num_resonances=len(dips),
max_extinction_dB=float(-10 * np.log10(np.min(T_through) + 1e-10)))
# Plot
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
ax1.plot(wavelengths, T_through, 'b-', label='Through')
ax1.plot(wavelengths, T_drop, 'r-', label='Drop')
ax1.set_ylabel('Transmission')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax2.plot(wavelengths, 10*np.log10(T_through + 1e-10), 'b-', label='Through')
ax2.plot(wavelengths, 10*np.log10(T_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_filter_response", fig)
plt.close(fig)WDM System Analysis
def simulate_wdm_system():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="WDM_Systems", create_if_not_exists=True)
params = {
"num_channels": 4,
"channel_spacing_ghz": 100, # 100 GHz spacing
"center_wavelength": 1550, # nm
"data_rate_gbps": 10
}
run = project.run(
name=f"wdm_{params['num_channels']}ch",
config=params
)
with lumapi.INTERCONNECT() as ic:
ic.load("wdm_template.icp")
# Configure channels
for i in range(params["num_channels"]):
freq_offset = (i - params["num_channels"]/2 + 0.5) * params["channel_spacing_ghz"] * 1e9
center_freq = 3e8 / (params["center_wavelength"] * 1e-9)
ic.setnamed(f"tx_{i}", "frequency", center_freq + freq_offset)
ic.setnamed(f"tx_{i}", "data rate", params["data_rate_gbps"] * 1e9)
ic.run()
# Analyze each channel
for i in range(params["num_channels"]):
ber = ic.getresult(f"ber_analyzer_{i}", "BER")
eye = ic.getresult(f"eye_diagram_{i}", "eye diagram")
run.log(step=i,
channel=i,
ber=float(ber["BER"]) if ber["BER"] > 0 else 1e-40,
eye_opening=float(eye["eye opening"]))
run.log(step=params["num_channels"], system_complete=True)Transceiver Design
def simulate_transceiver():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="TransceiverDesign", create_if_not_exists=True)
run = project.run(
name="100g_pam4_transceiver",
config={
"modulation": "PAM4",
"data_rate": 100, # Gbps
"link_length": 2 # km
}
)
with lumapi.INTERCONNECT() as ic:
ic.load("transceiver_100g.icp")
ic.run()
# Get eye diagrams
eye_tx = ic.getresult("eye_tx", "eye diagram")
eye_rx = ic.getresult("eye_rx", "eye diagram")
# Get BER
ber = ic.getresult("ber_tester", "BER")
run.log(step=0,
tx_eye_opening=float(eye_tx["eye opening"]),
rx_eye_opening=float(eye_rx["eye opening"]),
ber=float(ber["BER"]))
# Log eye diagram images
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# ... plot eye diagrams
run.log_matplotlib("eye_diagrams", fig)
plt.close(fig)Companion App Integration (Planned)
Coming Soon — The OptixLog Companion App will enable GUI-based workflows.
Planned Configuration
interconnect:
enabled: true
extract:
- s_parameters: true
- eye_diagrams: true
- ber_results: true
- optical_spectra: true
file_pattern: "*.icp"Automatic Extraction (Planned)
| Data Type | Extraction |
|---|---|
| S-parameters | Full S-matrix vs frequency |
| Eye diagrams | Eye opening, jitter |
| BER | Bit error rate curves |
| Optical spectra | Power vs wavelength |
| Circuit topology | Component list and connections |
| Simulation time | Runtime statistics |
Parameter Sweeps
Coupler Ratio Optimization
def optimize_coupler():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="CouplerOptimization", create_if_not_exists=True)
coupling_coeffs = np.linspace(0.05, 0.5, 10)
results = []
for k in coupling_coeffs:
run = project.run(
name=f"coupler_k{k:.2f}",
config={"coupling_coefficient": k}
)
with lumapi.INTERCONNECT() as ic:
ic.load("coupler_test.icp")
ic.setnamed("coupler", "coupling coefficient", k)
ic.run()
result = ic.getresult("ONA", "input 1/mode 1/transmission")
transmission = np.abs(result["transmission"])**2
max_T = float(np.max(transmission))
run.log(step=0, coupling_coeff=k, max_transmission=max_T)
results.append({"k": k, "T": max_T})
# Find optimal
optimal_k = max(results, key=lambda x: x["T"])["k"]
summary_run = project.run(name="coupler_sweep_summary", config={"sweep_type": "coupling"})
summary_run.log(step=0, optimal_coupling=optimal_k)Tips for INTERCONNECT + OptixLog
1. Log Circuit Topology
# Log component list
components = ic.getresult("::Root Element", "elements")
run.log(step=0,
num_components=len(components),
circuit_topology=str(components))2. Handle Time-Domain vs Frequency-Domain
# Check simulation mode
sim_mode = ic.get("simulation mode")
if sim_mode == "frequency domain":
# Get S-parameters
S = ic.getresult("ONA", "S-parameters")
else:
# Get time-domain signals
signal = ic.getresult("scope", "signal")3. Save Circuit Files
ic.save("optimized_circuit.icp")
run.log_file("circuit_file", "optimized_circuit.icp", "application/octet-stream")Next Steps
- CML Compiler — Compact model generation
- MODE Integration — Feed MODE results to INTERCONNECT
- Multiphysics — Thermal and electrical effects