This commit is contained in:
2025-11-22 18:08:14 +08:00
parent af9a0f6c3c
commit 634343a2ff
9 changed files with 1347 additions and 1 deletions

0
org/other/__init__.py Normal file
View File

View File

@@ -0,0 +1,151 @@
import os
from dataclasses import dataclass
from typing import Dict, List
import numpy as np
from matplotlib import pyplot as plt
AIR_INDEX = 1.0 # normal incidence, non-absorbing ambient
WAVELENGTH_MIN = 0.4 # µm
WAVELENGTH_MAX = 25.0 # µm
def cauchy_index(wavelength_um: np.ndarray) -> np.ndarray:
"""
Dispersion model for PDMS based on Cauchy equation fitted to literature data:
n(λ) = A + B / λ^2 + C / λ^4
The coefficients (A=1.385, B=0.0125, C=0.00045) match n≈1.43 @ 0.55 µm
and n≈1.40 beyond 2 µm.
"""
A = 1.385
B = 0.0125
C = 0.00045
lam2 = wavelength_um ** 2
return A + B / lam2 + C / (lam2 * wavelength_um ** 2)
def extinction_coeff(wavelength_um: np.ndarray) -> np.ndarray:
"""
Construct an approximate extinction coefficient profile by superposing
Gaussians centered at the known vibrational bands of PDMS in the mid-IR.
Peaks taken from FTIR measurements (Si-O-Si at ~9.2 µm, Si-CH3 rocking at
12.7 µm, CH stretches at 3.4 µm, etc.).
"""
peaks = [
# (center µm, amplitude, width µm)
(3.4, 0.06, 0.25),
(8.0, 0.30, 0.50),
(9.2, 0.65, 0.60),
(10.6, 0.35, 0.45),
(12.7, 0.45, 0.35),
(14.5, 0.25, 0.60),
]
k = np.full_like(wavelength_um, 5e-4)
for center, amp, width in peaks:
k += amp * np.exp(-0.5 * ((wavelength_um - center) / width) ** 2)
# enforce plausible upper bound
return np.clip(k, 0, 2.0)
def thin_film_emissivity(
wavelength_um: np.ndarray, thickness_um: float, n_complex: np.ndarray
) -> np.ndarray:
"""
Normal-incidence emissivity for a PDMS slab in air computed using
the transfer-matrix solution for a single absorbing layer.
"""
n0 = AIR_INDEX
ns = AIR_INDEX
thickness_m = thickness_um * 1e-6
# vacuum wavenumber
beta = 2 * np.pi / (wavelength_um * 1e-6)
delta = beta * n_complex * thickness_m
r01 = (n0 - n_complex) / (n0 + n_complex)
r12 = (n_complex - ns) / (n_complex + ns)
exp_term = np.exp(2j * delta)
numerator_r = r01 + r12 * exp_term
denominator = 1 + r01 * r12 * exp_term
r = numerator_r / denominator
t01 = 2 * n0 / (n0 + n_complex)
t12 = 2 * n_complex / (n_complex + ns)
exp_half = np.exp(1j * delta)
t = (t01 * t12 * exp_half) / denominator
R = np.abs(r) ** 2
T = (ns.real / n0) * np.abs(t) ** 2
A = 1 - R - T
return np.clip(A.real, 0, 1)
@dataclass
class EmissivityResult:
wavelengths: np.ndarray
emissivity_map: Dict[float, np.ndarray]
window_avg: Dict[float, float]
def compute_emissivity(thicknesses_um: List[float]) -> EmissivityResult:
wavelengths = np.linspace(WAVELENGTH_MIN, WAVELENGTH_MAX, 1200)
n = cauchy_index(wavelengths)
k = extinction_coeff(wavelengths)
n_complex = n + 1j * k
emissivity_map = {}
window_avg = {}
window_mask = (wavelengths >= 8) & (wavelengths <= 13)
for d in thicknesses_um:
eps = thin_film_emissivity(wavelengths, d, n_complex)
emissivity_map[d] = eps
window_avg[d] = float(np.trapz(eps[window_mask], wavelengths[window_mask]) /
np.trapz(np.ones_like(wavelengths[window_mask]),
wavelengths[window_mask]))
return EmissivityResult(wavelengths, emissivity_map, window_avg)
def plot_emissivity(result: EmissivityResult, outdir: str) -> str:
plt.figure(figsize=(9, 5))
for d, eps in result.emissivity_map.items():
label = f"{d:.0f} µm (ε̄₈₋₁₃={result.window_avg[d]:.2f})"
plt.plot(result.wavelengths, eps, label=label)
plt.axvspan(8, 13, color="tab:gray", alpha=0.15, label="Atmospheric window")
plt.xlabel("Wavelength (µm)")
plt.ylabel("Spectral emissivity")
plt.title("PDMS Thin-Film Emissivity vs. Wavelength")
plt.ylim(0, 1.05)
plt.xlim(WAVELENGTH_MIN, WAVELENGTH_MAX)
plt.legend()
plt.grid(alpha=0.3)
os.makedirs(outdir, exist_ok=True)
output_path = os.path.join(outdir, "question1_emissivity.png")
plt.tight_layout()
plt.savefig(output_path, dpi=300)
plt.close()
return output_path
def main():
thicknesses = [1, 5, 10, 25, 50, 100]
result = compute_emissivity(thicknesses)
outdir = os.path.join(os.path.dirname(__file__), "outputs")
figure_path = plot_emissivity(result, outdir)
summary_lines = ["thickness_um,avg_emissivity_8_13um"]
for d in thicknesses:
summary_lines.append(f"{d},{result.window_avg[d]:.4f}")
csv_path = os.path.join(outdir, "question1_emissivity_summary.csv")
with open(csv_path, "w", encoding="utf-8") as f:
f.write("\n".join(summary_lines))
print(f"Figure saved to: {figure_path}")
print(f"Summary saved to: {csv_path}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,157 @@
import importlib.util
import math
import os
from dataclasses import dataclass
from typing import Dict, List, 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
SIGMA = 5.670374419e-8
THICKNESSES = [1, 5, 10, 25, 50, 100] # µm
T_AMB = 300.0 # K (≈27 ℃)
T_SKY = 280.0 # Clear dry sky equivalent radiation temperature
SOLAR_IRR = 900.0 # W/m^2, clear sky noon
H_CONV = 8.0 # W/m^2/K, natural convection + light wind
def load_emissivity_module():
here = os.path.dirname(os.path.abspath(__file__))
target = os.path.join(here, "question1_pdms_emissivity.py")
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 solar_absorptance(thickness_um: float) -> float:
"""
Empirical assumption: In PDMS silver-coated system, visible light absorption
is determined by thin film scattering and internal losses.
At 1 µm, α≈0.05, absorption increases by 0.01 for every 10 µm increase in thickness, capped at 0.15.
"""
return min(0.05 + 0.001 * thickness_um, 0.15)
@dataclass
class CoolingResult:
thickness_um: float
eps_window: float
alpha_solar: float
net_power_at_amb: float
eq_temp_K: float
@property
def eq_temp_C(self) -> float:
return self.eq_temp_K - 273.15
@property
def delta_T(self) -> float:
return self.eq_temp_K - T_AMB
def net_cooling_power(temp_K: float, emissivity: float, alpha_s: float) -> float:
radiative = emissivity * SIGMA * (temp_K**4 - T_SKY**4)
solar_gain = alpha_s * SOLAR_IRR
convective = H_CONV * (temp_K - T_AMB)
return -solar_gain - convective + radiative
def solve_equilibrium(emissivity: float, alpha_s: float) -> float:
low, high = 250.0, 330.0
f_low = net_cooling_power(low, emissivity, alpha_s)
f_high = net_cooling_power(high, emissivity, alpha_s)
if f_low * f_high > 0:
# No sign change, return endpoint with smaller energy consumption
return low if abs(f_low) < abs(f_high) else high
for _ in range(80):
mid = 0.5 * (low + high)
f_mid = net_cooling_power(mid, emissivity, alpha_s)
if abs(f_mid) < 1e-4:
return mid
if f_low * f_mid <= 0:
high, f_high = mid, f_mid
else:
low, f_low = mid, f_mid
return 0.5 * (low + high)
def evaluate() -> Tuple[List[CoolingResult], str]:
q1 = load_emissivity_module()
emissivity_data = q1.compute_emissivity(THICKNESSES)
results: List[CoolingResult] = []
for d in THICKNESSES:
eps_window = emissivity_data.window_avg[d]
alpha_s = solar_absorptance(d)
net_amb = net_cooling_power(T_AMB, eps_window, alpha_s)
eq_temp = solve_equilibrium(eps_window, alpha_s)
results.append(
CoolingResult(
thickness_um=d,
eps_window=eps_window,
alpha_solar=alpha_s,
net_power_at_amb=net_amb,
eq_temp_K=eq_temp,
)
)
outdir = os.path.join(os.path.dirname(__file__), "outputs")
os.makedirs(outdir, exist_ok=True)
csv_path = os.path.join(outdir, "question2_cooling_summary.csv")
with open(csv_path, "w", encoding="utf-8") as f:
f.write("thickness_um,eps_8_13,alpha_solar,net_power_amb_Wm2,eq_temp_C,delta_T_C\n")
for res in results:
f.write(
f"{res.thickness_um},{res.eps_window:.4f},{res.alpha_solar:.3f},"
f"{res.net_power_at_amb:.2f},{res.eq_temp_C:.2f},{res.delta_T:.2f}\n"
)
fig_path = os.path.join(outdir, "question2_cooling_results.png")
plot_results(results, fig_path)
return results, csv_path
def plot_results(results: List[CoolingResult], fig_path: str) -> None:
thickness = [r.thickness_um for r in results]
net_power = [r.net_power_at_amb for r in results]
delta_T = [r.delta_T for r in results]
fig, ax1 = plt.subplots(figsize=(9, 5))
ax1.bar(thickness, net_power, width=4, alpha=0.6, label="Net Cooling Power @T_amb")
ax1.set_xlabel("PDMS Film Thickness (µm)")
ax1.set_ylabel("Net Cooling Power (W/m²)")
ax1.axhline(0, color="black", linewidth=0.8)
ax2 = ax1.twinx()
ax2.plot(thickness, delta_T, color="tab:red", marker="o", label="Equilibrium Temperature Difference")
ax2.set_ylabel("Equilibrium Temperature Difference (K)")
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines + lines2, labels + labels2, loc="upper right")
fig.tight_layout()
plt.savefig(fig_path, dpi=300)
plt.close(fig)
def main():
results, csv_path = evaluate()
print(f"Results written to: {csv_path}")
for r in results:
print(
f"d={r.thickness_um:>3} um, eps_8_13={r.eps_window:.2f}, alpha_sol={r.alpha_solar:.2f}, "
f"q_net(T_amb)={r.net_power_at_amb:.1f} W/m2, T_eq={r.eq_temp_C:.1f} °C "
f"(DeltaT={r.delta_T:.1f} K)"
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,236 @@
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()

View File

@@ -0,0 +1,41 @@
import os
from PyPDF2 import PdfReader
def find_pdf(start_dir: str, filename: str) -> str:
"""Return the absolute path to the target PDF, searching downward if needed."""
candidate = os.path.join(start_dir, filename)
if os.path.exists(candidate):
return candidate
for root, _, files in os.walk(start_dir):
if filename in files:
return os.path.join(root, filename)
raise FileNotFoundError(f"Unable to locate {filename} under {start_dir}")
def main() -> None:
script_dir = os.path.dirname(os.path.abspath(__file__))
pdf_name = "2025 APMCM Problem B.pdf"
pdf_path = find_pdf(script_dir, pdf_name)
reader = PdfReader(pdf_path)
pages = []
for idx, page in enumerate(reader.pages, start=1):
text = page.extract_text() or ""
pages.append(f"\n=== Page {idx} ===\n{text.strip()}")
output_text = "".join(pages)
print(output_text)
output_path = os.path.join(script_dir, "problem_text_pypdf.txt")
with open(output_path, "w", encoding="utf-8") as f:
f.write(output_text)
print(f"\nText saved to: {output_path}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,27 @@
import pdfplumber
import os
# 使用当前脚本所在目录
script_dir = os.path.dirname(os.path.abspath(__file__))
pdf_path = os.path.join(script_dir, "2025 APMCM Problem B.pdf")
print(f"Looking for PDF at: {pdf_path}")
print(f"File exists: {os.path.exists(pdf_path)}")
if os.path.exists(pdf_path):
with pdfplumber.open(pdf_path) as pdf:
full_text = ""
for i, page in enumerate(pdf.pages):
text = page.extract_text()
if text:
full_text += f"\n=== Page {i+1} ===\n{text}\n"
print(full_text)
# 保存到文本文件
output_path = os.path.join(script_dir, "problem_text.txt")
with open(output_path, "w", encoding="utf-8") as f:
f.write(full_text)
print(f"\n文本已保存到: {output_path}")
else:
print(f"文件不存在: {pdf_path}")
print(f"当前工作目录: {os.getcwd()}")
print(f"目录内容: {os.listdir('.')}")