import numpy as np import matplotlib.pyplot as plt from scipy.interpolate import CubicSpline from scipy.integrate import simpson import os # ----------------------------- # Configuration (Update File Path!) # ----------------------------- DATA_FILE_PATH = "/Users/spasolreisa/IdeaProjects/asiaMath/data.txt" # Replace with your data.txt absolute path THICKNESSES = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0] # Expand thickness range for evaluation T_AMBIENT = 300 # Ambient temperature (K) SOLAR_IRRADIANCE = 1000 # AM1.5 solar irradiance (W/m²) CONVECTION_COEFF = 10 # Convection coefficient (W/(m²K)) SIGMA = 5.67e-8 # Stefan-Boltzmann constant (W/(m²K⁴)) # ----------------------------- # 1. Fixed Data Parsing Function (Critical Fix for "wl" String Error) # ----------------------------- def read_split_data(file_path): """Read and parse split-format data (wl+n followed by wl+k)""" if not os.path.exists(file_path): raise FileNotFoundError(f"File not found: {file_path}") # Read all lines, skip empty lines and comments with open(file_path, 'r', encoding='utf-8') as f: lines = [] for line in f: stripped = line.strip() if stripped and not stripped.startswith('#'): lines.append(stripped) # Step 1: Identify all headers (lines containing "wl" and either "n" or "k") header_indices = [] for i, line in enumerate(lines): parts = line.split() # Header must be exactly 2 parts: ["wl", "n"] or ["wl", "k"] (case-insensitive) if len(parts) == 2 and parts[0].lower() == "wl" and parts[1].lower() in ["n", "k"]: header_indices.append(i) # Validate: Must have exactly 2 headers (one for n, one for k) if len(header_indices) != 2: raise ValueError( f"Invalid number of headers! Expected 2 (wl+n and wl+k), found {len(header_indices)}.\nCheck data.txt format.") # Step 2: Split data into n-block and k-block n_header_idx = header_indices[0] k_header_idx = header_indices[1] # Ensure n-header comes before k-header if n_header_idx > k_header_idx: n_header_idx, k_header_idx = k_header_idx, n_header_idx # Extract n data (between n-header and k-header) n_lines = lines[n_header_idx + 1: k_header_idx] # Extract k data (after k-header) k_lines = lines[k_header_idx + 1:] # Step 3: Parse n data (skip any invalid lines) wl_n, n_list = [], [] for line in n_lines: parts = line.split() # Data line must have exactly 2 numeric parts if len(parts) != 2: continue # Skip lines with wrong column count try: wl_val = float(parts[0]) n_val = float(parts[1]) wl_n.append(wl_val) n_list.append(n_val) except ValueError: continue # Skip non-numeric lines # Step 4: Parse k data (skip any invalid lines) wl_k, k_list = [], [] for line in k_lines: parts = line.split() if len(parts) != 2: continue try: wl_val = float(parts[0]) k_val = float(parts[1]) wl_k.append(wl_val) k_list.append(k_val) except ValueError: continue # Validate: Must have at least 1 data point for n and k if len(wl_n) == 0: raise ValueError("No valid n data found! Check the format between wl+n and wl+k headers.") if len(wl_k) == 0: raise ValueError("No valid k data found! Check the format after wl+k header.") # Convert to numpy arrays wl_n, n_list = np.array(wl_n), np.array(n_list) wl_k, k_list = np.array(wl_k), np.array(k_list) # Align wavelengths (if n and k have different wavelength points) if not np.allclose(wl_n, wl_k, rtol=1e-6): print("Warning: Wavelengths for n and k do not match. Automatically aligning...") # Use n's wavelengths as reference, interpolate k to match k_list = np.interp(wl_n, np.sort(wl_k), k_list[np.argsort(wl_k)]) wl_k = wl_n # Sync k's wavelengths to n's # Sort by wavelength (ascending) to avoid interpolation errors sorted_idx = np.argsort(wl_n) sorted_wl = wl_n[sorted_idx] sorted_n = n_list[sorted_idx] sorted_k = k_list[sorted_idx] print(f"Data loaded successfully: {len(sorted_wl)} valid wavelength points") print(f"Wavelength range: {sorted_wl.min():.2f}–{sorted_wl.max():.2f} μm") return sorted_wl, sorted_n, sorted_k # ----------------------------- # 2. Core Functions (Unchanged) # ----------------------------- def planck_function(wl, T): """Planck's law: Blackbody radiation (W/(m³sr))""" wl_m = wl * 1e-6 # Convert μm to m c1 = 3.7418e8 # First radiation constant (Wμm⁴/m²) c2 = 14388 # Second radiation constant (μmK) return c1 / (wl_m ** 5 * (np.exp(c2 / (wl * T)) - 1)) def solar_spectrum_am15(wl): """AM1.5 global solar irradiance (W/(m²μm))""" spectrum = np.zeros_like(wl) mask = (wl >= 0.3) & (wl <= 2.5) wl_masked = wl[mask] # Empirical fit to AM1.5 data (valid for 0.3–2.5 μm) spectrum[mask] = np.where( wl_masked < 0.5, 800 + 400 * wl_masked, np.where(wl_masked < 1.0, 1000 - 200 * (wl_masked - 0.5), np.where(wl_masked < 1.5, 900 - 100 * (wl_masked - 1.0), 750 - 200 * (wl_masked - 1.5))) ) return spectrum def fresnel_reflectance(n1, k1, n2, k2): """Fresnel reflectance (normal incidence, complex refractive index)""" m1, m2 = n1 + 1j * k1, n2 + 1j * k2 return np.abs((m1 - m2) / (m1 + m2)) ** 2 def thin_film_optical_properties(n_film, k_film, d, wl): """Calculate emissivity (ε), absorptivity (α), transmissivity (T) of thin film""" R12 = fresnel_reflectance(1.0, 0.0, n_film, k_film) # Air→Film R23 = fresnel_reflectance(n_film, k_film, 1.0, 0.0) # Film→Air delta = 2 * np.pi * n_film * d / wl # Phase difference alpha_abs = 4 * np.pi * k_film * d / wl # Absorption attenuation # Total reflectance and transmissivity (multiple-beam interference) R_total = (R12 + R23 * np.exp(-alpha_abs) + 2 * np.sqrt(R12 * R23 * np.exp(-alpha_abs)) * np.cos(2 * delta)) / \ (1 + R12 * R23 * np.exp(-alpha_abs) + 2 * np.sqrt(R12 * R23 * np.exp(-alpha_abs)) * np.cos(2 * delta)) T_total = (1 - R12) * (1 - R23) * np.exp(-alpha_abs) / \ (1 + R12 * R23 * np.exp(-alpha_abs) + 2 * np.sqrt(R12 * R23 * np.exp(-alpha_abs)) * np.cos(2 * delta)) alpha_total = 1 - R_total - T_total # Kirchhoff's law (α=ε for thermal equilibrium) return alpha_total, R_total, T_total # α=ε for emissivity # ----------------------------- # 3. Evaluation Model (Unchanged) # ----------------------------- def evaluate_radiative_cooling(wl_all, n_all, k_all, thickness): """Calculate KPIs and comprehensive score for a given PDMS thickness""" cs_n = CubicSpline(wl_all, n_all) cs_k = CubicSpline(wl_all, k_all) # KPI 1: Average Emissivity in 8–13 μm (weighted by Planck function) wl_window = np.linspace(8, 13, 500) # Check if data covers the window (otherwise use nearest values) if wl_all.min() > 8 or wl_all.max() < 13: print(f"Warning: Data does not fully cover 8–13 μm window. Extrapolating...") n_window = cs_n(wl_window, extrapolate=True) k_window = cs_k(wl_window, extrapolate=True) else: n_window = cs_n(wl_window) k_window = cs_k(wl_window) eps_window, _, _ = thin_film_optical_properties(n_window, k_window, thickness, wl_window) planck = planck_function(wl_window, T_AMBIENT) eps_avg = simpson(eps_window * planck, wl_window) / simpson(planck, wl_window) # KPI 2: Average Solar Absorptivity in 0.3–2.5 μm (weighted by AM1.5) wl_solar = np.linspace(0.3, 2.5, 500) if wl_all.min() > 2.5 or wl_all.max() < 0.3: print(f"Warning: Data does not cover solar spectrum (0.3–2.5 μm). Using default PDMS properties...") n_solar = np.ones_like(wl_solar) * 1.4 # Typical PDMS n in solar range k_solar = np.ones_like(wl_solar) * 1e-6 # Typical PDMS k in solar range else: n_solar = cs_n(wl_solar, extrapolate=True) k_solar = cs_k(wl_solar, extrapolate=True) alpha_solar, _, _ = thin_film_optical_properties(n_solar, k_solar, thickness, wl_solar) solar_irr = solar_spectrum_am15(wl_solar) alpha_avg = simpson(alpha_solar * solar_irr, wl_solar) / simpson(solar_irr, wl_solar) # KPI 3: Maximum Cooling Temperature (ΔT_max) def heat_flux(T_film): planck_film = planck_function(wl_window, T_film) eps_eff = simpson(eps_window * planck_film, wl_window) / simpson(planck_film, wl_window) return SIGMA * eps_eff * T_film ** 4 - alpha_avg * SOLAR_IRRADIANCE - CONVECTION_COEFF * (T_film - T_AMBIENT) # Newton-Raphson iteration (stable convergence) T_film = T_AMBIENT - 10 # Initial guess for _ in range(50): q = heat_flux(T_film) if abs(q) < 1e-3: break # Numerical derivative (more stable than analytical) dq_dT = (heat_flux(T_film + 1e-4) - heat_flux(T_film - 1e-4)) / (2e-4) T_film -= q / dq_dT # Prevent unrealistic temperatures if T_film < 200 or T_film > T_AMBIENT: T_film = max(200, min(T_AMBIENT - 5, T_film)) delta_T = T_AMBIENT - T_film # KPI 4: Cooling Efficiency Ratio (η_CR) eta_cr = eps_avg / (alpha_avg + 0.01) # +0.01 to avoid division by zero # Comprehensive Score (0–100) score = 0.0 score += 40 * min(eps_avg, 1.0) # Cap at 1.0 (ideal emissivity) score += 35 * (1 - min(alpha_avg, 1.0)) # Lower absorption = higher score score += 15 * min(delta_T / 40, 1.0) # ΔT theoretical upper limit = 40K score += 10 * min(eta_cr / 100, 1.0) # Cap at 100 (ideal ratio) return { "thickness": thickness, "eps_8-13": eps_avg, "alpha_0.3-2.5": alpha_avg, "delta_T_max": delta_T, "eta_cr": eta_cr, "comprehensive_score": score } # ----------------------------- # 4. Main Execution (Unchanged) # ----------------------------- if __name__ == "__main__": try: # Read data (fixed parsing logic) wl_all, n_all, k_all = read_split_data(DATA_FILE_PATH) print("\n" + "-" * 50 + "\n") # Evaluate each thickness results = [] for d in THICKNESSES: res = evaluate_radiative_cooling(wl_all, n_all, k_all, d) results.append(res) print(f"Thickness: {d} μm") print(f" - Avg Emissivity (8–13 μm): {res['eps_8-13']:.4f}") print(f" - Avg Solar Absorptivity (0.3–2.5 μm): {res['alpha_0.3-2.5']:.4f}") print(f" - Max Cooling Temperature: {res['delta_T_max']:.2f} K") print(f" - Cooling Efficiency Ratio: {res['eta_cr']:.2f}") print(f" - Comprehensive Score: {res['comprehensive_score']:.1f}/100\n") # Convert results to numpy array for plotting results_arr = np.array([[ res["thickness"], res["eps_8-13"], res["alpha_0.3-2.5"], res["delta_T_max"], res["comprehensive_score"] ] for res in results]) # Plot KPIs vs Thickness fig, axes = plt.subplots(2, 2, figsize=(14, 10)) fig.suptitle("PDMS Thin Film Radiative Cooling Performance vs Thickness", fontsize=16, fontweight='bold') # Emissivity (8–13 μm) axes[0, 0].plot(results_arr[:, 0], results_arr[:, 1], 'o-', color='darkred', linewidth=2, markersize=6) axes[0, 0].set_xlabel("Thickness (μm)", fontsize=12), axes[0, 0].set_ylabel("Avg Emissivity (8–13 μm)", fontsize=12) axes[0, 0].grid(True, alpha=0.3), axes[0, 0].set_ylim(0, 1.05) # Solar Absorptivity (0.3–2.5 μm) axes[0, 1].plot(results_arr[:, 0], results_arr[:, 2], 's-', color='darkblue', linewidth=2, markersize=6) axes[0, 1].set_xlabel("Thickness (μm)", fontsize=12), axes[0, 1].set_ylabel( "Avg Solar Absorptivity (0.3–2.5 μm)", fontsize=12) axes[0, 1].grid(True, alpha=0.3), axes[0, 1].set_ylim(0, 0.5) # Max Cooling Temperature axes[1, 0].plot(results_arr[:, 0], results_arr[:, 3], '^-', color='darkgreen', linewidth=2, markersize=6) axes[1, 0].set_xlabel("Thickness (μm)", fontsize=12), axes[1, 0].set_ylabel("Max Cooling Temperature (K)", fontsize=12) axes[1, 0].grid(True, alpha=0.3) # Comprehensive Score axes[1, 1].plot(results_arr[:, 0], results_arr[:, 4], 'd-', color='darkorange', linewidth=2, markersize=6) axes[1, 1].set_xlabel("Thickness (μm)", fontsize=12), axes[1, 1].set_ylabel("Comprehensive Score (0–100)", fontsize=12) axes[1, 1].grid(True, alpha=0.3), axes[1, 1].set_ylim(0, 100) plt.tight_layout() plt.savefig("PDMS_radiative_cooling_evaluation.png", dpi=300, bbox_inches='tight') plt.show() # Highlight optimal thickness optimal = max(results, key=lambda x: x["comprehensive_score"]) print("=" * 50) print(f"Optimal PDMS Thickness: {optimal['thickness']} μm") print(f"Best Comprehensive Score: {optimal['comprehensive_score']:.1f}/100") print( f"Key Performance: ε(8-13μm)={optimal['eps_8-13']:.4f}, α(0.3-2.5μm)={optimal['alpha_0.3-2.5']:.4f}, ΔT={optimal['delta_T_max']:.2f}K") print("=" * 50) except Exception as e: print(f"\nError: {e}") print("\nTroubleshooting Steps:") print("1. Check data.txt format: Ensure it has exactly two headers (e.g., 'wl n' and 'wl k')") print("2. Example valid format:") print(" wl n") print(" 0.40 1.41491") print(" 0.41 1.41403") print(" ...") print(" wl k") print(" 0.40 1.40E-06") print(" 0.41 1.38E-06") print("3. Ensure no extra 'wl' strings in data lines (only numbers)") print("4. Use space or tab as separator (avoid commas)")