Quick Start

This guide will get you running your first FDTD simulation in minutes.

Your First Simulation

Let’s create a simple 2D plane wave simulation using the TFSF (Total-Field/Scattered-Field) formulation:

from prismo import Simulation, TFSFSource, FieldMonitor

# Create a 2D simulation domain
sim = Simulation(
    size=(2.0e-6, 2.0e-6, 0.0),     # 2µm × 2µm (note: in meters!)
    resolution=40e6,                 # 40 points per meter (40 ppµm)
    boundary_conditions="pml",       # Perfectly Matched Layer boundaries
    pml_layers=10,                   # 10 grid points for PML
)

# Add a plane wave source
source = TFSFSource(
    center=(1.0e-6, 1.0e-6, 0.0),   # Center of domain
    size=(1.0e-6, 1.0e-6, 0.0),     # TFSF region size
    direction="+x",                  # Propagate in +x direction
    polarization="y",                # E-field polarized in y
    frequency=150e12,                # 150 THz (2µm wavelength)
    pulse=False,                     # Continuous wave
    amplitude=1.0,
)
sim.add_source(source)

# Add a field monitor to record data
monitor = FieldMonitor(
    center=(1.0e-6, 1.0e-6, 0.0),
    size=(1.8e-6, 1.8e-6, 0.0),     # Monitor region
    components=["Ey"],               # Record Ey component
    time_domain=True,
)
sim.add_monitor(monitor)

# Run the simulation
sim_time = 50e-15  # 50 femtoseconds
sim.run(sim_time)

# Get and visualize results
time_points, ey_data = monitor.get_time_data("Ey")
print(f"Captured {len(time_points)} time steps")
print(f"Field shape: {ey_data.shape}")

Understanding the Output

The simulation will:

  1. Create a computational grid with PML absorbing boundaries

  2. Inject a plane wave using TFSF formulation (artifact-free)

  3. Record the Ey field component at every time step

  4. Return time-domain data as NumPy arrays

The ey_data array has shape (time_steps, ny, nx) where:

  • time_steps: Number of time steps recorded

  • ny, nx: Spatial grid dimensions

Basic Visualization

Add visualization to see the field evolution:

import matplotlib.pyplot as plt
import numpy as np

# Plot the final field distribution
plt.figure(figsize=(10, 8))
vmax = np.max(np.abs(ey_data)) * 0.8

plt.imshow(
    ey_data[-1],  # Last time step
    cmap="RdBu_r",
    vmin=-vmax,
    vmax=vmax,
    origin="lower",
    extent=[0, 2.0, 0, 2.0],  # Physical dimensions in µm
)

plt.colorbar(label="Ey (V/m)")
plt.xlabel("x (µm)")
plt.ylabel("y (µm)")
plt.title("Electric Field at Final Time")
plt.tight_layout()
plt.show()

Key Concepts

Units

Prismo uses SI units throughout:

  • Length: meters (m)

  • Time: seconds (s)

  • Frequency: Hertz (Hz)

  • Fields: V/m (electric), A/m (magnetic)

💡 Tip: For convenience, use scientific notation:

  • 1e-6 for micrometers (µm)

  • 1e-15 for femtoseconds (fs)

  • 1e12 for THz

Grid Resolution

The resolution parameter defines spatial discretization:

  • Higher resolution → better accuracy, longer computation

  • Rule of thumb: Use at least 20 points per wavelength

  • For λ = 2µm at 40 ppµm: 80 points per wavelength ✓

Boundary Conditions

  • PML (Perfectly Matched Layer): Absorbing boundaries (default)

  • Prevents reflections from domain edges

  • Typically use 8-12 PML layers

Time Step

The time step dt is automatically calculated based on the Courant stability condition:

print(f"Time step: {sim.dt:.3e} seconds")

Common Patterns

Adding Multiple Sources

# Add multiple dipole sources
from prismo import ElectricDipole

dipole1 = ElectricDipole(
    position=(0.5e-6, 1.0e-6, 0.0),
    polarization="y",
    frequency=150e12,
    pulse=True,
    pulse_width=10e-15,
)
sim.add_source(dipole1)

dipole2 = ElectricDipole(
    position=(1.5e-6, 1.0e-6, 0.0),
    polarization="y",
    frequency=150e12,
    pulse=True,
    pulse_width=10e-15,
)
sim.add_source(dipole2)

Frequency-Domain Monitoring

monitor = FieldMonitor(
    center=(1.0e-6, 1.0e-6, 0.0),
    size=(1.8e-6, 1.8e-6, 0.0),
    components=["Ey", "Ez"],
    time_domain=True,
    frequencies=[150e12, 200e12],  # Monitor at specific frequencies
)
sim.add_monitor(monitor)

# After simulation
freq_field = monitor.get_frequency_data("Ey", 150e12)

Progress Monitoring

def progress_callback(step, total_steps, sim_time, elapsed_time):
    if step % 100 == 0:
        print(f"Progress: {step}/{total_steps} ({step/total_steps*100:.1f}%)")

sim.run(sim_time, progress_callback=progress_callback)

Complete Example Scripts

Check out the example scripts in the examples/ directory:

  • tfsf_plane_wave.py: TFSF plane wave demonstration

  • basic_waveguide.py: Gaussian beam in waveguide

  • plane_wave_validation.py: Validation against analytical solutions

Run them with:

python examples/tfsf_plane_wave.py

Next Steps

Troubleshooting

Simulation Takes Too Long

  • Reduce grid resolution

  • Decrease simulation time

  • Use smaller computational domain

Instabilities or NaN Values

  • Check Courant condition (should be < 1.0)

  • Ensure sources aren’t too strong

  • Verify PML boundaries are thick enough

Memory Issues

  • Reduce grid size or resolution

  • Limit monitor regions

  • Use selective component monitoring

For more help, see the FAQ or open an issue.