ExamplesMeep Examples

Absorbed Power Density

Compute the absorbed power density in a dielectric cylinder illuminated by a plane wave

Absorbed Power Density

This example computes the absorbed power density in a SiO₂ cylinder illuminated by a plane wave. It demonstrates how to use DFT fields to calculate spatially-resolved absorption.

Reference: Based on the Meep absorbed power density tutorial.


Physics Background

Power Absorption

The absorbed power density in a lossy material is given by:

$$ P_{abs}(x,y) = 2\pi f \cdot \text{Im}[\mathbf{E}^* \cdot \mathbf{D}] $$

where:

  • f is the frequency
  • E is the electric field
  • D is the displacement field
  • Im denotes the imaginary part

Absorption Length

The absorption length L_abs characterizes how deeply light penetrates:

$$ L_{abs} = \frac{\lambda}{Im(n)} $$


Simulation Overview

This simulation:

  1. Creates a 2D cell with a SiO₂ cylinder
  2. Illuminates it with a plane wave
  3. Computes DFT fields inside the cylinder
  4. Calculates absorbed power density from E and D fields
  5. Validates by comparing with flux box measurement

Configuration Parameters

ParameterValueDescription
resolution100Pixels per μm
dpml1.0PML thickness
r1.0Cylinder radius (μm)
wavelength1.0Illumination wavelength
materialSiO₂Cylinder material

Complete Code

"""
Absorbed Power Density Simulation with OptixLog Integration

Computes the absorbed power density in a SiO2 cylinder illuminated by a plane wave.
"""

import os
import meep as mp
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
import numpy as np
from meep.materials import SiO2
from optixlog import Optixlog


def main():
    api_key = os.getenv("OPTIX_API_KEY", "your_api_key_here")
    
    client = Optixlog(api_key=api_key)
    project = client.project(name="MeepExamples", create_if_not_exists=True)
    
    # Simulation parameters
    resolution = 100
    dpml = 1.0
    r = 1.0  # cylinder radius
    dair = 2.0  # air padding
    wvl = 1.0
    fcen = 1 / wvl
    
    run = project.run(
        name="absorbed_power_density_SiO2",
        config={
            "simulation_type": "absorbed_power_density",
            "material": "SiO2",
            "resolution": resolution,
            "cylinder_radius": r,
            "wavelength": wvl,
        }
    )
    
    # Log initial parameters
    run.log(step=0, resolution=resolution, wavelength=wvl, cylinder_radius=r)

    # Setup simulation
    pml_layers = [mp.PML(thickness=dpml)]
    s = 2 * (dpml + dair + r)
    cell_size = mp.Vector3(s, s)

    # Planewave source
    sources = [
        mp.Source(
            mp.GaussianSource(fcen, fwidth=0.1 * fcen, is_integrated=True),
            center=mp.Vector3(-0.5 * s + dpml),
            size=mp.Vector3(0, s),
            component=mp.Ez,
        )
    ]

    symmetries = [mp.Mirror(mp.Y)]
    geometry = [mp.Cylinder(material=SiO2, center=mp.Vector3(), radius=r, height=mp.inf)]

    sim = mp.Simulation(
        resolution=resolution,
        cell_size=cell_size,
        boundary_layers=pml_layers,
        sources=sources,
        k_point=mp.Vector3(),
        symmetries=symmetries,
        geometry=geometry,
    )

    # Add DFT fields monitor
    dft_fields = sim.add_dft_fields(
        [mp.Dz, mp.Ez],
        fcen, 0, 1,
        center=mp.Vector3(),
        size=mp.Vector3(2 * r, 2 * r),
        yee_grid=True,
    )

    # Flux box for validation
    flux_box = sim.add_flux(
        fcen, 0, 1,
        mp.FluxRegion(center=mp.Vector3(x=-r), size=mp.Vector3(0, 2 * r), weight=+1),
        mp.FluxRegion(center=mp.Vector3(x=+r), size=mp.Vector3(0, 2 * r), weight=-1),
        mp.FluxRegion(center=mp.Vector3(y=+r), size=mp.Vector3(2 * r, 0), weight=-1),
        mp.FluxRegion(center=mp.Vector3(y=-r), size=mp.Vector3(2 * r, 0), weight=+1),
    )

    sim.run(until_after_sources=100)

    # Calculate absorbed power density
    Dz = sim.get_dft_array(dft_fields, mp.Dz, 0)
    Ez = sim.get_dft_array(dft_fields, mp.Ez, 0)
    absorbed_power_density = 2 * np.pi * fcen * np.imag(np.conj(Ez) * Dz)

    # Compare with flux measurement
    dxy = 1 / resolution**2
    absorbed_power = np.sum(absorbed_power_density) * dxy
    absorbed_flux = mp.get_fluxes(flux_box)[0]
    err = abs(absorbed_power - absorbed_flux) / absorbed_flux
    
    print(f"📊 Absorbed power (DFT fields): {absorbed_power:.6f}")
    print(f"📊 Absorbed flux (flux box): {absorbed_flux:.6f}")
    print(f"📊 Relative error: {err:.2e}")

    # Log results
    run.log(step=2,
            absorbed_power_dft=absorbed_power,
            absorbed_flux=absorbed_flux,
            relative_error=err,
            max_power_density=float(np.amax(absorbed_power_density)))

    # Plot cell structure
    fig1, ax1 = plt.subplots(figsize=(10, 8))
    sim.plot2D(ax=ax1)
    ax1.set_title("Simulation Cell with SiO2 Cylinder")
    run.log_matplotlib("cell_structure", fig1)
    plt.close(fig1)

    # Plot absorbed power density map
    fig2, ax2 = plt.subplots(figsize=(10, 8))
    x = np.linspace(-r, r, Dz.shape[0])
    y = np.linspace(-r, r, Dz.shape[1])
    
    im = ax2.pcolormesh(
        x, y,
        np.transpose(absorbed_power_density),
        cmap="inferno_r",
        shading="gouraud",
        vmin=0,
        vmax=np.amax(absorbed_power_density),
    )
    ax2.set_xlabel("x (μm)")
    ax2.set_ylabel("y (μm)")
    ax2.set_aspect("equal")
    
    labs = wvl / np.imag(np.sqrt(SiO2.epsilon(fcen)[0][0]))
    ax2.set_title(f"Absorbed Power Density\nSiO2 Labs(λ={wvl} μm) = {labs:.2f} μm")
    plt.colorbar(im, ax=ax2, label="Power Density (a.u.)")
    plt.tight_layout()
    
    run.log_matplotlib("power_density_map", fig2)
    plt.close(fig2)

    # Log final summary
    run.log(step=3,
            total_absorbed_power=absorbed_power,
            absorption_length_um=labs,
            simulation_completed=True)
    
    print(f"\n✅ Simulation complete!")


