Skip to content

The Hangar

Complete, runnable aircraft — climb in and turn the key. The first squadron ships inside the package as modules; the rest are copy-paste recipes. SI units everywhere, as always.

The shipped squadron

Script Run What it flies
Feature tour python -m ikarus.examples.feature_tour The full airshow: TiO₂ cross metasurface — materials, structure plots, order-resolved efficiencies, field maps, spectrum, circular polarization, HDF5. Output lands in ikarus_tour_output/.
Grating diffraction python -m ikarus.examples.grating_diffraction 1-D TiO₂ binary grating; propagating orders + exit angles vs. wavelength.
Metasurface spectrum python -m ikarus.examples.metasurface_spectrum R/T spectrum of a 2-D meta-atom.
Inverse metamirror python -m ikarus.examples.inverse_metamirror A GA evolves a reflective meta-atom.
Fresnel validation python -m ikarus.examples.validation_fresnel The machine-precision sanity anchor.
Save & load python -m ikarus.examples.save_load Write a result to HDF5 (totals, orders, metadata, fields) and load it back.

Fresnel validation — the trust anchor

Before believing any solver, make it reproduce something you can derive by hand. One interface, analytic answer, fifteen decimal places:

import numpy as np
from ikarus import RCWA

rcwa = RCWA(period_x=1e-6, period_y=1e-6, n_orders=0)   # specular only
rcwa.add_uniform_layer(np.inf, 1.0)      # air
rcwa.add_uniform_layer(np.inf, 1.5)      # glass (constant index)

rcwa.set_source(wavelength=600e-9, theta=0, polarization="linear")
_, _, res = rcwa.simulate()

R_fresnel = ((1.0 - 1.5) / (1.0 + 1.5)) ** 2
print(f"Ikarus  R = {res.R_total:.12f}")
print(f"Fresnel R = {R_fresnel:.12f}")
print(f"|diff| = {abs(res.R_total - R_fresnel):.2e}")   # ~1e-15 

Anti-reflection thin film — the classic

The quarter-wave trick, in eight lines:

import numpy as np
from ikarus import RCWA

n_film, lam0 = 1.23, 550e-9          # ideal AR index ≈ sqrt(1.5)
d = lam0 / (4 * n_film)              # quarter-wave thickness

rcwa = RCWA(period_x=1e-6, period_y=1e-6, n_orders=0)
rcwa.add_uniform_layer(np.inf, 1.0)
rcwa.add_uniform_layer(d, n_film)
rcwa.add_uniform_layer(np.inf, 1.5)

for wl in (450e-9, 550e-9, 650e-9):
    rcwa.set_source(wavelength=wl, theta=0, polarization="linear")
    print(f"{wl*1e9:.0f} nm: R = {rcwa.simulate()[2].R_total:.4f}")
# the minimum sits at 550 nm, as designed

Guided-mode resonance filter — the drama queen

A high-index grating that doubles as a waveguide: at just the right wavelength the light couples in, circulates, and exits as a needle-sharp reflection peak.

import numpy as np
from ikarus import RCWA

period = 880e-9
rcwa = RCWA(period_x=period, period_y=period, resolution=(256, 2), n_orders=(25, 0))

topo = np.zeros((200, 2), dtype=int)
topo[:100, :] = 1                     # 50% duty cycle
rcwa.add_uniform_layer(np.inf, "Air")
rcwa.add_layer(180e-9, topo, ["Si3N4", "Air"])
rcwa.add_uniform_layer(np.inf, "SiO2")

for wl in np.linspace(1.0e-6, 1.1e-6, 11):
    rcwa.set_source(wavelength=wl, theta=0, polarization="linear", linear_pol_angle=0)
    print(f"{wl*1e9:.0f} nm: R = {rcwa.simulate()[2].R_total:.3f}")
    # a narrow resonance spikes inside the band

Inverse design: AR coating

No solid material has the n ≈ 1.21 a glass AR coating wants — so let evolution build one out of structure: a subwavelength Si₃N₄ moth-eye whose fill fraction fakes the unattainable index. Broadband 300–600 nm, worst-case optimized:

import os
for v in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS"):
    os.environ.setdefault(v, "1")        # single-thread BLAS for the tight loop

import numpy as np
from ikarus.inverse import MetaAtom, free, pixels, Target, optimize

atom = MetaAtom(period=180e-9, cover="Air", substrate="SiO2")
atom.add_pattern(topology=pixels(8, 8, symmetry="c4v"),
                 materials=["Air", "Si3N4"], height=free(40e-9, 200e-9))

target = Target.minimize("R", band=(300e-9, 600e-9, 6), worst_case=True)
best = optimize(atom, target, n_orders=6, pop=16, n_gen=10, seed=0)
print(best.report())

