SDKFrameworksAnsysLumericalPlanned

Lumerical Integration

Integrating OptixLog with Ansys Lumerical photonics tools via lumapi and the planned Companion App

Lumerical Integration

Ansys Lumerical is the industry standard for photonics simulation. OptixLog provides integration pathways to bring experiment tracking and collaboration to your Lumerical workflows.

Lumerical tools communicate via lumapi, an inter-process Python API. While powerful, it's not as seamless as native Python simulation tools. We're working on solutions to make integration easier.


The Reality of lumapi

Let's be honest: lumapi is not the ideal integration point. Here's why:

How lumapi Works

┌─────────────────┐         ┌─────────────────┐
│  Python Script  │◄───────►│  Lumerical GUI  │
│    (lumapi)     │   IPC   │   (Running)     │
└─────────────────┘         └─────────────────┘

lumapi launches Lumerical in the background and communicates via inter-process calls. This means:

  • Lumerical must be installed — No cloud-only option
  • License required — Each lumapi session consumes a license
  • Platform quirks — Path setup varies by OS
  • Performance overhead — IPC adds latency
  • GUI dependency — Some operations require the GUI process

Our Honest Assessment

AspectRatingNotes
Setup complexity⚠️ MediumRequires PATH configuration
Reliability✅ GoodOnce configured, works well
Performance⚠️ MediumIPC overhead on data transfer
Headless operation❌ LimitedSome features need GUI
Documentation✅ GoodLumerical provides docs

Despite these challenges, lumapi is the best option available today for programmatic Lumerical integration.


Integration Approaches

Option 1: lumapi + OptixLog SDK (Available Now)

For scripted workflows, integrate OptixLog directly:

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

# Initialize
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="LumericalProject", create_if_not_exists=True)

run = project.run(
    name="waveguide_analysis",
    config={
        "tool": "Lumerical MODE",
        "waveguide_width": 0.5,
        "waveguide_height": 0.22
    }
)

# Connect to Lumerical
with lumapi.MODE() as mode:
    mode.load("waveguide.lms")
    mode.run()
    
    # Get results
    neff = mode.getresult("FDE", "neff")
    
    # Log to OptixLog
    for i, n in enumerate(neff["neff"]):
        run.log(step=i, mode_index=i, neff=float(np.real(n)))

print("✅ Results logged to OptixLog")

Option 2: Companion App (Planned)

Coming Soon — The OptixLog Companion App is in active development.

For GUI-centric workflows, we're building a desktop companion app:

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    Your Workstation                              │
│                                                                  │
│  ┌─────────────┐    ┌──────────────────────────────────────┐   │
│  │ Lumerical   │    │     OptixLog Companion App           │   │
│  │ FDTD/MODE   │    │  ┌─────────────────────────────────┐ │   │
│  │ etc.        │    │  │  System Tray Icon               │ │   │
│  │             │    │  │  ════════════════               │ │   │
│  │ Save .fsp ──┼────┼──┼─► Watch Directory               │ │   │
│  │             │    │  │  Parse Results                  │ │   │
│  │             │    │  │  [Sync to OptixLog] ◄── Click   │ │   │
│  └─────────────┘    │  └─────────────────────────────────┘ │   │
│                     └──────────────────────────────────────┘   │
│                                      │                          │
└──────────────────────────────────────┼──────────────────────────┘
                                       │ HTTPS

                            ┌──────────────────┐
                            │  OptixLog Cloud  │
                            └──────────────────┘

Planned Features

  • System tray application (Windows .exe, Linux AppImage)
  • Directory watching for simulation file changes
  • Automatic result extraction from .fsp, .lms, .ldev files
  • One-click sync to upload results
  • Configuration UI for API key and project mapping

Configuration (Planned)

# ~/.optixlog/companion.yaml
connection:
  api_key: "your_api_key"
  api_url: "https://optixlog.com"  # Or self-hosted

monitoring:
  watch_paths:
    - "C:/Users/engineer/Lumerical"
    - "/home/engineer/simulations"
  file_patterns:
    - "*.fsp"   # FDTD
    - "*.lms"   # MODE
    - "*.ldev"  # DEVICE
    - "*.icp"   # INTERCONNECT

sync:
  auto_sync: false  # Manual sync preferred
  sync_on_save: false
  batch_interval: 300  # seconds

project_mapping:
  default: "Lumerical Experiments"
  paths:
    "C:/Users/engineer/Lumerical/RingResonators": "Ring Resonator Designs"
    "C:/Users/engineer/Lumerical/Waveguides": "Waveguide Library"

Lumerical Product Suite


Setting Up lumapi

import sys
# Add Lumerical's Python API to path
sys.path.append("C:/Program Files/Lumerical/v241/api/python")
import lumapi

Or set PYTHONPATH:

set PYTHONPATH=%PYTHONPATH%;C:\Program Files\Lumerical\v241\api\python
import sys
sys.path.append("/opt/lumerical/v241/api/python")
import lumapi

Or in .bashrc:

export PYTHONPATH=$PYTHONPATH:/opt/lumerical/v241/api/python
import sys
sys.path.append("/Applications/Lumerical/v241/api/python")
import lumapi

