# Rows used by the live optimization table.
optimization_display_rows = []
def print_optimization_table_row(
step,
beta,
FOM_tot,
FOMn,
FOMe,
FOMd,
RE_red,
RE_green,
RE_blue,
grad_max_abs,
):
# Convert metrics to floats and redraw the current table.
optimization_display_rows.append(
{
"step": int(step),
"beta": float(beta),
"FOM total": float(FOM_tot),
"nominal": float(FOMn),
"eroded": float(FOMe),
"dilated": float(FOMd),
"RE red (%)": float(RE_red * 100),
"green (%)": float(RE_green * 100),
"blue (%)": float(RE_blue * 100),
"max grad": float(grad_max_abs),
}
)
df = pd.DataFrame(optimization_display_rows).set_index("step")
clear_output(wait=True)
display(
df.style.format(
{
"beta": "{:.1f}",
"FOM total": "{:.4f}",
"nominal": "{:.3f}",
"eroded": "{:.3f}",
"dilated": "{:.3f}",
"RE red (%)": "{:.3f}",
"green (%)": "{:.3f}",
"blue (%)": "{:.3f}",
"max grad": "{:.2e}",
}
)
)
# Save the eroded, nominal, and dilated patterns for this iteration.
def plot_density(params, beta=BETA_START, step=0):
# Renders and saves the eroded/nominal/dilated pattern for this step
params_nominal, _, _, _ = preprocess(params, beta=beta, symmetrize=True)
params_eroded, _, _, _ = preprocess(params, beta=beta, eta=DETA, symmetrize=True)
params_dilated, _, _, _ = preprocess(params, beta=beta, eta=-DETA, symmetrize=True)
fig, ax = plt.subplots(
1, 3, figsize=(9, 3), sharex=True, sharey=True, tight_layout=True
)
ax[0].pcolormesh(x_grid, y_grid, params_eroded, vmin=-1, vmax=1, cmap=cmap2)
ax[1].pcolormesh(x_grid, y_grid, params_nominal, vmin=-1, vmax=1, cmap=cmap2)
pc = ax[2].pcolormesh(x_grid, y_grid, params_dilated, vmin=-1, vmax=1, cmap=cmap2)
cax = ax[2].inset_axes([1.05, 0, 0.05, 1])
cbar = plt.colorbar(pc, cax=cax)
cbar.ax.set_yticks([-1, 1])
cbar.ax.set_yticklabels(["air", "SiN"])
ax[0].set_title(f"step {step} eroded")
ax[1].set_title(f"step {step} nominal")
ax[2].set_title(f"step {step} dilated")
[a.set_aspect("equal") for a in ax]
ax[0].set(
xticks=[],
yticks=[],
)
FIG_DIR.mkdir(parents=True, exist_ok=True)
fig.savefig(FIG_DIR / f"step{step}.png")
plt.close(fig)
# Reproducible initial field with a small bias toward air.
rng = np.random.default_rng(RANDOM_SEED)
params_init = rng.normal(
loc=ETA_BIAS, scale=1, size=(N_pixel, N_pixel)
) # biased toward air
optimizer = adam(learning_rate=LR, beta1=ADAM_B1, beta2=ADAM_B2, eps=ADAM_EPS)
# Track convergence and retain the best pre-update design.
params = params_init.copy()
opt_state = optimizer.init(params)
params_history = []
grad_history = []
FOM_history = []
FOMned_history = []
RE_red_history = []
RE_green_history = []
RE_blue_history = []
beta_history = []
# Each iteration runs differentiable FDTD, evaluates the adjoint gradient, and updates all pixels.
for step in range(N_STEP):
params_history.append(params.copy())
# Binarization schedule: hold beta flat for N_KEEP_BETA steps, then ramp it up
beta = (
BETA_START
if step < N_KEEP_BETA
else BETA_START + BETA_INCREMENT * (step - N_KEEP_BETA + 1)
)
if SAVE_FIG:
# Save the geometry evaluated at this step before updating it.
plot_density(params, beta=beta, step=step)
# Switch on the eroded/nominal/dilated robustness ensemble once binarization begins
robust = False if step < N_KEEP_BETA else True
(grad,), (FOM_tot, FOMn, FOMe, FOMd, RE_red, RE_green, RE_blue) = grad_aux_p2f(
params, beta=beta, step=step, robust=robust
)
grad_max_abs = np.abs(grad).max()
# Adam is written for gradient descent; negate grad to ascend the FOM
updates, opt_state = optimizer.update(-grad, opt_state, params)
params = apply_updates(params, updates)
# Store history.
grad_history.append(grad.copy())
FOM_history.append(float(FOM_tot))
FOMned_history.append([FOMn, FOMe, FOMd])
RE_red_history.append(float(RE_red))
RE_green_history.append(float(RE_green))
RE_blue_history.append(float(RE_blue))
beta_history.append(float(beta))
# Print progress table.
print_optimization_table_row(
step=step,
beta=beta,
FOM_tot=FOM_tot,
FOMn=FOMn,
FOMe=FOMe,
FOMd=FOMd,
RE_red=RE_red,
RE_green=RE_green,
RE_blue=RE_blue,
grad_max_abs=grad_max_abs,
)
print(f"Best FOM_tot: {float(np.max(FOM_history)):.6f}")
# Save the history for analysis without rerunning cloud simulations.
np.savez(
SIM_DIR / "opt_history.npz",
params=np.array(params_history),
grad=np.array(grad_history),
FOM=np.array(FOM_history),
FOMned=np.array(FOMned_history),
RE_red=np.array(RE_red_history),
RE_green=np.array(RE_green_history),
RE_blue=np.array(RE_blue_history),
beta=np.array(beta_history),
)