ExamplesMeep Examples

MPB Band Structure Tutorial

Introduction to photonic band structure calculations with MPB

MPB Band Structure Tutorial

This tutorial introduces photonic band structure calculations using MPB (MIT Photonic Bands), which is integrated with Meep.

MPB vs Meep: MPB computes frequency-domain eigenmodes efficiently, while Meep performs time-domain simulations. Use MPB for band structures and mode profiles.


Physics Background

Photonic Band Structure

In a periodic dielectric structure, light propagates as Bloch waves:

$$ \mathbf{H}{\mathbf{k}}(\mathbf{r}) = e^{i\mathbf{k}\cdot\mathbf{r}} \mathbf{u}{\mathbf{k}}(\mathbf{r}) $$

The eigenvalue equation determines allowed frequencies ω(k):

$$ \nabla \times \frac{1}{\epsilon(\mathbf{r})} \nabla \times \mathbf{H} = \frac{\omega^2}{c^2} \mathbf{H} $$

Band Gaps

Frequency ranges with no propagating modes are called photonic band gaps. These enable:

  • Perfect mirrors
  • Waveguides
  • Cavities with high Q

Simulation Overview

Define Unit Cell

Specify the primitive cell and lattice vectors.

Create Geometry

Place dielectric objects in the unit cell.

Define K-Path

Choose high-symmetry path through Brillouin zone.

Compute Bands

Solve for eigenfrequencies at each k-point.


Configuration Parameters

ParameterValueDescription
resolution32Grid resolution
num_bands8Number of bands to compute
geometry_latticeSquareLattice type

Complete Code

"""
MPB Band Structure Tutorial with OptixLog Integration

Introduction to photonic band structure calculations.
"""

import os
import meep as mp
from meep import mpb
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
import numpy as np
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)
    
    # Parameters
    num_bands = 8
    resolution = 32
    
    run = project.run(
        name="mpb_band_structure_tutorial",
        config={
            "simulation_type": "mpb_band_structure",
            "resolution": resolution,
            "num_bands": num_bands,
            "lattice_type": "square",
            "rod_radius": 0.2,
            "rod_epsilon": 12
        }
    )
    
    run.log(step=0, resolution=resolution, num_bands=num_bands)
    
    # Define square lattice
    geometry_lattice = mp.Lattice(size=mp.Vector3(1, 1))
    
    # Dielectric rod at center
    r = 0.2  # rod radius
    eps = 12  # rod permittivity (silicon)
    
    geometry = [
        mp.Cylinder(
            radius=r,
            material=mp.Medium(epsilon=eps)
        )
    ]
    
    # High-symmetry k-points for square lattice
    # Γ (0,0) → X (0.5,0) → M (0.5,0.5) → Γ (0,0)
    k_points = [
        mp.Vector3(),           # Γ
        mp.Vector3(0.5, 0),     # X
        mp.Vector3(0.5, 0.5),   # M
        mp.Vector3(),           # Γ
    ]
    
    k_points_interp = mp.interpolate(16, k_points)
    
    # Create ModeSolver
    ms = mpb.ModeSolver(
        geometry_lattice=geometry_lattice,
        geometry=geometry,
        resolution=resolution,
        num_bands=num_bands,
        k_points=k_points_interp,
    )
    
    print("⚡ Computing TM band structure...")
    ms.run_tm()
    tm_freqs = ms.all_freqs
    
    run.log(step=1, phase="tm_bands_computed", num_k_points=len(k_points_interp))
    
    print("⚡ Computing TE band structure...")
    ms.run_te()
    te_freqs = ms.all_freqs
    
    run.log(step=2, phase="te_bands_computed")
    
    # Process results
    k_dist = []
    current_dist = 0
    prev_k = k_points_interp[0]
    
    for k in k_points_interp:
        dk = mp.Vector3(k.x - prev_k.x, k.y - prev_k.y)
        current_dist += np.sqrt(dk.x**2 + dk.y**2)
        k_dist.append(current_dist)
        prev_k = k
    
    # High-symmetry point positions
    hs_points = [0]
    for i, k in enumerate(k_points_interp):
        if any(k.x == kp.x and k.y == kp.y for kp in k_points[1:-1]):
            if i > 0:
                hs_points.append(k_dist[i])
    hs_points.append(k_dist[-1])
    hs_labels = ['Γ', 'X', 'M', 'Γ']
    
    # Create band structure plot
    fig, axes = plt.subplots(1, 2, figsize=(14, 6))
    
    # TM bands
    for band_idx in range(num_bands):
        band = [tm_freqs[k_idx][band_idx] for k_idx in range(len(k_points_interp))]
        axes[0].plot(k_dist, band, 'b-', linewidth=1.5)
    
    axes[0].set_xlabel('Wave Vector')
    axes[0].set_ylabel('Frequency (c/a)')
    axes[0].set_title('TM Band Structure')
    axes[0].set_xlim(0, k_dist[-1])
    axes[0].set_ylim(0, 0.8)
    
    # Add high-symmetry labels
    for pos in hs_points:
        axes[0].axvline(pos, color='gray', linestyle='--', alpha=0.5)
    axes[0].set_xticks(hs_points)
    axes[0].set_xticklabels(hs_labels)
    axes[0].grid(True, alpha=0.3, axis='y')
    
    # TE bands
    for band_idx in range(num_bands):
        band = [te_freqs[k_idx][band_idx] for k_idx in range(len(k_points_interp))]
        axes[1].plot(k_dist, band, 'r-', linewidth=1.5)
    
    axes[1].set_xlabel('Wave Vector')
    axes[1].set_ylabel('Frequency (c/a)')
    axes[1].set_title('TE Band Structure')
    axes[1].set_xlim(0, k_dist[-1])
    axes[1].set_ylim(0, 0.8)
    
    for pos in hs_points:
        axes[1].axvline(pos, color='gray', linestyle='--', alpha=0.5)
    axes[1].set_xticks(hs_points)
    axes[1].set_xticklabels(hs_labels)
    axes[1].grid(True, alpha=0.3, axis='y')
    
    plt.tight_layout()
    run.log_matplotlib("band_structure", fig)
    plt.close(fig)
    
    # Find band gaps
    tm_gap = find_gap(tm_freqs)
    te_gap = find_gap(te_freqs)
    
    run.log(step=3,
            tm_gap_size=float(tm_gap['size']) if tm_gap else 0,
            te_gap_size=float(te_gap['size']) if te_gap else 0,
            simulation_completed=True)
    
    if tm_gap:
        print(f"📊 TM gap: {tm_gap['size']:.4f} at ω = {tm_gap['mid']:.4f}")
    if te_gap:
        print(f"📊 TE gap: {te_gap['size']:.4f} at ω = {te_gap['mid']:.4f}")
    
    print(f"\n✅ Band structure calculation complete!")


