afem/output/build_pptx.py

1046 lines
56 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Build the AFEM group meeting PPTX deck -- Chinese version."""
from pptx import Presentation
from pptx.util import Inches, Pt, Emu, Cm
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
from pptx.oxml.ns import qn
# Color palette (Nature-style restrained)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
BLACK = RGBColor(0x1A, 0x1A, 0x1A)
DARK_GRAY = RGBColor(0x33, 0x33, 0x33)
BODY_GRAY = RGBColor(0x44, 0x44, 0x44)
CAPTION = RGBColor(0x88, 0x88, 0x88)
LIGHT_LINE = RGBColor(0xDD, 0xDD, 0xDD)
LIGHTER_LINE = RGBColor(0xEE, 0xEE, 0xEE)
ACCENT_BLUE = RGBColor(0x2C, 0x5F, 0x8A)
ACCENT_TEAL = RGBColor(0x3A, 0x7B, 0x7B)
ACCENT_WARM = RGBColor(0x8B, 0x45, 0x2C)
ACCENT_GREEN = RGBColor(0x3A, 0x7B, 0x4F)
HIGHLIGHT_BG = RGBColor(0xE8, 0xF0, 0xF8)
WARN_BG = RGBColor(0xFE, 0xF3, 0xE8)
TABLE_HDR = RGBColor(0xE8, 0xF0, 0xF8)
TABLE_ALT = RGBColor(0xF5, 0xF7, 0xFA)
SLIDE_W = Inches(13.333)
SLIDE_H = Inches(7.5)
TITLE_SIZE = Pt(28)
SUBHEAD_SIZE = Pt(18)
BODY_SIZE = Pt(14)
SMALL_SIZE = Pt(12)
CAPTION_SIZE = Pt(8)
TAKEAWAY_SIZE = Pt(11)
prs = Presentation()
prs.slide_width = SLIDE_W
prs.slide_height = SLIDE_H
blank_layout = prs.slide_layouts[6]
def add_blank_slide():
return prs.slides.add_slide(blank_layout)
def set_slide_bg(slide, color=WHITE):
bg = slide.background
fill = bg.fill
fill.solid()
fill.fore_color.rgb = color
def add_rect(slide, left, top, width, height, fill_color=None, line_color=None, line_width=None):
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, width, height)
shape.line.fill.background()
if fill_color:
shape.fill.solid()
shape.fill.fore_color.rgb = fill_color
else:
shape.fill.background()
if line_color:
shape.line.color.rgb = line_color
shape.line.fill.solid()
if line_width:
shape.line.width = line_width
return shape
def add_textbox(slide, left, top, width, height, text="", font_size=BODY_SIZE,
font_color=BODY_GRAY, bold=False, alignment=PP_ALIGN.LEFT,
font_name='Microsoft YaHei', anchor=MSO_ANCHOR.TOP, line_spacing=1.3):
txbox = slide.shapes.add_textbox(left, top, width, height)
txbox.text_frame.word_wrap = True
tf = txbox.text_frame
tf.paragraphs[0].alignment = alignment
tf.paragraphs[0].space_before = Pt(0)
tf.paragraphs[0].space_after = Pt(0)
tf.paragraphs[0].line_spacing = line_spacing
run = tf.paragraphs[0].add_run()
run.text = text
run.font.size = font_size
run.font.color.rgb = font_color
run.font.bold = bold
run.font.name = font_name
rPr = run._r.get_or_add_rPr()
rPr.set(qn('a:eaTypeface'), font_name)
return txbox
def add_multiline_textbox(slide, left, top, width, height, lines, font_size=BODY_SIZE,
font_color=BODY_GRAY, font_name='Microsoft YaHei',
line_spacing=1.5, alignment=PP_ALIGN.LEFT):
txbox = slide.shapes.add_textbox(left, top, width, height)
txbox.text_frame.word_wrap = True
tf = txbox.text_frame
for i, line_data in enumerate(lines):
if isinstance(line_data, str):
text, is_bold, fs, clr = line_data, False, font_size, font_color
elif len(line_data) == 2:
text, is_bold = line_data
fs, clr = font_size, font_color
elif len(line_data) == 3:
text, is_bold, fs = line_data
clr = font_color
else:
text, is_bold, fs, clr = line_data
if i == 0:
p = tf.paragraphs[0]
else:
p = tf.add_paragraph()
p.alignment = alignment
p.space_before = Pt(2)
p.space_after = Pt(2)
p.line_spacing = line_spacing
run = p.add_run()
run.text = text
run.font.size = fs
run.font.color.rgb = clr
run.font.bold = is_bold
run.font.name = font_name
rPr = run._r.get_or_add_rPr()
rPr.set(qn('a:eaTypeface'), font_name)
return txbox
def add_bullet_textbox(slide, left, top, width, height, bullets, font_size=BODY_SIZE,
font_color=BODY_GRAY, font_name='Microsoft YaHei',
bullet_char="-", line_spacing=1.5):
txbox = slide.shapes.add_textbox(left, top, width, height)
txbox.text_frame.word_wrap = True
tf = txbox.text_frame
for i, bullet_text in enumerate(bullets):
if i == 0:
p = tf.paragraphs[0]
else:
p = tf.add_paragraph()
p.alignment = PP_ALIGN.LEFT
p.space_before = Pt(3)
p.space_after = Pt(3)
p.line_spacing = line_spacing
run_marker = p.add_run()
run_marker.text = f"{bullet_char} "
run_marker.font.size = font_size
run_marker.font.color.rgb = ACCENT_BLUE
run_marker.font.name = font_name
rPr = run_marker._r.get_or_add_rPr()
rPr.set(qn('a:eaTypeface'), font_name)
run_text = p.add_run()
run_text.text = bullet_text
run_text.font.size = font_size
run_text.font.color.rgb = font_color
run_text.font.name = font_name
rPr2 = run_text._r.get_or_add_rPr()
rPr2.set(qn('a:eaTypeface'), font_name)
return txbox
def add_top_bar(slide):
add_rect(slide, Inches(0), Inches(0), SLIDE_W, Pt(3), fill_color=ACCENT_BLUE)
def add_slide_number(slide, num):
add_textbox(slide, Inches(11.8), Inches(7.05), Inches(1.2), Inches(0.35),
text=str(num), font_size=Pt(9), font_color=CAPTION,
alignment=PP_ALIGN.RIGHT)
def add_source_label(slide, text, left=None, top=None):
if left is None:
left = Inches(0.6)
if top is None:
top = Inches(6.95)
add_textbox(slide, left, top, Inches(6), Inches(0.35),
text=text, font_size=CAPTION_SIZE, font_color=CAPTION)
def add_takeaway_bar(slide, text):
add_rect(slide, Inches(0.6), Inches(6.55), Inches(12.1), Inches(0.38),
fill_color=HIGHLIGHT_BG)
add_textbox(slide, Inches(0.75), Inches(6.55), Inches(11.85), Inches(0.38),
text=f">> {text}", font_size=TAKEAWAY_SIZE, font_color=ACCENT_BLUE,
bold=False, anchor=MSO_ANCHOR.MIDDLE)
def add_slide_title(slide, title_text):
add_top_bar(slide)
add_textbox(slide, Inches(0.6), Inches(0.35), Inches(12.1), Inches(0.7),
text=title_text, font_size=TITLE_SIZE, font_color=BLACK, bold=True)
add_rect(slide, Inches(0.6), Inches(1.05), Inches(1.5), Pt(2), fill_color=ACCENT_BLUE)
def add_kpi_box(slide, left, top, width, height, value, label, color=ACCENT_BLUE):
add_rect(slide, left, top, width, height, fill_color=HIGHLIGHT_BG)
add_textbox(slide, left + Inches(0.1), top + Inches(0.08), width - Inches(0.2), Inches(0.4),
text=value, font_size=Pt(22), font_color=color, bold=True,
alignment=PP_ALIGN.CENTER)
add_textbox(slide, left + Inches(0.1), top + Inches(0.5), width - Inches(0.2), Inches(0.35),
text=label, font_size=Pt(10), font_color=CAPTION,
alignment=PP_ALIGN.CENTER)
# ======================================================================
# SLIDE 1: TITLE
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_rect(slide, Inches(0), Inches(0), SLIDE_W, Inches(0.08), fill_color=ACCENT_BLUE)
add_rect(slide, Inches(0), Inches(0), Inches(0.08), SLIDE_H, fill_color=ACCENT_BLUE)
add_textbox(slide, Inches(1.2), Inches(1.6), Inches(10.5), Inches(1.4),
text="AFEM基于 GNN + PPO 强化学习\n的自适应网格细化方法",
font_size=Pt(38), font_color=BLACK, bold=True, line_spacing=1.3)
add_textbox(slide, Inches(1.2), Inches(3.2), Inches(10.5), Inches(0.9),
text="二维 Helmholtz 电磁散射问题的智能网格优化 -- 算法流程与创新汇总",
font_size=Pt(18), font_color=BODY_GRAY)
add_rect(slide, Inches(1.2), Inches(4.2), Inches(3.0), Pt(2), fill_color=ACCENT_BLUE)
meta_lines = [
("组会汇报 | 2025 年 5 月", False, Pt(14), CAPTION),
("", False, Pt(8), CAPTION),
("物理场景:二维 Helmholtz 方程 / 圆形介质散射体 / SBC 吸收边界", False, Pt(12), CAPTION),
("方法栈GNN (Message Passing) / PPO / 连续尺寸场 / 残差型误差估计", False, Pt(12), CAPTION),
]
add_multiline_textbox(slide, Inches(1.2), Inches(4.5), Inches(10.5), Inches(1.6),
meta_lines, line_spacing=1.5)
add_slide_number(slide, 1)
# ======================================================================
# SLIDE 2: BACKGROUND
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "研究背景:为什么自适应网格细化很重要")
left_bullets = [
"Helmholtz 方程描述电磁波在介质中的散射与传播,是电磁兼容、隐身设计、天线仿真等领域的基础方程",
"有限元 (FEM) 求解精度高度依赖网格质量:网格过粗导致数值色散/污染效应;网格过密浪费计算资源",
"高频 (k >> 1) 下污染效应严重kh > 0.5 时 FEM 解定性错误,后续误差指示子完全不可靠",
"核心挑战:如何用最少的网格单元达到目标精度?在误差大的区域加密,误差小的区域保持稀疏",
]
add_bullet_textbox(slide, Inches(0.6), Inches(1.35), Inches(6.0), Inches(3.2),
left_bullets, font_size=SMALL_SIZE)
add_rect(slide, Inches(7.2), Inches(1.35), Inches(5.5), Inches(3.2), fill_color=HIGHLIGHT_BG)
physics_lines = [
("物理方程", True, Pt(14), ACCENT_BLUE),
("", False, Pt(4), BODY_GRAY),
("nabla^2 u_scat + k^2 * eps_r * u_scat = k^2 * (1-eps_r) * u_inc", True, Pt(13), ACCENT_TEAL),
("", False, Pt(4), BODY_GRAY),
("入射波:沿 -x 方向的平面波 u_inc = exp(i*k*x)", False, Pt(11), BODY_GRAY),
("散射体圆形介质柱eps_r 随机采样)", False, Pt(11), BODY_GRAY),
("边界条件SBC 吸收边界 du/dn = i*k*u", False, Pt(11), BODY_GRAY),
("计算域:可配矩形域 [Lx, Ly]", False, Pt(11), BODY_GRAY),
]
add_multiline_textbox(slide, Inches(7.4), Inches(1.5), Inches(5.1), Inches(2.8),
physics_lines, line_spacing=1.3)
kpis = [
("kh > 1.4", "高频下典型 kh 值\n(远超 0.5 安全线)", ACCENT_WARM),
("400 -> 20,000", "网格单元数变化范围\n(初始 -> 最大上限)", ACCENT_BLUE),
("[2, 20]", "训练波数 k 覆盖范围\n(涵盖中频到高频)", ACCENT_TEAL),
]
for i, (val, label, clr) in enumerate(kpis):
add_kpi_box(slide, Inches(0.6 + i * 4.2), Inches(4.95), Inches(3.8), Inches(1.1),
val, label, color=clr)
add_takeaway_bar(slide, "Helmholtz 高频求解的核心矛盾:精度 vs 效率。需要智能网格细化策略来平衡二者。")
add_source_label(slide, "参考文献Ainsworth & Oden, A Posteriori Error Estimation in Finite Element Analysis, 2000")
add_slide_number(slide, 2)
# ======================================================================
# SLIDE 3: KNOWLEDGE GAP
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "知识缺口与技术瓶颈")
add_rect(slide, Inches(0.6), Inches(1.35), Inches(5.7), Inches(2.5), fill_color=None,
line_color=LIGHT_LINE, line_width=Pt(1))
add_textbox(slide, Inches(0.8), Inches(1.4), Inches(5.3), Inches(0.4),
text="传统自适应方法的局限", font_size=SUBHEAD_SIZE, font_color=ACCENT_WARM, bold=True)
trad_bullets = [
"基于误差指示子的 h-adaptivity 细化规则完全由人工设计",
"细化判据固定(如设定误差阈值),无法适应不同 PDE 的物理特征",
"SOLVE-ESTIMATE-MARK-REFINE 循环不考虑长期回报(每一步仅看当前误差)",
"无法学习特定问题的网格模式,无法迁移到新 PDE 配置",
]
add_bullet_textbox(slide, Inches(0.8), Inches(1.85), Inches(5.3), Inches(1.8),
trad_bullets, font_size=Pt(11))
add_rect(slide, Inches(7.0), Inches(1.35), Inches(5.7), Inches(2.5), fill_color=None,
line_color=ACCENT_BLUE, line_width=Pt(1.5))
add_textbox(slide, Inches(7.2), Inches(1.4), Inches(5.3), Inches(0.4),
text="本工作的目标", font_size=SUBHEAD_SIZE, font_color=ACCENT_BLUE, bold=True)
goal_bullets = [
"用强化学习 (RL) 替代人工规则,自动发现最优细化策略",
"GNN 处理变长拓扑:每个三角形单元是一个独立的 RL agent",
"连续尺寸场输出 -> 概率性元素选择 -> 非均匀自适应网格",
"物理预算约束 + 误差驱动奖励 -> 计算资源集中在物理关键区域",
]
add_bullet_textbox(slide, Inches(7.2), Inches(1.85), Inches(5.3), Inches(1.8),
goal_bullets, font_size=Pt(11), bullet_char=">")
add_textbox(slide, Inches(0.6), Inches(4.2), Inches(12.1), Inches(0.4),
text="本次汇报的核心创新(相较前序工作)", font_size=SUBHEAD_SIZE,
font_color=BLACK, bold=True)
innovations = [
("[1] 无量纲化残差误差估计", "真空波数 k 归一化残差+相位/空间特征+GVN介质内 eta 不被压低", ACCENT_BLUE),
("[2] Score-based 连续尺寸场", "score = -x_i 纯排序 + 物理预算约束 + Reverse Dörfler 动作掩码", ACCENT_TEAL),
("[3] L2 聚合奖励设计", "sqrt(sum eta_child^2) <= eta_parent 保证 r_local >= 0永不惩罚细化", ACCENT_GREEN),
("[4] 尺度不变性架构", "N_init x domain_area + lambda 无量纲化特征 + ln 压缩 + 前渐近区约束", ACCENT_WARM),
]
for i, (title, desc, clr) in enumerate(innovations):
y = Inches(4.7 + i * 0.6)
add_rect(slide, Inches(0.6), y, Pt(3), Inches(0.45), fill_color=clr)
add_textbox(slide, Inches(0.85), y - Inches(0.02), Inches(3.0), Inches(0.45),
text=title, font_size=Pt(13), font_color=clr, bold=True)
add_textbox(slide, Inches(3.8), y - Inches(0.02), Inches(8.7), Inches(0.45),
text=desc, font_size=Pt(11), font_color=BODY_GRAY)
add_takeaway_bar(slide, "核心思路:让网格细化的每一步都具有明确的物理语义,而非纯数据驱动的黑箱映射")
add_slide_number(slide, 3)
# ======================================================================
# SLIDE 4: SYSTEM OVERVIEW
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "系统架构RL 自适应网格细化闭环管线")
stages = [
("物理问题\n采样", ACCENT_BLUE),
("初始网格\n生成", ACCENT_BLUE),
("GNN\n观测", ACCENT_TEAL),
("Actor\n动作", ACCENT_TEAL),
("尺寸场\n排序", ACCENT_WARM),
("预算\n选择", ACCENT_WARM),
("网格\n细化", ACCENT_GREEN),
("FEM\n求解", ACCENT_GREEN),
("误差\n估计", ACCENT_GREEN),
("Reward\n计算", ACCENT_GREEN),
]
y_center = Inches(2.6)
box_w = Inches(1.1)
box_h = Inches(0.85)
gap = (Inches(12.1) - box_w * 10) / 9
for i, (label, clr) in enumerate(stages):
x = Inches(0.6) + i * (box_w + gap)
add_rect(slide, x, y_center, box_w, box_h, fill_color=HIGHLIGHT_BG,
line_color=clr, line_width=Pt(1.5))
add_textbox(slide, x, y_center + Inches(0.05), box_w, box_h - Inches(0.1),
text=label, font_size=Pt(11), font_color=clr, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
if i < len(stages) - 1:
arrow_x = x + box_w
add_textbox(slide, arrow_x, y_center + Inches(0.22), gap, Inches(0.35),
text=">", font_size=Pt(16), font_color=LIGHT_LINE, bold=True,
alignment=PP_ALIGN.CENTER)
add_textbox(slide, Inches(6.0), Inches(3.55), Inches(1.5), Inches(0.35),
text="<-- 下一轮迭代(多步 rollout", font_size=Pt(10), font_color=ACCENT_TEAL,
alignment=PP_ALIGN.CENTER)
# RL modeling
add_textbox(slide, Inches(0.6), Inches(4.1), Inches(6.0), Inches(0.35),
text="RL 问题建模", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
rl_lines = [
("Agent = 每个三角形单元(数量动态变化,约 400 -> 20,000", False, Pt(11), BODY_GRAY),
("State = GNN 节点 14 维特征(几何 + PDE 残差 + 振幅 + 相位方向 + 物理参数)", False, Pt(11), BODY_GRAY),
("Action = 1 维连续标量 x_i -> score = -x_i 排序 -> top-k 选择细化单元", False, Pt(11), BODY_GRAY),
("Reward = 零和预算审查: refined 获 r_local+0.3x(eta/mu-1)-0.06; unrefined r=0", False, Pt(11), BODY_GRAY),
]
add_multiline_textbox(slide, Inches(0.6), Inches(4.5), Inches(6.0), Inches(2.0),
rl_lines, line_spacing=1.6)
# PPO training
add_textbox(slide, Inches(7.2), Inches(4.1), Inches(5.5), Inches(0.35),
text="PPO 训练配置", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
train_lines = [
("双 GNN 架构Policy / Value 各自独立 MessagePassingBase", False, Pt(11), BODY_GRAY),
("2 层消息传递 + GVN 全局虚拟节点 (注意力门控广播)inner 残差 + LayerNormlatent_dim=64", False, Pt(11), BODY_GRAY),
("DiagGaussian 连续动作分布log_std 可学习clamp [-4, -1]", False, Pt(11), BODY_GRAY),
("256 步 Rollout5 EpochsGAE lambda=0.95lr=3e-4梯度裁剪 0.5", False, Pt(11), BODY_GRAY),
]
add_multiline_textbox(slide, Inches(7.2), Inches(4.5), Inches(5.5), Inches(2.0),
train_lines, line_spacing=1.6)
add_takeaway_bar(slide, "闭环 RL 管线:物理求解 -> GNN 感知 -> 策略决策 -> 网格操作 -> 误差反馈 -> 策略更新")
add_slide_number(slide, 4)
# ======================================================================
# SLIDE 5: INNOVATION 1 - Non-dimensionalized residual error
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "创新 [1]:无量纲化残差误差估计 -- 消除几何尺度偏差")
add_textbox(slide, Inches(0.6), Inches(1.25), Inches(5.8), Inches(0.35),
text="前序问题:原始残差包含 h_K、h_e 等几何尺度,不同区域不可直接比较", font_size=Pt(13), font_color=ACCENT_WARM)
add_textbox(slide, Inches(0.6), Inches(1.55), Inches(5.8), Inches(0.35),
text="解决方案:改用真空波数 k 归一化,介质内残差不再被 sqrt(eps_r) 压低", font_size=Pt(13), font_color=ACCENT_BLUE)
formulas = [
("内部残差 r_int",
"(h_K/k) * sqrt(V) * |k^2*eps_r*u + k^2*(eps_r-1)*u_inc|_K",
"单元内部 PDE 残差;真空波数 k 归一化SBC 条件保留 k_local"),
("梯度跳变 r_jump",
"sqrt(1/2 * sum_{e in dK} (h_e/k) * |[[grad u * n]]|^2_e)",
"相邻单元梯度跳变h_e/k 使细化后跳变自然衰减"),
("SBC 边界 r_sbc",
"(h_bnd/k) * |du/dn - i*k_local*u|",
"Sommerfeld 吸收边界残差,仅在边界单元非零"),
]
for i, (name, formula, desc) in enumerate(formulas):
x = Inches(0.6 + i * 4.1)
add_rect(slide, x, Inches(2.0), Inches(3.85), Inches(1.65), fill_color=HIGHLIGHT_BG)
add_textbox(slide, x + Inches(0.15), Inches(2.05), Inches(3.55), Inches(0.3),
text=name, font_size=Pt(13), font_color=ACCENT_BLUE, bold=True)
add_textbox(slide, x + Inches(0.15), Inches(2.35), Inches(3.55), Inches(0.65),
text=formula, font_size=Pt(11), font_color=BLACK)
add_textbox(slide, x + Inches(0.15), Inches(3.05), Inches(3.55), Inches(0.5),
text=desc, font_size=Pt(10), font_color=CAPTION)
add_rect(slide, Inches(0.6), Inches(3.95), Inches(12.1), Inches(0.7), fill_color=None,
line_color=ACCENT_BLUE, line_width=Pt(1.5))
add_textbox(slide, Inches(0.8), Inches(4.0), Inches(3.5), Inches(0.55),
text="逐单元误差指示子", font_size=Pt(15), font_color=BLACK, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_textbox(slide, Inches(4.0), Inches(4.0), Inches(3.5), Inches(0.55),
text="eta_K = sqrt(r_int^2 + r_jump^2 + r_sbc^2)", font_size=Pt(15),
font_color=ACCENT_BLUE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_textbox(slide, Inches(7.5), Inches(4.0), Inches(5.0), Inches(0.55),
text="三项均严格无量纲\n跨介质、跨频率公平可比", font_size=Pt(13),
font_color=ACCENT_GREEN, anchor=MSO_ANCHOR.MIDDLE)
add_textbox(slide, Inches(0.6), Inches(4.85), Inches(12.1), Inches(0.3),
text="量纲分析验证", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
da_lines = [
("k_local ~ [L]^-1, h_e ~ [L], |jump|^2 ~ [L]^-2 => h_e/k * |jump|^2 ~ [L]^2 * [L]^-2 = 1 严格无量纲", False, Pt(11), BODY_GRAY),
("GNN 输入用 log10 压缩的特征Reward 用原始 eta_K不经 log 压缩),两者公式一致,物理语义对齐", False, Pt(11), BODY_GRAY),
]
add_multiline_textbox(slide, Inches(0.6), Inches(5.15), Inches(12.1), Inches(0.8),
da_lines, line_spacing=1.5)
add_takeaway_bar(slide, "真空波数 k 归一化使介质内残差自然放大 ~sqrt(eps_r) 倍,为 RL agent 提供正确的介质内/外优先级信号")
add_slide_number(slide, 5)
# ======================================================================
# SLIDE 6: INNOVATION 2 - 12D enhanced input features
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "创新 [2]14 维增强输入特征 -- 赋予 GNN 振幅与相位方向感知")
add_textbox(slide, Inches(0.6), Inches(1.25), Inches(12.1), Inches(0.35),
text="前序 11 维 -> 现 12 维,新增 dist_to_interface。全部尺度相关特征均以真空波长 lambda=2*pi/k 无量纲化", font_size=Pt(13), font_color=ACCENT_BLUE)
# Feature table — compact layout to avoid overflow
row_h = Inches(0.30)
table_top = Inches(1.65)
cols = [Inches(0.6), Inches(2.0), Inches(5.5), Inches(9.8)]
col_w = [Inches(1.4), Inches(3.5), Inches(4.3), Inches(3.1)]
headers = ["维度", "特征名称", "物理含义", "归一化"]
for j, (cx, hdr, w) in enumerate(zip(cols, headers, col_w)):
add_rect(slide, cx, table_top, w, row_h, fill_color=TABLE_HDR)
add_textbox(slide, cx + Inches(0.06), table_top, w - Inches(0.12), row_h,
text=hdr, font_size=Pt(9), font_color=BLACK, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
features = [
("volume", "无量纲单元面积", "volume / lambda^2"),
("internal_residual", "内部残差k_local 无量纲化 + log10", "--"),
("gradient_jump", "梯度跳变残差k_local 无量纲化 + log10", "--"),
("sbc_residual", "SBC 边界残差k_local 无量纲化 + log10", "--"),
("element_penalty", "单元惩罚系数 lambda", "--"),
("timestep", "当前 rollout 步数", "--"),
("wave_number", "Helmholtz 波数 k", "--"),
("k_local_sqrt_vol", "k x sqrt(eps_r) x sqrt(volume)", "--"),
("is_sbc_boundary", "是否与 SBC 边界相邻 (0/1)", "--"),
("dist_to_interface", "到介质边界的带符号距离 [新增]", "sign(d)*ln(1+|d|/lambda)"),
("epsilon_r", "单元中点介电常数(内=eps_r, 外=1.0", "--"),
("total_solution_magnitude", "散射场复数解的振幅", "--"),
]
for i, (name, meaning, norm) in enumerate(features):
y = table_top + row_h + i * row_h
bg = TABLE_ALT if i % 2 == 1 else WHITE
is_new = "[新增]" in meaning
cells = [name, meaning, norm]
for j, (cx, cell_text, w) in enumerate(zip(cols, cells, col_w)):
add_rect(slide, cx, y, w, row_h, fill_color=bg, line_color=LIGHTER_LINE, line_width=Pt(0.5))
clr = ACCENT_TEAL if is_new and j == 1 else BODY_GRAY
bld = is_new and j == 1
add_textbox(slide, cx + Inches(0.06), y, w - Inches(0.12), row_h,
text=cell_text, font_size=Pt(8), font_color=clr, bold=bld,
anchor=MSO_ANCHOR.MIDDLE)
# Edge feature note — positioned after table (table bottom = 1.65 + 0.30 + 12*0.30 = 5.55")
add_textbox(slide, Inches(0.6), Inches(5.65), Inches(12.1), Inches(0.25),
text="边特征 (1 维)euclidean_distance / lambda -- 相邻单元中点无量纲距离 | 合计14 (节点) + 1 (边) = 15 维图特征",
font_size=Pt(9), font_color=BODY_GRAY)
add_takeaway_bar(slide, "全部与尺度相关的特征均以 lambda 做无量纲归一化dist_to_interface 用 sign·ln(1+|d|) 对数压缩,近场线性、远场自然压缩,与残差 log10 风格统一")
add_slide_number(slide, 6)
# ======================================================================
# SLIDE 7: INNOVATION 3 - Score-based sizing field
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "创新 [3]Score-based 连续尺寸场 + 物理预算约束 + 动作掩码")
add_textbox(slide, Inches(0.6), Inches(1.25), Inches(5.7), Inches(0.35),
text="前序方案S_i = N_base x Softplus(x_i) / Softplus(0) x median_area", font_size=Pt(12), font_color=ACCENT_WARM)
add_textbox(slide, Inches(0.6), Inches(1.55), Inches(5.7), Inches(0.35),
text="--> 依赖 median_area 基准,域缩放后语义漂移 (1x1 -> 2x2 基准 x4)", font_size=Pt(10), font_color=CAPTION)
add_textbox(slide, Inches(6.9), Inches(1.25), Inches(5.6), Inches(0.35),
text="当前方案score = -x_i 纯排序 + 物理预算约束", font_size=Pt(12), font_color=ACCENT_BLUE)
add_textbox(slide, Inches(6.9), Inches(1.55), Inches(5.6), Inches(0.35),
text="--> score 排序丢失面积语义,但获得尺度不变性", font_size=Pt(10), font_color=CAPTION)
add_rect(slide, Inches(0.6), Inches(2.1), Inches(12.1), Inches(3.1), fill_color=HIGHLIGHT_BG)
add_textbox(slide, Inches(0.8), Inches(2.15), Inches(5.0), Inches(0.3),
text="细化选择算法", font_size=Pt(14), font_color=BLACK, bold=True)
algo_steps = [
("Step 1: 物理预算",
"A_budget_i = 1/2 x (lambda_local_i / 6)^2 仅用于 N_budget 计算\nN_budget = max(N_phys, ceil(5 x N_init)) rho_min=5.0,至少 5 倍初始单元数"),
("Step 2: Score 排序",
"score = -x_i (Actor 输出标量)\nx 越小 -> 优先级越高,纯排序,不设正负门槛"),
("Step 3: 双过滤器",
"eligible = {i | area_i > V_min_safeguard AND i in Reverse_Dorfler_set}\narea_floor: 纯数值底线 (1e-10 x domain_area)\nReverse Dorfler: 能量尾部淘汰 (eps_noise=0.01, >=20% floor)"),
("Step 4: Top-k 选择",
"num = min(|eligible|, N_current//4, remaining//3) (自适应 cap, 增速 N//4)\nselected = top-k by score -> 1-to-4 切分细化"),
]
for i, (title, content) in enumerate(algo_steps):
y = Inches(2.55 + i * 0.63)
add_textbox(slide, Inches(0.9), y, Inches(2.0), Inches(0.55),
text=title, font_size=Pt(11), font_color=ACCENT_BLUE, bold=True)
add_textbox(slide, Inches(2.9), y, Inches(9.5), Inches(0.55),
text=content, font_size=Pt(10), font_color=BODY_GRAY)
add_rect(slide, Inches(0.6), Inches(5.45), Inches(12.1), Inches(0.95), fill_color=None,
line_color=ACCENT_BLUE, line_width=Pt(0.5))
add_textbox(slide, Inches(0.8), Inches(5.5), Inches(11.7), Inches(0.85),
text="为什么用 Reverse Dörfler 而非 P95 硬阈值P95 在重尾分布下会被奇异点推至极高一刀切屏蔽大片中等误差区域。Reverse Dörfler 基于能量累积 (L2 范数平方和),自适应于任意分布形态,剔除确认无价值的底部噪声,保留 >=20% 单元确保 Agent 选择空间。",
font_size=Pt(11), font_color=BODY_GRAY)
add_takeaway_bar(slide, "Score-based 排序 + 物理预算 + Reverse Dörfler 掩码:三层保障确保细化资源只投入到物理上需要的地方")
add_slide_number(slide, 7)
# ======================================================================
# SLIDE 8: INNOVATION 4 - L2 aggregation reward
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "创新 [4]L2 聚合奖励设计 -- 保证非负,永不惩罚细化")
add_rect(slide, Inches(0.6), Inches(1.25), Inches(12.1), Inches(0.85), fill_color=HIGHLIGHT_BG)
add_textbox(slide, Inches(0.8), Inches(1.3), Inches(11.7), Inches(0.75),
text="核心洞察:对 1-to-4 切分,用 L2 聚合 sqrt(sum eta_child^2) <= eta_parent 天然成立 -- 因为平方后 int 项 1->1/4 而 jump/sbc 项 1->1。\n如果用 L1 sumsum eta_child > eta_parent因 jump/sbc 项不变),会导致「细化=惩罚」。L2 聚合从根本上避免了这一结构性负偏置。",
font_size=Pt(12), font_color=BLACK)
add_rect(slide, Inches(0.6), Inches(2.35), Inches(7.5), Inches(1.85), fill_color=None,
line_color=ACCENT_BLUE, line_width=Pt(1.5))
add_textbox(slide, Inches(0.8), Inches(2.4), Inches(7.1), Inches(0.3),
text="逐步奖励计算", font_size=Pt(14), font_color=BLACK, bold=True)
reward_lines = [
("r_local_i = log(eta_old_i + eps) - log( sqrt(sum_{j:M[j]=i} eta_new_j^2) + eps )", True, Pt(13), ACCENT_BLUE),
("", False, Pt(4), BODY_GRAY),
("- 纯 int 主导区: eta_parent^2 = int^2, sum eta_child^2 = int^2/4 -> r_local = log(2) = +0.69 (强正奖励)", False, Pt(11), BODY_GRAY),
("- 纯 jump/sbc 主导区: eta_parent^2 = jump^2, sum eta_child^2 = jump^2 -> r_local = 0 (中性)", False, Pt(11), BODY_GRAY),
("- 永不惩罚细化 -- 与 L1 sum 方案根本不同", False, Pt(11), BODY_GRAY),
]
add_multiline_textbox(slide, Inches(0.8), Inches(2.7), Inches(7.1), Inches(1.4),
reward_lines, line_spacing=1.35)
add_rect(slide, Inches(8.5), Inches(2.35), Inches(4.2), Inches(1.85), fill_color=WARN_BG)
add_textbox(slide, Inches(8.7), Inches(2.4), Inches(3.8), Inches(0.3),
text="epsilon_dynamic 动态截断", font_size=Pt(14), font_color=BLACK, bold=True)
ed_lines = [
("eps = max(0.05 x mean(eta_new), 1e-6)", True, Pt(11), ACCENT_WARM),
("", False, Pt(4), BODY_GRAY),
("自适应钳制,切断远场", False, Pt(11), BODY_GRAY),
("低 eta 区的 reward hacking", False, Pt(11), BODY_GRAY),
("", False, Pt(4), BODY_GRAY),
("防止 log(0) 数值爆炸,", False, Pt(11), BODY_GRAY),
("锚定当前误差分布而非", False, Pt(11), BODY_GRAY),
("固定阈值", False, Pt(11), BODY_GRAY),
]
add_multiline_textbox(slide, Inches(8.7), Inches(2.7), Inches(3.8), Inches(1.4),
ed_lines, line_spacing=1.2)
add_textbox(slide, Inches(0.6), Inches(4.45), Inches(6.0), Inches(0.3),
text="动作惩罚与元素上限", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
pen_lines = [
("penalty_i = lambda x (n_i-1) + (lambda_limit/N_old) x 1[达到上限], lambda=0.06, lambda_limit=10000", False, Pt(12), BODY_GRAY),
("lambda 仅为 r_local 均值的约 1/6轻微抑制网格膨胀不影响主要学习信号", False, Pt(11), CAPTION),
]
add_multiline_textbox(slide, Inches(0.6), Inches(4.8), Inches(6.0), Inches(0.7),
pen_lines, line_spacing=1.5)
add_textbox(slide, Inches(7.2), Inches(4.45), Inches(5.5), Inches(0.3),
text="Actor 奖励设计原则", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
glob_lines = [
("global_bonus 被 Helmholtz 污染误差污染", False, Pt(12), BODY_GRAY),
("E_new > E_old 可发生在正确细化后", False, Pt(11), BODY_GRAY),
("惩罚 Agent 做对的事 → 策略崩塌 (x<0→0.01)", False, Pt(11), BODY_GRAY),
("修正: global_bonus 仅诊断, 不注入 Actor reward", False, Pt(11), CAPTION),
]
add_multiline_textbox(slide, Inches(7.2), Inches(4.8), Inches(5.5), Inches(0.7),
glob_lines, line_spacing=1.5)
add_takeaway_bar(slide, "零和预算审查: 奖金 0.3*(eta/mu-1) 全场求和为零 (Doerfler 准则 RL 对偶); unrefined r=0; global_bonus 仅诊断")
add_slide_number(slide, 8)
# ======================================================================
# SLIDE 9: REWARD CALIBRATION
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "奖励标度校准:随机策略下各分量量级实测")
add_textbox(slide, Inches(0.6), Inches(1.25), Inches(12.1), Inches(0.35),
text="随机策略下 1,321 个 refined-parent 样本实测score-based 尺寸场)", font_size=Pt(12), font_color=CAPTION)
kpi_data = [
("+0.364", "r_local (L2 聚合)", "局部误差改善,主体信号"),
("+0.045", "penalty (lambda=0.02)", "仅占 r_local 的约 1/8"),
("+0.069", "alpha x Delta_logE (alpha=0.2)", "全局改善信号,约 r_local/5"),
("+0.387", "净奖励 net reward", "r_local >> penalty [check]"),
]
for i, (val, label, desc) in enumerate(kpi_data):
x = Inches(0.6 + i * 3.1)
add_rect(slide, x, Inches(1.7), Inches(2.85), Inches(1.2), fill_color=HIGHLIGHT_BG)
add_textbox(slide, x + Inches(0.1), Inches(1.75), Inches(2.65), Inches(0.4),
text=val, font_size=Pt(24), font_color=ACCENT_BLUE, bold=True,
alignment=PP_ALIGN.CENTER)
add_textbox(slide, x + Inches(0.1), Inches(2.15), Inches(2.65), Inches(0.3),
text=label, font_size=Pt(11), font_color=BLACK, bold=True,
alignment=PP_ALIGN.CENTER)
add_textbox(slide, x + Inches(0.1), Inches(2.45), Inches(2.65), Inches(0.35),
text=desc, font_size=Pt(9), font_color=CAPTION,
alignment=PP_ALIGN.CENTER)
add_textbox(slide, Inches(0.6), Inches(3.2), Inches(12.1), Inches(0.3),
text="设计验证", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
design_checks = [
("[OK] r_local >> penalty", "局部 credit assignment 不被惩罚信号淹没agent 能清晰感知细化 -> 误差下降的因果关系"),
("[OK] alpha x Delta_logE = r_local / 5", "全局信号提供趋势引导但不主导局部决策,避免 loss of local credit assignment"),
("[OK] r_local >= 0 保证", "L2 聚合天然保证非负,网络永远不会因细化而受到惩罚"),
]
for i, (check, detail) in enumerate(design_checks):
add_textbox(slide, Inches(0.8), Inches(3.5 + i * 0.45), Inches(2.8), Inches(0.35),
text=check, font_size=Pt(12), font_color=ACCENT_GREEN, bold=True)
add_textbox(slide, Inches(3.6), Inches(3.5 + i * 0.45), Inches(9.1), Inches(0.35),
text=detail, font_size=Pt(11), font_color=BODY_GRAY)
add_textbox(slide, Inches(0.6), Inches(5.1), Inches(12.1), Inches(0.3),
text="奖励信号链", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
flow_steps = [
("FEM 求解", "eta_K per element", ACCENT_BLUE),
("L2 聚合", "log(eta_old / sqrt(sum_chi^2))", ACCENT_TEAL),
("+ eps_dynamic", "截断保护", ACCENT_WARM),
("- penalty", "lambda x (n-1) 防膨胀", ACCENT_WARM),
("+ global", "alpha x Delta_logE 仅细化单元", ACCENT_GREEN),
("-> r_i", "送入 PPO GAE", ACCENT_BLUE),
]
for i, (step_name, step_desc, clr) in enumerate(flow_steps):
x = Inches(0.6 + i * 2.05)
add_rect(slide, x, Inches(5.45), Inches(1.8), Inches(0.7), fill_color=HIGHLIGHT_BG,
line_color=clr, line_width=Pt(1))
add_textbox(slide, x + Inches(0.1), Inches(5.48), Inches(1.6), Inches(0.3),
text=step_name, font_size=Pt(11), font_color=clr, bold=True,
alignment=PP_ALIGN.CENTER)
add_textbox(slide, x + Inches(0.1), Inches(5.78), Inches(1.6), Inches(0.3),
text=step_desc, font_size=Pt(9), font_color=CAPTION,
alignment=PP_ALIGN.CENTER)
if i < len(flow_steps) - 1:
add_textbox(slide, x + Inches(1.8), Inches(5.6), Inches(0.25), Inches(0.3),
text=">", font_size=Pt(14), font_color=LIGHT_LINE, bold=True,
alignment=PP_ALIGN.CENTER)
add_takeaway_bar(slide, "奖励各分量量级经过标定,满足 r_local >> penalty 且 alpha x Delta_logE 适度agent 能学到细化 = 有益的信息")
add_slide_number(slide, 9)
# ======================================================================
# SLIDE 10: SCALE INVARIANCE
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "创新 [5]:尺度不变性架构 -- 从 1x1 到 2x2 的泛化")
add_textbox(slide, Inches(0.6), Inches(1.25), Inches(12.1), Inches(0.4),
text="问题1x1 域训练 -> 2x2 域测试时,中心介质处网格未加密,远场误差显著增大", font_size=Pt(14), font_color=ACCENT_WARM)
add_textbox(slide, Inches(0.6), Inches(1.75), Inches(12.1), Inches(0.3),
text="根因分析(双重漂移)", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
roots = [
("N_init 不随 domain area 缩放", "4x 面积用同数量单元 -> h 2x, area 4x", "N_init *= domain_area"),
("特征绝对值漂移", "volume/edge/dist 值随 domain 线性或平方放大", "全部用 lambda 无量纲化"),
("dist 远场 OOD", "2x2 域远角 dist/lambda 可达训练域 3x", "sign·ln(1+|d|/lambda) 对数压缩"),
]
for i, (problem, cause, fix) in enumerate(roots):
x = Inches(0.6 + i * 4.1)
add_rect(slide, x, Inches(2.1), Inches(3.85), Inches(1.3), fill_color=HIGHLIGHT_BG)
add_textbox(slide, x + Inches(0.1), Inches(2.13), Inches(3.65), Inches(0.3),
text=problem, font_size=Pt(13), font_color=ACCENT_WARM, bold=True)
add_textbox(slide, x + Inches(0.1), Inches(2.45), Inches(3.65), Inches(0.4),
text=f"原因: {cause}", font_size=Pt(10), font_color=BODY_GRAY)
add_textbox(slide, x + Inches(0.1), Inches(2.85), Inches(3.65), Inches(0.4),
text=f"--> {fix}", font_size=Pt(11), font_color=ACCENT_GREEN)
add_rect(slide, Inches(0.6), Inches(3.65), Inches(7.5), Inches(2.05), fill_color=None,
line_color=ACCENT_BLUE, line_width=Pt(1))
add_textbox(slide, Inches(0.8), Inches(3.7), Inches(7.1), Inches(0.3),
text="四项联动改进 = 完整的尺度不变性", font_size=Pt(14), font_color=BLACK, bold=True)
k_mesh_lines = [
("1. N_init = N_base x (k/k_ref)^k_exponent x domain_area exponent/k_ref 可配,保证每单位面积密度一致)", False, Pt(12), BODY_GRAY),
("2. volume -> volume / lambda^2, euclidean_distance -> euclidean_distance / lambda", False, Pt(12), BODY_GRAY),
("3. dist_to_interface -> sign(d)*ln(1+|d|/lambda) (近场线性、远场对数压缩,与 log10 残差风格一致)", False, Pt(12), BODY_GRAY),
("4. 介质区前渐近区边缘约束: 强制迭代细化至 h <= lambda_d/N (N=1.5)", False, Pt(12), BODY_GRAY),
("--> 四项联动N_init 修 h 漂移 + lambda 归一化修特征绝对值 + tanh 修远场 OOD", False, Pt(11), CAPTION),
]
add_multiline_textbox(slide, Inches(0.8), Inches(4.0), Inches(7.1), Inches(1.5),
k_mesh_lines, line_spacing=1.3)
add_textbox(slide, Inches(8.5), Inches(3.7), Inches(4.2), Inches(0.3),
text="N_init 缩放效果示例", font_size=Pt(13), font_color=BLACK, bold=True)
k_table_lines = [
("exponent 可配: ^2 = 理论最优, ^1.5 = 工程折中", False, Pt(10), BODY_GRAY),
("N_init 始终 = COMSOL 目标的 30-50%", False, Pt(10), BODY_GRAY),
("", False, Pt(4), BODY_GRAY),
("改前: 无 domain_area 缩放", True, Pt(10), ACCENT_WARM),
("-> 换 domain size 后 N_init 不变", False, Pt(10), CAPTION),
("-> h 随 domain 缩放,特征 OOD", False, Pt(10), CAPTION),
]
add_multiline_textbox(slide, Inches(8.5), Inches(4.0), Inches(4.2), Inches(1.7),
k_table_lines, line_spacing=1.3)
add_takeaway_bar(slide, "N_init x domain_area + lambda 无量纲化 + ln 对数压缩:三项联动使模型可物理一致地泛化到任意尺寸测试域")
add_slide_number(slide, 10)
# ======================================================================
# SLIDE 11: DUAL GNN ARCHITECTURE
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "双 GNN 架构与 PPO 训练细节")
add_textbox(slide, Inches(0.6), Inches(1.3), Inches(12.1), Inches(0.35),
text="图观测 -> MessagePassingBase (Policy/Value 各自独立) -> Actor/Critic 头", font_size=Pt(13), font_color=BLACK, bold=True)
add_rect(slide, Inches(0.6), Inches(1.8), Inches(5.8), Inches(3.0), fill_color=HIGHLIGHT_BG)
add_textbox(slide, Inches(0.8), Inches(1.85), Inches(5.4), Inches(0.3),
text="MessagePassingBase (x2, Policy / Value 各自独立基座)", font_size=Pt(13), font_color=ACCENT_BLUE, bold=True)
gnn_items = [
("节点嵌入", "Linear(14 -> 64)"),
("边嵌入", "Linear(1 -> 64)"),
("MP Step 1", "EdgeModule: MLP([src|dst|edge_attr]) -> 64d"),
("", "NodeModule: MLP([node|scatter_mean(入边)]) -> 64d"),
("", "+ inner 残差 + LayerNorm"),
("MP Step 2", "同 Step 1堆叠 2 层"),
("GVN 全局虚拟节点", "h_V = Σ(η_v/Ση)·h_v (η_K 加权池化)"),
("", "α = σ(W[h_v||h_V])h_v += scale·α ⊙ W_V·h_V"),
("输出", "节点隐向量 (num_nodes, 64)"),
]
for i, (label, detail) in enumerate(gnn_items):
y = Inches(2.25 + i * 0.32)
if label:
add_textbox(slide, Inches(0.9), y, Inches(1.6), Inches(0.28),
text=label, font_size=Pt(10), font_color=ACCENT_BLUE, bold=True)
add_textbox(slide, Inches(2.5), y, Inches(3.7), Inches(0.28),
text=detail, font_size=Pt(10), font_color=BODY_GRAY)
add_rect(slide, Inches(7.0), Inches(1.8), Inches(5.7), Inches(1.4), fill_color=None,
line_color=ACCENT_TEAL, line_width=Pt(1))
add_textbox(slide, Inches(7.2), Inches(1.85), Inches(5.3), Inches(0.3),
text="Actor 头(策略网络)", font_size=Pt(13), font_color=ACCENT_TEAL, bold=True)
actor_items = [
("MLP: 2 层 Tanh (64 -> 64 -> 64)", False, Pt(11), BODY_GRAY),
("Linear(64 -> 1): 输出 x_i (连续标量)", False, Pt(11), BODY_GRAY),
("log_std: 可学习参数,初始化 -2.0 (std = 0.135)", False, Pt(11), BODY_GRAY),
("DiagGaussian(mu, sigma): 每节点独立动作分布", False, Pt(11), BODY_GRAY),
]
add_multiline_textbox(slide, Inches(7.2), Inches(2.2), Inches(5.3), Inches(0.9),
actor_items, line_spacing=1.3)
add_rect(slide, Inches(7.0), Inches(3.4), Inches(5.7), Inches(1.4), fill_color=None,
line_color=ACCENT_GREEN, line_width=Pt(1))
add_textbox(slide, Inches(7.2), Inches(3.45), Inches(5.3), Inches(0.3),
text="Critic 头(价值网络)", font_size=Pt(13), font_color=ACCENT_GREEN, bold=True)
critic_items = [
("MLP: 2 层 Tanh (64 -> 64 -> 1)", False, Pt(11), BODY_GRAY),
("输出: V_i(s) 逐节点价值 (num_agents, 1)", False, Pt(11), BODY_GRAY),
("spatial value function: 不做聚合,保持逐节点", False, Pt(11), BODY_GRAY),
("GAE 中用 scatter_add 做子->父投影,处理变长拓扑", False, Pt(11), BODY_GRAY),
]
add_multiline_textbox(slide, Inches(7.2), Inches(3.75), Inches(5.3), Inches(0.9),
critic_items, line_spacing=1.3)
add_textbox(slide, Inches(0.6), Inches(5.1), Inches(12.1), Inches(0.3),
text="PPO 关键设计", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
ppo_details = [
("单路 GAE", "scatter_add 将子单元值聚合回父单元,无需多路 GAE", ACCENT_BLUE),
("log_std clamp", "每步 optimizer.step() 后 clamp 到 [-4.0, -1.0]std in [0.018, 0.368]", ACCENT_TEAL),
("熵正则", "entropy_coefficient=0.001,防止 log_std 过早收敛到下限", ACCENT_GREEN),
("梯度裁剪", "max_grad_norm=0.5,稳定训练过程", ACCENT_WARM),
]
for i, (tag, desc, clr) in enumerate(ppo_details):
x = Inches(0.6 + i * 3.1)
add_rect(slide, x, Inches(5.45), Inches(2.85), Inches(0.85), fill_color=HIGHLIGHT_BG)
add_textbox(slide, x + Inches(0.1), Inches(5.5), Inches(2.65), Inches(0.3),
text=tag, font_size=Pt(13), font_color=clr, bold=True, alignment=PP_ALIGN.CENTER)
add_textbox(slide, x + Inches(0.1), Inches(5.8), Inches(2.65), Inches(0.4),
text=desc, font_size=Pt(10), font_color=BODY_GRAY, alignment=PP_ALIGN.CENTER)
add_takeaway_bar(slide, "双 GNN 各自独立建模 + DiagGaussian 连续动作 + scatter_add 单路 GAE -> 适合变长 agent 拓扑的 RL 训练框架")
add_slide_number(slide, 11)
# ======================================================================
# SLIDE 12: TRAINING OBSERVATIONS
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "训练观察与诊断:奖励稀疏性与大波数泛化")
add_rect(slide, Inches(0.6), Inches(1.3), Inches(5.8), Inches(2.4), fill_color=WARN_BG)
add_textbox(slide, Inches(0.8), Inches(1.35), Inches(5.4), Inches(0.35),
text="观察 1: 75% rollout 步骤零 reward", font_size=Pt(14), font_color=ACCENT_WARM, bold=True)
obs1_lines = [
("4 步 rollout 中,第 0 步细化后介质区已达标 (h/lambda = 13 > N=15 参考线)", False, Pt(11), BODY_GRAY),
("步 1-3 全为零 reward75% 的 FEM 求解白白浪费", False, Pt(11), BODY_GRAY),
("原因: 1-to-4 切分太粗,一步即达标,不存在差一点的中间状态", False, Pt(11), BODY_GRAY),
("偶尔的 spike (reward ~60) 来自随机探索中极负的 x_i 触发第二步细化", False, Pt(11), BODY_GRAY),
("--> 步 0 的 reward 信号足够训练「在哪里细化」的判断,但多步策略无法学习", False, Pt(11), CAPTION),
]
add_multiline_textbox(slide, Inches(0.8), Inches(1.7), Inches(5.4), Inches(1.85),
obs1_lines, line_spacing=1.35)
add_rect(slide, Inches(7.0), Inches(1.3), Inches(5.7), Inches(2.4), fill_color=WARN_BG)
add_textbox(slide, Inches(7.2), Inches(1.35), Inches(5.3), Inches(0.35),
text="观察 2: 高 k 扇形阴影区网格偏粗", font_size=Pt(14), font_color=ACCENT_WARM, bold=True)
obs2_lines = [
("k in [2,20] 训练,小 k 尚可,大 k 效果不佳", False, Pt(11), BODY_GRAY),
("介质后方 +x 方向扇形区域网格偏粗,误差较大", False, Pt(11), BODY_GRAY),
("根本原因: 污染效应 -> 初始 kh > 0.5 时 FEM 解定性错误 (GIGO)", False, Pt(11), BODY_GRAY),
("粗网格 -> 错误解 -> 不可靠 eta -> 垃圾 GNN 特征 -> 垃圾动作", False, Pt(11), BODY_GRAY),
("2 层 GNN 感受野仅约 10 个单元,网络不知道自己在介质后方", False, Pt(11), BODY_GRAY),
]
add_multiline_textbox(slide, Inches(7.2), Inches(1.7), Inches(5.3), Inches(1.85),
obs2_lines, line_spacing=1.35)
add_textbox(slide, Inches(0.6), Inches(4.0), Inches(12.1), Inches(0.3),
text="训练日志解读 (k in [2,20], 随机 PDE, 4 步 rollout)", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
log_lines = [
("loss ~ 0.10-0.18, explained_var ~ 0.65-0.78", "Critic 对价值函数的解释力中等偏上,尚可但非极强"),
("reward 间歇性 spike (0 -> 13 -> 60 -> 0)", "随机探索 + GAE 信度传播,信号稀疏但偶尔强正奖励"),
("agent 数量在 100-3500 间大幅波动", "取决于 PDE 随机采样和细化触发情况"),
("loss/ev 趋于平台期", "可能是 k^2 与 N=15 互斥的问题(已用 k^1.5 修复)"),
]
for i, (log, interpret) in enumerate(log_lines):
add_textbox(slide, Inches(0.8), Inches(4.35 + i * 0.42), Inches(4.0), Inches(0.35),
text=log, font_size=Pt(11), font_color=ACCENT_BLUE, bold=True)
add_textbox(slide, Inches(5.0), Inches(4.35 + i * 0.42), Inches(7.7), Inches(0.35),
text=interpret, font_size=Pt(11), font_color=BODY_GRAY)
add_takeaway_bar(slide, "训练瓶颈非算法设计问题,而是物理前提 (污染效应 GIGO) 和多步细化粒度 (1-to-4 太粗) 的工程限制")
add_slide_number(slide, 12)
# ======================================================================
# SLIDE 13: INNOVATION SUMMARY
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "创新点汇总与可复用价值")
innovations = [
("[1]", "无量纲化\n残差误差估计",
"真空波数 k 归一化残差\n介质内 η 不再被压低\nGNN+Reward 统一使用 k 归一化",
ACCENT_BLUE),
("[2]", "Score-based\n连续尺寸场",
"score = -x_i 纯排序\n物理预算 N_budget 约束\nReverse Dörfler 双过滤器掩码",
ACCENT_TEAL),
("[3]", "L2 聚合\n奖励设计",
"sqrt(sum eta_child^2) <= eta_parent 天然成立\n永不惩罚细化 (r_local >= 0)\nint 主导区强正奖励约 +0.69",
ACCENT_GREEN),
("[4]", "尺度不变性\n架构",
"N_init x domain_area 缩放\nlambda 无量纲化全部特征\nsign·ln 对数压缩 + 前渐近区约束",
ACCENT_WARM),
("[5]", "双 GNN +\n变长拓扑 RL",
"Policy/Value 独立 GNN 基座\nscatter_add 单路 GAE\nDiagGaussian + log_std clamp",
RGBColor(0x5B, 0x3A, 0x8B)),
]
for i, (num, title, desc, clr) in enumerate(innovations):
x = Inches(0.6 + i * 2.5)
add_rect(slide, x, Inches(1.35), Inches(2.3), Inches(3.1), fill_color=HIGHLIGHT_BG)
add_rect(slide, x, Inches(1.35), Inches(2.3), Pt(3), fill_color=clr)
add_textbox(slide, x + Inches(0.15), Inches(1.5), Inches(2.0), Inches(0.7),
text=title, font_size=Pt(13), font_color=clr, bold=True,
alignment=PP_ALIGN.LEFT, line_spacing=1.2)
add_textbox(slide, x + Inches(0.15), Inches(2.3), Inches(2.0), Inches(2.0),
text=desc, font_size=Pt(10), font_color=BODY_GRAY, line_spacing=1.4)
add_textbox(slide, Inches(0.6), Inches(4.7), Inches(12.1), Inches(0.3),
text="可复用价值(超越本项目的通用方法贡献)", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
reuse_items = [
("L2 聚合 + 父子映射", "适用于任何分裂型变长 agent RL 场景(网格细化、树搜索、层次化决策)"),
("真空波数 k 归一化方法", "残差归一化用 k₀ 非 k_local介质内物理信号不再被压低"),
("Score-based + 预算约束选择", "适用于资源受限的排序-选择问题:传感器部署、计算资源分配、实验设计优化"),
("Reverse Dörfler 动作掩码", "能量尾部淘汰的思想可推广到任何需要排除低信号样本的场景"),
]
for i, (tag, desc) in enumerate(reuse_items):
add_textbox(slide, Inches(0.8), Inches(5.05 + i * 0.42), Inches(2.8), Inches(0.35),
text=tag, font_size=Pt(11), font_color=ACCENT_BLUE, bold=True)
add_textbox(slide, Inches(3.7), Inches(5.05 + i * 0.42), Inches(9.0), Inches(0.35),
text=desc, font_size=Pt(11), font_color=BODY_GRAY)
add_slide_number(slide, 13)
# ======================================================================
# SLIDE 14: LIMITATIONS
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_slide_title(slide, "局限性与未解决问题")
limitations = [
("污染效应 (GIGO: Garbage-In-Garbage-Out)",
[
"高 k 下初始 kh > 0.5 时 FEM 解定性错误,误差指示子 eta_K 完全不可靠",
"RL 无法在错误解的基础上学到正确策略 -- 这是物理前提而非算法问题",
"缓解: N_init x domaine_area 使真空始终 >= 12 单元/lambda但高 k 下余量有限",
]),
("GNN 感受野受限",
[
"2 层消息传递,每个节点感受野仅约 10 个单元,无法感知全局几何结构",
"介质后方扇形阴影区GNN 不知道自己在介质背后,小 k 学到的真空不需细化被错误泛化",
"需要: 更多几何上下文特征(入射波方向、与介质相对位置)或更深的 GNN",
]),
("1-to-4 切分粒度",
[
"一步细化即可达标 (每波长单元数 >= N=15 参考线),多步 rollout 中 75% 步骤零 reward",
"高 eps_r 介质区可能需要 2-3 步细化,但 PPO GAE 在 4 步序列中传播稀疏信号效率极低",
"需要: 更细粒度的切分方案(如 1-to-2 边切分)或递减的 N_per_wavelength 目标",
]),
("泛化到更多散射体配置",
[
"当前仅在单个圆形介质柱上训练;多散射体、非圆形、复杂材料的泛化未经验证",
"训练波数 [2,20] 覆盖范围有限,更高 k 需要更深的初始网格和更强的特征表达",
"需要: 更丰富的 PDE 问题分布、课程学习、域随机化策略",
]),
]
for i, (title, points) in enumerate(limitations):
x = Inches(0.6 + (i % 2) * 6.3)
y = Inches(1.3 + (i // 2) * 2.8)
add_rect(slide, x, y, Inches(5.9), Inches(2.45), fill_color=None,
line_color=LIGHT_LINE, line_width=Pt(1))
add_rect(slide, x + Pt(1), y + Pt(1), Pt(3), Inches(0.35), fill_color=ACCENT_WARM)
add_textbox(slide, x + Inches(0.2), y + Inches(0.05), Inches(5.3), Inches(0.35),
text=title, font_size=Pt(14), font_color=ACCENT_WARM, bold=True)
for j, point in enumerate(points):
add_textbox(slide, x + Inches(0.2), y + Inches(0.45 + j * 0.45), Inches(5.3), Inches(0.4),
text=f"- {point}", font_size=Pt(10), font_color=BODY_GRAY)
add_slide_number(slide, 14)
# ======================================================================
# SLIDE 15: SUMMARY & DISCUSSION
# ======================================================================
slide = add_blank_slide()
set_slide_bg(slide, WHITE)
add_top_bar(slide)
add_rect(slide, Inches(0.6), Inches(2.0), Pt(4), Inches(4.0), fill_color=ACCENT_BLUE)
add_textbox(slide, Inches(0.85), Inches(2.0), Inches(11.5), Inches(1.0),
text="总 结", font_size=Pt(36), font_color=BLACK, bold=True)
summary_points = [
"提出了一套完整的 RL 自适应网格细化框架:从物理建模、误差估计、状态表征、动作空间到奖励设计的全链路创新",
"真空波数 k 归一化残差使介质内 η 自然放大Agent 获得正确的物理优先级信号",
"Score-based 尺寸场 + 物理预算约束 + Reverse Dörfler 掩码实现了资源感知的细化单元选择",
"L2 聚合奖励设计从数学上保证了细化奖励非负,从根本上避免了 L1 sum 的结构性负偏置",
"sign(d)*ln(1+|d|/lambda) 对数压缩 + lambda 归一化全部特征实现了域尺寸的尺度不变泛化",
]
for i, point in enumerate(summary_points):
add_textbox(slide, Inches(0.85), Inches(3.1 + i * 0.42), Inches(0.4), Inches(0.35),
text=f"{i+1}.", font_size=Pt(14), font_color=ACCENT_BLUE, bold=True)
add_textbox(slide, Inches(1.25), Inches(3.1 + i * 0.42), Inches(11.2), Inches(0.35),
text=point, font_size=Pt(13), font_color=BODY_GRAY)
add_rect(slide, Inches(0.85), Inches(5.4), Inches(11.5), Inches(0.05), fill_color=LIGHT_LINE)
add_textbox(slide, Inches(0.85), Inches(5.6), Inches(11.5), Inches(0.4),
text="讨论与后续方向", font_size=SUBHEAD_SIZE, font_color=BLACK, bold=True)
discussion_points = [
"如何处理污染效应 (GIGO)-> 更高阶 FEM (p-refinement) + 显式 kh 特征 + 更深的初始网格",
"如何提升多步细化效率?-> 递减的 N_per_wavelength 目标 + 更细粒度切分 (1-to-2) + 课程学习",
"如何拓展到更复杂场景?-> 多散射体、三维 Helmholtz、Maxwell 方程组、时域问题",
]
for i, point in enumerate(discussion_points):
add_textbox(slide, Inches(0.85), Inches(6.05 + i * 0.35), Inches(0.35), Inches(0.3),
text=">", font_size=Pt(12), font_color=ACCENT_TEAL, bold=True)
add_textbox(slide, Inches(1.2), Inches(6.05 + i * 0.35), Inches(11.2), Inches(0.3),
text=point, font_size=Pt(12), font_color=BODY_GRAY)
add_textbox(slide, Inches(8.5), Inches(7.0), Inches(4.5), Inches(0.4),
text="谢谢!欢迎讨论。", font_size=Pt(18), font_color=ACCENT_BLUE, bold=True,
alignment=PP_ALIGN.RIGHT)
add_slide_number(slide, 15)
# Save
output_path = "/public/home/dxw/Codes/afem/output/final_presentation_cn.pptx"
prs.save(output_path)
print(f"PPTX saved to {output_path}")
print(f"Slides: {len(prs.slides)}")