RCWA & Results¶
The user-facing façade. It collects the layer stack, the illumination and the numerical settings, then orchestrates the stateless solver into diffraction efficiencies, complex coefficients and real-space fields. If you only learn two objects in Ikarus, learn these.
RCWA¶
RCWA(period_x, period_y, resolution=32, n_orders=25,
dtype=np.complex128, materials=None, convergence_tol=1e-6)
Purpose. Create a solver bound to one unit cell.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
period_x, period_y |
float |
— | Unit-cell periods (m); must be positive. |
resolution |
int or (int, int) |
32 |
Real-space sampling grid for the convolution matrices. Raised internally to ≥ 4*n_orders + 1. |
n_orders |
int or (int, int) |
25 |
Max positive harmonic order M per axis. Total harmonics \(P=(2M_x+1)(2M_y+1)\). |
dtype |
numpy dtype | complex128 |
Working precision. |
materials |
MaterialLibrary |
None |
Custom library; defaults to the shared default_library. |
convergence_tol |
float |
1e-6 |
Default tolerance for auto_converge. |
Raises. ValueError if a period is non-positive.
Properties¶
| Property | Description |
|---|---|
n_orders |
(Mx, My) tuple; settable (resets the convergence cache). |
shapes |
The shape-primitive library: rcwa.shapes.circle(...). |
last_solution |
The most recent FieldSolution, or None. |
layers |
The list of Layer objects. |
source |
The current Source, or None. |
Building the stack¶
add_uniform_layer(height, material, name="") -> Layer¶
Append a uniform (single-material) layer. Use height=np.inf for the
semi-infinite cover (first) and substrate (last).
rcwa.add_uniform_layer(np.inf, "Air") # cover
rcwa.add_uniform_layer(220e-9, "TiO2") # interior
rcwa.add_uniform_layer(np.inf, "SiO2") # substrate
add_layer(height, topology, materials, resolution=None, name="") -> Layer¶
Append a patterned layer. topology is an integer (Nx, Ny) array; materials
is the list it indexes into.
import numpy as np
topo = np.zeros((128, 128), dtype=int)
topo[32:96, 32:96] = 1 # a square of material[1]
rcwa.add_layer(200e-9, topo, ["Air", "Si"]) # 0 -> Air, 1 -> Si
Defining the source¶
set_source(**kwargs) -> Source¶
Create or update the illumination. The first call must include
wavelength. Later calls update only what you pass and retain the rest —
the backbone of every sweep. Accepts every Source field:
wavelength, theta, phi, polarization, linear_pol_angle.
rcwa.set_source(wavelength=600e-9, theta=0, polarization="linear", linear_pol_angle=90)
rcwa.set_source(theta=15) # keeps wavelength + polarization
Solving¶
simulate(auto_converge="never", converge_tol=None, max_orders=200, verbose=False) -> (T, R, result)¶
Run a simulation; return the zero-order coefficients plus the full
SimulationResult.
auto_converge |
Behaviour |
|---|---|
"never" (default) |
Use the current n_orders. |
"once" |
Find and cache a converged n_orders (later calls reuse it). |
"always" |
Re-converge on every call. |
Returns. (T, R, result) — T/R are the zero-order coefficients
(complex scalar for linear polarization, {"co", "cross"} for circular);
result is a SimulationResult.
Energy-balance warnings
simulate emits a RuntimeWarning if R+T exceeds 1.01 (likely
unconverged) or 1.5 (numerical breakdown — reduce n_orders or raise
resolution). See Troubleshooting.
Field extraction¶
get_fields(z_positions=None, plane="xy", nx=64, ny=64, x_position=0.0, y_position=0.0) -> dict¶
Reconstruct real-space E/H fields from the last simulation (solving first
if needed). Returns a dict of FieldMap.
plane="xy"→ one map per depthzinz_positions(meters;z=0at the cover/first-layer interface, increasing into the stack).plane="xz"/"yz"→ a single cross-section sweeping the whole stack at fixedy_position/x_position.
xy = rcwa.get_fields(z_positions=[80e-9], plane="xy", nx=128, ny=128)
xz = rcwa.get_fields(plane="xz", nx=160, y_position=rcwa.period_y / 2)["xz"]
Each map carries the structure permittivity on its grid (FieldMap.eps) so
plots can overlay material outlines.
Visualization¶
visualize_structure(plane="xz", layer_index=None, **kwargs)¶
Plot the layer stack (plane="xz") or a patterned layer's topology
(plane="xy", with layer_index). Requires matplotlib. See
Fields & Visualization.
visualize_fields(field_map=None, component="intensity", **kwargs)¶
Plot a reconstructed field map (computes an xz map if none is supplied).
Result I/O¶
save_results(path, include=("T", "R", "metadata"), result=None)¶
Write the most recent (or supplied) result to HDF5. include may also contain
"fields". Requires h5py.
load_results(path) (staticmethod)¶
Load an Ikarus HDF5 result file into a nested dict.
SimulationResult¶
The full flight report, returned as the third element of simulate().
Attributes¶
| Attribute | Type | Description |
|---|---|---|
T, R |
complex or dict | Zero-order coefficient(s); abs(.)**2 = specular efficiency. |
T_total, R_total |
float |
Total transmitted / reflected power. |
T_orders, R_orders |
ndarray |
Per-order efficiencies, aligned with orders. |
orders |
(ndarray, ndarray) |
(p, q) integer order labels. |
theta_out_trn, phi_out_trn |
ndarray |
Transmitted exit angles (deg); NaN if evanescent. |
theta_out_ref, phi_out_ref |
ndarray |
Reflected exit angles (deg). |
energy_balance |
float |
R_total + T_total. |
solution |
FieldSolution |
Underlying modal solution (for field reconstruction). |
Properties¶
| Property | Description |
|---|---|
R_phase, T_phase |
Phase (rad) of the zero-order coefficient — a float (linear) or {"co","cross"} dict (circular). |
Methods¶
order_index(p, q) -> int¶
Flat index of harmonic (p, q) into the *_orders / angle arrays. Raises
KeyError if the order is outside the truncated set.
i = result.order_index(0, 0)
print("specular T:", result.T_orders[i], "at", result.theta_out_trn[i], "deg")
ip1 = result.order_index(1, 0) # +1 reflected order of a grating
print("R(+1):", result.R_orders[ip1])
Best practices¶
energy_balancefirst, always: a lossless structure must read 1 within convergence error.- Specular metasurface design →
T/R(+T_phase/R_phase). Grating order steering →T_orders/R_orderswithorder_index. - Exit-angle arrays carry
NaNfor evanescent orders —np.isfinite(...)before plotting.