ceviche

repository·master·Indexed 19 days ago

https://github.com/fancompute/ceviche

A library for multi-frequency Finite-Difference Frequency-Domain (FDFD) and Finite-Difference Time-Domain (FDTD) simulations. It is specifically designed for nanophotonic device optimization, mode conversion analysis, and simulating electromagnetic field propagation. Key features include support for Bloch periodic boundary conditions, Perfectly Matched Layers (PML), waveguide mode insertion via `insert_mode`, and spectral power analysis.

Tokens
3.1K
Snippets
12
Records
13
Agent score
65%

What's inside ceviche

  1. Implement Bloch periodic boundary conditions

    master

    When simulating waves with angled incidence in a periodic domain, standard periodic boundary conditions will cause discontinuities at the boundaries. To simulate a plane wave that correctly wraps around the boundary with a phase shift, use the bloch_phases argument in fdfd_hz.

    To implement this, calculate the phase shift required for the $y$-direction based on the wave vector component $k_y$ and the total length of the domain $L_y$: $\text{phase} = k_y \times L_y$.

    # ky is the y-component of the wave vector
    # Ly is the total length of the simulation in the y direction
    F_Bloch = fdfd_hz(omega, dL, eps_r, npml, bloch_phases=[0, ky * Ly])
    
    # Solve with the Bloch-enabled object
    Ex, Ey, Hz = F_Bloch.solve(source_angle)
  2. Setup multi-frequency simulation parameters

    master

    A multi-frequency FDFD simulation requires defining both physical and numerical parameters. Key parameters include:

    • omega: Central frequency (rad/s).
    • omega_mod: Modulation frequency (rad/s).
    • Nsb: Number of sidebands on each side of the central wavelength (total bands = 2*Nsb + 1).
    • dl: Spatial resolution in meters.
    • Npml: Number of pixels in the Perfectly Matched Layers (PML).
    • delta: Modulation depth profile.
    • phi: Modulation phase profile.
  3. Simulate a plane wave with angled incidence

    master

    To simulate a plane wave at an angle, you must construct a source that incorporates a spatial phase variation.

    1. Calculate the wave vector components: $k_x = k_0 \cos(\theta)$ $k_y = k_0 \sin(\theta)$
    2. Create a phase array: Generate a vector representing the $y$ positions across the simulation and compute $\exp(i \cdot k_y \cdot y)$.
    3. Apply to source: Multiply the source amplitude by this phase array.
    4. TFSF-like effect: To prevent unwanted reflections (e.g., a left-traveling wave when you want a right-traveling wave), add a secondary source panel immediately behind the primary source with a phase offset of $\exp(-i \cdot k_x \cdot dL - i\pi)$.
    # 1. Compute wave vector
    kx = k0 * np.cos(angle_rad)
    ky = k0 * np.sin(angle_rad)
    
    # 2. Get y positions
    Ly = Ny * dL
    y_vec = np.linspace(-Ly / 2, Ly / 2, Ny)
    
    # 3. Create angled source
    source_amp_y = np.exp(1j * ky * y_vec)
    source_angle = np.zeros(grid_shape, dtype=complex)
    source_angle[source_loc_x, :] = source_amp * source_amp_y
    
    # 4. Add cancellation source (TFSF effect)
    source_angle[source_loc_x-1, :] = source_angle[source_loc_x, :] * np.exp(-1j * kx * dL - 1j * np.pi)
  4. Example: Simulating a waveguide and splitter

    master

    This recipe demonstrates a complete workflow:

    1. Loading permittivity data.
    2. Initializing the fdtd object.
    3. Defining a Gaussian pulsed source.
    4. Running measure_fields to get time-domain responses.
    5. Using plot_spectral_power to analyze the results in the frequency domain.
    from ceviche import fdtd
    from ceviche.utils import measure_fields, plot_spectral_power
    import autograd.numpy as np
    
    # 1. Setup
    F = fdtd(eps_r, dL=5e-8, npml=[20, 20, 0])
    steps = 10000
    
    # 2. Define Source (Gaussian pulse)
    omega = 2 * np.pi * 3e8 / 2e-6
    omega_sim = omega * F.dt
    gaussian = lambda t: np.exp(-(t - 2000)**2 / 2 / 100**2) * np.cos(omega_sim * t)
    source = lambda t: J_in * 5 * gaussian(t)
    
    # 3. Simulate
    measured = measure_fields(F, source, steps, J_outs)
    
    # 4. Analyze
    plot_spectral_power(measured, dt=F.dt, f_top=1e16)
  5. Measure fields using `measure_fields()`

    master

    The measure_fields utility runs an FDTD simulation and records the field values at specific locations over a set number of time steps.

    Arguments:

    • F: An instance of the fdtd class.
    • source: A function (or lambda) that defines the source term as a function of time t.
    • steps: Total number of time steps to simulate.
    • J_probes: A single array or a list of arrays representing the spatial distribution of the source/probes (e.g., current density J).
    # For a single probe/source
    measured_wg = measure_fields(F_wg, source, steps, J_wg)
    
    # For multiple probes (e.g., multiple output ports)
    measured = measure_fields(F, source, steps, J_outs)
  6. Set up an FDFD simulation with `fdfd_hz`

    master

    To initialize a Finite-Difference Frequency-Domain (FDFD) simulation for the $H_z$ component, use the fdfd_hz function. You must provide the angular frequency, spatial resolution, permittivity distribution, and Perfectly Matched Layer (PML) configuration.

    Parameters:

    • omega: Angular frequency ($\omega$).
    • dL: Spatial resolution (grid spacing in meters).
    • eps_r: A 2D NumPy array representing the relative permittivity ($\epsilon_r$) of the grid.
    • npml: A list/array defining the number of PML pixels in each direction (e.g., [40, 0] for 40 pixels in $x$ and 0 in $y$).
    • bloch_phases (optional): A list of phases applied to the periodic boundaries to implement Bloch boundary conditions.
    from ceviche import fdfd_hz
    
    # Example setup
    F = fdfd_hz(omega, dL, eps_r, npml)
  7. Visualize field profiles with ceviche.viz

    master

    Use ceviche.viz.real to visualize the real part of the electric field (Ez) profiles. This function accepts an outline parameter (usually the permittivity array epsr) to show the physical structure alongside the field distribution.

    import ceviche.viz
    
    # Visualize the Ez profile at the central frequency (index Nsb)
    ceviche.viz.real(Ez[Nsb], outline=epsr, ax=ax[0], cbar=False)
    
    # Visualize the Ez profile at the first sideband (index Nsb + 1)
    ceviche.viz.real(Ez[Nsb + 1], outline=epsr, ax=ax[1], cbar=False)
  8. Perform multi-frequency FDFD simulations with fdfd_mf_ez

    master

    To simulate electromagnetic field propagation across multiple frequencies (including sidebands), use the fdfd_mf_ez function. This function requires the central frequency, spatial resolution, permittivity profile, modulation frequency, modulation depth, modulation phase, number of sidebands, and PML (Perfectly Matched Layer) pixel counts.

    Workflow:

    1. Initialize the domain: Define the permittivity epsr and the modulation profiles delta (depth) and phi (phase).
    2. Prepare the source: Use insert_mode to create a mode at the central frequency and pad the source array with zeros to account for the sidebands (Nsb).
    3. Run simulation: Call fdfd_mf_ez to create a simulation object, then call .solve(source) to compute the electric (Ez) and magnetic (Hx, Hy) fields across all frequency bands.
    from ceviche import fdfd_mf_ez
    from ceviche.modes import insert_mode
    import numpy as np
    
    # ... setup epsr, omega, dl, omega_mod, delta, phi, Nsb, Npml ...
    
    # 1. Create and pad the source
    source_0 = np.array([insert_mode(omega, dl, input_slice.x, input_slice.y, epsr, m=1)])
    pad_left = np.zeros([Nsb, Nx, Ny])
    pad_right = np.zeros([Nsb, Nx, Ny])
    source = np.concatenate((np.concatenate((pad_left, source_0)), pad_right))
    
    # 2. Define and solve simulation
    simulation = fdfd_mf_ez(omega, dl, epsr, omega_mod, delta, phi, Nsb, [Npml, Npml])
    Hx, Hy, Ez = simulation.solve(source)
  9. Solve an FDFD simulation for electromagnetic fields

    master

    Once an FDFD object is initialized, use the .solve() method to compute the electromagnetic fields. You must pass a source array that matches the grid shape of the simulation.

    Returns: Returns a tuple of three arrays: (Ex, Ey, Hz) representing the electric field components and the magnetic field component.

    # Solve for fields using a source array
    Ex, Ey, Hz = F.solve(source)
  10. Initialize an FDTD simulation with `fdtd()`

    master

    To start a Finite-Difference Time-Domain (FDTD) simulation, use the fdtd class. You must provide a permittivity distribution (eps_r) and specify the spatial resolution (dL) and the number of pixels for the Perfectly Matched Layers (npml).

    eps_r should be a 3D array of shape (Nx, Ny, Nz).

    from ceviche import fdtd
    
    # dL: spatial resolution in meters
    # npml: list of PML pixel counts in each direction [x, y, z]
    F = fdtd(eps_r, dL=dL, npml=[20, 20, 0])
  11. Analyze frequency spectra with `get_spectrum()` and `plot_spectral_power()`

    master

    To convert time-domain field measurements into the frequency domain, use get_spectrum() or the visualization utility plot_spectral_power().

    • get_spectrum(series, dt=...): Returns the frequency axis and the spectral values.
    • plot_spectral_power(series, dt=..., f_top=...): Plots the spectral power of a time-series signal. Use f_top to limit the frequency range shown in the plot.
    from ceviche.utils import get_spectrum, plot_spectral_power
    
    # Get frequency and spectrum data
    freq, spect = get_spectrum(measured_wg, dt=F.dt)
    
    # Plot spectral power up to a specific frequency
    plot_spectral_power(measured_wg, dt=F.dt, f_top=15e14)