SDKFrameworksAnsysLumericalPlanned

Lumerical CML Compiler

Integrate OptixLog with Lumerical CML Compiler for compact model generation

Lumerical CML Compiler Integration

The Lumerical CML (Compact Model Library) Compiler generates foundry-qualified compact models from device-level simulations. OptixLog helps track model generation, compare model versions, and manage your PDK development workflow.

Two Integration Methods:

  1. lumapi — Script your model compilation in Python (available now)
  2. Companion App — Sync from GUI with one click (coming soon)

What is CML Compiler?

CML Compiler creates compact models for INTERCONNECT from:

  • FDTD simulations → S-parameter models
  • MODE simulations → Waveguide models
  • Custom data → Interpolated models

These models enable fast circuit simulation while maintaining physical accuracy.


lumapi Integration

Basic Model Generation

import lumapi
import os
import numpy as np
from optixlog import Optixlog


def generate_waveguide_cml():
    """Generate a compact waveguide model from MODE data."""
    
    # Initialize OptixLog
    client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
    project = client.project(name="CML_Models", create_if_not_exists=True)
    
    # Model parameters
    params = {
        "model_name": "strip_waveguide_500nm",
        "waveguide_width": 0.5,    # μm
        "waveguide_height": 0.22,  # μm
        "wavelength_range": [1.5, 1.6],  # μm
        "temperature_range": [20, 80],    # °C
    }
    
    run = project.run(
        name=f"cml_{params['model_name']}",
        config={
            "tool": "Lumerical CML Compiler",
            "model_type": "waveguide",
            **params
        }
    )
    
    # Step 1: Generate MODE data across parameter space
    wavelengths = np.linspace(params["wavelength_range"][0], 
                              params["wavelength_range"][1], 21)
    temperatures = np.linspace(params["temperature_range"][0],
                               params["temperature_range"][1], 5)
    
    neff_data = []
    ng_data = []
    
    with lumapi.MODE() as mode:
        mode.load("waveguide_template.lms")
        
        for temp in temperatures:
            neff_vs_wl = []
            ng_vs_wl = []
            
            for wl in wavelengths:
                # Update wavelength
                mode.set("wavelength", wl * 1e-6)
                
                # Apply temperature dependence (simplified)
                dn_dT = 1.86e-4  # Si thermo-optic coefficient
                mode.setnamed("waveguide", "index", 3.48 + dn_dT * (temp - 25))
                
                mode.findmodes()
                
                neff = mode.getdata("FDE::data::mode1", "neff")
                ng = mode.getdata("FDE::data::mode1", "ng")
                
                neff_vs_wl.append(float(np.real(neff)))
                ng_vs_wl.append(float(np.real(ng)))
            
            neff_data.append(neff_vs_wl)
            ng_data.append(ng_vs_wl)
            
            run.log(step=len(neff_data)-1,
                    temperature=temp,
                    neff_at_1550=float(neff_vs_wl[len(wavelengths)//2]))
    
    # Step 2: Create CML model
    with lumapi.CML() as cml:
        # Create new model
        cml.newmodel()
        cml.set("name", params["model_name"])
        cml.set("description", f"Strip waveguide {params['waveguide_width']} μm wide")
        
        # Add wavelength parameter
        cml.addparameter("wavelength")
        cml.set("type", "wavelength")
        cml.set("default", 1.55e-6)
        
        # Add temperature parameter
        cml.addparameter("temperature")
        cml.set("type", "temperature")
        cml.set("default", 25)
        cml.set("min", params["temperature_range"][0])
        cml.set("max", params["temperature_range"][1])
        
        # Add length parameter
        cml.addparameter("length")
        cml.set("type", "length")
        cml.set("default", 100e-6)
        
        # Set model data (simplified)
        cml.set("neff data", np.array(neff_data))
        cml.set("ng data", np.array(ng_data))
        cml.set("wavelength points", wavelengths * 1e-6)
        cml.set("temperature points", temperatures)
        
        # Compile model
        cml.compile()
        
        # Export
        model_path = f"{params['model_name']}.cml"
        cml.export(model_path)
        
        run.log_file("cml_model", model_path, "application/octet-stream")
        
    run.log(step=len(temperatures),
            model_generated=True,
            wavelength_points=len(wavelengths),
            temperature_points=len(temperatures))
    
    print(f"✅ CML model generated: {params['model_name']}")


if __name__ == "__main__":
    generate_waveguide_cml()

Common CML Workflows

S-Parameter Model from FDTD

def generate_spar_model():
    """Generate S-parameter model from FDTD simulation."""
    
    client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
    project = client.project(name="CML_SPar_Models", create_if_not_exists=True)
    
    run = project.run(
        name="directional_coupler_spar",
        config={
            "device": "directional_coupler",
            "coupling_length": 10.0,  # μm
            "gap": 0.2  # μm
        }
    )
    
    # Run FDTD to get S-parameters
    with lumapi.FDTD() as fdtd:
        fdtd.load("directional_coupler.fsp")
        fdtd.run()
        
        # Extract S-parameters from mode expansion monitors
        S11 = fdtd.getresult("port1", "S11")
        S21 = fdtd.getresult("port1", "S21")
        S31 = fdtd.getresult("port1", "S31")
        S41 = fdtd.getresult("port1", "S41")
        
        frequencies = S11["f"]
        
    # Create CML model
    with lumapi.CML() as cml:
        cml.newmodel()
        cml.set("name", "directional_coupler")
        cml.set("ports", 4)
        cml.set("model type", "s-parameter")
        
        # Build S-matrix
        num_freq = len(frequencies)
        S_matrix = np.zeros((4, 4, num_freq), dtype=complex)
        S_matrix[0, 0, :] = S11["S"]
        S_matrix[1, 0, :] = S21["S"]
        S_matrix[2, 0, :] = S31["S"]
        S_matrix[3, 0, :] = S41["S"]
        # ... fill in rest of matrix (reciprocity)
        
        cml.set("S matrix", S_matrix)
        cml.set("frequency", frequencies)
        
        cml.compile()
        cml.export("directional_coupler.cml")
        
        run.log_file("spar_model", "directional_coupler.cml", "application/octet-stream")
    
    # Log coupling characteristics
    coupling_ratio = np.abs(S31["S"])**2
    run.log(step=0,
            max_coupling=float(np.max(coupling_ratio)),
            coupling_at_1550=float(coupling_ratio[len(frequencies)//2]))

Multi-Parameter Model

def generate_parametric_model():
    """Generate model with multiple design parameters."""
    
    client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
    project = client.project(name="ParametricCML", create_if_not_exists=True)
    
    # Parameter ranges
    widths = [0.4, 0.45, 0.5, 0.55, 0.6]  # μm
    gaps = [0.15, 0.2, 0.25]               # μm
    wavelengths = np.linspace(1.5, 1.6, 11)
    
    run = project.run(
        name="parametric_coupler",
        config={
            "widths": widths,
            "gaps": gaps,
            "wavelength_range": [1.5, 1.6]
        }
    )
    
    # Collect data across parameter space
    all_data = {}
    
    for w_idx, width in enumerate(widths):
        for g_idx, gap in enumerate(gaps):
            key = f"w{width}_g{gap}"
            
            with lumapi.FDTD() as fdtd:
                fdtd.load("coupler_template.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()
                
                S = fdtd.getresult("FDTD::ports", "S")
                all_data[key] = S
                
            run.log(step=w_idx * len(gaps) + g_idx,
                    width=width,
                    gap=gap,
                    data_collected=True)
    
    # Create parametric CML
    with lumapi.CML() as cml:
        cml.newmodel()
        cml.set("name", "parametric_coupler")
        
        cml.addparameter("width")
        cml.set("type", "length")
        cml.set("values", np.array(widths) * 1e-6)
        
        cml.addparameter("gap")
        cml.set("type", "length")
        cml.set("values", np.array(gaps) * 1e-6)
        
        # Set interpolated data
        cml.set("data", all_data)
        
        cml.compile()
        cml.export("parametric_coupler.cml")
        
        run.log_file("parametric_model", "parametric_coupler.cml", "application/octet-stream")
    
    run.log(step=len(widths)*len(gaps),
            total_parameter_combinations=len(widths)*len(gaps),
            model_complete=True)

Companion App Integration (Planned)

Coming Soon — The OptixLog Companion App will enable GUI-based CML workflows.

Planned Configuration

cml:
  enabled: true
  extract:
    - model_parameters: true
    - port_count: true
    - frequency_range: true
    - compilation_log: true
  file_pattern: "*.cml"

Automatic Extraction (Planned)

Data TypeExtraction
Model nameFrom CML metadata
ParametersNames, types, ranges
Port countNumber of optical/electrical ports
Frequency dataCoverage and resolution
Compilation statusSuccess/errors
Source simulationsLinks to FDTD/MODE runs

Model Validation

Compare Model to Full Simulation

def validate_cml_model():
    """Compare CML model against full FDTD."""
    
    client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
    project = client.project(name="CML_Validation", create_if_not_exists=True)
    
    run = project.run(
        name="coupler_validation",
        config={"model": "directional_coupler.cml"}
    )
    
    # Run INTERCONNECT with CML model
    with lumapi.INTERCONNECT() as ic:
        ic.load("coupler_test_circuit.icp")
        ic.run()
        
        cml_result = ic.getresult("ONA", "transmission")
    
    # Run full FDTD reference
    with lumapi.FDTD() as fdtd:
        fdtd.load("directional_coupler.fsp")
        fdtd.run()
        
        fdtd_result = fdtd.getresult("transmission", "T")
    
    # Compare
    wavelengths = cml_result["lambda"] * 1e9
    cml_T = np.abs(cml_result["transmission"])**2
    fdtd_T = fdtd_result["T"]
    
    # Interpolate to same wavelength grid
    from scipy.interpolate import interp1d
    fdtd_interp = interp1d(fdtd_result["lambda"].flatten()*1e9, 
                           fdtd_T.flatten(), 
                           fill_value="extrapolate")
    fdtd_T_interp = fdtd_interp(wavelengths.flatten())
    
    # Calculate error
    mse = np.mean((cml_T.flatten() - fdtd_T_interp)**2)
    max_error = np.max(np.abs(cml_T.flatten() - fdtd_T_interp))
    
    run.log(step=0,
            mse=float(mse),
            max_error=float(max_error),
            model_valid=max_error < 0.05)
    
    # Plot comparison
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
    
    ax1.plot(wavelengths.flatten(), cml_T.flatten(), 'b-', label='CML Model')
    ax1.plot(wavelengths.flatten(), fdtd_T_interp, 'r--', label='FDTD Reference')
    ax1.set_xlabel('Wavelength (nm)')
    ax1.set_ylabel('Transmission')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    ax1.set_title('Model vs Reference')
    
    ax2.plot(wavelengths.flatten(), cml_T.flatten() - fdtd_T_interp, 'g-')
    ax2.set_xlabel('Wavelength (nm)')
    ax2.set_ylabel('Error (CML - FDTD)')
    ax2.grid(True, alpha=0.3)
    ax2.set_title(f'Error (MSE: {mse:.2e}, Max: {max_error:.4f})')
    
    plt.tight_layout()
    run.log_matplotlib("model_validation", fig)
    plt.close(fig)

Tips for CML + OptixLog

1. Version Your Models

import datetime

run = project.run(
    name=f"waveguide_model_v{version}_{datetime.date.today()}",
    config={
        "version": version,
        "changes": "Updated thermal model",
        "based_on": previous_model_id
    }
)

2. Track Source Simulations

# Link CML model to source FDTD/MODE simulations
run = project.run(
    name="coupler_cml",
    config={
        "source_fdtd_run": fdtd_run_id,
        "source_mode_run": mode_run_id,
        "compilation_settings": {...}
    }
)

3. Log Model Quality Metrics

run.log(step=0,
        frequency_points=len(frequencies),
        parameter_combinations=total_combinations,
        interpolation_method="spline",
        max_extrapolation_error=float(max_error))

Next Steps

On this page