def find_gap(all_freqs):
    """Find the largest complete band gap"""
    num_bands = len(all_freqs[0])
    
    for band in range(num_bands - 1):
        # Get max of lower band and min of upper band
        lower_max = max(freqs[band] for freqs in all_freqs)
        upper_min = min(freqs[band + 1] for freqs in all_freqs)
        
        if upper_min > lower_max:
            gap_size = (upper_min - lower_max) / ((upper_min + lower_max) / 2)
            return {
                'size': gap_size,
                'bottom': lower_max,
                'top': upper_min,
                'mid': (upper_min + lower_max) / 2,
                'bands': (band, band + 1)
            }
    
    return None


if __name__ == "__main__":
    main()

Key Concepts

Lattice Definition

geometry_lattice = mp.Lattice(size=mp.Vector3(1, 1))

K-Point Path

High-symmetry points for square lattice:

  • Γ = (0, 0) — Zone center
  • X = (0.5, 0) — Edge center
  • M = (0.5, 0.5) — Corner

ModeSolver

ms = mpb.ModeSolver(
    geometry_lattice=geometry_lattice,
    geometry=geometry,
    resolution=resolution,
    num_bands=num_bands,
    k_points=k_points,
)

ms.run_tm()  # TM polarization
ms.run_te()  # TE polarization

Expected Results

Square Lattice of Rods

With r=0.2, ε=12 (silicon rods in air):

  • TM gap between bands 1-2
  • No complete TE gap (typically)

Band Gap Location

TM gap appears around ω ≈ 0.3-0.4 (c/a).


OptixLog Integration

Logged Metrics

MetricDescription
resolutionMPB grid resolution
num_bandsBands computed
tm_gap_sizeTM gap (fractional)
te_gap_sizeTE gap (fractional)

Logged Plots

  • band_structure — TM and TE band diagrams

Further Reading

On this page