SDKFrameworks

Meep Integration

Complete guide to integrating OptixLog with Meep FDTD simulations

Meep Integration

Complete guide to integrating OptixLog with Meep, the open-source FDTD simulation software for photonics and electromagnetics.

Meep is one of the most popular photonics simulation tools. OptixLog makes it easy to track your simulations, compare results, and share findings.


Why Use OptixLog with Meep?

ChallengeOptixLog Solution
Parameter sweeps generate hundreds of runsAutomatic organization and comparison
Field data is large and hard to versionCloud storage with visualization
Reproducing results requires digging through filesFull config tracking and source code logging
Sharing results means packaging filesInstant web links to runs
MPI runs duplicate outputsAutomatic master-only logging

Installation

pip install meep optixlog matplotlib numpy
conda install -c conda-forge meep
pip install optixlog matplotlib numpy

Basic Integration Pattern

Every Meep + OptixLog simulation follows this structure:

import os
import meep as mp
import matplotlib
matplotlib.use("agg")  # Required for headless operation
import matplotlib.pyplot as plt
import numpy as np
from optixlog import Optixlog

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

# 2. Create run with simulation config
run = project.run(
    name="waveguide_simulation",
    config={
        "resolution": 10,
        "wavelength": 1.55,
        "material": "silicon",
        # ... all your parameters
    }
)

# 3. Build Meep simulation
sim = mp.Simulation(
    cell_size=mp.Vector3(16, 8),
    geometry=[...],
    sources=[...],
    boundary_layers=[mp.PML(1.0)],
    resolution=10
)

# 4. Run and log metrics
sim.run(until=100)

run.log(step=0, 
        transmission=calculate_transmission(),
        power=calculate_power())

# 5. Log visualizations
fig, ax = plt.subplots()
# ... create plot
run.log_matplotlib("results", fig)
plt.close(fig)

What to Log

Configuration (once per run)

Always store your simulation parameters in the config:

run = project.run(
    name="ring_resonator_sweep",
    config={
        # Geometry
        "ring_radius": 5.0,
        "waveguide_width": 0.5,
        "coupling_gap": 0.1,
        
        # Material
        "core_index": 3.4,
        "clad_index": 1.44,
        
        # Simulation
        "resolution": 20,
        "cell_size": [20, 20],
        "pml_thickness": 1.0,
        
        # Source
        "center_frequency": 0.15,
        "frequency_width": 0.1,
        "source_type": "GaussianSource",
        
        # Run settings
        "run_until": 200,
        "decay_threshold": 1e-6
    }
)

Metrics (during/after simulation)

Log scalar values at each step:

# Log simulation progress
run.log(step=0, phase="initialization")

# Log computed quantities
run.log(step=1,
        max_transmission=0.98,
        insertion_loss_dB=0.15,
        bandwidth_nm=40,
        quality_factor=5000)

# Log flux values
run.log(step=2,
        input_power=float(sum(input_flux)),
        output_power=float(sum(output_flux)))

Field Visualizations

Log field data as images:

# Get field data from Meep
ez_data = sim.get_array(
    center=mp.Vector3(),
    size=cell,
    component=mp.Ez
)

# Option 1: Using log_array_as_image (fastest)
run.log_array_as_image("ez_field", ez_data, cmap='RdBu')

# Option 2: Using matplotlib for custom plots
fig, ax = plt.subplots(figsize=(10, 6))
im = ax.imshow(np.real(ez_data).T, cmap='RdBu', origin='lower')
ax.set_title('Ez Field Distribution')
plt.colorbar(im, ax=ax)
run.log_matplotlib("ez_field_custom", fig)
plt.close(fig)

Spectra and Curves

Log transmission, reflection, and other spectra:

# Extract flux data
freqs = mp.get_flux_freqs(flux_monitor)
flux_values = mp.get_fluxes(flux_monitor)
wavelengths = [1/f for f in freqs]

# Create spectrum plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(wavelengths, transmission, 'b-', linewidth=2)
ax.set_xlabel('Wavelength (μm)')
ax.set_ylabel('Transmission')
ax.set_title('Transmission Spectrum')
ax.grid(True, alpha=0.3)

run.log_matplotlib("transmission_spectrum", fig)
plt.close(fig)

Common Meep Workflows

Flux Calculation

# Add flux monitors
input_flux = sim.add_flux(
    fcen, df, nfreq,
    mp.FluxRegion(center=mp.Vector3(-5, 0), size=mp.Vector3(0, 2))
)

output_flux = sim.add_flux(
    fcen, df, nfreq,
    mp.FluxRegion(center=mp.Vector3(5, 0), size=mp.Vector3(0, 2))
)

# Run simulation
sim.run(until_after_sources=mp.stop_when_fields_decayed(50, mp.Ez, pt, 1e-3))

