SDKFrameworksAnsysLumericalPlanned

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:

  1. lumapi — Script your simulations in Python (available now)
  2. 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 TypeExtraction
S-parametersFull S-matrix vs frequency
Eye diagramsEye opening, jitter
BERBit error rate curves
Optical spectraPower vs wavelength
Circuit topologyComponent list and connections
Simulation timeRuntime 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

On this page