Author: Priyanuj Bordoloi, Stanford University
Symmetry Breaking and the q-BIC¶
When the symmetry of a metasurface's unit-cell is deliberately broken by a small perturbation (here parameterized by the dimensionless asymmetry parameter $\alpha$ that changes the radii of neighbouring disks), a formerly dark bound-state-in-the-continuum (BIC) acquires a finite radiative linewidth. The resulting leaky mode is called a quasi-BIC (q-BIC). It appears in the transmittance spectrum as a sharp, asymmetric Fano resonance, arising from the interference between the narrow discrete resonance and the broad radiative continuum background.
The radiative Q factor of the q-BIC obeys the inverse-quadratic scaling law for small perturbations:
$$Q_r = \frac{B}{\alpha^{2}}$$
where $B$ is a geometry-dependent constant independent of $\alpha$. This means the resonance becomes arbitrarily sharp as $\alpha \to 0$, approaching the ideal BIC limit. For larger $\alpha$ the scaling breaks down because the perturbation is no longer weak.
Design Inspiration — Hu, Lawrence & Dionne (ACS Photonics 2020)¶
The specific geometry used here, a biperiodic nanodisk lattice in which a central disk and four corner disks form the unit cell, with symmetry broken by a diameter offset $\alpha$ between adjacent disks, is directly inspired by the design introduced by Hu, Lawrence, and Dionne (ACS Photonics 7, 36, 2020). In that work, a biperiodic diamond disk metasurface was designed to support high-Q asymmetric electric ($\mathbf{p}_\alpha$) and magnetic ($\mathbf{m}_\alpha$) dipole resonances in the ultraviolet, achieving optical chirality enhancements exceeding 1000-fold compared to circularly polarized light in free space. Moreover, the achiral design makes the metasurface immune to producing any structural chirality in the far-field emission.
Critically for our choice of material and wavelength range, that paper explicitly identified hafnium oxide (HfO$_2$) as an alternative UV-transparent material to diamond with sufficiently high refractive index ($n \sim 2.1$ at 247 nm) to support Mie resonances, and negligible absorption losses down to $\lambda \sim 220$ nm. We adopt HfO$_2$ as the disk material here because it is more experimentally accessible than diamond; it can be deposited by atomic layer deposition (ALD) with precise thickness control, while still satisfying the low-loss UV transparency requirement that underpins the high-Q resonance design.
The wavelength range of this simulation ($\lambda_0 \approx 247$ nm) sits squarely in the mid-ultraviolet, where the chiroptical absorption features of most small chiral molecules (pharmaceuticals, amino acids) are located, and where Hu et al. demonstrated that biperiodic dielectric metasurfaces can deliver the largest optical chirality enhancements. This study is broken down into 2 sections:
Part 1 — Resonance Finder (example case). Demonstrates the q-BIC resonance
finding methodology on a representative unit-cell geometry: sweep the asymmetry
parameter alpha, fit Fano lineshapes, confirm the Q ∝ α⁻² radiative scaling
law, and visualize near-field and chirality maps.
Part 2 — Geometry Sweep & the Kerker Transition. Independently sweeps the
disk diameter d0 at fixed alpha = 0.05 and shows how the transmittance and
optical chirality evolve. Within the swept range, one geometry sits close to a
(generalized) Kerker-type condition — the disk's electric- and magnetic-dipole
responses become comparable, the q-BIC's far-field transmittance signature
nearly disappears, and the locally confined optical chirality is strongly
enhanced.