# Extract and log
input_power = mp.get_fluxes(input_flux)
output_power = mp.get_fluxes(output_flux)

transmission = [o/i for i, o in zip(input_power, output_power) if i > 0]

run.log(step=0,
        avg_transmission=float(np.mean(transmission)),
        max_transmission=float(max(transmission)))

Field Animation

Log field evolution over time:

def log_field_callback(sim):
    step = int(sim.meep_time())
    
    if step % 10 == 0:  # Every 10 time units
        ez = sim.get_array(center=mp.Vector3(), size=cell, component=mp.Ez)
        run.log_array_as_image(f"field_t{step}", ez, cmap='RdBu')

sim.run(
    mp.at_every(10, log_field_callback),
    until=100
)

Mode Analysis

import meep.mpb as mpb

# Run MPB calculation
ms = mpb.ModeSolver(...)
ms.run()

# Log band structure
freqs = ms.all_freqs
for band_idx, band_freqs in enumerate(freqs):
    for k_idx, freq in enumerate(band_freqs):
        run.log(step=k_idx, 
                band=band_idx,
                frequency=float(freq),
                k_point=float(k_idx))

MPI Support

For parallel Meep simulations, OptixLog automatically handles MPI:

mpirun -n 4 python simulation.py
from optixlog import Optixlog

client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))

# MPI info automatically detected
print(f"Process {client.rank}/{client.size}")

project = client.project(name="ParallelSimulations")
run = project.run(name="mpi_simulation")

# Only master logs - workers return None automatically
run.log(step=0, value=42)  # Safe to call from all processes

Best Practice: Check is_master for expensive operations

# Field extraction can be expensive - only do on master
if client.is_master:
    field = sim.get_array(...)
    run.log_array_as_image("field", field, cmap='RdBu')

# Always sync before continuing
client.barrier()

Parameter Sweeps

Simple Loop

from optixlog import Optixlog

client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="WaveguideSweep")

for width in [0.4, 0.5, 0.6, 0.7, 0.8]:
    run = project.run(
        name=f"width_{width}",
        config={"waveguide_width": width, "resolution": 20}
    )
    
    # Run simulation
    transmission = simulate_waveguide(width)
    
    run.log(step=0, transmission=transmission)
import itertools

widths = [0.4, 0.5, 0.6]
gaps = [0.1, 0.15, 0.2]
radii = [3.0, 5.0, 7.0]

for width, gap, radius in itertools.product(widths, gaps, radii):
    run = project.run(
        name=f"w{width}_g{gap}_r{radius}",
        config={
            "waveguide_width": width,
            "coupling_gap": gap,
            "ring_radius": radius
        }
    )
    
    result = simulate_ring(width, gap, radius)
    run.log(step=0, **result)

Parallel Sweeps

from concurrent.futures import ProcessPoolExecutor

def run_simulation(config):
    client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
    project = client.project(name="ParallelSweep")
    
    run = project.run(name=config["name"], config=config)
    result = simulate(config)
    run.log(step=0, **result)
    return result

configs = [{"name": f"run_{i}", "param": i} for i in range(20)]

with ProcessPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(run_simulation, configs))

Explore our complete Meep examples:

Getting Started

Waveguides & Couplers

Gratings & Diffraction

Cavities & Resonators

Radiation & Emission

Nonlinear & Advanced


Tips & Best Practices

1. Always use matplotlib.use("agg")

For headless operation (servers, HPC):

import matplotlib
matplotlib.use("agg")  # Before importing pyplot!
import matplotlib.pyplot as plt

2. Close figures after logging

Prevent memory leaks:

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

3. Use meaningful run names

# Bad
run = project.run(name="run1")

# Good
run = project.run(name="ring_r5um_gap100nm_res20")

4. Log at appropriate frequency

# Don't log every timestep
for t in range(10000):
    sim.run(until=1)
    
    if t % 100 == 0:  # Every 100 steps
        run.log(step=t, field_max=get_max_field())

5. Store complete configs

# Store everything needed to reproduce
run = project.run(
    name="simulation",
    config={
        "geometry": {"type": "ring", "radius": 5.0},
        "material": {"index": 3.4, "loss": 0.001},
        "source": {"freq": 0.15, "width": 0.1},
        "mesh": {"resolution": 20, "pml": 1.0},
        "meep_version": mp.__version__,
        "numpy_version": np.__version__
    }
)

Troubleshooting

"matplotlib backend" errors

# Add at the very top of your script
import matplotlib
matplotlib.use("agg")

Large field arrays

For very high resolution simulations:

# Downsample before logging
field = sim.get_array(...)
field_downsampled = field[::2, ::2]  # 2x downsampling
run.log_array_as_image("field", field_downsampled)

MPI logging issues

# Ensure only master logs
if client.is_master:
    run.log(step=0, result=value)

Next Steps

On this page