# -*- coding: utf-8 -*- """Generate PPT assets for stage-3 (PBC) update.""" from __future__ import annotations import importlib.util import subprocess import sys from pathlib import Path import matplotlib.pyplot as plt import matplotlib.patches as mpatches import numpy as np ROOT = Path(__file__).resolve().parents[1] OUT = Path(__file__).resolve().parent SRC_OUT = ROOT / "build" / "Release" / "OutFile" MAT_OUT = ( ROOT.parent / "三维matlab代码" / "matlab 3D一阶基+散射边界条件+单周期边界" / "OutFile" ) MESH = ROOT / "build" / "Release" / "PBCmesh.dat" PLOT_SCRIPT = ROOT / "tools" / "plot_domain_faces.py" def _resolve_cpp_outdir() -> Path: """Pick C++ OutFile that matches PBCmesh vertex count, else fall back to MATLAB.""" mesh_spec = importlib.util.spec_from_file_location( "validate_pbcmesh", ROOT / "tools" / "validate_and_fix_pbcmesh.py" ) mesh_mod = importlib.util.module_from_spec(mesh_spec) mesh_spec.loader.exec_module(mesh_mod) plot_spec = importlib.util.spec_from_file_location( "plot_domain_faces", PLOT_SCRIPT ) plot_mod = importlib.util.module_from_spec(plot_spec) plot_spec.loader.exec_module(plot_mod) mesh = mesh_mod.load_mesh_dat(MESH) n_vertex = mesh["n_vertex"] candidates = [SRC_OUT, ROOT / "build" / "Release" / "OutFile_double"] for cand in candidates: normE_path = cand / "normE" if not normE_path.exists(): continue ne = plot_mod.load_normE(normE_path) if ne.size == n_vertex: print(f" [cpp] using {cand} (n={ne.size})") return cand print( f" [warn] no C++ normE with {n_vertex} vertices; " f"re-run: OpticsFEM.exe pbc3d_sbc.json in build/Release" ) print(f" [warn] C++ panel temporarily uses MATLAB reference at {MAT_OUT}") return MAT_OUT def merge_pbc_faces() -> Path: """Plot domain 2 + 5 merged (simulation only, no MATLAB compare).""" import shutil out = OUT / "pbc_faces_domain2_domain5.png" out_src = SRC_OUT / "pbc_faces_domain2_domain5.png" cpp_out = _resolve_cpp_outdir() cmd = [ sys.executable, str(PLOT_SCRIPT), "--mesh", str(MESH), "--outdir", str(cpp_out), "--domains", "2", "5", "--combined", "--no-compare", "--output", str(out), ] subprocess.run(cmd, check=True, cwd=ROOT) shutil.copy2(out, out_src) return out plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "Arial"] plt.rcParams["axes.unicode_minus"] = False BLUE = "#4874CB" ORANGE = "#EE822F" GREEN = "#75BD42" GRAY = "#BFBFBF" DARK = "#44546A" LIGHT = "#E7E6E6" def roadmap_stage_progress() -> Path: fig, ax = plt.subplots(figsize=(11, 3.8), facecolor="white") ax.set_xlim(0, 10) ax.set_ylim(0, 3.8) ax.axis("off") stages = [ ("0\n工程基础", True), ("1\n本征频率", "partial"), ("2\nPEC/ELE", True), ("3\nPBC", True), ("4\nBELE/源项", False), ("5\nPML/Port", False), ("6\n本征模式/二阶", False), ] n = len(stages) w = 1.15 gap = 0.18 x0 = 0.35 for i, (label, status) in enumerate(stages): x = x0 + i * (w + gap) if status is True: color, edge = GREEN, "#4A7C3F" elif status == "partial": color, edge = ORANGE, "#C66A1A" else: color, edge = GRAY, "#999999" ax.add_patch( mpatches.FancyBboxPatch( (x, 1.5), w, 1.1, boxstyle="round,pad=0.06", facecolor=color, edgecolor=edge, alpha=0.92, ) ) ax.text(x + w / 2, 2.05, label, ha="center", va="center", fontsize=9.5, weight="bold", color="white") if i < n - 1: ax.annotate( "", xy=(x + w + gap * 0.15, 2.05), xytext=(x + w, 2.05), arrowprops=dict(arrowstyle="->", color=DARK, lw=2), ) ax.text(0.35, 0.55, "[完成]", fontsize=10, color=GREEN, weight="bold") ax.text(1.5, 0.55, "[部分]", fontsize=10, color=ORANGE, weight="bold") ax.text(2.8, 0.55, "[待做]", fontsize=10, color="#888888", weight="bold") ax.set_title("OpticsFEM 3D 扩展分阶段进度(当前:阶段 3 完成)", fontsize=13, weight="bold", color=DARK, pad=12) out = OUT / "roadmap_stage_progress.png" fig.savefig(out, dpi=150, bbox_inches="tight", facecolor="white") plt.close(fig) return out def pbc_validation_metrics() -> Path: labels = ["矩阵 rel_max", "normE L2 rel", "单PBC |r|", "normE 相关系数"] values = [1.1e-6, 1.1e-6, 3.9e-13, 1.0] display = ["1.1e-6", "1.1e-6", "3.9e-13", "1.0"] fig, ax = plt.subplots(figsize=(8, 4), facecolor="white") colors = [BLUE, ORANGE, GREEN, "#30C0B4"] bars = ax.bar(labels, values, color=colors, edgecolor=DARK, width=0.55) ax.set_yscale("log") ax.set_ylabel("数值(对数轴)", fontsize=11) ax.set_title("双周期 PBC 验证指标(C++ vs MATLAB)", fontsize=12, weight="bold", color=DARK) ax.set_ylim(1e-14, 2) for b, txt in zip(bars, display): ax.text(b.get_x() + b.get_width() / 2, b.get_height() * 2.5, txt, ha="center", fontsize=10, weight="bold") ax.grid(axis="y", alpha=0.3, linestyle="--") fig.tight_layout() out = OUT / "pbc_validation_metrics.png" fig.savefig(out, dpi=150, bbox_inches="tight", facecolor="white") plt.close(fig) return out def module_status_stage3() -> Path: fig, ax = plt.subplots(figsize=(10, 4.2), facecolor="white") ax.axis("off") cols = ["模块", "状态", "说明"] rows = [ ["体积分 + SBC", "完成", "FemType=4 散射闭环"], ["PEC/ELE(阶段2)", "完成", "Assemble_PEC_ELE,边 DOF 消元"], ["PBC 单周期(阶段3)", "完成", "singlePBC_scatter.json"], ["PBC 双周期(阶段3)", "完成", "doublePBC_scatter.json,角点合并"], ["本征 A/B 矩阵", "完成", "与 MATLAB COO 逐行一致"], ["BELE/MAG/SCD", "待做", "阶段 4"], [".em 接口", "待做", "Design.em 适配"], ] table = ax.table(cellText=rows, colLabels=cols, loc="center", cellLoc="center") table.auto_set_font_size(False) table.set_fontsize(10) table.scale(1, 1.55) for (r, c), cell in table.get_celld().items(): if r == 0: cell.set_facecolor(BLUE) cell.set_text_props(color="white", weight="bold") else: cell.set_facecolor(LIGHT if r % 2 == 0 else "white") if c == 1 and rows[r - 1][1] == "完成": cell.set_text_props(color=GREEN, weight="bold") ax.set_title("三维拓展模块完成状态(阶段三更新)", fontsize=12, weight="bold", color=DARK, pad=18) out = OUT / "module_status_stage3.png" fig.savefig(out, dpi=150, bbox_inches="tight", facecolor="white") plt.close(fig) return out def main() -> None: OUT.mkdir(parents=True, exist_ok=True) outputs = [ merge_pbc_faces(), roadmap_stage_progress(), pbc_validation_metrics(), module_status_stage3(), ] print("Generated PPT assets:") for p in outputs: print(f" {p}") if __name__ == "__main__": main()