coating = best.metaatom
wl = np.linspace(300e-9, 600e-9, 13)
R = []
for w in wl:
    coating.set_source(wavelength=w, theta=0, polarization="linear")
    R.append(coating.simulate()[2].R_total)
print("worst-case R:", f"{max(R)*100:.2f}%")     # ~1.5% vs ~3.8% bare glass
AR coating before/after
An inverse-designed subwavelength Si₃N₄ moth-eye cuts glass reflection from ~3.5–4% (bare) to ~1% across the visible.

Beam deflector — power steering

Maximize power into the +1 reflected order at 1550 nm:

import os
os.environ.setdefault("OMP_NUM_THREADS", "1")
from ikarus.inverse import MetaAtom, free, pixels, Target, optimize

atom = MetaAtom(period=1.2e-6, cover="Air", substrate="SiO2")
atom.add_pattern(topology=pixels(16, 4, symmetry="mirror_y"),
                 materials=["Air", "Si"], height=free(0.2e-6, 0.6e-6))

best = optimize(atom, Target.maximize("R", order=(1, 0), at=1550e-9),
                n_orders=(12, 4), pop=40, n_gen=30)
print(best.report())

Save & load results

A simulation is expensive; its result shouldn't evaporate when the script ends. Ikarus writes results to a self-describing HDF5 file — totals, per-order efficiencies, exit angles, the full geometry/source metadata, and (optionally) reconstructed fields — that any HDF5 tool (h5py, h5ls, HDFView) can read, and that loads back into a plain dict with no RCWA object required.

Needs the io extra

pip install "ikarus-rcwa[io]" (h5py).

Save the most recent (or a supplied) result. include picks what goes in — add "fields" to store a reconstructed cross-section too:

import numpy as np
from ikarus import RCWA, shapes

period, N = 500e-9, 96
rcwa = RCWA(period_x=period, period_y=period, resolution=(N, N), n_orders=(8, 8))
rcwa.add_uniform_layer(np.inf, "Air")
rcwa.add_layer(220e-9, shapes.circle(radius=0.3, grid_shape=(N, N)), ["Air", "TiO2"])
rcwa.add_uniform_layer(np.inf, "SiO2")
rcwa.set_source(wavelength=600e-9, theta=10, polarization="linear")
_, _, result = rcwa.simulate()

rcwa.save_results("metasurface.h5",
                  include=["T", "R", "metadata", "fields"], result=result)

Load it back later — anywhere, no solver state needed:

from ikarus import RCWA

data = RCWA.load_results("metasurface.h5")        # a nested dict

print(f"R = {data['R_total']:.4f}   T = {data['T_total']:.4f}")
meta = data["metadata"]
print("period:", meta["period_x"], "  source:", meta["source"]["wavelength"])

# per-order data round-trips too:
p, q, t = data["order_p"], data["order_q"], data["T_orders"]
for i in np.argsort(-t)[:3]:
    print(f"order ({p[i]:+d},{q[i]:+d}): T = {t[i]:.4f}")

Running the shipped python -m ikarus.examples.save_load prints:

computed:  R = 0.1114   T = 0.8886
saved   -> ikarus_save_load_output/metasurface.h5  (418 KB)

file contents: R, R_orders, R_total, T, T_orders, T_total, energy_balance,
fields/xz/E, fields/xz/H, fields/xz/coord_x, fields/xz/coord_z, metadata,
order_p, order_q, phi_out_ref, phi_out_trn, theta_out_ref, theta_out_trn

loaded:    R_total = 0.1114   T_total = 0.8886
geometry:  period = 500 nm, n_orders = (8, 8)
source:    lambda = 600 nm, theta = 10 deg, pol = linear
brightest transmitted orders:
   (+0,+0): T = 0.2751
   (-1,+0): T = 0.1946
   (+1,+0): T = 0.1726

round-trip verified: loaded values match the originals exactly.

What's in the file

Group / dataset Contents
R_total, T_total, energy_balance scalar totals
R, T complex zero-order coefficients (or a co/cross group for circular polarization)
R_orders, T_orders per-order efficiencies
order_p, order_q the (p, q) order labels
theta_out_*, phi_out_* per-order exit angles (deg)
metadata JSON: periods, n_orders, resolution, the layer stack, and the source
fields/... reconstructed E/H + coordinates (only if "fields" was included)

Sweeps and archives

To archive a whole Sweep, save each point in a loop (save_results(f"run_{i}.h5", result=res)), or stack the arrays you care about (res.R_total, res.axes) with numpy.savez. For a single design, one HDF5 file with include=["T","R","metadata","fields"] captures everything needed to reproduce and re-plot it later. Full API: Tools → HDF5 I/O.


Continue exploring: Flight School for the step-by-step versions · API Reference for every knob · Need for Speed for making big runs fast.