0. Imports¶
import numpy as np
import matplotlib.pyplot as plt
import tidy3d as td
import tidy3d.web as web
from tidy3d.plugins.dispersion import FastDispersionFitter
from scipy.signal import find_peaks
from scipy.optimize import curve_fit
1. Background Medium¶
n_air = 1 # refractive index of the background medium
air = td.Medium(permittivity=n_air**2)
2. Shared Geometry, Source, Monitor & Analysis Utilities¶
These functions are identical between the two parts, so they
are defined once here and parameterized explicitly — each part supplies its own
geometry (h, px, py, r0), material (medium), and wavelength config
(freq0, fwidth, freqs, lda0) without sharing values between parts.
def create_disks(r0, h, px, py, alpha, medium):
'''
Build the 5-disk unit cell.
Central disk radius = r1 = r0*(1 - alpha/2)
Corner disk radius = r2 = r0*(1 + alpha/2)
alpha=0 -> all equal (symmetric BIC)
alpha>0 -> broken symmetry (q-BIC)
'''
d0 = r0 * 2
delta = alpha * d0
r1 = (d0 - delta / 2) / 2 # central disk
r2 = (d0 + delta / 2) / 2 # corner disks
centers = [
(0, 0, h / 2), # center
(-px/2, -py/2, h / 2), # lower-left corner
(-px/2, py/2, h / 2), # upper-left corner
( px/2, -py/2, h / 2), # lower-right corner
( px/2, py/2, h / 2), # upper-right corner
]
radii = [r1, r2, r2, r2, r2]
structures = []
for center, radius in zip(centers, radii):
cyl = td.Cylinder(center=center, radius=radius, length=h, axis=2)
structures.append(td.Structure(geometry=cyl, medium=medium))
return structures
def make_circ_sources(freq0, fwidth, source_z):
'''Left circular polarization = Ex + i*Ey (phase = -pi/2 on Ey).'''
pulse = td.GaussianPulse(freq0=freq0, fwidth=fwidth)
pw_x = td.PlaneWave(
source_time=pulse,
size=(td.inf, td.inf, 0),
center=(0, 0, source_z),
direction='-',
pol_angle=0,
)
pulse_y = td.GaussianPulse(freq0=freq0, fwidth=fwidth, phase=-np.pi/2)
pw_y = td.PlaneWave(
source_time=pulse_y,
size=(td.inf, td.inf, 0),
center=(0, 0, source_z),
direction='-',
pol_angle=np.pi/2,
)
return [pw_x, pw_y]
def make_monitors(h, freqs):
'''Flux + XY/YZ field monitors. Depends on h since the XY monitor sits at z = h/2.'''
flux_monitor = td.FluxMonitor(
center=[0, 0, -0.75],
size=[td.inf, td.inf, 0],
freqs=freqs,
name='flux',
)
xy_monitor = td.FieldMonitor(
center=[0, 0, h / 2],
size=[td.inf, td.inf, 0],
freqs=freqs,
name='xy_field',
)
yz_monitor = td.FieldMonitor(
center=[0, 0, h / 2],
size=[0, td.inf, td.inf],
freqs=freqs,
name='yz_field',
)
return [flux_monitor, xy_monitor, yz_monitor]
def make_sim(h, px, py, r0, alpha, medium, freq0, fwidth, freqs, lda0,
source_z, run_time, Lz, include_structures=True):
'''Build simulation for given geometry, material, and wavelength config.
Set include_structures=False for the normalization run.'''
structures = []
if include_structures:
structures += create_disks(r0, h, px, py, alpha, medium)
return td.Simulation(
size=(px, py, Lz),
grid_spec=td.GridSpec.auto(min_steps_per_wvl=20, wavelength=lda0),
structures=structures,
sources=make_circ_sources(freq0, fwidth, source_z),
monitors=make_monitors(h, freqs),
run_time=run_time,
boundary_spec=td.BoundarySpec(
x=td.Boundary.periodic(),
y=td.Boundary.periodic(),
z=td.Boundary.pml(),
),
)
def get_plot_geom(h, px, py, z_pad=0.3):
'''Return (xy_xlim, xy_ylim, y_pad, z_min_yz, z_max_yz, yz_aspect) for a given geometry.'''
xy_xlim = (-px/2 * 1e3, px/2 * 1e3)
xy_ylim = (-py/2 * 1e3, py/2 * 1e3)
y_pad = py / 2 * 1e3
z_min_yz = (-z_pad) * 1e3
z_max_yz = (h + z_pad) * 1e3
yz_aspect = (2 * y_pad) / (z_max_yz - z_min_yz)
return xy_xlim, xy_ylim, y_pad, z_min_yz, z_max_yz, yz_aspect
def draw_disks(ax, r0, h, px, py, alpha, color='cyan', lw=1.0):
'''Draw disk outlines on an XY axis. Coordinates in nm.'''
d0 = r0 * 2
delta = alpha * d0
r1 = (d0 - delta / 2) / 2
r2 = (d0 + delta / 2) / 2
centers_um = [
(0, 0 ),
(-px/2, -py/2 ),
(-px/2, py/2 ),
( px/2, -py/2 ),
( px/2, py/2 ),
]
radii_um = [r1, r2, r2, r2, r2]
t = np.linspace(0, 2*np.pi, 300)
for (cx, cy), r in zip(centers_um, radii_um):
ax.plot((cx + r * np.cos(t)) * 1e3,
(cy + r * np.sin(t)) * 1e3,
color=color, lw=lw, ls='--', alpha=0.8)
def get_E_norm(monitor_data, monitor_ref, freq):
'''Field-amplitude enhancement |E|/|E0|, normalized to the reference run.'''
def E_amp(md):
Ex = md.Ex.sel(f=freq, method='nearest')
Ey = md.Ey.sel(f=freq, method='nearest')
Ez = md.Ez.sel(f=freq, method='nearest')
return np.sqrt(np.abs(Ex)**2 + np.abs(Ey)**2 + np.abs(Ez)**2).squeeze()
E = np.array(E_amp(monitor_data))
E_ref = np.array(E_amp(monitor_ref))
return E / float(E_ref.mean())
def get_chirality(monitor_data, monitor_ref, freq):
'''Local optical chirality density C = -(omega/2c^2) * Im(E* . H),
normalized by the spatial mean of |C| in the reference run.
The prefactor cancels in the ratio but is kept for physical correctness.'''
omega = 2 * np.pi * freq
prefactor = -omega / (2 * td.C_0**2)
def C_field(md):
Ex = md.Ex.sel(f=freq, method='nearest')
Ey = md.Ey.sel(f=freq, method='nearest')
Ez = md.Ez.sel(f=freq, method='nearest')
Hx = md.Hx.sel(f=freq, method='nearest')
Hy = md.Hy.sel(f=freq, method='nearest')
Hz = md.Hz.sel(f=freq, method='nearest')
return (prefactor * np.imag(
np.conj(Ex)*Hx + np.conj(Ey)*Hy + np.conj(Ez)*Hz
)).squeeze()
C = np.array(C_field(monitor_data))
C_ref = np.array(C_field(monitor_ref))
return C / float(np.abs(C_ref).mean())
def get_cos_phi(monitor_data, freq):
'''cos(phi_iE,H) = Im(E*.H) / (|E||H|) — polarization-state indicator in [-1, 1].'''
def fields(md):
Ex = md.Ex.sel(f=freq, method='nearest')
Ey = md.Ey.sel(f=freq, method='nearest')
Ez = md.Ez.sel(f=freq, method='nearest')
Hx = md.Hx.sel(f=freq, method='nearest')
Hy = md.Hy.sel(f=freq, method='nearest')
Hz = md.Hz.sel(f=freq, method='nearest')
EdotH_im = np.imag(np.conj(Ex)*Hx + np.conj(Ey)*Hy + np.conj(Ez)*Hz)
E_amp = np.sqrt(np.abs(Ex)**2 + np.abs(Ey)**2 + np.abs(Ez)**2)
H_amp = np.sqrt(np.abs(Hx)**2 + np.abs(Hy)**2 + np.abs(Hz)**2)
return (EdotH_im / (E_amp * H_amp + 1e-30)).squeeze()
return np.array(fields(monitor_data))
def fano(f, f0, gamma, q_fano, A, offset):
eps = 2 * (f - f0) / gamma
return offset + A * (eps + q_fano)**2 / (eps**2 + 1)
def fit_dips(freqs, ldas, T, prominence, distance, window, Q_MIN, Q_MAX, T_dip_max):
'''Find and fit all dips, return list of successful fit results.'''
dips, _ = find_peaks(-T, prominence=prominence, distance=distance)
fitted = []
for idx in dips:
i_lo = max(0, idx - window)
i_hi = min(len(freqs) - 1, idx + window)
f_win = freqs[i_lo:i_hi]
T_win = T[i_lo:i_hi]
if T_win.min() >= T_dip_max:
print(f' Dip at {ldas[idx]*1e3:.2f} nm — actual T_min = {T_win.min():.3f} >= {T_dip_max}, phantom, skipped')
continue
best_popt, best_res = None, np.inf
for qg in [-3, -1, 0, 1, 3]:
for gg_frac in [4, 8, 16]:
try:
p0 = [freqs[idx],
(freqs[i_hi] - freqs[i_lo]) / gg_frac,
qg, -0.1, T_win.max()]
popt, _ = curve_fit(fano, f_win, T_win, p0=p0, maxfev=20000)
res = np.sum((fano(f_win, *popt) - T_win)**2)
if res < best_res:
best_res, best_popt = res, popt
except Exception:
pass
if best_popt is not None:
Q = best_popt[0] / abs(best_popt[1])
T_at_res = fano(best_popt[0], *best_popt)
if not (Q_MIN <= Q <= Q_MAX):
print(f' Dip at {ldas[idx]*1e3:.2f} nm — Q = {Q:.1f} outside [{Q_MIN}, {Q_MAX}], skipped')
continue
fitted.append(dict(
popt = best_popt,
Q = Q,
T_at_res = T_at_res,
T_min = T_win.min(),
lda_nm = td.C_0 / best_popt[0] * 1e3,
f_win = f_win,
T_win = T_win,
))
print(f' Dip at {ldas[idx]*1e3:.2f} nm — Q = {Q:.1f}, actual T_min = {T_win.min():.3f}')
else:
print(f' Dip at {ldas[idx]*1e3:.2f} nm — fit failed')
# ── Deduplicate: if two fits within 1 nm, keep deeper dip ────────────────
deduped = []
for fit in fitted:
too_close = False
for kept in deduped:
if abs(fit['lda_nm'] - kept['lda_nm']) < 1.0:
too_close = True
if fit['T_min'] < kept['T_min']:
deduped.remove(kept)
deduped.append(fit)
print(f" Deduped: kept {fit['lda_nm']:.2f} nm over {kept['lda_nm']:.2f} nm")
else:
print(f" Deduped: kept {kept['lda_nm']:.2f} nm, dropped {fit['lda_nm']:.2f} nm")
break
if not too_close:
deduped.append(fit)
return deduped
Part 1: qBIC mode generation using lattice perturbation.¶
Demonstrates the q-BIC resonance finding methodology on a representative unit-cell geometry: sweep the asymmetry
parameter alpha, fit Fano lineshapes, confirm the Q ∝ α⁻² radiative scaling law, and visualize near-field and chirality maps.
We implement the biperiodic disk unit cell in HfO$_2$ and sweep the asymmetry parameter $\alpha$ to study the emergence and tunability of the q-BIC resonance. The key parameters are:
| Quantity | Symbol | Value |
|---|---|---|
| Asymmetry parameter | $\alpha$ | swept: 0, 0.05, 0.10, 0.15, 0.20 |
| Unperturbed disk diameter | $d_0$ | 110 nm |
| Unit cell period | $p_x = p_y$ | 210 nm |
| Disk height | $h$ | 60 nm |
| Central wavelength | $\lambda_0$ | 247 nm |
The simulation uses circularly polarized normal-incidence excitation (sum of $x$- and $y$-polarized plane waves with a $90°$ phase shift), consistent with the illumination scheme in Hu et al. We consider this to be LCP. The transmittance is normalized against an empty-cell reference run, and resonances are identified by Fano fitting:
$$T(f) = T_{\rm bg} + A \, \frac{(\varepsilon + q)^2}{\varepsilon^2 + 1}, \quad \varepsilon = \frac{2(f - f_0)}{\gamma}$$
where $f_0$ is the resonance frequency, $\gamma$ is the linewidth (FWHM), $q$ is the Fano asymmetry parameter, and $Q = f_0 / \gamma$ is extracted from the fit.
1. Simulation Parameters¶
Define all fixed simulation parameters for this part. The wavelength
window (240–254 nm) is centred on the expected q-BIC resonance of this example
geometry. alpha_list drives the asymmetry sweep: alpha = 0 is the ideal BIC
(dark mode, no feature in transmission); each nonzero value introduces
progressively stronger symmetry breaking.
# ── Wavelength range ──────────────────────────────────────────────────────────
p1_lda0 = 0.247 # central wavelength (μm)
p1_freq0 = td.C_0 / p1_lda0
p1_ldas = np.linspace(p1_lda0 - 0.007, p1_lda0 + 0.007, 700) # 240–254 nm
p1_freqs = td.C_0 / p1_ldas
p1_fwidth = 0.5 * (np.max(p1_freqs) - np.min(p1_freqs))
# ── Geometry (example case) ───────────────────────────────────────────────────
p1_h = 0.060 # disk height (μm)
p1_px = 0.210 # unit cell period x (μm)
p1_py = p1_px # unit cell period y (μm)
p1_d0 = 0.110 # unperturbed disk diameter (μm)
p1_r0 = p1_d0 / 2 # unperturbed disk radius (μm)
# alpha = 0 -> perfect lattice (BIC, dark mode)
# alpha > 0 -> broken symmetry (q-BIC, bright mode)
alpha_list = [0.0, 0.05, 0.10, 0.15, 0.2] # sweep over asymmetry
# ── Runtime ───────────────────────────────────────────────────────────────────
run_time = 10e-12 # (s) — shared by both parts
Lz = 2.0 # simulation domain height (μm) — shared by both parts
source_z = 0.75 # source z position (μm) — shared by both parts
print(f'Wavelength range: {p1_ldas.min()*1e3:.1f} – {p1_ldas.max()*1e3:.1f} nm')
print(f'Central wavelength: {p1_lda0*1e3:.1f} nm')
print(f'Disk diameter: {p1_d0*1e3:.1f} nm, height: {p1_h*1e3:.1f} nm')
print(f'Unit cell: {p1_px*1e3:.0f} x {p1_py*1e3:.0f} nm')
Wavelength range: 240.0 – 254.0 nm Central wavelength: 247.0 nm Disk diameter: 110.0 nm, height: 60.0 nm Unit cell: 210 x 210 nm
2. Material Dispersion Fit¶
HfO2 is dispersive in the UV across this window, so we fit a
Sellmeier-like pole expansion (up to 4 poles) to the Siefke ALD-HfO2 dataset
using Tidy3D's FastDispersionFitter, restricted to a slightly padded range
around p1_ldas to avoid edge artifacts.
data = np.loadtxt('./misc/HfO2_Siefke_n_k.csv', skiprows=1, delimiter=',')
wl_um = data[:, 0]
n_data = data[:, 1]
k_data = data[:, 2]
wl_min = p1_ldas.min() - 0.010
wl_max = p1_ldas.max() + 0.010
mask = (wl_um >= wl_min) & (wl_um <= wl_max)
wl_sel = wl_um[mask]
n_sel = n_data[mask]
k_sel = k_data[mask]
print(f'Fitting over {mask.sum()} points: {wl_sel.min()*1e3:.1f} – {wl_sel.max()*1e3:.1f} nm')
tmp_fname = 'HfO2_subset_p1.csv'
np.savetxt(tmp_fname, np.column_stack([wl_sel, n_sel, k_sel]),
delimiter=',', header='wl,n,k', comments='')
p1_fitter = FastDispersionFitter.from_file(tmp_fname, skiprows=1, delimiter=',')
p1_hafnia, p1_rms_error = p1_fitter.fit(max_num_poles=4, tolerance_rms=1e-3)
print(f'Dispersion fit RMS error: {p1_rms_error:.5f}')
eps_complex = p1_hafnia.eps_model(p1_freqs)
n_fit = np.real(np.sqrt(eps_complex))
k_fit = np.imag(np.sqrt(eps_complex))
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
ax1.scatter(wl_sel * 1e3, n_sel, s=15, color='steelblue', label='Raw data', zorder=3)
ax1.plot(p1_ldas * 1e3, n_fit, color='tomato', lw=2, label='Fit')
ax1.set_xlabel('Wavelength (nm)'); ax1.set_ylabel('n')
ax1.set_title('HfO2 Real Refractive Index'); ax1.legend()
ax2.scatter(wl_sel * 1e3, k_sel, s=15, color='steelblue', label='Raw data', zorder=3)
ax2.plot(p1_ldas * 1e3, k_fit, color='tomato', lw=2, label='Fit')
ax2.set_xlabel('Wavelength (nm)'); ax2.set_ylabel('k')
ax2.set_title('HfO2 Extinction Coefficient'); ax2.legend()
plt.tight_layout()
plt.show()
Output()
Fitting over 17 points: 231.3 – 262.7 nm
Dispersion fit RMS error: 0.00020
3. Plot Geometry¶
sim_check = make_sim(p1_h, p1_px, p1_py, p1_r0, 0.10, p1_hafnia,
p1_freq0, p1_fwidth, p1_freqs, p1_lda0,
source_z, run_time, Lz)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
sim_check.plot(z=p1_h/2, ax=ax1)
sim_check.plot_grid(z=0, ax=ax1)
ax1.set_title('XY cross-section (mid-disk)')
sim_check.plot(x=0, ax=ax2)
ax2.set_title('XZ cross-section')
plt.tight_layout()
plt.show()
p1_xy_xlim, p1_xy_ylim, p1_y_pad, p1_z_min_yz, p1_z_max_yz, p1_yz_aspect = \
get_plot_geom(p1_h, p1_px, p1_py, z_pad=0.3)
4. Run Simulations¶
Submit two Batch objects — one for the actual runs (disks present) and
one for the normalization runs (empty cell). Results are cached to
data/p1_actual/ and data/p1_norm/ so they can be reloaded without re-running
if the kernel restarts.
p1_sims_actual = {f'alpha={a:.2f}': make_sim(p1_h, p1_px, p1_py, p1_r0, a, p1_hafnia,
p1_freq0, p1_fwidth, p1_freqs, p1_lda0,
source_z, run_time, Lz,
include_structures=True) for a in alpha_list}
p1_sims_norm = {f'alpha={a:.2f}': make_sim(p1_h, p1_px, p1_py, p1_r0, a, p1_hafnia,
p1_freq0, p1_fwidth, p1_freqs, p1_lda0,
source_z, run_time, Lz,
include_structures=False) for a in alpha_list}
p1_batch_actual = web.Batch(simulations=p1_sims_actual, verbose=True)
p1_batch_norm = web.Batch(simulations=p1_sims_norm, verbose=True)
print('Running actual simulations (Part 1)...')
p1_results_actual = p1_batch_actual.run(path_dir='data/p1_actual')
print('Running normalization simulations (Part 1)...')
p1_results_norm = p1_batch_norm.run(path_dir='data/p1_norm')
Running actual simulations (Part 1)...
Output()
13:57:56 EDT Started working on Batch containing 5 tasks.
13:58:01 EDT Maximum FlexCredit cost: 0.870 for the whole batch.
Use 'Batch.real_cost()' to get the billed FlexCredit cost after completion.
Output()
14:02:37 EDT Batch complete.
Output()
Running normalization simulations (Part 1)...
14:03:31 EDT Started working on Batch containing 5 tasks.
14:03:36 EDT Maximum FlexCredit cost: 0.207 for the whole batch.
Use 'Batch.real_cost()' to get the billed FlexCredit cost after completion.
Output()
14:03:56 EDT Batch complete.
# ── Extract normalized transmittance for each alpha ───────────────────────────
p1_T_dict = {}
for key in p1_sims_actual.keys():
T_actual = np.array(p1_results_actual[key]['flux'].flux)
T_ref = np.array(p1_results_norm[key]['flux'].flux)
p1_T_dict[key] = T_actual / T_ref
print('p1_T_dict keys:', list(p1_T_dict.keys()))
for key, T in p1_T_dict.items():
print(f' {key}: T range [{T.min():.3f}, {T.max():.3f}]')
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:5: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p1_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:6: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p1_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:5: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p1_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:6: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p1_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:5: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p1_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:6: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p1_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:5: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p1_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:6: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p1_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:5: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p1_results_actual[key]['flux'].flux)
p1_T_dict keys: ['alpha=0.00', 'alpha=0.05', 'alpha=0.10', 'alpha=0.15', 'alpha=0.20'] alpha=0.00: T range [0.944, 0.970] alpha=0.05: T range [0.006, 1.011] alpha=0.10: T range [0.003, 1.006] alpha=0.15: T range [0.000, 1.004] alpha=0.20: T range [0.000, 1.003]
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/1473335795.py:6: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p1_results_norm[key]['flux'].flux)
5. Fano Fitting and Waterfall Plot¶
Q_MIN = 10
Q_MAX = 1000000
T_DIP_MAX = 0.5
PROMINENCE = 0.02
DISTANCE = 10
WINDOW = 60
OFFSET_STEP = 1.2
fig, ax = plt.subplots(figsize=(8, 6))
for k, alpha_val in enumerate(alpha_list):
key = f'alpha={alpha_val:.2f}'
T = p1_T_dict[key]
offset = k * OFFSET_STEP
ax.plot(p1_ldas * 1e3, T + offset, lw=1.8, color='steelblue')
ax.text(p1_ldas[-1] * 1e3 + 0.3, T[-1] + offset,
f'$\\alpha$={alpha_val:.2f}', fontsize=9, va='center', color='steelblue')
if alpha_val > 0.0:
fitted = fit_dips(p1_freqs, p1_ldas, T, PROMINENCE, DISTANCE, WINDOW,
Q_MIN, Q_MAX, T_DIP_MAX)
for fit in fitted:
popt = fit['popt']
Q = fit['Q']
lda_nm = fit['lda_nm']
T_at_res = fit['T_at_res']
f_win = fit['f_win']
f_dense = np.linspace(f_win.min(), f_win.max(), 500)
l_dense = td.C_0 / f_dense * 1e3
ax.plot(l_dense, fano(f_dense, *popt) + offset,
color='tomato', lw=1.5, ls='--')
ax.annotate(f'Q={Q:.0f}\n ',
xy=(lda_nm, T_at_res + offset),
xytext=(lda_nm + 0.4, T_at_res + offset + 0.03),
fontsize=7, color='tomato',
arrowprops=dict(arrowstyle='->', color='tomato', lw=0.6))
ax.set_xlabel('Wavelength (nm)', fontsize=12)
ax.set_ylabel('Transmittance', fontsize=12)
ax.set_title('Transmittance Waterfall — Alpha Sweep (Example Geometry)', fontsize=13)
ax.set_xlim(p1_ldas.min() * 1e3, p1_ldas.max() * 1e3 + 2)
ax.set_yticks([])
plt.tight_layout()
plt.savefig('p1_transmittance_waterfall.png', dpi=300, bbox_inches='tight')
plt.show()
Dip at 242.52 nm — Q = 7950.5, actual T_min = 0.006 Dip at 250.37 nm — actual T_min = 0.953 >= 0.5, phantom, skipped Dip at 252.24 nm — Q = 8454.2, actual T_min = 0.013 Dip at 242.66 nm — Q = 2003.0, actual T_min = 0.003 Dip at 249.01 nm — actual T_min = 0.963 >= 0.5, phantom, skipped Dip at 252.20 nm — Q = 1917.8, actual T_min = 0.011 Dip at 242.82 nm — Q = 903.4, actual T_min = 0.000 Dip at 248.25 nm — actual T_min = 0.974 >= 0.5, phantom, skipped Dip at 252.30 nm — Q = 857.7, actual T_min = 0.000 Dip at 243.06 nm — Q = 524.9, actual T_min = 0.001 Dip at 252.46 nm — Q = 493.2, actual T_min = 0.000
6. Mode Grouping and Q ∝ α⁻² Scaling¶
p1_all_resonances = []
for alpha_val in alpha_list:
if alpha_val == 0.0:
continue
key = f'alpha={alpha_val:.2f}'
T = p1_T_dict[key]
fitted = fit_dips(p1_freqs, p1_ldas, T, PROMINENCE, DISTANCE, WINDOW,
Q_MIN, Q_MAX, T_DIP_MAX)
for fit in fitted:
p1_all_resonances.append((alpha_val, fit['lda_nm'], fit['Q']))
CLUSTER_TOL_NM = 2.0
p1_groups = []
for r in p1_all_resonances:
alpha_val, lda_nm, Q = r
placed = False
for group in p1_groups:
group_lda_mean = np.mean([x[1] for x in group])
if abs(lda_nm - group_lda_mean) < CLUSTER_TOL_NM:
group.append(r)
placed = True
break
if not placed:
p1_groups.append([r])
p1_groups = sorted(p1_groups, key=lambda g: np.mean([x[1] for x in g]))
p1_mode_alphas = [[x[0] for x in sorted(g, key=lambda x: x[0])] for g in p1_groups]
p1_mode_ldas = [[x[1] for x in sorted(g, key=lambda x: x[0])] for g in p1_groups]
p1_mode_Qs = [[x[2] for x in sorted(g, key=lambda x: x[0])] for g in p1_groups]
colors = ['steelblue', 'tomato', 'seagreen', 'darkorange']
markers = ['o', 's', '^', 'D']
def inv_square(alpha, C):
return C / alpha**2
alpha_chiral = 0.05
p1_key_chiral = f'alpha={alpha_chiral:.2f}'
p1_res_chiral = []
for group in p1_groups:
for (a, lda_nm, Q) in group:
if a == alpha_chiral:
p1_res_chiral.append((lda_nm, Q))
print(f'Found {len(p1_groups)} mode group(s)')
for i, group in enumerate(p1_groups):
print(f' Mode {i+1}: mean lda = {np.mean([x[1] for x in group]):.2f} nm, {len(group)} alpha point(s)')
print(f'res_chiral for alpha={alpha_chiral}: {p1_res_chiral}')
Dip at 242.52 nm — Q = 7950.5, actual T_min = 0.006 Dip at 250.37 nm — actual T_min = 0.953 >= 0.5, phantom, skipped Dip at 252.24 nm — Q = 8454.2, actual T_min = 0.013 Dip at 242.66 nm — Q = 2003.0, actual T_min = 0.003 Dip at 249.01 nm — actual T_min = 0.963 >= 0.5, phantom, skipped Dip at 252.20 nm — Q = 1917.8, actual T_min = 0.011 Dip at 242.82 nm — Q = 903.4, actual T_min = 0.000 Dip at 248.25 nm — actual T_min = 0.974 >= 0.5, phantom, skipped Dip at 252.30 nm — Q = 857.7, actual T_min = 0.000 Dip at 243.06 nm — Q = 524.9, actual T_min = 0.001 Dip at 252.46 nm — Q = 493.2, actual T_min = 0.000 Found 2 mode group(s) Mode 1: mean lda = 242.79 nm, 4 alpha point(s) Mode 2: mean lda = 252.27 nm, 4 alpha point(s) res_chiral for alpha=0.05: [(np.float64(242.52777156578216), np.float64(7950.547687949862)), (np.float64(252.23545915388863), np.float64(8454.162496843213))]
fig, axes = plt.subplots(1, len(p1_groups), figsize=(5 * len(p1_groups), 4), sharey=False)
if len(p1_groups) == 1:
axes = [axes]
for i, (alphas_g, ldas_g, Qs_g) in enumerate(zip(p1_mode_alphas, p1_mode_ldas, p1_mode_Qs)):
ax = axes[i]
alphas_arr = np.array(alphas_g)
Qs_arr = np.array(Qs_g)
lda_mean = np.mean(ldas_g)
c = colors[i % len(colors)]
m = markers[i % len(markers)]
pert_nm = alphas_arr * p1_d0 * 1e3
pert_dense = np.linspace(pert_nm.min() * 0.8, pert_nm.max() * 1.2, 200)
ax.scatter(pert_nm, Qs_arr, color=c, marker=m, s=60, zorder=3, label='Simulated Q')
if len(alphas_arr) >= 2:
try:
popt, _ = curve_fit(inv_square, alphas_arr, Qs_arr, p0=[1.0])
C_fit = popt[0]
alpha_dense = pert_dense / (p1_d0 * 1e3)
ax.plot(pert_dense, inv_square(alpha_dense, C_fit),
color=c, lw=2, ls='--', label=f'$Q={C_fit:.3f}/\\alpha^2$')
except Exception:
print(f'Mode {i+1}: inverse square fit failed')
ax.set_xlabel('Perturbation size $\\delta$ (nm)', fontsize=12)
ax.set_ylabel('Quality factor Q', fontsize=12)
ax.set_title(f'Mode {i+1} | $\\lambda$ = {lda_mean:.1f} nm', fontsize=12)
ax.legend(fontsize=9)
fig.suptitle('Q factor vs Perturbation Size (Example Geometry)', fontsize=13)
plt.tight_layout()
plt.savefig('p1_Q_vs_alpha_modes.png', dpi=300, bbox_inches='tight')
plt.show()
7. Near-Field Maps at Resonance¶
for i, group in enumerate(p1_groups):
group_sorted = sorted(group, key=lambda x: x[0])
n_cols = len(group_sorted)
lda_mean = np.mean([x[1] for x in group_sorted])
fig, axes = plt.subplots(2, n_cols, figsize=(4 * n_cols, 8))
if n_cols == 1:
axes = axes.reshape(2, 1)
for j, (alpha_val, lda_nm, Q) in enumerate(group_sorted):
key = f'alpha={alpha_val:.2f}'
freq_res = td.C_0 / (lda_nm / 1e3)
ax_xy = axes[0, j]
ax_yz = axes[1, j]
E_xy = get_E_norm(p1_results_actual[key]['xy_field'],
p1_results_norm[key]['xy_field'], freq_res)
x_coords = np.array(p1_results_actual[key]['xy_field'].Ex
.sel(f=freq_res, method='nearest').x) * 1e3
y_coords = np.array(p1_results_actual[key]['xy_field'].Ex
.sel(f=freq_res, method='nearest').y) * 1e3
im1 = ax_xy.pcolormesh(x_coords, y_coords, E_xy.T, cmap='hot', shading='auto')
fig.colorbar(im1, ax=ax_xy, label=r'$|E|/|E_0|$')
draw_disks(ax_xy, p1_r0, p1_h, p1_px, p1_py, alpha_val, color='cyan', lw=1.2)
uc = plt.Rectangle((p1_xy_xlim[0], p1_xy_ylim[0]),
p1_xy_xlim[1] - p1_xy_xlim[0],
p1_xy_ylim[1] - p1_xy_ylim[0],
edgecolor='white', facecolor='none', lw=1.0, ls=':')
ax_xy.add_patch(uc)
ax_xy.set_xlim(p1_xy_xlim); ax_xy.set_ylim(p1_xy_ylim)
ax_xy.set_xlabel('x (nm)', fontsize=10); ax_xy.set_ylabel('y (nm)', fontsize=10)
ax_xy.set_aspect('equal')
ax_xy.set_title(f'XY | $\\alpha$={alpha_val:.2f}\n'
f'$\\lambda$={lda_nm:.1f} nm Q={Q:.0f}', fontsize=10)
E_yz = get_E_norm(p1_results_actual[key]['yz_field'],
p1_results_norm[key]['yz_field'], freq_res)
y_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex
.sel(f=freq_res, method='nearest').y) * 1e3
z_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex
.sel(f=freq_res, method='nearest').z) * 1e3
im2 = ax_yz.pcolormesh(y_coords_yz, z_coords_yz, E_yz.T, cmap='hot', shading='auto')
fig.colorbar(im2, ax=ax_yz, label=r'$|E|/|E_0|$')
ax_yz.axhline(0, color='cyan', lw=1.2, ls='--', alpha=0.8, label='disk bottom')
ax_yz.axhline(p1_h * 1e3, color='cyan', lw=1.2, ls='--', alpha=0.8, label='disk top')
ax_yz.axhline(0, color='white', lw=0.8, ls=':', alpha=0.5, label='substrate')
ax_yz.axvline(-p1_y_pad, color='white', lw=1.0, ls='--', alpha=0.6, label='unit cell wall')
ax_yz.axvline( p1_y_pad, color='white', lw=1.0, ls='--', alpha=0.6)
ax_yz.set_xlim(-p1_y_pad, p1_y_pad)
ax_yz.set_ylim(p1_z_min_yz, p1_z_max_yz)
ax_yz.set_aspect(p1_yz_aspect)
ax_yz.set_xlabel('y (nm)', fontsize=10); ax_yz.set_ylabel('z (nm)', fontsize=10)
ax_yz.set_title('YZ | x = 0', fontsize=10)
if j == 0:
ax_yz.legend(fontsize=7, loc='upper right')
fig.suptitle(f'Mode {i+1} | $\\lambda$ $\\approx$ {lda_mean:.1f} nm', fontsize=13)
plt.tight_layout()
plt.savefig(f'p1_fields_mode{i+1}.png', dpi=300, bbox_inches='tight')
plt.show()
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:21: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:41: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:43: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:21: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:41: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:43: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:21: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:41: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:43: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:21: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:41: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:43: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:21: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:41: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:43: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:21: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:41: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:43: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:21: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:41: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:43: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:21: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[key]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:142: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E = np.array(E_amp(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:143: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword E_ref = np.array(E_amp(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:41: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3851934302.py:43: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[key]['yz_field'].Ex
8. Optical Chirality Density Maps¶
For alpha = 0.05 we compute the local optical chirality density,
normalized by the spatial mean of |C| in the reference run.
print(f'Resonances for {p1_key_chiral}:')
for j, (lda_nm, Q) in enumerate(p1_res_chiral):
print(f' Res {j+1}: {lda_nm:.2f} nm Q={Q:.1f}')
for j, (lda_nm, Q) in enumerate(p1_res_chiral):
freq_res = td.C_0 / (lda_nm / 1e3)
C_xy = get_chirality(p1_results_actual[p1_key_chiral]['xy_field'],
p1_results_norm[p1_key_chiral]['xy_field'], freq_res)
x_coords = np.array(p1_results_actual[p1_key_chiral]['xy_field'].Ex
.sel(f=freq_res, method='nearest').x) * 1e3
y_coords = np.array(p1_results_actual[p1_key_chiral]['xy_field'].Ex
.sel(f=freq_res, method='nearest').y) * 1e3
C_yz = get_chirality(p1_results_actual[p1_key_chiral]['yz_field'],
p1_results_norm[p1_key_chiral]['yz_field'], freq_res)
y_coords_yz = np.array(p1_results_actual[p1_key_chiral]['yz_field'].Ex
.sel(f=freq_res, method='nearest').y) * 1e3
z_coords_yz = np.array(p1_results_actual[p1_key_chiral]['yz_field'].Ex
.sel(f=freq_res, method='nearest').z) * 1e3
clim = max(np.abs(C_xy).max(), np.abs(C_yz).max())
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
im1 = ax1.pcolormesh(x_coords, y_coords, C_xy.T,
cmap='RdBu', shading='auto', vmin=-clim, vmax=clim)
fig.colorbar(im1, ax=ax1, label=r'$C / \langle|C_0|\rangle$')
draw_disks(ax1, p1_r0, p1_h, p1_px, p1_py, alpha_chiral, color='black', lw=1.2)
ax1.set_xlim(p1_xy_xlim); ax1.set_ylim(p1_xy_ylim); ax1.set_aspect('equal')
ax1.set_xlabel('x (nm)', fontsize=11); ax1.set_ylabel('y (nm)', fontsize=11)
ax1.set_title(f'XY | z = {p1_h/2*1e3:.0f} nm (mid-disk)', fontsize=11)
im2 = ax2.pcolormesh(y_coords_yz, z_coords_yz, C_yz.T,
cmap='RdBu', shading='auto', vmin=-clim, vmax=clim)
fig.colorbar(im2, ax=ax2, label=r'$C / \langle|C_0|\rangle$')
ax2.axhline(0, color='black', lw=1.2, ls='--', alpha=0.8, label='disk bottom')
ax2.axhline(p1_h * 1e3, color='black', lw=1.2, ls='--', alpha=0.8, label='disk top')
ax2.axvline(-p1_y_pad, color='black', lw=1.0, ls='--', alpha=0.6, label='unit cell wall')
ax2.axvline( p1_y_pad, color='black', lw=1.0, ls='--', alpha=0.6)
ax2.set_xlim(-p1_y_pad, p1_y_pad); ax2.set_ylim(p1_z_min_yz, p1_z_max_yz)
ax2.set_aspect(p1_yz_aspect)
ax2.set_xlabel('y (nm)', fontsize=11); ax2.set_ylabel('z (nm)', fontsize=11)
ax2.set_title('YZ | x = 0', fontsize=11)
ax2.legend(fontsize=8, loc='upper right')
fig.suptitle(f'Optical Chirality | $\\alpha$={alpha_chiral:.2f} | '
f'Res {j+1} | $\\lambda$={lda_nm:.1f} nm Q={Q:.0f}', fontsize=13)
plt.tight_layout()
plt.savefig(f'p1_chirality_alpha{alpha_chiral:.2f}_res{j+1}_{lda_nm:.1f}nm.png',
dpi=300, bbox_inches='tight')
plt.show()
Resonances for alpha=0.05: Res 1: 242.53 nm Q=7950.5 Res 2: 252.24 nm Q=8454.2
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:165: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C = np.array(C_field(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:166: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C_ref = np.array(C_field(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3645847515.py:10: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[p1_key_chiral]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3645847515.py:12: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[p1_key_chiral]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:165: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C = np.array(C_field(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:166: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C_ref = np.array(C_field(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3645847515.py:17: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[p1_key_chiral]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3645847515.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[p1_key_chiral]['yz_field'].Ex
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:165: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C = np.array(C_field(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:166: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C_ref = np.array(C_field(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3645847515.py:10: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(p1_results_actual[p1_key_chiral]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3645847515.py:12: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(p1_results_actual[p1_key_chiral]['xy_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:165: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C = np.array(C_field(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:166: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C_ref = np.array(C_field(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3645847515.py:17: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(p1_results_actual[p1_key_chiral]['yz_field'].Ex /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3645847515.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(p1_results_actual[p1_key_chiral]['yz_field'].Ex
9. Spatially Averaged Chirality Spectrum¶
p1_xy_actual = p1_results_actual[p1_key_chiral]['xy_field']
p1_xy_ref = p1_results_norm[p1_key_chiral]['xy_field']
freqs_xr = p1_xy_actual.Ex.f.values
omega_arr = 2 * np.pi * freqs_xr
prefactor = -omega_arr / (2 * td.C_0**2)
EdotH_actual = (np.conj(p1_xy_actual.Ex) * p1_xy_actual.Hx +
np.conj(p1_xy_actual.Ey) * p1_xy_actual.Hy +
np.conj(p1_xy_actual.Ez) * p1_xy_actual.Hz).imag.mean(dim=['x', 'y']).squeeze()
EdotH_ref = (np.conj(p1_xy_ref.Ex) * p1_xy_ref.Hx +
np.conj(p1_xy_ref.Ey) * p1_xy_ref.Hy +
np.conj(p1_xy_ref.Ez) * p1_xy_ref.Hz).imag.mean(dim=['x', 'y']).squeeze()
C_actual_phys = prefactor * np.array(EdotH_actual)
C_ref_phys = prefactor * np.array(EdotH_ref)
p1_C_norm = C_actual_phys / float(np.abs(C_ref_phys).mean())
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(p1_ldas * 1e3, p1_C_norm, lw=2.0, color='steelblue',
label=r'$\langle C \rangle / \langle|C_0|\rangle$')
ax.axhline(0, color='gray', lw=0.8, ls='--', alpha=0.5)
for j, (lda_nm, Q) in enumerate(p1_res_chiral):
ax.axvline(lda_nm, color='tomato', lw=1.0, ls=':', label=f'Res {j+1}: {lda_nm:.1f} nm')
ax.set_xlabel('Wavelength (nm)', fontsize=12)
ax.set_ylabel(r'$\langle C \rangle / \langle|C_0|\rangle$', fontsize=12)
ax.set_title(f'Spatially Averaged Optical Chirality vs Wavelength | '
f'$\\alpha$={alpha_chiral:.2f} (Example Geometry)', fontsize=12)
ax.legend(fontsize=9)
plt.tight_layout()
plt.savefig(f'p1_chirality_avg_spectrum_alpha{alpha_chiral:.2f}.png', dpi=300, bbox_inches='tight')
plt.show()
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2922822606.py:16: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C_actual_phys = prefactor * np.array(EdotH_actual) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2922822606.py:17: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C_ref_phys = prefactor * np.array(EdotH_ref)
Summary¶
In this section, we have demonstrated computationally how quasi-bound states in the continuum emerge in a biperiodic HfO₂ nanodisk metasurface through controlled symmetry breaking. By sweeping the asymmetry parameter $\alpha$, we confirmed the key theoretical prediction — that the radiative Q factor scales as $Q \propto \alpha^{-2}$, and showed how the q-BIC manifests as a sharp Fano resonance in the transmittance spectrum that becomes progressively narrower and weaker as the lattice approaches the ideal symmetric BIC limit.
The near-field electric field maps at resonance confirm that the q-BIC mode produces large local field enhancements $|E|/|E_0| \gg 1$ concentrated within and immediately around the HfO₂ nanodisks. This strong field confinement is the fundamental mechanism that makes q-BIC metasurfaces attractive for enhancing light-matter interactions at the nanoscale.
However, the optical chirality maps reveal a critical limitation for chiroptical applications: the local chirality density $C$ exhibits regions of alternating sign across the unit cell, with hotspots of opposite handedness in close spatial proximity. For circular dichroism spectroscopy, where chiral analyte molecules are randomly distributed throughout the near-field volume, contributions from regions of opposite $C$ sign partially cancel when averaged, significantly reducing the net chiroptical enhancement relative to the local peak values.
Achieving large, spatially uniform-sign chirality enhancements, where $C/C_{\rm CPL}$ is everywhere positive (or everywhere negative) across the unit cell, requires an additional design ingredient beyond the q-BIC alone. As shown by Hu et al., this is accomplished by spectrally overlapping the asymmetric electric dipole mode $\mathbf{p}_\alpha$ and magnetic dipole mode $\mathbf{m}_\alpha$ of the biperiodic lattice, inducing a Kerker-like condition in which the scattered fields preserve the helicity of the incident circular polarization throughout the near-field volume.
This is precisely the design challenge addressed in the next section, where we tune the disk aspect ratio to bring $\mathbf{p}_\alpha$ and $\mathbf{m}_\alpha$ into spectral alignment and evaluate the resulting improvement in spatially averaged chirality enhancement.
Part 2: Geometry Sweep & the Kerker Transition.¶
In the previous section we established that the HfO2 biperiodic nanodisk metasurface supports a q-BIC resonance whose radiative Q factor scales as $Q \propto \alpha^{-2}$, and that this resonance produces large but spatially non-uniform near-field chirality enhancements. The next design challenge is to find the geometry that maximises the spatially averaged optical chirality $\langle C \rangle$, the quantity that governs sensitivity for molecules randomly distributed around the metasurface.
The averaged chirality is not simply maximised by maximising Q. It requires a Kerker-like condition in which the asymmetric electric dipole mode $\mathbf{p}_\alpha$ and magnetic dipole mode $\mathbf{m}_\alpha$ of the biperiodic lattice are spectrally overlapped. When these two modes are degenerate, the scattered fields from the metasurface preserve the helicity of the incident circularly polarized light throughout the near-field volume, producing chirality enhancements of a single sign everywhere in the unit cell. The geometry, specifically the disk diameter $d_0$, height $h$, or lattice period $p_x$, is the tuning knob that shifts $\mathbf{p}_\alpha$ and $\mathbf{m}_\alpha$ relative to each other, because the electric and magnetic Mie resonances of a disk depend differently on the aspect ratio $d/h$.
The Kerker Condition in a Biperiodic Lattice¶
For a single dielectric disk, the electric dipole p and magnetic dipole m Mie resonances can be brought into spectral overlap by tuning the aspect ratio $d/h$. When p and m are spectrally degenerate and of equal amplitude, the forward-scattered field is that of a Huygens source and the backward-scattered field vanishes — this is the first Kerker condition, and it corresponds to unity transmission with fully preserved incident polarization in the scattered near field.
In the biperiodic lattice with asymmetry $\alpha > 0$, the two broad Mie modes split into high-Q asymmetric counterparts $\mathbf{p}_\alpha$ and $\mathbf{m}_\alpha$. As shown by Hu, Lawrence and Dionne (ACS Photonics 7, 36, 2020), when $\mathbf{p}_\alpha$ and $\mathbf{m}_\alpha$ are spectrally overlapped the condition
$$\cos(\phi_{iE,H}) = \frac{\text{Im}(\mathbf{E}^* \cdot \mathbf{H})}{|\mathbf{E}||\mathbf{H}|} \approx -1$$
is satisfied uniformly across the unit cell, meaning the near-field ellipticity matches that of the incident LCP wave everywhere. This directly maximises the plane-averaged chirality:
$$\langle C \rangle = -\frac{\omega}{2c^2} \left\langle \text{Im}(\mathbf{E}^* \cdot \mathbf{H}) \right\rangle_{xy}$$
since $\text{Im}(\mathbf{E}^* \cdot \mathbf{H}) = |\mathbf{E}||\mathbf{H}|\cos(\phi_{iE,H})$ and a uniform negative $\cos(\phi_{iE,H})$ prevents cancellation between regions of opposite chirality sign when spatially averaged.
What This Section Does¶
We fix $\alpha = 0.05$, small enough to maintain high Q while still allowing far-field coupling, and sweep one geometric parameter (h, px, or d0) across a range chosen to straddle the Kerker condition. For each geometry we run a pair of FDTD simulations (disks present and empty reference) and extract:
Transmittance $T(\lambda)$ — the normalized flux spectrum. The q-BIC appears as a sharp Fano dip. As the geometry is tuned, the positions of $\mathbf{p}_\alpha$ and $\mathbf{m}_\alpha$ shift relative to each other; when they overlap the two dips merge and transmission approaches unity at the resonance — a hallmark of the Kerker condition.
Spatially averaged XY chirality $\langle C(\lambda) \rangle$ — computed as:
$$\langle C(\lambda) \rangle = -\frac{\omega}{2c^2} \left\langle \text{Im}(\mathbf{E}^* \cdot \mathbf{H}) \right\rangle_{xy}$$
normalized by the mean $|C|$ of the reference run. This spectrum peaks sharply at the Kerker wavelength and its sign is predominantly single-valued — in contrast to the mixed-sign maps seen in the previous section where no Kerker condition was imposed.
The sweep value that produces the globally largest $|\langle C \rangle|$ is then identified and its near-field chirality $C$ and polarization state $\cos(\phi_{iE,H})$ maps are plotted on XY and YZ cross-sections to confirm spatial uniformity of the enhancement.
Key Parameters¶
| Quantity | Symbol | Value |
|---|---|---|
| Fixed asymmetry | $\alpha$ | 0.05 |
| Swept parameter | SWEEP_PARAM |
d0 / h / px
|
| Central wavelength | $\lambda_0$ | 258 nm |
| Base disk height | $h$ | 80 nm |
| Base period | $p_x = p_y$ | 210 nm |
| Base disk diameter | $d_0$ | 110 nm |
1. Base Parameters¶
SWEEP_PARAM and SWEEP_VALUES are the only knobs needed to switch
between sweeping disk height (h), unit cell period (px), or disk diameter
(d0). The base geometry values are used for all parameters that are not
currently being swept. This part's geometry and wavelength window are
independent of Part 1's example case.
# ── Wavelength range ──────────────────────────────────────────────────────────
p2_lda0 = 0.258
p2_freq0 = td.C_0 / p2_lda0
p2_ldas = np.linspace(p2_lda0 - 0.007, p2_lda0 + 0.007, 500)
p2_freqs = td.C_0 / p2_ldas
p2_fwidth = 0.5 * (np.max(p2_freqs) - np.min(p2_freqs))
# ── Base geometry (used as default when not swept) ────────────────────────────
p2_h_base = 0.080 # disk height (μm)
p2_px_base = 0.210 # unit cell period (μm)
p2_d0_base = 0.110 # disk diameter (μm)
p2_alpha = 0.05 # fixed asymmetry
# ── Sweep config — change SWEEP_PARAM and SWEEP_VALUES ───────────────────────
# Options: 'h', 'px', 'd0'
p2_SWEEP_PARAM = 'd0'
p2_SWEEP_VALUES = np.linspace(0.106, 0.112, 9) # μm
print(f'Sweeping: {p2_SWEEP_PARAM} over {p2_SWEEP_VALUES*1e3} nm')
print(f'Alpha fixed at: {p2_alpha}')
print(f'Wavelength range: {p2_ldas.min()*1e3:.1f} – {p2_ldas.max()*1e3:.1f} nm')
Sweeping: d0 over [106. 106.75 107.5 108.25 109. 109.75 110.5 111.25 112. ] nm Alpha fixed at: 0.05 Wavelength range: 251.0 – 265.0 nm
2. Material Dispersion Fit¶
Independent fit over Part 2's own wavelength window.
data = np.loadtxt('./misc/HfO2_Siefke_n_k.csv', skiprows=1, delimiter=',')
wl_um = data[:, 0]
n_data = data[:, 1]
k_data = data[:, 2]
wl_min = p2_ldas.min() - 0.010
wl_max = p2_ldas.max() + 0.010
mask = (wl_um >= wl_min) & (wl_um <= wl_max)
wl_sel = wl_um[mask]
n_sel = n_data[mask]
k_sel = k_data[mask]
print(f'Fitting over {mask.sum()} points: {wl_sel.min()*1e3:.1f} – {wl_sel.max()*1e3:.1f} nm')
tmp_fname = 'HfO2_subset_p2.csv'
np.savetxt(tmp_fname, np.column_stack([wl_sel, n_sel, k_sel]),
delimiter=',', header='wl,n,k', comments='')
p2_fitter = FastDispersionFitter.from_file(tmp_fname, skiprows=1, delimiter=',')
p2_hafnia, p2_rms_error = p2_fitter.fit(max_num_poles=4, tolerance_rms=1e-3)
print(f'Dispersion fit RMS error: {p2_rms_error:.5f}')
eps_complex = p2_hafnia.eps_model(p2_freqs)
n_fit = np.real(np.sqrt(eps_complex))
k_fit = np.imag(np.sqrt(eps_complex))
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
ax1.scatter(wl_sel * 1e3, n_sel, s=15, color='steelblue', label='Raw data', zorder=3)
ax1.plot(p2_ldas * 1e3, n_fit, color='tomato', lw=2, label='Fit')
ax1.set_xlabel('Wavelength (nm)'); ax1.set_ylabel('n')
ax1.set_title('HfO2 Real Refractive Index'); ax1.legend()
ax2.scatter(wl_sel * 1e3, k_sel, s=15, color='steelblue', label='Raw data', zorder=3)
ax2.plot(p2_ldas * 1e3, k_fit, color='tomato', lw=2, label='Fit')
ax2.set_xlabel('Wavelength (nm)'); ax2.set_ylabel('k')
ax2.set_title('HfO2 Extinction Coefficient'); ax2.legend()
plt.tight_layout()
plt.show()
Output()
Fitting over 16 points: 242.2 – 274.3 nm
Dispersion fit RMS error: 0.00019
3. Geometry Builder for the Sweep¶
def get_geometry(sweep_val):
'''Return (h, px, py, d0, r0) for a given sweep value.'''
h_g = p2_h_base
px_g = p2_px_base
d0_g = p2_d0_base
if p2_SWEEP_PARAM == 'h':
h_g = sweep_val
elif p2_SWEEP_PARAM == 'px':
px_g = sweep_val
elif p2_SWEEP_PARAM == 'd0':
d0_g = sweep_val
py_g = px_g
r0_g = d0_g / 2
return h_g, px_g, py_g, d0_g, r0_g
def p2_sweep_key(val):
return f'{p2_SWEEP_PARAM}={val*1e3:.1f}nm'
4. Run Simulations¶
Two Batch objects are submitted: one with disks present (p2_sims_actual)
and one with an empty unit cell (p2_sims_norm). Results are cached to disk so
the kernel can be restarted without re-running.
p2_sims_actual = {}
p2_sims_norm = {}
for v in p2_SWEEP_VALUES:
key = p2_sweep_key(v)
h_g, px_g, py_g, d0_g, r0_g = get_geometry(v)
p2_sims_actual[key] = make_sim(h_g, px_g, py_g, r0_g, p2_alpha, p2_hafnia,
p2_freq0, p2_fwidth, p2_freqs, p2_lda0,
source_z, run_time, Lz, include_structures=True)
p2_sims_norm[key] = make_sim(h_g, px_g, py_g, r0_g, p2_alpha, p2_hafnia,
p2_freq0, p2_fwidth, p2_freqs, p2_lda0,
source_z, run_time, Lz, include_structures=False)
p2_batch_actual = web.Batch(simulations=p2_sims_actual, verbose=True)
p2_batch_norm = web.Batch(simulations=p2_sims_norm, verbose=True)
print('Running actual simulations (Part 2)...')
p2_results_actual = p2_batch_actual.run(path_dir='data/p2_actual')
print('Running normalization simulations (Part 2)...')
p2_results_norm = p2_batch_norm.run(path_dir='data/p2_norm')
Output()
14:04:32 EDT WARNING: Simulation has 1.47e+06 time steps. The 'run_time' may be unnecessarily large, unless there are very long-lived resonances.
WARNING: Simulation has 1.33e+06 time steps. The 'run_time' may be unnecessarily large, unless there are very long-lived resonances.
Running actual simulations (Part 2)...
14:04:35 EDT Started working on Batch containing 9 tasks.
14:04:43 EDT Maximum FlexCredit cost: 1.312 for the whole batch.
Use 'Batch.real_cost()' to get the billed FlexCredit cost after completion.
Output()
14:28:22 EDT Batch complete.
Output()
Running normalization simulations (Part 2)...
14:28:43 EDT Started working on Batch containing 9 tasks.
14:28:51 EDT Maximum FlexCredit cost: 0.246 for the whole batch.
Use 'Batch.real_cost()' to get the billed FlexCredit cost after completion.
Output()
14:29:57 EDT Batch complete.
5. Extract Transmittance & Average XY Chirality¶
For each sweep value we extract transmittance T(lambda) and the
spatially-averaged XY chirality <C(lambda)>.
p2_T_dict = {}
p2_C_dict = {}
for val in p2_SWEEP_VALUES:
key = p2_sweep_key(val)
T_actual = np.array(p2_results_actual[key]['flux'].flux)
T_ref = np.array(p2_results_norm[key]['flux'].flux)
p2_T_dict[key] = T_actual / T_ref
xy_actual = p2_results_actual[key]['xy_field']
xy_ref = p2_results_norm[key]['xy_field']
freqs_xr = xy_actual.Ex.f.values
omega_arr = 2 * np.pi * freqs_xr
prefactor = -omega_arr / (2 * td.C_0**2)
def avg_chirality(xy):
EdotH_im = (np.conj(xy.Ex) * xy.Hx +
np.conj(xy.Ey) * xy.Hy +
np.conj(xy.Ez) * xy.Hz).imag.mean(dim=['x', 'y']).squeeze()
return np.array(EdotH_im)
C_actual_raw = avg_chirality(xy_actual)
C_ref_raw = avg_chirality(xy_ref)
C_actual_phys = prefactor * C_actual_raw
C_ref_phys = prefactor * C_ref_raw
p2_C_dict[key] = C_actual_phys / float(np.abs(C_ref_phys).mean())
print(f'{key}: T=[{p2_T_dict[key].min():.3f}, {p2_T_dict[key].max():.3f}] '
f'C=[{p2_C_dict[key].min():.3f}, {p2_C_dict[key].max():.3f}]')
LDA_MIN = 254
LDA_MAX = 262
lda_nm = p2_ldas * 1e3
sweep_colors = plt.cm.viridis(np.linspace(0.1, 0.9, len(p2_SWEEP_VALUES)))
fig, (ax_T, ax_C) = plt.subplots(1, 2, figsize=(12, 5))
for k, val in enumerate(p2_SWEEP_VALUES):
key = p2_sweep_key(val)
label = f'{p2_SWEEP_PARAM} = {val*1e3:.1f} nm'
color = sweep_colors[k]
ax_T.plot(lda_nm, p2_T_dict[key], lw=1.8, color=color, label=label)
ax_C.plot(lda_nm, p2_C_dict[key], lw=1.8, color=color, label=label)
ax_T.set_xlabel('Wavelength (nm)', fontsize=12); ax_T.set_ylabel('Transmittance', fontsize=12)
ax_T.set_title('Transmittance vs d0', fontsize=12)
ax_T.set_xlim(LDA_MIN, LDA_MAX)
ax_C.set_xlabel('Wavelength (nm)', fontsize=12)
ax_C.set_ylabel(r'$\langle C \rangle / \langle|C_0|\rangle$', fontsize=12)
ax_C.set_title('Averaged Chirality vs d0', fontsize=12)
ax_C.set_xlim(LDA_MIN, LDA_MAX)
ax_C.legend(fontsize=8, loc='best')
fig.suptitle(f'Geometry Sweep (alpha = {p2_alpha})', fontsize=13)
plt.tight_layout()
plt.savefig('p2_geometry_sweep_T_and_C.png', dpi=300, bbox_inches='tight')
plt.show()
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:7: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p2_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:8: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p2_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:7: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p2_results_actual[key]['flux'].flux)
d0=106.0nm: T=[0.033, 1.007] C=[-237.475, 326.499] d0=106.8nm: T=[0.079, 1.000] C=[-451.117, 432.416]
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:8: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p2_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:7: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p2_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:8: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p2_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:7: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p2_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:8: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p2_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im)
d0=107.5nm: T=[0.001, 1.000] C=[-275.534, 270.167] d0=108.2nm: T=[0.008, 1.005] C=[-507.142, 529.769]
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:7: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p2_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:8: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p2_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:7: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p2_results_actual[key]['flux'].flux)
d0=109.0nm: T=[0.726, 1.004] C=[1.401, 4248.611] d0=109.8nm: T=[0.472, 1.007] C=[1.416, 3552.322]
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:8: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p2_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:7: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p2_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:8: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p2_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:7: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p2_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:8: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p2_results_norm[key]['flux'].flux)
d0=110.5nm: T=[0.031, 1.007] C=[-593.129, 604.261]
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:7: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_actual = np.array(p2_results_actual[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:8: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword T_ref = np.array(p2_results_norm[key]['flux'].flux) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2948041685.py:22: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(EdotH_im)
d0=111.2nm: T=[0.070, 1.001] C=[-193.123, 148.056] d0=112.0nm: T=[0.114, 0.999] C=[-149.903, 170.825]
6. The Kerker Transition in terms of T_min and Peak Chirality vs d0¶
For each sweep value, extract the minimum transmittance T_min (how deep
the Fano dip is — small T_min means strong far-field coupling) and the peak
|<C>| (the strongest chirality reached anywhere in the window). If a Kerker-like
condition is crossed within the sweep, T_min should rise sharply (the dip
nearly disappears) right where |<C>| spikes, then both should relax back to
their normal Fano-resonance behaviour on either side — a visible transition
through the Kerker regime rather than a single isolated point.
p2_Tmin_vs_d0 = np.array([p2_T_dict[p2_sweep_key(v)].min() for v in p2_SWEEP_VALUES])
p2_Cpeak_vs_d0 = np.array([np.abs(p2_C_dict[p2_sweep_key(v)]).max() for v in p2_SWEEP_VALUES])
p2_d0_nm = p2_SWEEP_VALUES * 1e3
fig, ax1 = plt.subplots(figsize=(8, 5))
ax2 = ax1.twinx()
l1, = ax1.plot(p2_d0_nm, p2_Tmin_vs_d0, 'o-', color='steelblue', lw=2, ms=7,
label=r'$T_{min}$ (dip depth)')
l2, = ax2.plot(p2_d0_nm, p2_Cpeak_vs_d0, 's-', color='tomato', lw=2, ms=7,
label=r'peak $|\langle C\rangle|$')
ax1.set_xlabel('d0 (nm)', fontsize=12)
ax1.set_ylabel(r'$T_{min}$', fontsize=12, color='steelblue')
ax2.set_ylabel(r'peak $|\langle C\rangle / \langle|C_0|\rangle|$', fontsize=12, color='tomato')
ax1.tick_params(axis='y', labelcolor='steelblue')
ax2.tick_params(axis='y', labelcolor='tomato')
ax1.set_title('Transmittance Dip Collapsing as Chirality Spikes — the Kerker Transition',
fontsize=12)
ax1.legend(handles=[l1, l2], loc='upper right', fontsize=9)
plt.tight_layout()
plt.savefig('p2_kerker_transition.png', dpi=300, bbox_inches='tight')
plt.show()
p2_kerker_idx = np.argmax(p2_Tmin_vs_d0)
p2_kerker_val = p2_SWEEP_VALUES[p2_kerker_idx]
print(f'Shallowest Fano dip (closest approach to the Kerker condition): '
f'd0 = {p2_kerker_val*1e3:.1f} nm (T_min = {p2_Tmin_vs_d0[p2_kerker_idx]:.3f}, '
f'peak |<C>| = {p2_Cpeak_vs_d0[p2_kerker_idx]:.1f})')
Shallowest Fano dip (closest approach to the Kerker condition): d0 = 109.0 nm (T_min = 0.726, peak |<C>| = 4248.6)
7. Near-Field Maps at the Near-Kerker Geometry¶
At the geometry identified above (the largest T_min in the sweep — the
point closest to the Kerker condition, which also coincides with the peak
chirality), compute and plot four near-field maps: local chirality C and the
polarization-state indicator cos(phi_iE,H), in both the XY and YZ planes.
p2_best_val = p2_kerker_val
p2_best_key = p2_sweep_key(p2_best_val)
C_norm_best = p2_C_dict[p2_best_key]
ki = np.argmax(np.abs(C_norm_best))
p2_best_lda = p2_ldas[ki] * 1e3
p2_best_freq = td.C_0 / (p2_best_lda / 1e3)
print(f'Near-Kerker geometry : {p2_SWEEP_PARAM} = {p2_best_val*1e3:.1f} nm')
print(f'Peak |<C>| wavelength: {p2_best_lda:.2f} nm')
print(f'Peak <C> value : {C_norm_best[ki]:.3f}')
res_act = p2_results_actual[p2_best_key]
res_norm = p2_results_norm[p2_best_key]
h_best, px_best, py_best, d0_best, r0_best = get_geometry(p2_best_val)
C_xy = get_chirality(res_act['xy_field'], res_norm['xy_field'], p2_best_freq)
cos_xy = get_cos_phi(res_act['xy_field'], p2_best_freq)
x_coords = np.array(res_act['xy_field'].Ex.sel(f=p2_best_freq, method='nearest').x) * 1e3
y_coords = np.array(res_act['xy_field'].Ex.sel(f=p2_best_freq, method='nearest').y) * 1e3
C_yz = get_chirality(res_act['yz_field'], res_norm['yz_field'], p2_best_freq)
cos_yz = get_cos_phi(res_act['yz_field'], p2_best_freq)
y_coords_yz = np.array(res_act['yz_field'].Ex.sel(f=p2_best_freq, method='nearest').y) * 1e3
z_coords_yz = np.array(res_act['yz_field'].Ex.sel(f=p2_best_freq, method='nearest').z) * 1e3
p2_xy_xlim, p2_xy_ylim, p2_y_pad, _, _, _ = get_plot_geom(h_best, px_best, py_best, z_pad=0.08)
z_pad_best = 0.08
y_lim_best = p2_y_pad
z_min_yz_best = -z_pad_best * 1e3
z_max_yz_best = (h_best + z_pad_best) * 1e3
yz_asp_best = (2 * y_lim_best) / (z_max_yz_best - z_min_yz_best)
clim_C = max(np.abs(C_xy).max(), np.abs(C_yz).max())
clim_cos = 1.0
fig, axes = plt.subplots(2, 2, figsize=(11, 9), gridspec_kw=dict(hspace=0.38, wspace=0.35))
ax_C_xy, ax_cos_xy = axes[0, 0], axes[0, 1]
ax_C_yz, ax_cos_yz = axes[1, 0], axes[1, 1]
im1 = ax_C_xy.pcolormesh(x_coords, y_coords, C_xy.T, cmap='RdBu', shading='auto',
vmin=-clim_C, vmax=clim_C)
fig.colorbar(im1, ax=ax_C_xy, label=r'$C/\langle|C_0|\rangle$')
draw_disks(ax_C_xy, r0_best, h_best, px_best, py_best, p2_alpha, color='black', lw=1.2)
ax_C_xy.set_xlim(p2_xy_xlim); ax_C_xy.set_ylim(p2_xy_ylim); ax_C_xy.set_aspect('equal')
ax_C_xy.set_xlabel('x (nm)', fontsize=11); ax_C_xy.set_ylabel('y (nm)', fontsize=11)
ax_C_xy.set_title(f'XY — Local Chirality $C$\n'
f'{p2_SWEEP_PARAM}={p2_best_val*1e3:.1f} nm | '
f'$\\lambda$={p2_best_lda:.1f} nm', fontsize=11)
im2 = ax_cos_xy.pcolormesh(x_coords, y_coords, cos_xy.T, cmap='PiYG', shading='auto',
vmin=-clim_cos, vmax=clim_cos)
fig.colorbar(im2, ax=ax_cos_xy, label=r'$\cos(\phi_{iE,H})$')
draw_disks(ax_cos_xy, r0_best, h_best, px_best, py_best, p2_alpha, color='black', lw=1.2)
ax_cos_xy.set_xlim(p2_xy_xlim); ax_cos_xy.set_ylim(p2_xy_ylim); ax_cos_xy.set_aspect('equal')
ax_cos_xy.set_xlabel('x (nm)', fontsize=11); ax_cos_xy.set_ylabel('y (nm)', fontsize=11)
ax_cos_xy.set_title(f'XY — $\\cos(\\phi_{{iE,H}})$\n'
f'{p2_SWEEP_PARAM}={p2_best_val*1e3:.1f} nm | '
f'$\\lambda$={p2_best_lda:.1f} nm', fontsize=11)
im3 = ax_C_yz.pcolormesh(y_coords_yz, z_coords_yz, C_yz.T, cmap='RdBu', shading='auto',
vmin=-clim_C, vmax=clim_C)
fig.colorbar(im3, ax=ax_C_yz, label=r'$C/\langle|C_0|\rangle$')
ax_C_yz.axhline(0, color='black', lw=1.2, ls='--', alpha=0.8, label='disk bottom')
ax_C_yz.axhline(h_best*1e3, color='black', lw=1.2, ls='--', alpha=0.8, label='disk top')
ax_C_yz.axvline(-y_lim_best, color='black', lw=1.0, ls='--', alpha=0.6, label='unit cell wall')
ax_C_yz.axvline( y_lim_best, color='black', lw=1.0, ls='--', alpha=0.6)
ax_C_yz.set_xlim(-y_lim_best, y_lim_best); ax_C_yz.set_ylim(z_min_yz_best, z_max_yz_best)
ax_C_yz.set_aspect(yz_asp_best)
ax_C_yz.set_xlabel('y (nm)', fontsize=11); ax_C_yz.set_ylabel('z (nm)', fontsize=11)
ax_C_yz.set_title('YZ — Local Chirality $C$ | $x=0$', fontsize=11)
ax_C_yz.legend(fontsize=7, loc='upper right')
im4 = ax_cos_yz.pcolormesh(y_coords_yz, z_coords_yz, cos_yz.T, cmap='PiYG', shading='auto',
vmin=-clim_cos, vmax=clim_cos)
fig.colorbar(im4, ax=ax_cos_yz, label=r'$\cos(\phi_{iE,H})$')
ax_cos_yz.axhline(0, color='black', lw=1.2, ls='--', alpha=0.8, label='disk bottom')
ax_cos_yz.axhline(h_best*1e3, color='black', lw=1.2, ls='--', alpha=0.8, label='disk top')
ax_cos_yz.axvline(-y_lim_best, color='black', lw=1.0, ls='--', alpha=0.6, label='unit cell wall')
ax_cos_yz.axvline( y_lim_best, color='black', lw=1.0, ls='--', alpha=0.6)
ax_cos_yz.set_xlim(-y_lim_best, y_lim_best); ax_cos_yz.set_ylim(z_min_yz_best, z_max_yz_best)
ax_cos_yz.set_aspect(yz_asp_best)
ax_cos_yz.set_xlabel('y (nm)', fontsize=11); ax_cos_yz.set_ylabel('z (nm)', fontsize=11)
ax_cos_yz.set_title(r'YZ — $\cos(\phi_{iE,H})$ | $x=0$', fontsize=11)
ax_cos_yz.legend(fontsize=7, loc='upper right')
fig.suptitle(
f'Near-field Chirality & Polarization State — Near-Kerker Geometry\n'
f'{p2_SWEEP_PARAM} = {p2_best_val*1e3:.1f} nm | '
f'$\\lambda$ = {p2_best_lda:.1f} nm | $\\alpha$ = {p2_alpha}',
fontsize=12
)
plt.savefig(f'p2_nearfield_chirality_kerker_{p2_SWEEP_PARAM}.png', dpi=300, bbox_inches='tight')
plt.show()
Near-Kerker geometry : d0 = 109.0 nm Peak |<C>| wavelength: 258.55 nm Peak <C> value : 4248.611
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:165: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C = np.array(C_field(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:166: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C_ref = np.array(C_field(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:183: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(fields(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3260157534.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword x_coords = np.array(res_act['xy_field'].Ex.sel(f=p2_best_freq, method='nearest').x) * 1e3 /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3260157534.py:20: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords = np.array(res_act['xy_field'].Ex.sel(f=p2_best_freq, method='nearest').y) * 1e3 /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:165: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C = np.array(C_field(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:166: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword C_ref = np.array(C_field(monitor_ref)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/2740503689.py:183: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword return np.array(fields(monitor_data)) /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3260157534.py:24: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword y_coords_yz = np.array(res_act['yz_field'].Ex.sel(f=p2_best_freq, method='nearest').y) * 1e3 /var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_37396/3260157534.py:25: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword z_coords_yz = np.array(res_act['yz_field'].Ex.sel(f=p2_best_freq, method='nearest').z) * 1e3
Summary¶
In this section we have systematically swept the HfO2 nanodisk geometry, fixing the asymmetry at $\alpha = 0.05$ — to identify the unit-cell dimensions that maximise the spatially averaged optical chirality $\langle C \rangle$ in the mid-ultraviolet. The key finding is that the averaged chirality is not monotonically related to the resonance Q factor, but instead peaks sharply at a specific geometry where the asymmetric electric dipole mode $\mathbf{p}_\alpha$ and magnetic dipole mode $\mathbf{m}_\alpha$ are brought into spectral overlap — the Kerker condition.
At this optimal geometry, the transmittance spectrum shows the two Fano dips associated with $\mathbf{p}_\alpha$ and $\mathbf{m}_\alpha$ merging toward unity transmission, while the averaged chirality spectrum exhibits a sharp peak of single sign. The near-field maps confirm this picture: the local chirality density $C$ and the polarization state indicator $\cos(\phi_{iE,H})$ are spatially uniform in sign across the unit cell, in stark contrast to the alternating-sign patterns seen in the previous section where no Kerker condition was imposed. Regions where $\cos(\phi_{iE,H}) \approx -1$ extend throughout the near-field volume above and below the disk layer, indicating that the scattered fields preserve the helicity of the incident LCP wave everywhere a chiral analyte molecule might sit.
These results validate the design strategy proposed by Hu, Lawrence and Dionne: the biperiodic disk geometry simultaneously provides high-Q resonances through the q-BIC mechanism and uniform-sign chirality enhancement through the Kerker condition, with the two effects independently controlled by $\alpha$ and the disk aspect ratio respectively. The HfO2 material system delivers this functionality in the mid-ultraviolet, where the chiroptical absorption features of most pharmaceutically and biochemically relevant small chiral molecules are found, and where conventional high-index materials such as silicon are too lossy to sustain high-Q resonances.