# mos_transfer_temp_sweep.py # Parent script for Multi-Temperature MOSFET Transfer Sweep # Runs mos_transfer_single_temp.py for various temperatures, combines data, and plots results import os import sys import subprocess import time import numpy as np import matplotlib.pyplot as plt # Configuration import argparse parser = argparse.ArgumentParser(description="Run multi-temperature MOSFET transfer curves sweep.") parser.add_argument("--vds", type=float, default=float(os.environ.get("VDS", 1.0)), help="Drain-Source Voltage Vds (V).") args = parser.parse_args(sys.argv[1:] if len(sys.argv) > 1 else []) vds_target = args.vds DEV_DIR = os.environ.get("DEV_DIR", "devices/LDMOS") default_out_dirname = f"output_transfer_temp_{time.strftime('%y%m%d')}_01" OUT_DIR = os.path.join(os.environ.get("OUT_DIR", os.path.join(DEV_DIR, default_out_dirname)), "") os.makedirs(OUT_DIR, exist_ok=True) # Temperature list in Celsius temps_C = [-40.0, -20.0, 0.0, 25.0, 50.0, 75.0, 100.0, 125.0] print("=============================================================================") # простые математические выражения, избегаем латекса в stdout print("MOSFET Transfer Sweep across Temperatures") print("=============================================================================") print(f"Device directory: {DEV_DIR}") print(f"Output directory: {OUT_DIR}") print(f"Vds: {vds_target:.3f} V") print(f"Temperatures (C): {temps_C}") print("=============================================================================") # Run simulation subprocesses start_time = time.time() for temp in temps_C: print(f"\n>>> Running simulation at T = {temp:.1f} C...") cmd = [ sys.executable, "mos_transfer_single_temp.py", "--temp", str(temp), "--out_dir", OUT_DIR, "--vds", str(vds_target) ] # Set DEV_DIR env for child child_env = os.environ.copy() child_env["DEV_DIR"] = DEV_DIR proc_start = time.time() try: # Run process and wait for completion subprocess.run(cmd, env=child_env, check=True) print(f"Finished T = {temp:.1f} C in {time.time() - proc_start:.1f}s.") except subprocess.CalledProcessError as e: print(f"Error: Simulation for T = {temp:.1f} C failed: {e}") # Continue to other temperatures instead of hard-failing print(f"\nAll simulations completed in {time.time() - start_time:.1f}s.") # --- Combine Data and Plot --- print("\nCombining data and generating plots...") sweep_results = {} for temp in temps_C: csv_file = os.path.join(OUT_DIR, f"transfer_vgs_sweep_{temp:+.1f}C.csv") if os.path.exists(csv_file): try: data = np.loadtxt(csv_file, delimiter=",", skiprows=1) if data.ndim == 2 and data.shape[0] > 0: vgs = data[:, 0] ids = data[:, 1] iav = data[:, 2] sweep_results[temp] = (vgs, ids, iav) else: print(f"Warning: Empty or malformed data in {csv_file}") except Exception as e: print(f"Warning: Could not read {csv_file}: {e}") if not sweep_results: print("Error: No sweep results found to plot or combine. Aborting.") sys.exit(1) # Write combined CSV file combined_csv_file = os.path.join(OUT_DIR, "mos_transfer_all_temps.csv") with open(combined_csv_file, "w") as f_combined: headers = [] for temp in sorted(sweep_results.keys()): headers.append(f"Vgs_T_{temp:+.1f}C(V)") headers.append(f"Ids_T_{temp:+.1f}C(A)") headers.append(f"Iav_T_{temp:+.1f}C(A)") f_combined.write(",".join(headers) + "\n") # Find max length to align columns max_len = max(len(v[0]) for v in sweep_results.values()) for idx in range(max_len): row_cells = [] for temp in sorted(sweep_results.keys()): vgs_vals, ids_vals, iav_vals = sweep_results[temp] if idx < len(vgs_vals): row_cells.append(f"{vgs_vals[idx]:.6e}") row_cells.append(f"{ids_vals[idx]:.6e}") row_cells.append(f"{iav_vals[idx]:.6e}") else: row_cells.append("") row_cells.append("") row_cells.append("") f_combined.write(",".join(row_cells) + "\n") print(f"Saved combined curves data to {combined_csv_file}") # Set styling for premium looks plt.rcParams.update({ 'font.family': 'sans-serif', 'font.size': 11, 'grid.alpha': 0.3, 'grid.linestyle': '--' }) # Generate distinct premium colors colors = plt.cm.plasma(np.linspace(0.1, 0.9, len(sweep_results))) # Plot 1: Linear Scale Plot (Ids in mA) plt.figure(figsize=(10, 7)) for color_idx, temp in enumerate(sorted(sweep_results.keys())): vgs_vals, ids_vals, _ = sweep_results[temp] plt.plot(vgs_vals, ids_vals * 1e3, '-', color=colors[color_idx], linewidth=2, label=f"T = {temp:+.1f} °C") plt.grid(True) plt.xlabel("Gate-Source Voltage Vgs (V)", fontsize=12) plt.ylabel("Drain-Source Current Ids (mA)", fontsize=12) plt.title(f"LDMOS Transfer Characteristics vs Temperature (Linear Scale, Vds={vds_target:.3f}V)", fontsize=14, fontweight="bold") plt.legend(loc="upper left", fontsize=10) plt.tight_layout() plot_path_linear = os.path.join(OUT_DIR, "mos_transfer_curves_linear.png") plt.savefig(plot_path_linear, dpi=300) plt.close() print(f"Saved linear scale plot to {plot_path_linear}") # Plot 2: Logarithmic Scale Plot (Ids in Amperes) plt.figure(figsize=(10, 7)) for color_idx, temp in enumerate(sorted(sweep_results.keys())): vgs_vals, ids_vals, _ = sweep_results[temp] plt.plot(vgs_vals, np.abs(ids_vals), '-', color=colors[color_idx], linewidth=2, label=f"T = {temp:+.1f} °C") plt.yscale('log') plt.ylim(1e-12, 1e-1) # standard bounds for threshold inspection plt.grid(True, which="both") plt.xlabel("Gate-Source Voltage Vgs (V)", fontsize=12) plt.ylabel("Drain-Source Current Ids (A)", fontsize=12) plt.title(f"LDMOS Transfer Characteristics vs Temperature (Log Scale, Vds={vds_target:.3f}V)", fontsize=14, fontweight="bold") plt.legend(loc="lower right", fontsize=10) plt.tight_layout() plot_path_log = os.path.join(OUT_DIR, "mos_transfer_curves_log.png") plt.savefig(plot_path_log, dpi=300) plt.close() print(f"Saved log scale plot to {plot_path_log}") print("\nAll tasks finished successfully!")