Metamaterials Tutorials

Step-by-step guides from basic concepts to advanced applications

1
Beginner

Getting Started with Split-Ring Resonators

Learn the basics of SRR design and understand magnetic resonance in metamaterials.

Step 1: Understanding the LC Model

An SRR behaves like an LC resonant circuit. The ring provides inductance L, while the gap provides capacitance C. The resonance frequency is:

f₀ = 1 / (2π√LC)

Step 2: Design Your First SRR

from metamaterials import UnitCellDesigner

# Create an SRR for 5 THz operation
srr = UnitCellDesigner(
    cell_type='srr',
    radius=50e-6,      # 50 μm radius
    track_width=8e-6,  # 8 μm track
    gap_width=4e-6     # 4 μm gap
)

# Calculate resonance
f_res = srr.calculate_resonance()
print(f"Resonance: {f_res/1e12:.2f} THz")

# Get circuit parameters
L = srr.get_inductance()
C = srr.get_capacitance()
print(f"L = {L*1e9:.2f} nH, C = {C*1e15:.3f} fF")

Step 3: Analyze the Response

Near resonance, the SRR produces a strong magnetic dipole moment, leading to negative effective permeability μ(ω) in a narrow frequency band above ω₀.

Key Takeaway

Reducing SRR dimensions increases the resonance frequency. To reach optical frequencies, nanoscale fabrication is required.

2
Intermediate

Achieving Negative Refractive Index

Combine electric and magnetic resonances to create a negative index material.

Step 1: The Dual Requirement

For n < 0, you need both ε < 0 and μ < 0 simultaneously. This requires overlapping the electric response (from wires) with the magnetic response (from SRRs).

Step 2: Design the Composite Structure

from metamaterials import UnitCellDesigner, DispersionModel

# Design SRR + wire hybrid
hybrid = UnitCellDesigner(
    cell_type='hybrid',
    radius=80e-6,
    track_width=10e-6,
    gap_width=5e-6
)

# Create dispersion model
model = DispersionModel(
    model_type='drude_lorentz',
    epsilon_inf=1.0,
    omega_p=2*np.pi*15e12,    # Electric plasma frequency
    omega_0=2*np.pi*5e12,     # Magnetic resonance
    gamma_e=2*np.pi*0.5e12,   # Electric damping
    gamma_m=2*np.pi*0.3e12,   # Magnetic damping
    F=0.4                      # Filling factor
)

Step 3: Find the NIM Band

# Sweep frequency and find where both ε' < 0 and μ' < 0
frequencies = np.linspace(1e12, 15e12, 1000)
nim_band = model.find_nim_band((1e12, 15e12))

print(f"NIM band: {nim_band[0]/1e12:.1f} - {nim_band[1]/1e12:.1f} THz")

# Calculate figure of merit
n_min, fom = model.calculate_fom()
print(f"Minimum n' = {n_min:.2f}, FOM = {fom:.1f}")

Important

High losses (low FOM) are a major challenge for NIMs. The FOM = |Re(n)|/Im(n) should be > 3 for practical applications.

3
Advanced

Designing a Transformation Optics Cloak

Create an invisibility cloak using coordinate transformations.

Step 1: Define the Transformation

A cylindrical cloak compresses the region r ∈ [0, b] into r' ∈ [a, b], leaving a hidden region r < a where light cannot penetrate.

from metamaterials import CloakDesigner

# Design cylindrical cloak
cloak = CloakDesigner(
    shape='cylindrical',
    inner_radius=1.0,   # Hide objects smaller than 1λ
    outer_radius=2.5,   # Cloak shell extends to 2.5λ
    n_layers=20
)

# Get required material parameters
r_values = np.linspace(1.0, 2.5, 20)
for r in r_values:
    params = cloak.get_material_profile(r)
    print(f"r={r:.2f}λ: εr={params['eps_r']:.2f}, εθ={params['eps_theta']:.2f}")

Step 2: Simulate the Cloaking Effect

# Run full-wave simulation
field = cloak.simulate_field(
    wavelength=10e-6,
    angle=0,            # Normal incidence
    polarization='TE'
)

# Calculate scattering
total_scat = cloak.calculate_scattering()
print(f"Total scattering: {total_scat:.1f} dB")

# Compare to uncloaked object
uncloaked_scat = -10 * np.log10(np.pi * 1.0**2)  # Geometric cross-section
reduction = uncloaked_scat - total_scat
print(f"Scattering reduction: {reduction:.1f} dB")

Challenge

The material parameters at r = a become singular (εr → 0, εθ → ∞). Practical cloaks use reduced parameter sets that sacrifice bandwidth for realizability.

4
Advanced

Perfect Absorber Optimization

Design a metamaterial absorber with >99% absorption through impedance matching.

Step 1: Impedance Matching Condition

Perfect absorption occurs when the metamaterial impedance matches free space (Z = Z₀) while having non-zero imaginary parts of ε and μ for energy dissipation.

Step 2: MIM Structure Design

from metamaterials import PerfectAbsorber

# Design metal-insulator-metal absorber
absorber = PerfectAbsorber(
    structure='patch_array',
    period=20e-6,
    patch_size=15e-6,
    dielectric_thickness=500e-9,
    metal_thickness=100e-9,
    metal='gold',
    dielectric='sio2'
)

# Optimize for target frequency
target_freq = 10e12
absorber.optimize(target_freq, bandwidth='narrowband')

# Get absorption spectrum
freqs, absorption = absorber.calculate_spectrum()
peak_idx = np.argmax(absorption)
print(f"Peak absorption: {absorption[peak_idx]*100:.1f}% at {freqs[peak_idx]/1e12:.2f} THz")

Step 3: Angular Performance

# Check angular stability
angles = np.linspace(0, 80, 17)
angular_response = absorber.angular_response(target_freq, angles)

for angle, abs_te, abs_tm in zip(angles, angular_response['TE'], angular_response['TM']):
    print(f"θ={angle:2.0f}°: TE={abs_te*100:.1f}%, TM={abs_tm*100:.1f}%")
5
Expert

FDTD Simulation with Dispersive Materials

Implement full-wave time-domain simulation including material dispersion.

from metamaterials import FDTDSolver, DispersionModel

# Setup dispersive NIM model
nim_model = DispersionModel(
    model_type='drude_lorentz',
    omega_p=2*np.pi*15e12,
    omega_0=2*np.pi*5e12,
    gamma=2*np.pi*0.3e12
)

# Initialize FDTD with dispersive material support
fdtd = FDTDSolver(
    grid_size=(512, 512),
    dx=50e-9,
    pml_layers=30,
    dispersive=True
)

# Add Gaussian pulse source
fdtd.add_source(
    'gaussian',
    position=(100, 256),
    params={
        'f0': 5e12,
        'bandwidth': 2e12,
        'polarization': 'Ez'
    }
)

# Add NIM slab
fdtd.add_structure(
    geometry={'type': 'slab', 'x1': 200, 'x2': 312},
    material=nim_model
)

# Add field monitors
fdtd.add_monitor('reflection', position=150)
fdtd.add_monitor('transmission', position=350)

# Run simulation
fdtd.run(
    n_steps=10000,
    callbacks=[
        ('field_snapshot', 500, 'Ez'),
        ('energy_evolution', 100)
    ]
)

# Extract S-parameters
sparams = fdtd.calculate_sparams()
print(f"|S11| = {sparams['S11_mag']:.3f}, |S21| = {sparams['S21_mag']:.3f}")