Update sweep tolerances and step size controls

This commit is contained in:
pchang718
2026-06-15 07:06:26 +08:00
parent f1ec8335de
commit edac7ad030
6 changed files with 161 additions and 46 deletions
+11 -10
View File
@@ -11,6 +11,7 @@ export OPENBLAS_NUM_THREADS = 4
# Default simulation control options
avalanche ?= false
refine ?= true
refine_v_step ?= 50.0
PYTHON := .venv/bin/python
@@ -56,14 +57,17 @@ help-detail:
@echo " Default: false (normal sweep without avalanche)"
@echo " refine=true|false - Toggle dynamic adaptive refinement during sweep"
@echo " Default: true (enable grid splitting at milestones)"
@echo " refine_v_step=<voltage> - Set voltage interval (V) to trigger dynamic refinement"
@echo " Default: 50.0 (e.g., every 50V). Less than 1.0 disables it."
@echo " checkpoint=<filepath> - Specify seed/recovery pickle file to resume from"
@echo " Default: automatically searches for latest checkpoints"
@echo ""
@echo "Usage Examples:"
@echo " make sweep avalanche=true"
@echo " make sweep refine=false"
@echo " make sweep refine_v_step=25.0"
@echo " make resume checkpoint=output_this_run/seed_500V.pkl"
@echo " make resume-bg avalanche=true"
@echo " make resume-bg avalanche=true refine_v_step=30.0"
@echo "============================================================================="
# --- 歷史運行歸檔備份 ---
@@ -71,9 +75,6 @@ help-detail:
backup-run:
@if [ -f output_this_run/sweeping.log ] || [ -f output_this_run/simulation_time.log ]; then \
echo ">>> [Backup] 備份上一次的日誌與輸出檔案..."; \
rm -f sweeping.last_log simulation_time.last_log; \
[ -f output_this_run/sweeping.log ] && mv output_this_run/sweeping.log sweeping.last_log || true; \
[ -f output_this_run/simulation_time.log ] && mv output_this_run/simulation_time.log simulation_time.last_log || true; \
rm -rf output_last_run; \
mv output_this_run output_last_run || true; \
fi
@@ -106,18 +107,18 @@ static: device_2d.msh solve_static_2d.py
# 依賴於對應的網格與掃描腳本
sweep: device_2d.msh solve_sweep_recon.py
$(MAKE) backup-run
@echo ">>> [Sweep] 開始高壓偏壓漂移-擴散模擬 (avalanche=$(avalanche), refine=$(refine))..."
AVALANCHE=$(avalanche) REFINE=$(refine) $(PYTHON) solve_sweep_recon.py > output_this_run/sweeping.log 2>&1
@echo ">>> [Sweep] 開始高壓偏壓漂移-擴散模擬 (avalanche=$(avalanche), refine=$(refine), refine_v_step=$(refine_v_step))..."
AVALANCHE=$(avalanche) REFINE=$(refine) REFINE_V_STEP=$(refine_v_step) $(PYTHON) solve_sweep_recon.py > output_this_run/sweeping.log 2>&1
resume:
@echo ">>> [Resume] 從指定的 Checkpoint ($(checkpoint)) 或最新的自動備份接續掃描 (avalanche=$(avalanche), refine=$(refine))..."
@echo ">>> [Resume] 從指定的 Checkpoint ($(checkpoint)) 或最新的自動備份接續掃描 (avalanche=$(avalanche), refine=$(refine), refine_v_step=$(refine_v_step))..."
@mkdir -p output_this_run
AVALANCHE=$(avalanche) REFINE=$(refine) $(PYTHON) resume_run.py $(checkpoint) >> output_this_run/sweeping.log 2>&1
AVALANCHE=$(avalanche) REFINE=$(refine) REFINE_V_STEP=$(refine_v_step) $(PYTHON) resume_run.py $(checkpoint) >> output_this_run/sweeping.log 2>&1
resume-bg:
@echo ">>> [Resume-BG] 在背景從指定的 Checkpoint ($(checkpoint)) 或最新的自動備份接續掃描 (avalanche=$(avalanche), refine=$(refine))..."
@echo ">>> [Resume-BG] 在背景從指定的 Checkpoint ($(checkpoint)) 或最新的自動備份接續掃描 (avalanche=$(avalanche), refine=$(refine), refine_v_step=$(refine_v_step))..."
@mkdir -p output_this_run
AVALANCHE=$(avalanche) REFINE=$(refine) nohup $(PYTHON) resume_run.py $(checkpoint) >> output_this_run/sweeping.log 2>&1 &
AVALANCHE=$(avalanche) REFINE=$(refine) REFINE_V_STEP=$(refine_v_step) nohup $(PYTHON) resume_run.py $(checkpoint) >> output_this_run/sweeping.log 2>&1 &
# --- 萃取與監控收斂曲線 ---
show-conv:
+8 -7
View File
@@ -421,9 +421,10 @@ def refine_and_interpolate(device_old, v_bias, is_avalanche_enabled=False, time_
# 2. 計算空乏區最深邊界 (E > 2e4 V/cm)
dep_nodes = [y_val for y_val, e_val in zip(y_si, emag_si) if e_val > 2.0e4]
y_dep = max(dep_nodes) if dep_nodes else 15.0 * um
# 預測加密外推 8 µm
y_box_max = min(y_dep + 8.0 * um, H_SI)
print(f"Detected depletion max depth: {y_dep / um:.2f} um. Dynamic Box encryption boundary set to: {y_box_max / um:.2f} um.")
# 預測加密外推 10 µm (Guard Band)
y_box_max = min(y_dep + 10.0 * um, H_SI)
y_medium_max = min(y_dep + 30.0 * um, H_SI)
print(f"Detected depletion max depth: {y_dep / um:.2f} um. Dynamic Box encryption boundary set to: {y_box_max / um:.2f} um, medium boundary set to: {y_medium_max / um:.2f} um.")
# 3. 動態寫出 bgmesh.pos
LcMin = 0.15 * um
@@ -456,7 +457,7 @@ def refine_and_interpolate(device_old, v_bias, is_avalanche_enabled=False, time_
# 4. 動態調用 Gmsh 重建網格
mesh_out_path = f"{OUT_DIR}device_2d_refined.msh"
generate_mesh_2d.create_mesh(y_box_max=y_box_max, mesh_out=mesh_out_path, bgmesh_pos=bgmesh_pos_path)
generate_mesh_2d.create_mesh(y_box_max=y_box_max, y_medium_max=y_medium_max, mesh_out=mesh_out_path, bgmesh_pos=bgmesh_pos_path)
# 5. 載入新網格並設定物理與 solutions
devsim.create_gmsh_mesh(mesh=device_new_name, file=mesh_out_path)
@@ -804,8 +805,8 @@ def refine_and_interpolate(device_old, v_bias, is_avalanche_enabled=False, time_
devsim.equation(device=device_new_name, region=reg, name="PotentialEquation", variable_name="Potential",
edge_model="PotentialEdgeFlux", variable_update="default", min_error=1e-3)
try:
res_s1 = devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-5, charge_error=1e12, maximum_iterations=20, rollback=False, info=True)
iters_s1 = len(res_s1.get("iterations", [])) if (isinstance(res_s1, dict) and "iterations" in res_s1) else 20
res_s1 = devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-3, charge_error=1e12, maximum_iterations=12, rollback=False, info=True)
iters_s1 = len(res_s1.get("iterations", [])) if (isinstance(res_s1, dict) and "iterations" in res_s1) else 12
print(" Stage 1 Coupled log_damp solve finished.")
except devsim.error as e:
print(f" Stage 1 Coupled log_damp solve errored: {e}. Proceeding to Stage 2 anyway.")
@@ -847,7 +848,7 @@ def refine_and_interpolate(device_old, v_bias, is_avalanche_enabled=False, time_
print("[Stage 2] Running log_damp coupled precision solve (max 30 iters)...")
try:
res_stage2 = devsim.solve(type="dc", absolute_error=1e10, relative_error=3e-3,
res_stage2 = devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-3,
charge_error=1e12, maximum_iterations=30, info=True)
iters_s2 = len(res_stage2.get("iterations", [])) if (isinstance(res_stage2, dict) and "iterations" in res_stage2) else 30
# We check both absolute error and charge error convergence through devsim solve.
+18 -7
View File
@@ -3,7 +3,7 @@ import numpy as np
import os
from device_config import *
def create_mesh(y_box_max=25.0*um, mesh_out="device_2d.msh", bgmesh_pos="device_bgmesh.pos"):
def create_mesh(y_box_max=12.0*um, y_medium_max=20.0*um, mesh_out="device_2d.msh", bgmesh_pos="device_bgmesh.pos"):
gmsh.initialize()
gmsh.model.add("device_2d")
@@ -294,22 +294,33 @@ def create_mesh(y_box_max=25.0*um, mesh_out="device_2d.msh", bgmesh_pos="device_
gmsh.model.mesh.field.add("Threshold", 2)
gmsh.model.mesh.field.setNumber(2, "IField", 1)
gmsh.model.mesh.field.setNumber(2, "LcMin", 0.15 * um) # 0.15 um near interfaces
gmsh.model.mesh.field.setNumber(2, "LcMax", 20.0 * um) # 20 um far from interfaces
gmsh.model.mesh.field.setNumber(2, "LcMax", 20.0 * um) #放寬至 20 um
gmsh.model.mesh.field.setNumber(2, "DistMin", 0.15 * um) # Concentrated near interfaces
gmsh.model.mesh.field.setNumber(2, "DistMax", 1.0 * um) # Coarsen rapidly at 1.0 um
# Box field to transition background mesh size in the active well region (Y <= 6 um) to 1.5 um
# Box field to transition background mesh size in the active well region to 1.5 um, with 10 um transition zone to 20.0 um
gmsh.model.mesh.field.add("Box", 3)
gmsh.model.mesh.field.setNumber(3, "VIn", 1.5 * um) # Background surface mesh is 1.5 um (instead of 0.15 um)
gmsh.model.mesh.field.setNumber(3, "VOut", 20.0 * um)
gmsh.model.mesh.field.setNumber(3, "VIn", 1.5 * um) # Background surface mesh is 1.5 um
gmsh.model.mesh.field.setNumber(3, "VOut", 20.0 * um) # Outer mesh size (LcMax)
gmsh.model.mesh.field.setNumber(3, "Thickness", 10.0 * um) # 10 um transition zone
gmsh.model.mesh.field.setNumber(3, "XMin", -W_DEVICE)
gmsh.model.mesh.field.setNumber(3, "XMax", W_DEVICE)
gmsh.model.mesh.field.setNumber(3, "YMin", 0.0)
gmsh.model.mesh.field.setNumber(3, "YMax", y_box_max)
# Combine threshold field and box field using Min field
# Medium box field to transition background mesh size to 4.0 um, with 10 um transition zone to 20.0 um
gmsh.model.mesh.field.add("Box", 5)
gmsh.model.mesh.field.setNumber(5, "VIn", 4.0 * um) # Medium density region is 4.0 um
gmsh.model.mesh.field.setNumber(5, "VOut", 20.0 * um)
gmsh.model.mesh.field.setNumber(5, "Thickness", 10.0 * um)
gmsh.model.mesh.field.setNumber(5, "XMin", -W_DEVICE)
gmsh.model.mesh.field.setNumber(5, "XMax", W_DEVICE)
gmsh.model.mesh.field.setNumber(5, "YMin", 0.0)
gmsh.model.mesh.field.setNumber(5, "YMax", y_medium_max)
# Combine threshold field, box field, and medium box field using Min field
gmsh.model.mesh.field.add("Min", 4)
gmsh.model.mesh.field.setNumbers(4, "FieldsList", [2, 3])
gmsh.model.mesh.field.setNumbers(4, "FieldsList", [2, 3, 5])
# Restrict the combined field to only Silicon and Oxide regions
restrict_field = gmsh.model.mesh.field.add("Restrict")
+78
View File
@@ -1255,3 +1255,81 @@ DEVSIM 核心預設在 `solve` 未收斂時強制執行 `RestoreSolutions("_prev
* 這實現了本項目**有史以來第一次確定的、成功的、收斂的網格自適應加密**,為後續高偏壓下準確分析元件物理特性鋪平了道路。
### 21. 關於 100V 網格細化發散原因與「收緊基底+降電場閾值+優化加密頻率」組合拳探討(2026-06-13)
在 100V 的 bias sweep 中,動態網格加密再次遭遇發散(Stage 1 電位方程式未收斂,殘差停留在 ~117V,隨後進入 Stage 2 立即爆炸)。我們針對此問題進行了深入的物理與數值分析,並探討了對應的優化組合拳。
#### 21.1 物理與數值分析:為何 100V 網格細化會發散?
1. **輕摻雜基底下的空乏區極速擴展**
* 雖然 P-Well Doping 深度只有 $5\,\mu\text{m}$,但 Silicon Substrate 摻雜濃度極低($1 \times 10^{14}\text{ cm}^{-3}$)。
* 根據突面接面公式 $W = \sqrt{2 \epsilon V / q N_{\text{SUB}}}$ 計算,在 100V 下,空乏區理論上會向基底深處擴展至 **$36.0\,\mu\text{m}$**。
2. **電場偵測閾值偏高導致 Box 保護不足**
* 在 [dynamic_refine.py](file:///home/pchan/devsim2026/dynamic_refine.py) 中,我們定義空乏邊界的臨界電場為 $2.0 \times 10^4\text{ V/cm}$。
* 由於 100V 下的最大電場也僅約 $5.5 \times 10^4\text{ V/cm}$,此高閾值導致程式僅偵測到 $24.14\,\mu\text{m}$ 的深度。
* 加上 $8.0\,\mu\text{m}$ 緩衝後,`y_box_max` 被設為 $32.14\,\mu\text{m}$,這意味著 **$32.14 \sim 36.0\,\mu\text{m}$ 的空乏區尾部被排除在細網格 Box 之外**。
3. ** coarse 網格誤差在插值時引發數值衝擊**:
* 在 coarse 網格 `device_2d.msh` 中,密網格 Box 深度固定為 $25.0\,\mu\text{m}$。
* 隨著電壓掃描至 100V,空乏區擴展至 $36\,\mu\text{m}$,使得大量空間電荷與電場都落在了 coarse 網格的 **$20.0\,\mu\text{m}$ 極粗網格區域**。
* 因為 coarse 網格無法準確解析該區間的電學特性,偏置掃描在此處產生了極大的累積殘差;當觸發 refinement 時,程式試圖將這些不物理的粗糙解插值到 $1.5\,\mu\text{m}$ 的新密網格中(從 $20\,\mu\text{m}$ 內插至 $1.5\,\mu\text{m}$),瞬間產生的「數值邊界不連續衝擊」擊垮了 Stage 1 求解器,導致其發散。
#### 21.2 網格加密後記憶體降低之機制解釋
* **Coarse 網格(地基)**:密網格 Box 深度固定在 $25.0\,\mu\text{m}$,節點數共 **85,893**
* **55V Refined 網格**:由於 55V 時空乏區較淺,偵測計算出的 `y_box_max` 僅為 $23.0\,\mu\text{m}$。Box 體積縮小,使節點數減至 **39,925**,記憶體也隨之由 1.0GB 降到 0.7GB。
* **100V Refined 網格**`y_box_max` 回升至 $32.14\,\mu\text{m}$,節點數增加至 **65,057**。這解釋了為何記憶體在加密後較原 coarse 網格低,且隨偏置電壓(空乏區擴展)上升而增長。
#### 21.3 解決方案:明天擬採用的「組合拳」策略
為了在不大幅增加運算開銷的前提下,徹底消除高壓下細化網格的發散問題,我們討論出了以下三聯組合策略:
1. **收緊基底最粗網格限制(LcMax**
* 將 Silicon 基底的最粗網格尺寸 `LcMax`**$20.0\,\mu\text{m}$ 限制到 $4.0\,\mu\text{m}$**。
* **效果**:即使空乏區邊界逸出動態 Box,它碰到的也是 $4\,\mu\text{m}$ 的合理網格而非 $20\,\mu\text{m}$ 的大孔洞。這能保證 coarse 求解和插值點的數值精準度,將「插值比例」由 $13.3$ 倍(20 to 1.5)降至 $2.6$ 倍(4 to 1.5),徹底平息插值震盪。
2. **調低電場偵測閾值(y_dep**
* 將偵測臨界電場從 $2.0 \times 10^4\text{ V/cm}$ 調低至 **$5.0 \times 10^3\text{ V/cm}$**。
* **效果**:讓 `y_dep` 精確貼合空乏尾部,使 8 µm margin 成為 100% 安全的物理防禦線。
3. **優化網格加密電壓區間(調整頻率)**
* 考慮到每次重劃網格需要 $50 \sim 100\text{ s}$,為避免過度開銷,不宜採用過於頻繁的每 15V 加密。
* **決策**:在 LcMax=4 µm 的安全地基上,將網格加密區間從 50V **縮減至每 25V 或 30V 加密一次**。這能保證空乏區邊界在兩次加密間的移動量極小,維持在 margin 之內,實現最平滑的數值過渡。
### 22. 關於 Guard Band 擴展至 10 um 與網格更新頻率 Sweep 參數化設計(2026-06-14
針對動態網格加密在極高逆偏壓下的收斂與效率問題,我們進行了進一步的設計對齊:
#### 22.1 Guard Band 寬度與電場偵測閾值的權衡
* **維持穩定電場閾值(2.0e4 V/cm)**:不宜將臨界電場偵測閾值降得過低(如 $5 \times 10^3\text{ V/cm}$),以避免低載流子濃度區的數位 noise 引起空乏邊界偵測 `y_dep` 劇烈起伏。此外,逆偏壓下空乏前沿是快速向深處擴展的,低偏壓下的極低電場邊緣很快就會隨偏壓升高而進入中高電場區。
* **展開 Guard Band10.0 um)**:為了補償不降低電場閾值的空間,將加密邊界 `y_box_max` 的 margin 從 `8.0 * um` 擴展到 `10.0 * um`。這能在保持偵測穩定的同時,為空乏前沿的推進提供足夠大的加密保護網格。
#### 22.2 網格更新電壓間隔與基底摻雜(Doping)的物理耦合
根據空乏區寬度隨偏壓擴展速率公式:
$$\frac{dW}{dV} \propto \frac{1}{\sqrt{V \cdot N_{SUB}}}$$
為了使空乏前沿不越出 guard band 邊界 $W_{gb}$,兩次更新之間的電壓步長 $\Delta V$(`refine_v_step`)需滿足:
$$\Delta V < W_{gb} \cdot \sqrt{\frac{2 q \cdot N_{SUB} \cdot V}{\epsilon}}$$
這印證了:
1. 基底摻雜 $N_{SUB}$ 越低(如 $1 \times 10^{14}\text{ cm}^{-3}$),空乏區擴展越快,更新電壓間隔 $\Delta V$ 必須越小。
2. 隨著偏壓 $V$ 增大,空乏區擴展速率變緩,更新電壓間隔 $\Delta V$ 可以逐步放寬。
3. 增加 $W_{gb}$ 可以有效增加允許的 $\Delta V$,從而降低更新頻率。
#### 22.3 Sweep 參數設計與「完全不更新」機制
* 將多少伏特更新一次設為一個可調整的 sweep 參數 `refine_v_step`(預設為 `50.0` V)。
* 當此參數小於 `1.0` V(如 `0.0` V)時,程式判定為**完全不更新**(`is_refine_enabled = False`),直接 coarse 網格算到底,為基準比對與常規 sweep 提供簡便的開關機制。
### 23. 關於初始網格減負與動態「滑動地基」網格優化策略探討(2026-06-14)
在偏壓掃描前期(0V 到 5V),由於電壓基數小,自適應步長控制會採取非常細的步長(約為當時電壓的 1%)。這導致前期掃描佔據了總步數的 1/4 ~ 1/3。為了避免在前期背負過重的網格點矩陣運算,我們探討了「滑動地基」的動態網格尺寸限制優化方案:
#### 23.1 初始網格減負(0V 至 5V 爬坡期)
* **設計**:初始基礎網格的 4.0 um 最大網格尺寸限制僅侷限在 Y 在 0 到 20 um 的 Junction/Well 分佈區域。
* **效果**:在 Y 大於 20 um 的深處基底放寬至 20.0 um。這能將初始網格的節點數從 8.5 萬降低至 2 ~ 3 萬左右,使前期的 0 V 到 5 V 掃描速度提升數倍。
#### 23.2 動態「滑動地基」網格加密(Refinement 觸發後)
* **設計**:網格最大尺寸限制不再是全域常數,而是隨電場偵測深度 y_dep 動態演進的滑動視窗:
1. **高密度加密區**Y <= y_dep + 10 um):網格大小為 1.5 um。
2. **中密度過渡地基**y_dep + 10 um < Y <= y_dep + 30 um):最大網格限制為 4.0 um(相較於空乏區邊界推進了 20 um 的安全地基)。
3. **深處非空乏區**Y > y_dep + 30 um):最大網格限制放寬至 20.0 um。
* **效果**:高密度區與安全地基像滑動窗口一樣隨空乏前沿動態推進,深處空曠區始終保持輕量化,最大程度降低高壓段的運算規模與記憶體開銷。
+20 -11
View File
@@ -23,8 +23,12 @@ import numpy as np
OUT_DIR = "output_this_run/"
is_avalanche_enabled = os.environ.get("AVALANCHE", "false").lower() == "true"
refine_v_step = float(os.environ.get("REFINE_V_STEP", "50.0"))
is_refine_enabled = os.environ.get("REFINE", "true").lower() == "true"
print(f"Option: AVALANCHE={is_avalanche_enabled}, REFINE={is_refine_enabled}")
if refine_v_step < 1.0:
is_refine_enabled = False
print(f"refine_v_step < 1.0 V detected ({refine_v_step} V): Dynamic refinement is completely DISABLED.")
print(f"Option: AVALANCHE={is_avalanche_enabled}, REFINE={is_refine_enabled}, REFINE_V_STEP={refine_v_step}V")
os.makedirs(OUT_DIR, exist_ok=True)
import shutil
# 備份輸入幾何網格與配置參數,以及當前執行腳本本身以利模型重建
@@ -116,7 +120,7 @@ step_size = cp_data.get("step_size", 0.1)
step_count = cp_data.get("step_count", 0)
voltage_list = cp_data.get("voltage_list", [v_current])
current_list = cp_data.get("current_list", [0.0])
next_recon_v = cp_data.get("next_recon_v", (v_current // 50.0 + 1) * 50.0)
next_recon_v = cp_data.get("next_recon_v", (v_current // refine_v_step + 1) * refine_v_step)
state = cp_data["state"]
# Check if checkpoint is refined
@@ -441,6 +445,7 @@ start_sweep_time = time.time()
print("Resuming adaptive bias sweep...")
iter_history = []
sn = 0
just_refined = is_refined
if latest_cp and "seed_500V.pkl" in latest_cp:
step_size = 2.0528
@@ -477,7 +482,7 @@ while v_current < v_target:
devsim.equation(device=device, region="Silicon", name="HoleContinuityEquation", variable_name="Holes",
time_node_model="PCharge", edge_model=opts['Jp'], edge_volume_model=av_model,
variable_update="log_damp", node_model="HoleGeneration", min_error=1e5)
res1 = devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-5, charge_error=1e12, maximum_iterations=15, rollback=False, info=True)
res1 = devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-3, charge_error=1e12, maximum_iterations=7, rollback=False, info=True)
iters1 = len(res1.get("iterations", []))
except devsim.error:
pass # Ignore non-convergence in pre-conditioning
@@ -499,7 +504,7 @@ while v_current < v_target:
mem_stage1 = 0.0
# Stage 2: Precision Newton (Strict Tolerance)
res = devsim.solve(type="dc", absolute_error=1e10, relative_error=3e-3, charge_error=1e12, maximum_iterations=20, info=True)
res = devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-3, charge_error=1e12, maximum_iterations=20, info=True)
iters2 = len(res.get("iterations", []))
import psutil
@@ -599,7 +604,7 @@ while v_current < v_target:
try:
# Stage 1 pre-conditioning
try:
devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-5, charge_error=1e12, maximum_iterations=15, rollback=False, info=True)
devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-3, charge_error=1e12, maximum_iterations=7, rollback=False, info=True)
except devsim.error:
pass
@@ -651,12 +656,12 @@ while v_current < v_target:
else:
print("Avalanche probe skipped (AVALANCHE option is disabled).")
print("--- END RECON PROBE ---\n")
next_recon_v += 50.0
next_recon_v += refine_v_step
except devsim.error as ref_err:
print(f"\n!!! DYNAMIC REFINE FAILED at {v_current:.2f} V: {ref_err} !!!")
print("Restoring coarse device state and continuing simulation on the COARSE mesh.")
restore_state(device, state)
next_recon_v += 50.0
next_recon_v += refine_v_step
# Grow step size for next step adaptively based on Newton iterations (Rolling average)
iter_history.append(total_iters)
@@ -664,12 +669,15 @@ while v_current < v_target:
iter_history.pop(0)
avg_iters = sum(iter_history) / len(iter_history)
if avg_iters <= 10.0:
eff_iters = max(0.0, avg_iters - sn)
if avg_iters > 14.0:
step_size = max(step_size * 0.6, min_step)
sn = 0
elif eff_iters < 10.0:
step_size = min(step_size * 1.2, max_step)
elif avg_iters <= 14.0:
pass # Keep step size the same
sn = 0
else:
step_size = max(step_size * 0.8, min_step)
sn += 1
step_count += 1
@@ -718,6 +726,7 @@ while v_current < v_target:
restore_state(device, state)
step_size *= 0.577
iter_history = [] # Reset iteration history on failure
sn = 0 # Reset counter on failure
if step_size < min_step:
print("Step size has fallen below minimum limit. Aborting simulation.")
+26 -11
View File
@@ -23,8 +23,12 @@ import numpy as np
OUT_DIR = "output_this_run/"
is_avalanche_enabled = os.environ.get("AVALANCHE", "false").lower() == "true"
refine_v_step = float(os.environ.get("REFINE_V_STEP", "50.0"))
is_refine_enabled = os.environ.get("REFINE", "true").lower() == "true"
print(f"Option: AVALANCHE={is_avalanche_enabled}, REFINE={is_refine_enabled}")
if refine_v_step < 1.0:
is_refine_enabled = False
print(f"refine_v_step < 1.0 V detected ({refine_v_step} V): Dynamic refinement is completely DISABLED.")
print(f"Option: AVALANCHE={is_avalanche_enabled}, REFINE={is_refine_enabled}, REFINE_V_STEP={refine_v_step}V")
os.makedirs(OUT_DIR, exist_ok=True)
import shutil
# 備份輸入幾何網格與配置參數,以及當前執行腳本本身以利模型重建
@@ -277,6 +281,12 @@ devsim.write_devices(file=f"{OUT_DIR}sweep_preview_0V.tec", type="tecplot")
# 5. Define Sweep Parameters
v_target = 1000.0
if len(sys.argv) > 1:
try:
v_target = float(sys.argv[1])
print(f"Custom target voltage specified: {v_target} V")
except ValueError:
print(f"Warning: Invalid target voltage '{sys.argv[1]}', using default {v_target} V")
v_current = 0.0
step_size = 0.1 # Initial step size (V)
max_step = 50.0 # Maximum step size (V)
@@ -310,7 +320,7 @@ voltage_list = [0.0]
current_list = [0.0]
# Recon variables
next_recon_v = 50.0
next_recon_v = refine_v_step
with open(f"{OUT_DIR}recon_avalanche.log", "w") as f:
f.write("Voltage(V)\tAvalancheCurrent(A)\n")
@@ -323,6 +333,7 @@ print("Beginning adaptive bias sweep...")
step_count = 0
iter_history = []
just_refined = False
sn = 0
# Targets for saving intermediate state checkpoints
save_targets = [5.0, 50.0, 500.0]
@@ -353,7 +364,7 @@ while v_current < v_target:
devsim.equation(device=device, region="Silicon", name="HoleContinuityEquation", variable_name="Holes",
time_node_model="PCharge", edge_model=opts['Jp'], edge_volume_model=av_model,
variable_update="log_damp", node_model="HoleGeneration", min_error=1e5)
res1 = devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-5, charge_error=1e12, maximum_iterations=15, rollback=False, info=True)
res1 = devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-3, charge_error=1e12, maximum_iterations=7, rollback=False, info=True)
iters1 = len(res1.get("iterations", []))
except devsim.error:
pass # Ignore non-convergence in pre-conditioning
@@ -375,7 +386,7 @@ while v_current < v_target:
mem_stage1 = 0.0
# Stage 2: Precision Newton (Strict Tolerance)
res = devsim.solve(type="dc", absolute_error=1e10, relative_error=3e-3, charge_error=1e12, maximum_iterations=20, info=True)
res = devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-3, charge_error=1e12, maximum_iterations=20, info=True)
iters2 = len(res.get("iterations", []))
import psutil
@@ -476,7 +487,7 @@ while v_current < v_target:
try:
# Stage 1 pre-conditioning
try:
devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-5, charge_error=1e12, maximum_iterations=15, rollback=False, info=True)
devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-3, charge_error=1e12, maximum_iterations=7, rollback=False, info=True)
except devsim.error:
pass
@@ -528,12 +539,12 @@ while v_current < v_target:
else:
print("Avalanche probe skipped (AVALANCHE option is disabled).")
print("--- END RECON PROBE ---\n")
next_recon_v += 50.0
next_recon_v += refine_v_step
except devsim.error as ref_err:
print(f"\n!!! DYNAMIC REFINE FAILED at {v_current:.2f} V: {ref_err} !!!")
print("Restoring coarse device state and continuing simulation on the COARSE mesh.")
restore_state(device, state)
next_recon_v += 50.0
next_recon_v += refine_v_step
# Grow step size for next step adaptively based on Newton iterations (Rolling average)
iter_history.append(total_iters)
@@ -541,12 +552,15 @@ while v_current < v_target:
iter_history.pop(0)
avg_iters = sum(iter_history) / len(iter_history)
if avg_iters <= 10.0:
eff_iters = max(0.0, avg_iters - sn)
if avg_iters > 14.0:
step_size = max(step_size * 0.6, min_step)
sn = 0
elif eff_iters < 10.0:
step_size = min(step_size * 1.2, max_step)
elif avg_iters <= 14.0:
pass # Keep step size the same
sn = 0
else:
step_size = max(step_size * 0.8, min_step)
sn += 1
step_count += 1
@@ -596,6 +610,7 @@ while v_current < v_target:
restore_state(device, state)
step_size *= 0.577
iter_history = [] # Reset iteration history on failure
sn = 0 # Reset counter on failure
if step_size < min_step:
print("Step size has fallen below minimum limit. Aborting simulation.")