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?
| Challenge | OptixLog Solution |
|---|---|
| Parameter sweeps generate hundreds of runs | Automatic organization and comparison |
| Field data is large and hard to version | Cloud storage with visualization |
| Reproducing results requires digging through files | Full config tracking and source code logging |
| Sharing results means packaging files | Instant web links to runs |
| MPI runs duplicate outputs | Automatic master-only logging |
Installation
pip install meep optixlog matplotlib numpyconda install -c conda-forge meep
pip install optixlog matplotlib numpyBasic 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.pyfrom 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 processesBest 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)Grid Search
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))Example Gallery
Explore our complete Meep examples:
Getting Started
- Quick Start — Your first simulation
- Straight Waveguide — Basic waveguide
- Ring Resonator — Resonant structures
Waveguides & Couplers
- Bent Waveguide — Waveguide bends
- Coupler — Directional couplers
- Mode Decomposition — Eigenmode analysis
Gratings & Diffraction
- Binary Grating Analysis — Diffraction analysis
- Polarization Grating — Polarization optics
Cavities & Resonators
- Cavity Far-field — Far-field patterns
- Metal Cavity LDOS — Local density of states
Radiation & Emission
- Antenna Radiation — Antenna patterns
- Cherenkov Radiation — Cherenkov emission
Nonlinear & Advanced
- Third Harmonic — Nonlinear optics
- Multilevel Atom — Quantum systems
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 plt2. 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
- MPI Support — Parallel simulation guide
- API Reference — Complete method documentation
- Meep Examples — Full example gallery