if __name__ == "__main__":
    main()

Key Concepts

DFT Field Monitors

Use add_dft_fields to store time-averaged field data at specific frequencies:

dft_fields = sim.add_dft_fields(
    [mp.Dz, mp.Ez],  # Components to store
    fcen, 0, 1,       # Frequency, df, nfreq
    center=mp.Vector3(),
    size=mp.Vector3(2 * r, 2 * r),
    yee_grid=True,    # Store on Yee grid locations
)

Extracting DFT Arrays

After simulation, extract the complex field arrays:

Dz = sim.get_dft_array(dft_fields, mp.Dz, 0)
Ez = sim.get_dft_array(dft_fields, mp.Ez, 0)

Power Density Calculation

absorbed_power_density = 2 * np.pi * fcen * np.imag(np.conj(Ez) * Dz)

Validation with Flux Box

Use a flux box to validate the integrated absorption:

flux_box = sim.add_flux(
    fcen, 0, 1,
    mp.FluxRegion(center=mp.Vector3(x=-r), size=mp.Vector3(0, 2*r), weight=+1),
    mp.FluxRegion(center=mp.Vector3(x=+r), size=mp.Vector3(0, 2*r), weight=-1),
    # ... additional faces
)

Expected Results

Power Density Map

The absorbed power density shows:

  • Higher absorption near the illuminated surface (left side)
  • Exponential decay into the material
  • Shadow region on the back side

Validation

The relative error between DFT integration and flux box should be < 1%.


OptixLog Integration

Logged Metrics

MetricDescription
absorbed_power_dftTotal power from DFT integration
absorbed_fluxTotal power from flux box
relative_errorAgreement between methods
max_power_densityPeak absorption location
absorption_length_umMaterial absorption length

Logged Plots

  1. cell_structure — Simulation geometry visualization
  2. power_density_map — Spatially-resolved absorption

Running the Example

export OPTIX_API_KEY="your_api_key_here"
python absorbed_power_density.py

Further Reading

On this page