SDKFrameworks
Tidy3D Integration
Complete guide to integrating OptixLog with Tidy3D cloud FDTD simulations
Tidy3D Integration
Complete guide to integrating OptixLog with Tidy3D, the cloud-based FDTD simulation platform for photonics.
Tidy3D runs simulations in the cloud via the Flexcompute API. OptixLog complements this by tracking your experiments, comparing runs, and organizing results across multiple cloud jobs.
Why Use OptixLog with Tidy3D?
| Challenge | OptixLog Solution |
|---|---|
| Cloud jobs scattered across tasks | Unified experiment tracking |
| Parameter sweeps generate many task IDs | Automatic organization and comparison |
| Results downloaded to different paths | Centralized visualization |
| Hard to compare across simulation batches | Side-by-side run comparison |
| FlexCredit costs hard to track | Log costs alongside results |
Installation
pip install tidy3d optixlog matplotlib numpyMake sure you have both your Tidy3D API key (from Flexcompute) and OptixLog API key configured.
Basic Integration Pattern
Every Tidy3D + OptixLog workflow follows this structure:
import os
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
import numpy as np
import tidy3d as td
import tidy3d.web as web
from optixlog import Optixlog
# 1. Initialize OptixLog
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="Tidy3DSimulations", create_if_not_exists=True)
# 2. Create run with simulation config
run = project.run(
name="dielectric_cube_simulation",
config={
"wavelength": 0.75,
"resolution": 25,
"structure": "dielectric_cube",
"permittivity": 2.0
}
)
# 3. Define Tidy3D simulation
lda0 = 0.75
freq0 = td.C_0 / lda0
fwidth = freq0 / 10.0
freq_range = td.FreqRange(freq0=freq0, fwidth=fwidth)
sim = td.Simulation(
size=(4, 3, 3),
grid_spec=td.GridSpec.auto(min_steps_per_wvl=25),
structures=[...],
sources=[...],
monitors=[...],
run_time=3e-13
)
# 4. Run on Tidy3D cloud
data = web.run(sim, task_name="optixlog_demo", path="data/data.hdf5")
# 5. Log results to OptixLog
run.log(step=0,
grid_cells=int(np.prod(sim.grid.num_cells)),
run_time=sim.run_time)
# 6. Log visualizations
fig = data.plot_field("fields", "Ey", z=0)
run.log_matplotlib("field_distribution", fig.figure)
plt.close()What to Log
Configuration (once per run)
Store all simulation parameters:
run = project.run(
name="ring_resonator_tidy3d",
config={
# Frequency settings
"wavelength_um": 1.55,
"freq0_hz": freq0,
"fwidth_hz": fwidth,
# Domain
"sim_size": [4, 4, 4],
"run_time_s": 2e-13,
"min_steps_per_wvl": 20,
# Structures
"ring_radius": 5.0,
"waveguide_width": 0.5,
"material_index": 3.4,
# Boundaries
"boundary_type": "PML",
"pml_layers": 12
}
)Tidy3D Task Info
Log cloud task metadata:
# Upload simulation
task_id = web.upload(sim, task_name="my_simulation")
# Log task info
run.log(step=0,
task_id=task_id,
estimated_cost=web.estimate_cost(task_id))
# Run simulation
web.start(task_id)
web.monitor(task_id)
# Log actual cost after completion
run.log(step=1,
real_cost=web.real_cost(task_id),
status="completed")Grid and Solver Info
run.log(step=0,
num_cells=int(np.prod(sim.grid.num_cells)),
num_time_steps=sim.num_time_steps,
dt=float(sim.dt),
grid_shape=list(sim.grid.num_cells))Monitor Data
Log flux, field, and mode data:
# After simulation completes
sim_data = web.load(task_id, path="data/sim_data.hdf5")
# Log flux values
flux_data = sim_data["flux_monitor"].flux
run.log(step=1,
total_flux=float(flux_data.sum()),
max_flux=float(flux_data.max()))
# Log field statistics
field_data = sim_data["field_monitor"]
Ex = field_data.Ex
run.log(step=2,
Ex_max=float(np.abs(Ex).max()),
Ex_mean=float(np.abs(Ex).mean()))Common Tidy3D Workflows
Basic Simulation with Logging
import os
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
import numpy as np
import tidy3d as td
import tidy3d.web as web
from optixlog import Optixlog
def run_dielectric_simulation():
# Initialize OptixLog
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="Tidy3DExamples", create_if_not_exists=True)
# Parameters
lda0 = 0.75
freq0 = td.C_0 / lda0
fwidth = freq0 / 10.0
freq_range = td.FreqRange(freq0=freq0, fwidth=fwidth)
run = project.run(
name="dielectric_cube",
config={
"wavelength": lda0,
"freq0": freq0,
"fwidth": fwidth,
"permittivity": 2.0,
"cube_size": 1.5
}
)
# Create structure
cube = td.Structure(
geometry=td.Box(center=(0, 0, 0), size=(1.5, 1.5, 1.5)),
medium=td.Medium(permittivity=2.0)
)
# Create source
source = td.PointDipole(
center=(-1.5, 0, 0),
source_time=freq_range.to_gaussian_pulse(),
polarization="Ey"
)
# Create monitor
monitor = td.FieldMonitor(
center=(0, 0, 0),
size=(td.inf, td.inf, 0),
freqs=freq_range.freqs(num_points=1),
name="fields"
)
# Create simulation
sim = td.Simulation(
size=(4, 3, 3),
grid_spec=td.GridSpec.auto(min_steps_per_wvl=25),
structures=[cube],
sources=[source],
monitors=[monitor],
run_time=3e-13
)
# Log grid info
run.log(step=0,
grid_cells=int(np.prod(sim.grid.num_cells)),
num_time_steps=sim.num_time_steps)
# Run simulation
data = web.run(sim, task_name="optixlog_cube", path="data/cube.hdf5")
# Log results
run.log(step=1, simulation_completed=True)
# Plot and log field
ax = data.plot_field("fields", "Ey", z=0)
run.log_matplotlib("Ey_field", ax.figure)
plt.close()
print("✅ Simulation complete!")
if __name__ == "__main__":
run_dielectric_simulation()Transmission Calculation
import os
import tidy3d as td
import tidy3d.web as web
import numpy as np
from optixlog import Optixlog
def run_transmission_simulation():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="Tidy3DExamples", create_if_not_exists=True)
# Parameters
freq0 = 2e14
fwidth = 1e13
freq_range = td.FreqRange(freq0=freq0, fwidth=fwidth)
sim_size = [4, 4, 4]
run = project.run(
name="slab_transmission",
config={
"freq0": freq0,
"fwidth": fwidth,
"sim_size": sim_size,
"slab_permittivity": 6.0
}
)
# Slab structure
slab = td.Structure(
geometry=td.Box(center=[0, 0, 0], size=[td.inf, td.inf, 1]),
medium=td.Medium(permittivity=6.0)
)
# Plane wave source
source = td.PlaneWave(
center=(0, 0, 1.5),
direction="-",
size=(td.inf, td.inf, 0),
source_time=freq_range.to_gaussian_pulse(),
pol_angle=np.pi / 2
)
# Flux monitors
flux_in = td.FluxMonitor(
center=(0, 0, 0.8),
size=(td.inf, td.inf, 0),
freqs=freq_range.freqs(num_points=50),
name="flux_in"
)
flux_out = td.FluxMonitor(
center=(0, 0, -0.8),
size=(td.inf, td.inf, 0),
freqs=freq_range.freqs(num_points=50),
name="flux_out"
)
sim = td.Simulation(
size=sim_size,
grid_spec=td.GridSpec.auto(min_steps_per_wvl=20),
structures=[slab],
sources=[source],
monitors=[flux_in, flux_out],
run_time=2 / fwidth,
boundary_spec=td.BoundarySpec.all_sides(boundary=td.PML())
)
run.log(step=0, grid_cells=int(np.prod(sim.grid.num_cells)))
# Run
data = web.run(sim, task_name="transmission", path="data/transmission.hdf5")
# Calculate transmission
flux_in_data = data["flux_in"].flux
flux_out_data = data["flux_out"].flux
transmission = np.abs(flux_out_data / flux_in_data)
run.log(step=1,
avg_transmission=float(np.mean(transmission)),
max_transmission=float(np.max(transmission)),
min_transmission=float(np.min(transmission)))
print(f"📊 Average transmission: {np.mean(transmission):.4f}")
if __name__ == "__main__":
run_transmission_simulation()Mode Solver Integration
from tidy3d.plugins.mode import ModeSolver
import tidy3d as td
from optixlog import Optixlog
import os
def analyze_waveguide_modes():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="ModeSolverExamples", create_if_not_exists=True)
run = project.run(
name="waveguide_modes",
config={
"waveguide_width": 0.5,
"waveguide_height": 0.22,
"core_index": 3.48,
"clad_index": 1.44,
"wavelength": 1.55,
"num_modes": 4
}
)
# Define waveguide structure
wg = td.Structure(
geometry=td.Box(center=(0, 0, 0), size=(0.5, 0.22, td.inf)),
medium=td.Medium.from_nk(n=3.48, k=0, freq=td.C_0 / 1.55)
)
sim = td.Simulation(
size=(2, 2, 0.1),
grid_spec=td.GridSpec.auto(wavelength=1.55, min_steps_per_wvl=20),
structures=[wg],
sources=[],
monitors=[],
run_time=1e-12
)
# Mode solver
mode_solver = ModeSolver(
simulation=sim,
plane=td.Box(center=(0, 0, 0), size=(2, 2, 0)),
mode_spec=td.ModeSpec(num_modes=4),
freqs=[td.C_0 / 1.55]
)
mode_data = mode_solver.solve()
# Log mode effective indices
for mode_idx in range(4):
n_eff = mode_data.n_eff.sel(mode_index=mode_idx).values[0]
run.log(step=mode_idx,
mode_index=mode_idx,
n_eff_real=float(n_eff.real),
n_eff_imag=float(n_eff.imag))
# Plot and log mode profiles
for mode_idx in range(4):
fig, ax = plt.subplots()
mode_data.Ex.sel(mode_index=mode_idx).abs.plot(ax=ax)
ax.set_title(f"Mode {mode_idx}")
run.log_matplotlib(f"mode_{mode_idx}_profile", fig)
plt.close(fig)
if __name__ == "__main__":
analyze_waveguide_modes()Parameter Sweeps
Simple Sweep
from optixlog import Optixlog
import tidy3d as td
import tidy3d.web as web
import os
def sweep_permittivity():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="PermittivitySweep", create_if_not_exists=True)
permittivities = [2.0, 4.0, 6.0, 8.0, 12.0]
for eps in permittivities:
run = project.run(
name=f"eps_{eps}",
config={"permittivity": eps}
)
# Build and run simulation
cube = td.Structure(
geometry=td.Box(center=(0, 0, 0), size=(1, 1, 1)),
medium=td.Medium(permittivity=eps)
)
# ... rest of simulation setup
# Log results
run.log(step=0, permittivity=eps, result=computed_result)Batch Submissions
Tidy3D supports batch submissions for efficiency:
import tidy3d.web as web
from optixlog import Optixlog
def batch_sweep():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="BatchSweep", create_if_not_exists=True)
# Create dictionary of simulations
sims = {
f"sim_width_{w}": create_simulation(width=w)
for w in [0.4, 0.5, 0.6, 0.7, 0.8]
}
# Run all in batch
batch_data = web.run(sims, path_dir="data/batch/")
# Log results for each
for name, data in batch_data.items():
run = project.run(name=name, config={"width": float(name.split("_")[-1])})
# Extract and log metrics
flux = data["flux_monitor"].flux
run.log(step=0, total_flux=float(flux.sum()))Logging Tidy3D Plots
Built-in Visualization Methods
import matplotlib.pyplot as plt
# Simulation structure plot
fig, ax = plt.subplots()
sim.plot_eps(z=0, ax=ax)
run.log_matplotlib("structure_eps", fig)
plt.close(fig)
# 3D structure visualization (save as image)
sim.plot_3d()
plt.savefig("structure_3d.png")
run.log_file("structure_3d", "structure_3d.png", "image/png")
# Field data plot
ax = sim_data.plot_field("field_monitor", "Ey", z=0, val="real")
run.log_matplotlib("Ey_field", ax.figure)
plt.close()Custom Analysis Plots
import matplotlib.pyplot as plt
import numpy as np
# Transmission spectrum
freqs = sim_data["flux_monitor"].flux.f.values
flux = sim_data["flux_monitor"].flux.values
wavelengths = td.C_0 / freqs * 1e6 # Convert to μm
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(wavelengths, flux, 'b-', linewidth=2)
ax.set_xlabel('Wavelength (μm)')
ax.set_ylabel('Flux')
ax.set_title('Transmission Spectrum')
ax.grid(True, alpha=0.3)
run.log_matplotlib("transmission_spectrum", fig)
plt.close(fig)Tracking Costs
Log FlexCredit usage for budgeting:
# Before running
task_id = web.upload(sim, task_name="cost_tracking")
estimated = web.estimate_cost(task_id)
run.log(step=0,
task_id=task_id,
estimated_flexcredits=estimated)
# Run simulation
web.start(task_id)
web.monitor(task_id)
# After completion
import time
time.sleep(4) # Wait for cost calculation
real = web.real_cost(task_id)
run.log(step=1,
actual_flexcredits=real,
cost_efficiency=estimated / real if real > 0 else 0)S-Matrix Calculations
from tidy3d.plugins.smatrix import ComponentModeler, Port
from optixlog import Optixlog
import os
def compute_smatrix():
client = Optixlog(api_key=os.getenv("OPTIX_API_KEY"))
project = client.project(name="SMatrixExamples", create_if_not_exists=True)
run = project.run(
name="coupler_smatrix",
config={"device": "directional_coupler", "coupling_length": 10.0}
)
# Define ports and modeler
# ... setup code
modeler = ComponentModeler(
simulation=sim,
ports=[port1, port2, port3, port4],
freqs=freqs
)
smatrix = modeler.run()
# Log S-parameters
for i in range(4):
for j in range(4):
s_ij = smatrix.S[i, j]
run.log(step=i * 4 + j,
s_param=f"S{i+1}{j+1}",
magnitude=float(np.abs(s_ij)),
phase_deg=float(np.angle(s_ij) * 180 / np.pi))Tips & Best Practices
1. Use matplotlib.use("agg") for headless operation
import matplotlib
matplotlib.use("agg") # Before importing pyplot!
import matplotlib.pyplot as plt2. Log task IDs for reference
task_id = web.upload(sim, task_name="my_sim")
run.log(step=0, task_id=task_id)3. Track computation costs
run.log(step=0,
estimated_cost=web.estimate_cost(task_id),
grid_cells=int(np.prod(sim.grid.num_cells)))4. Store complete simulation configs
run = project.run(
name="simulation",
config={
"frequency": {"freq0": freq0, "fwidth": fwidth},
"domain": {"size": sim_size, "run_time": run_time},
"mesh": {"min_steps_per_wvl": 20},
"boundaries": {"type": "PML", "layers": 12},
"tidy3d_version": td.__version__
}
)5. Close figures after logging
fig, ax = plt.subplots()
# ... create plot
run.log_matplotlib("plot", fig)
plt.close(fig) # Important!Troubleshooting
Tidy3D API Key Issues
# Configure Tidy3D API key
import tidy3d.web as web
web.configure("YOUR_TIDY3D_API_KEY")Large Data Downloads
For simulations with large monitor data:
# Download only specific monitors
sim_data = web.load(task_id, path="data/sim.hdf5")
# Process in chunks if needed
for freq_idx in range(len(freqs)):
field_slice = sim_data["field_monitor"].Ex.isel(f=freq_idx)
run.log(step=freq_idx, field_max=float(np.abs(field_slice).max()))Simulation Divergence
# Log divergence info for debugging
try:
data = web.run(sim, task_name="test")
except Exception as e:
run.log(step=0, error=str(e), diverged=True)Next Steps
- Meep Integration — Open-source FDTD alternative
- API Reference — Complete method documentation
- Advanced Usage — Power user patterns