SDKFrameworksAnsysLumerical
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:
- lumapi — Script your model compilation in Python (available now)
- 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 Type | Extraction |
|---|---|
| Model name | From CML metadata |
| Parameters | Names, types, ranges |
| Port count | Number of optical/electrical ports |
| Frequency data | Coverage and resolution |
| Compilation status | Success/errors |
| Source simulations | Links 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
- INTERCONNECT Integration — Use CML models in circuits
- FDTD Integration — Generate source S-parameters
- MODE Integration — Generate waveguide models