Advanced Features

Frequency-Domain Monitors

DFT Monitors

Compute frequency-domain fields on-the-fly without storing all time steps:

from prismo import DFTMonitor

# Define frequencies of interest
wavelengths = np.linspace(1.5e-6, 1.6e-6, 11)
frequencies = 299792458.0 / wavelengths

# Create DFT monitor
dft = DFTMonitor(
    center=(4e-6, 0, 0),
    size=(0, 2e-6, 0),
    frequencies=frequencies.tolist(),
    components=['Ex', 'Ey', 'Ez'],
)

sim.add_monitor(dft)
sim.run(time=50e-15)

# Get frequency-domain data
field_freq = dft.get_frequency_data('Ex', frequency_index=5)
intensity = dft.get_intensity('Ex', frequency_index=5)
spectrum = dft.get_power_spectrum('Ex')

Flux Monitors

Calculate electromagnetic power flow through surfaces:

from prismo import FluxMonitor

flux = FluxMonitor(
    center=(4e-6, 0, 0),
    size=(0, 2e-6, 0),
    direction='x',  # Power flow direction
    frequencies=frequencies.tolist(),
)

sim.add_monitor(flux)
sim.run(time=50e-15)

# Get power flow
time_array, power_array = flux.get_time_domain_power()
freq_power = flux.get_frequency_domain_power()
transmission = flux.get_transmission()

Mode Solver

Computing Waveguide Modes

from prismo import ModeSolver

# Define waveguide cross-section
x = np.linspace(-2e-6, 2e-6, 100)
y = np.linspace(-1e-6, 1e-6, 80)

# Create permittivity profile
epsilon = np.ones((len(x), len(y)))  # Background
# Add waveguide core (e.g., Silicon)
for i, xi in enumerate(x):
    for j, yj in enumerate(y):
        if abs(xi) < 0.25e-6 and abs(yj) < 0.11e-6:
            epsilon[i, j] = 11.68  # Si at 1550nm

# Solve for modes
mode_solver = ModeSolver(
    wavelength=1.55e-6,
    x=x,
    y=y,
    epsilon=epsilon
)

modes = mode_solver.solve(num_modes=3, mode_type='TE')

# Access mode properties
fundamental = mode_solver.get_mode(0)
print(f"Effective index: {fundamental.neff.real:.4f}")

# Mode fields are available
Ex, Ey, Ez = fundamental.Ex, fundamental.Ey, fundamental.Ez
Hx, Hy, Hz = fundamental.Hx, fundamental.Hy, fundamental.Hz

Mode Expansion Monitors

Decompose fields into mode coefficients:

from prismo import ModeExpansionMonitor

# First, solve for modes
modes = mode_solver.solve(num_modes=2)

# Create mode expansion monitor
mode_monitor = ModeExpansionMonitor(
    center=(4e-6, 0, 0),
    size=(0, 2e-6, 0),
    modes=modes,
    direction='x',
    frequencies=frequencies.tolist(),
)

sim.add_monitor(mode_monitor)
sim.run(time=50e-15)

# Get mode coefficients
coeff_0 = mode_monitor.get_mode_coefficient(mode_index=0, domain='frequency')
power_0 = mode_monitor.get_mode_power(mode_index=0)

S-Parameter Extraction

Multi-Port S-Parameters

from prismo import SParameterAnalyzer, export_touchstone

# Create analyzer for 2-port device
s_analyzer = SParameterAnalyzer(
    num_ports=2,
    frequencies=frequencies,
    reference_impedance=50.0
)

# Add measurement data (from flux or mode monitors)
s_analyzer.add_port_data(
    port_index=1,
    excitation_port=0,
    power_forward=transmitted_power,
    power_backward=reflected_power
)

# Access S-parameters
s11 = s_analyzer.get_s_parameter(0, 0)  # Reflection
s21 = s_analyzer.get_s_parameter(1, 0)  # Transmission

# Calculate metrics
insertion_loss = s_analyzer.get_insertion_loss_db(1, 0)
return_loss = s_analyzer.get_return_loss_db(0)

# Check reciprocity
error = s_analyzer.check_reciprocity()

Export to Touchstone

For integration with circuit simulators:

export_touchstone(
    filename="device.s2p",
    frequencies=frequencies,
    s_matrix=s_analyzer.s_matrix,
    z0=50.0,
    comments=["Silicon waveguide", "1500-1600 nm"]
)

Data Export

CSV Export

from prismo import CSVExporter

exporter = CSVExporter(output_dir="./results")

# Export S-parameters
exporter.export_sparameters(
    filename="device_sparams",
    frequencies=frequencies,
    sparameters={'S11': s11, 'S21': s21},
    metadata={'device': 'waveguide', 'date': '2025-10-17'}
)

# Export spectrum
exporter.export_spectrum(
    filename="transmission",
    frequencies=frequencies,
    spectrum=transmission_data
)

Parquet Export

More efficient for large datasets:

from prismo import ParquetExporter

exporter = ParquetExporter(
    output_dir="./results",
    compression='snappy'  # or 'gzip', 'lz4', 'zstd'
)

# Same API as CSV
exporter.export_sparameters(...)
exporter.export_spectrum(...)

# Read back with Polars
import polars as pl
df = pl.read_parquet("results/device_sparams.parquet")

Parameter Sweeps

Automate design space exploration:

from prismo import ParameterSweep, SweepParameter

def run_simulation(params):
    # Run simulation with params['width'], params['height']
    # Return results dict
    return {'transmission': 0.85, 'bandwidth': 50e12}

sweep = ParameterSweep(
    parameters=[
        SweepParameter('width', np.linspace(0.4e-6, 0.6e-6, 11), unit='m'),
        SweepParameter('height', np.linspace(0.2e-6, 0.3e-6, 6), unit='m'),
    ],
    simulation_func=run_simulation,
    output_dir="./sweep_results",
    parallel=True,  # Use multi-core
    num_workers=4
)

# Run sweep
results = sweep.run()

# Find optimal parameters
optimal_params, optimal_results = sweep.find_optimal('transmission', maximize=True)

# Save results
sweep.save_results("sweep_results.json")

# Visualize
sweep.plot_sweep_1d('width', ['transmission'], save_path="sweep_1d.png")

Lumerical Import

Import FSP Files

from prismo import FSPParser

# Parse Lumerical project file
parser = FSPParser("my_project.fsp")
project = parser.parse()

# Inspect contents
print(f"Geometries: {len(project.geometries)}")
print(f"Sources: {len(project.sources)}")
print(f"Monitors: {len(project.monitors)}")

# Convert to Prismo simulation
sim = parser.to_prismo_simulation()

# Export summary
parser.export_summary("project_summary.json")

Import Material Database

from prismo import import_lumerical_material

# Import material from Lumerical file
material = import_lumerical_material("path/to/Silicon.txt", "Si_imported")

# Add to Prismo library
prismo.add_material('Silicon_imported', material)