Files
2025AsianB/org/other/question3_multilayer_optimization.py
2025-11-22 18:08:14 +08:00

237 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import itertools
import math
import os
import random
from dataclasses import dataclass
from typing import Dict, List, Sequence, Tuple
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams["font.sans-serif"] = ["DejaVu Sans", "Arial", "Helvetica"]
plt.rcParams["axes.unicode_minus"] = False
def load_q1_module():
here = os.path.dirname(os.path.abspath(__file__))
target = os.path.join(here, "question1_pdms_emissivity.py")
import importlib.util
spec = importlib.util.spec_from_file_location("q1", target)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore[arg-type]
return module
def planck_weight(wavelength_um: np.ndarray, temperature: float = 300.0) -> np.ndarray:
wl_m = wavelength_um * 1e-6
c1 = 3.7418e-16
c2 = 1.4388e-2
spectral = c1 / (wl_m**5 * (np.exp(c2 / (wl_m * temperature)) - 1))
return spectral
def solar_weight(wavelength_um: np.ndarray) -> np.ndarray:
center1, width1 = 0.6, 0.35
center2, width2 = 1.6, 0.45
return np.exp(-((wavelength_um - center1) / width1) ** 2) + 0.35 * np.exp(
-((wavelength_um - center2) / width2) ** 2
)
@dataclass
class Material:
name: str
n_const: float
k_const: float
def nk(self, wavelength_um: np.ndarray) -> np.ndarray:
n = np.full_like(wavelength_um, self.n_const, dtype=np.complex128)
k = np.full_like(wavelength_um, self.k_const, dtype=np.complex128)
return n - 1j * k
def pdms_index(wavelength_um: np.ndarray) -> np.ndarray:
q1 = load_q1_module()
n = q1.cauchy_index(wavelength_um)
k = q1.extinction_coeff(wavelength_um)
return n - 1j * k
def ag_index(wavelength_um: np.ndarray) -> np.ndarray:
n = 0.15 + 0.6 * np.exp(-((wavelength_um - 0.5) / 0.4) ** 2)
k = 4.5 + 3.5 * np.exp(-((wavelength_um - 10) / 6) ** 2)
return n - 1j * k
def transfer_matrix_stack(
wavelength_um: np.ndarray,
layer_nk: Sequence[np.ndarray],
thickness_um: Sequence[float],
substrate_nk: np.ndarray,
n0: float = 1.0,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
beta = 2 * np.pi / (wavelength_um * 1e-6)
q0 = n0
qs = substrate_nk
R = np.zeros_like(wavelength_um)
T = np.zeros_like(wavelength_um)
for idx, wl in enumerate(wavelength_um):
M = np.identity(2, dtype=complex)
for nk, d in zip(layer_nk, thickness_um):
n_layer = nk[idx]
delta = beta[idx] * n_layer * d * 1e-6
cos = np.cos(delta)
sin = 1j * np.sin(delta)
q = n_layer
Mj = np.array([[cos, sin / q], [q * sin, cos]], dtype=complex)
M = M @ Mj
numerator = (
q0 * M[0, 0]
+ q0 * qs[idx] * M[0, 1]
- M[1, 0]
- qs[idx] * M[1, 1]
)
denominator = (
q0 * M[0, 0]
+ q0 * qs[idx] * M[0, 1]
+ M[1, 0]
+ qs[idx] * M[1, 1]
)
r = numerator / denominator
t = 2 * q0 / denominator
R[idx] = np.abs(r) ** 2
T[idx] = np.real(qs[idx] / q0) * np.abs(t) ** 2
A = np.clip(1 - R - T, 0, 1)
return R, T, A
def evaluate_stack(design: Dict) -> Dict:
solar_wl = np.linspace(0.35, 2.5, 120)
ir_wl = np.linspace(8, 13, 200)
solar_w = solar_weight(solar_wl)
ir_w = planck_weight(ir_wl)
substrate = ag_index
layer_funcs = []
thickness = []
for layer in design["layers"]:
material = layer["material"]
thickness.append(layer["thickness"])
if material == "PDMS":
layer_funcs.append(pdms_index)
else:
mat = MATERIAL_LIBRARY[material]
layer_funcs.append(lambda wl, m=mat: m.nk(wl))
solar_nk = [func(solar_wl) for func in layer_funcs]
ir_nk = [func(ir_wl) for func in layer_funcs]
solar_R, _, solar_A = transfer_matrix_stack(
solar_wl, solar_nk, thickness, substrate(solar_wl)
)
ir_R, _, ir_A = transfer_matrix_stack(ir_wl, ir_nk, thickness, substrate(ir_wl))
alpha = float(np.trapz(solar_A * solar_w, solar_wl) / np.trapz(solar_w, solar_wl))
epsilon = float(np.trapz(ir_A * ir_w, ir_wl) / np.trapz(ir_w, ir_wl))
score = epsilon - 0.3 * alpha
return {"alpha": alpha, "epsilon": epsilon, "score": score}
MATERIAL_LIBRARY: Dict[str, Material] = {
"SiO2": Material("SiO2", 1.45, 1e-4),
"Al2O3": Material("Al2O3", 1.76, 1.5e-3),
"TiO2": Material("TiO2", 2.40, 5e-3),
"Si3N4": Material("Si3N4", 2.05, 2e-3),
"HfO2": Material("HfO2", 1.9, 2e-3),
}
def random_design() -> Dict:
num_layers = random.choice([2, 3])
middle_materials = random.sample(list(MATERIAL_LIBRARY.keys()), num_layers)
layers = [{"material": "PDMS", "thickness": random.uniform(10, 50)}]
for mat in middle_materials:
layers.append(
{
"material": mat,
"thickness": random.uniform(0.05, 2.0),
}
)
return {"layers": layers}
def optimize(iterations: int = 800) -> List[Dict]:
best_designs: List[Dict] = []
for _ in range(iterations):
design = random_design()
metrics = evaluate_stack(design)
design.update(metrics)
best_designs.append(design)
best_designs.sort(key=lambda x: x["score"], reverse=True)
return best_designs[:15]
def write_summary(designs: List[Dict], path: str) -> None:
with open(path, "w", encoding="utf-8") as f:
f.write("rank,score,epsilon,alpha,layers\n")
for idx, design in enumerate(designs, start=1):
layer_desc = ";".join(
f"{layer['material']}@{layer['thickness']:.3f}um"
for layer in design["layers"]
)
f.write(
f"{idx},{design['score']:.4f},{design['epsilon']:.4f},"
f"{design['alpha']:.4f},{layer_desc}\n"
)
def plot_pareto(designs: List[Dict], path: str) -> None:
eps = [d["epsilon"] for d in designs]
alpha = [d["alpha"] for d in designs]
scores = [d["score"] for d in designs]
fig, ax = plt.subplots(figsize=(6, 5))
scatter = ax.scatter(alpha, eps, c=scores, cmap="viridis", s=80)
ax.set_xlabel("Solar-weighted Absorption α")
ax.set_ylabel("8-13 µm Emissivity ε")
ax.set_title("Multilayer Design Performance Distribution")
plt.colorbar(scatter, label="Composite Score ε - 0.3α")
for idx, design in enumerate(designs[:5]):
ax.annotate(str(idx + 1), (design["alpha"], design["epsilon"]))
fig.tight_layout()
plt.savefig(path, dpi=300)
plt.close(fig)
def main():
designs = optimize()
outdir = os.path.join(os.path.dirname(__file__), "outputs")
os.makedirs(outdir, exist_ok=True)
summary_path = os.path.join(outdir, "question3_multilayer_summary.csv")
write_summary(designs, summary_path)
plot_path = os.path.join(outdir, "question3_pareto.png")
plot_pareto(designs, plot_path)
print(f"Optimal designs written to: {summary_path}")
print(f"Performance scatter plot: {plot_path}")
top = designs[0]
layer_desc = "; ".join(
f"{layer['material']}@{layer['thickness']:.2f}um" for layer in top["layers"]
)
print(
"Best design: score={:.3f}, ε={:.3f}, α={:.3f}, layers={}".format(
top["score"], top["epsilon"], top["alpha"], layer_desc
)
)
if __name__ == "__main__":
main()