Verify Installation

import lumapi

# Test connection
try:
    with lumapi.FDTD() as fdtd:
        print(f"✅ Connected to Lumerical FDTD")
        print(f"   Version: {fdtd.version()}")
except Exception as e:
    print(f"❌ Connection failed: {e}")

Basic Integration Pattern

Every Lumerical + OptixLog script follows this pattern:

import lumapi
import os
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
import numpy as np
from optixlog import Optixlog

def run_simulation():
    # 1. Initialize OptixLog
    client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
    project = client.project(name="LumericalProject", create_if_not_exists=True)
    
    # 2. Define parameters
    params = {
        "wavelength": 1.55,
        "waveguide_width": 0.5,
        "simulation_time": 1000  # fs
    }
    
    # 3. Create run with config
    run = project.run(
        name="fdtd_simulation",
        config=params
    )
    
    # 4. Run Lumerical simulation
    with lumapi.FDTD() as fdtd:
        # Load or build simulation
        fdtd.load("device.fsp")
        
        # Apply parameters
        fdtd.setnamed("waveguide", "x span", params["waveguide_width"] * 1e-6)
        
        # Run
        fdtd.run()
        
        # 5. Extract results
        T = fdtd.getresult("transmission", "T")
        
        # 6. Log metrics
        run.log(step=0,
                max_transmission=float(np.max(T["T"])),
                bandwidth=calculate_bandwidth(T))
        
        # 7. Log visualizations
        fig, ax = plt.subplots()
        ax.plot(T["lambda"] * 1e9, T["T"])
        ax.set_xlabel("Wavelength (nm)")
        ax.set_ylabel("Transmission")
        run.log_matplotlib("transmission_spectrum", fig)
        plt.close(fig)
    
    print("✅ Simulation complete!")

if __name__ == "__main__":
    run_simulation()

What to Log

Configuration

run = project.run(
    name="ring_resonator_v3",
    config={
        # Tool info
        "tool": "Lumerical FDTD",
        "version": "2024 R1",
        "solver": "3D",
        
        # Geometry
        "ring_radius": 5.0,        # μm
        "waveguide_width": 0.5,    # μm
        "coupling_gap": 0.2,       # μm
        
        # Materials
        "core_material": "Si (Silicon) - Palik",
        "clad_material": "SiO2 (Glass) - Palik",
        
        # Mesh
        "mesh_accuracy": 3,
        "override_mesh": True,
        "dx": 0.02,  # μm
        
        # Simulation
        "simulation_time": 2000,   # fs
        "auto_shutoff": 1e-5
    }
)

Simulation Progress

# Log mesh info
run.log(step=0,
        mesh_cells=int(fdtd.getnamed("FDTD", "mesh cells")),
        memory_mb=float(fdtd.getnamed("FDTD", "memory")))

# Log completion
run.log(step=1,
        simulation_time_actual=float(fdtd.getnamed("FDTD", "simulation time")),
        auto_shutoff_reached=True)

Results

# Transmission/Reflection
T = fdtd.getresult("T_monitor", "T")
run.log(step=2,
        max_T=float(np.max(T["T"])),
        min_T=float(np.min(T["T"])),
        center_wavelength=float(T["lambda"][np.argmax(T["T"])]))

# Mode data
neff = mode.getresult("FDE", "neff")
run.log(step=3,
        fundamental_neff=float(np.real(neff["neff"][0])),
        num_modes=len(neff["neff"]))

Tips & Best Practices

1. Use Context Managers

# Good - auto-closes
with lumapi.FDTD() as fdtd:
    fdtd.run()

# Avoid - may leave processes running
fdtd = lumapi.FDTD()
fdtd.run()
# fdtd.close()  # Easy to forget

2. Handle License Errors

try:
    with lumapi.FDTD() as fdtd:
        fdtd.run()
except lumapi.LumApiError as e:
    if "license" in str(e).lower():
        run.log(step=0, error="License unavailable", status="failed")
    raise

3. Extract Data Efficiently

# Get only what you need
T = fdtd.getresult("monitor", "T")  # Just transmission

# Avoid large data transfers when possible
# Don't: E = fdtd.getresult("field_monitor", "E")  # Can be huge

4. Close Figures After Logging

fig, ax = plt.subplots()
ax.plot(wavelengths, transmission)
run.log_matplotlib("spectrum", fig)
plt.close(fig)  # Important!

Troubleshooting

lumapi Import Fails

# Check path is correct
import sys
print(sys.path)

# Verify Lumerical installation
import os
print(os.path.exists("/opt/lumerical/v241/api/python/lumapi.py"))

Connection Timeout

# Increase timeout for slow starts
with lumapi.FDTD(hide=True) as fdtd:  # hide=True for headless
    pass

Memory Issues

# Clear memory between simulations
fdtd.switchtolayout()
fdtd.deleteall()

Sign Up for Companion App Beta

Interested in the GUI-friendly Companion App?

Join the waitlist →

We'll notify you when the beta is available for Windows and Linux.


Next Steps

On this page