1072 lines
46 KiB
Python
1072 lines
46 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Build GNN-AMR correction presentation PPTX from README content.
|
||
Nature-style academic presentation, simplified Chinese.
|
||
"""
|
||
|
||
import json
|
||
from pathlib import Path
|
||
|
||
import numpy as np
|
||
from pptx import Presentation
|
||
from pptx.util import Inches, Pt, Emu
|
||
from pptx.dml.color import RGBColor
|
||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||
from pptx.enum.shapes import MSO_SHAPE
|
||
|
||
# ── Paths ──
|
||
ROOT = Path(__file__).resolve().parent.parent.parent # afem/
|
||
OUTLOOK = ROOT / "outlook"
|
||
OUTPUT = OUTLOOK / "output"
|
||
ASSETS = OUTPUT / "assets" / "figures"
|
||
|
||
# ── Colors ──
|
||
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
||
BG_LIGHT = RGBColor(0xF8, 0xF9, 0xFA)
|
||
TEXT_DARK = RGBColor(0x1A, 0x1A, 0x2E)
|
||
TEXT_MID = RGBColor(0x4A, 0x4A, 0x5A)
|
||
TEXT_LIGHT = RGBColor(0x8A, 0x8A, 0x9A)
|
||
ACCENT_BLUE = RGBColor(0x21, 0x96, 0xF3)
|
||
ACCENT_RED = RGBColor(0xE9, 0x1E, 0x63)
|
||
ACCENT_GREEN = RGBColor(0x4C, 0xAF, 0x50)
|
||
ACCENT_ORANGE = RGBColor(0xFF, 0x98, 0x00)
|
||
BORDER_LIGHT = RGBColor(0xE0, 0xE0, 0xE0)
|
||
|
||
# ── Fonts ──
|
||
FONT_CN = "Microsoft YaHei"
|
||
FONT_EN = "Calibri"
|
||
|
||
# ── Slide dimensions (16:9) ──
|
||
SLIDE_W = Inches(13.333)
|
||
SLIDE_H = Inches(7.5)
|
||
|
||
|
||
def set_slide_bg(slide, color=BG_LIGHT):
|
||
"""Set slide background color."""
|
||
bg = slide.background
|
||
fill = bg.fill
|
||
fill.solid()
|
||
fill.fore_color.rgb = color
|
||
|
||
|
||
def add_shape_fill(slide, left, top, width, height, color, alpha=None):
|
||
"""Add a colored rectangle shape."""
|
||
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, width, height)
|
||
shape.fill.solid()
|
||
shape.fill.fore_color.rgb = color
|
||
shape.line.fill.background()
|
||
if alpha is not None:
|
||
shape.fill.fore_color.brightness = alpha
|
||
return shape
|
||
|
||
|
||
def add_text_box(slide, left, top, width, height, text, font_size=14,
|
||
color=TEXT_DARK, bold=False, align=PP_ALIGN.LEFT,
|
||
font_name=FONT_CN, line_spacing=1.3):
|
||
"""Add a text box with formatted text."""
|
||
txBox = slide.shapes.add_textbox(left, top, width, height)
|
||
tf = txBox.text_frame
|
||
tf.word_wrap = True
|
||
p = tf.paragraphs[0]
|
||
p.text = text
|
||
p.font.size = Pt(font_size)
|
||
p.font.color.rgb = color
|
||
p.font.bold = bold
|
||
p.font.name = font_name
|
||
p.alignment = align
|
||
p.space_after = Pt(2)
|
||
if line_spacing:
|
||
p.line_spacing = Pt(font_size * line_spacing)
|
||
return txBox
|
||
|
||
|
||
def add_bullet_list(slide, left, top, width, height, items, font_size=14,
|
||
color=TEXT_DARK, font_name=FONT_CN, bullet_char="•"):
|
||
"""Add a bulleted list."""
|
||
txBox = slide.shapes.add_textbox(left, top, width, height)
|
||
tf = txBox.text_frame
|
||
tf.word_wrap = True
|
||
for i, item in enumerate(items):
|
||
if i == 0:
|
||
p = tf.paragraphs[0]
|
||
else:
|
||
p = tf.add_paragraph()
|
||
p.text = f"{bullet_char} {item}"
|
||
p.font.size = Pt(font_size)
|
||
p.font.color.rgb = color
|
||
p.font.name = font_name
|
||
p.space_after = Pt(6)
|
||
p.line_spacing = Pt(font_size * 1.5)
|
||
return txBox
|
||
|
||
|
||
def add_caption(slide, left, top, width, text, font_size=9, color=TEXT_LIGHT):
|
||
"""Add a small caption/source line."""
|
||
return add_text_box(slide, left, top, width, Inches(0.3), text,
|
||
font_size=font_size, color=color)
|
||
|
||
|
||
def add_takeaway_strip(slide, text, top=None):
|
||
"""Add a bottom takeaway strip."""
|
||
if top is None:
|
||
top = SLIDE_H - Inches(0.6)
|
||
strip = add_shape_fill(slide, Inches(0), top, SLIDE_W, Inches(0.5),
|
||
RGBColor(0xE8, 0xF0, 0xFE))
|
||
add_text_box(slide, Inches(0.5), top + Inches(0.05), SLIDE_W - Inches(1),
|
||
Inches(0.4), f"✦ {text}", font_size=12, color=ACCENT_BLUE,
|
||
bold=True, align=PP_ALIGN.LEFT)
|
||
|
||
|
||
def add_source_label(slide, text):
|
||
"""Add source label at bottom-right."""
|
||
add_text_box(slide, SLIDE_W - Inches(4), SLIDE_H - Inches(0.35),
|
||
Inches(3.5), Inches(0.25), text,
|
||
font_size=8, color=TEXT_LIGHT, align=PP_ALIGN.RIGHT)
|
||
|
||
|
||
def add_image_safe(slide, img_path, left, top, width, height):
|
||
"""Add image if file exists, otherwise add placeholder."""
|
||
p = Path(img_path)
|
||
if p.exists() and p.stat().st_size > 0:
|
||
slide.shapes.add_picture(str(p), left, top, width, height)
|
||
else:
|
||
shape = add_shape_fill(slide, left, top, width, height, BORDER_LIGHT)
|
||
add_text_box(slide, left, top + height // 2 - Inches(0.2),
|
||
width, Inches(0.4), f"[{p.name}]", font_size=10,
|
||
color=TEXT_LIGHT, align=PP_ALIGN.CENTER)
|
||
|
||
|
||
# ============================================================================
|
||
# Slide builders
|
||
# ============================================================================
|
||
|
||
|
||
def slide_01_title(prs):
|
||
"""Title slide."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
# Top accent bar
|
||
add_shape_fill(slide, Inches(0), Inches(0), SLIDE_W, Inches(0.08), ACCENT_BLUE)
|
||
|
||
# Title
|
||
add_text_box(slide, Inches(1), Inches(1.8), Inches(11), Inches(1.2),
|
||
"GNN 引导的自适应网格加密",
|
||
font_size=36, color=TEXT_DARK, bold=True, align=PP_ALIGN.CENTER)
|
||
|
||
# Subtitle
|
||
add_text_box(slide, Inches(1), Inches(3.1), Inches(11), Inches(0.8),
|
||
"用于 2D Helmholtz 散射问题的图神经网络修正学习",
|
||
font_size=20, color=TEXT_MID, align=PP_ALIGN.CENTER)
|
||
|
||
# Separator line
|
||
add_shape_fill(slide, Inches(5), Inches(4.2), Inches(3), Inches(0.03), ACCENT_BLUE)
|
||
|
||
# Meta
|
||
add_text_box(slide, Inches(1), Inches(4.6), Inches(11), Inches(0.5),
|
||
"Problem-to-Solution: 用 GNN 学习残差驱动 AMR 的修正信号",
|
||
font_size=14, color=TEXT_LIGHT, align=PP_ALIGN.CENTER)
|
||
|
||
add_text_box(slide, Inches(1), Inches(5.5), Inches(11), Inches(0.5),
|
||
"AFEM Research Group | 2026",
|
||
font_size=12, color=TEXT_LIGHT, align=PP_ALIGN.CENTER)
|
||
|
||
|
||
def slide_02_background(prs):
|
||
"""Research background."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"研究背景:为什么这个问题重要",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_BLUE)
|
||
|
||
# Left column: physics problem
|
||
add_text_box(slide, Inches(0.6), Inches(1.5), Inches(5.5), Inches(0.5),
|
||
"物理问题:2D 介质圆柱电磁散射", font_size=18, color=ACCENT_BLUE, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(0.6), Inches(2.1), Inches(5.5), Inches(3.5), [
|
||
"求解散射场公式:∇²u + k²·ε_r·u = −k²·(ε_r−1)·u_inc",
|
||
"入射波 u_inc = exp(i·k·x),Sommerfeld 辐射 BC",
|
||
"介质圆柱参数:k ∈ [3,15],ε_r ∈ [2,8],r ∈ [0.05,0.25]",
|
||
"FEM 求解器:P1 三角元 + Sommerfeld BC",
|
||
"参考解:Mie 解析解(精确对比基准)",
|
||
], font_size=13)
|
||
|
||
# Right column: why it matters
|
||
add_text_box(slide, Inches(6.8), Inches(1.5), Inches(5.8), Inches(0.5),
|
||
"核心挑战:网格预算有限下的误差最小化",
|
||
font_size=18, color=ACCENT_RED, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(6.8), Inches(2.1), Inches(5.8), Inches(3.5), [
|
||
"精细网格 → 高精度,但计算代价高",
|
||
"粗网格 → 快速,但误差大",
|
||
"目标:在给定网格预算 N 下,最小化 FEM 解误差",
|
||
"传统 AMR 需要逐步求解 FEM → 计算开销大",
|
||
"能否用数据驱动方法替代?",
|
||
], font_size=13)
|
||
|
||
# Bottom box: parameter space
|
||
box = add_shape_fill(slide, Inches(0.6), Inches(5.6), Inches(12.1), Inches(1.2),
|
||
RGBColor(0xF0, 0xF4, 0xF8))
|
||
add_text_box(slide, Inches(0.8), Inches(5.7), Inches(11.5), Inches(0.4),
|
||
"参数空间", font_size=14, color=ACCENT_BLUE, bold=True)
|
||
add_text_box(slide, Inches(0.8), Inches(6.1), Inches(11.5), Inches(0.5),
|
||
"k ∈ [3,15] | ε_r ∈ [2,8] | cx, cy ∈ [0.2,0.8] | radius ∈ [0.05,0.25] | "
|
||
"统一初始网格 32×32 = 2048 单元",
|
||
font_size=12, color=TEXT_MID)
|
||
|
||
|
||
def slide_03_gap(prs):
|
||
"""Knowledge gap / bottleneck."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"技术瓶颈:传统残差驱动 AMR 的局限",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_RED)
|
||
|
||
# Left: traditional AMR
|
||
add_text_box(slide, Inches(0.6), Inches(1.5), Inches(5.5), Inches(0.5),
|
||
"传统残差驱动 AMR 流程", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(0.6), Inches(2.1), Inches(5.5), Inches(3.0), [
|
||
"每步:FEM 求解 → 计算残差估计器 η → 标记 top-k → 加密",
|
||
"残差 η = √(r_int² + r_jump² + r_sbc²)",
|
||
"需要每步都做 FEM solve → 计算量大",
|
||
"η 是局部最优,不能利用全局几何/物理信息",
|
||
"无法离线推理:必须在线计算残差",
|
||
], font_size=13)
|
||
|
||
# Right: what we want
|
||
add_text_box(slide, Inches(6.8), Inches(1.5), Inches(5.8), Inches(0.5),
|
||
"我们希望实现", font_size=18, color=ACCENT_GREEN, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(6.8), Inches(2.1), Inches(5.8), Inches(3.0), [
|
||
"用 GNN 学习 \"哪些单元应被加密\"",
|
||
"推理时只需几何/物理特征 → 无需 FEM solve",
|
||
"一次前向传播 → 全图单元标记",
|
||
"可离线部署,计算代价大幅降低",
|
||
"超越纯物理先验的标记策略",
|
||
], font_size=13)
|
||
|
||
# Bottom: key insight
|
||
box = add_shape_fill(slide, Inches(0.6), Inches(5.3), Inches(12.1), Inches(1.5),
|
||
RGBColor(0xFE, 0xF3, 0xE2))
|
||
add_text_box(slide, Inches(0.8), Inches(5.4), Inches(11.5), Inches(0.4),
|
||
"核心洞察", font_size=14, color=ACCENT_ORANGE, bold=True)
|
||
add_text_box(slide, Inches(0.8), Inches(5.85), Inches(11.5), Inches(0.8),
|
||
"物理先验 physics_score = h/λ_eff 可以粗略判断分辨率不足的单元,但它忽略了散射体几何、"
|
||
"波场相位、材料界面等关键信息。GNN 可以学习这些物理先验无法捕捉的修正信号。",
|
||
font_size=13, color=TEXT_MID)
|
||
|
||
|
||
def slide_04_approach(prs):
|
||
"""Core approach: correction learning."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"核心思路:GNN 学习修正信号",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_GREEN)
|
||
|
||
# Three mark schemes
|
||
add_text_box(slide, Inches(0.6), Inches(1.5), Inches(12), Inches(0.5),
|
||
"三种标记方案的对比", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
# Three boxes
|
||
box_w = Inches(3.7)
|
||
box_h = Inches(2.8)
|
||
gap = Inches(0.35)
|
||
x0 = Inches(0.6)
|
||
y_top = Inches(2.2)
|
||
|
||
# Box 1: teacher
|
||
b1 = add_shape_fill(slide, x0, y_top, box_w, box_h, RGBColor(0xE8, 0xF5, 0xE9))
|
||
add_text_box(slide, x0 + Inches(0.15), y_top + Inches(0.1), box_w - Inches(0.3), Inches(0.4),
|
||
"teacher_mark (η 信号)", font_size=14, color=ACCENT_GREEN, bold=True)
|
||
add_bullet_list(slide, x0 + Inches(0.15), y_top + Inches(0.55), box_w - Inches(0.3), Inches(2.0), [
|
||
"基于残差估计器 η 排序",
|
||
"取 top 3% 单元标记为 1",
|
||
"需要 FEM solve → 计算代价高",
|
||
"代表 \"最优\" 加密决策",
|
||
], font_size=12, color=TEXT_MID, bullet_char="▸")
|
||
|
||
# Box 2: physics
|
||
b2 = add_shape_fill(slide, x0 + box_w + gap, y_top, box_w, box_h, RGBColor(0xE3, 0xF2, 0xFD))
|
||
add_text_box(slide, x0 + box_w + gap + Inches(0.15), y_top + Inches(0.1),
|
||
box_w - Inches(0.3), Inches(0.4),
|
||
"physics_score (物理先验)", font_size=14, color=ACCENT_BLUE, bold=True)
|
||
add_bullet_list(slide, x0 + box_w + gap + Inches(0.15), y_top + Inches(0.55),
|
||
box_w - Inches(0.3), Inches(2.0), [
|
||
"h / λ_eff,纯几何计算",
|
||
"无需 FEM solve",
|
||
"忽略波场、几何、材料信息",
|
||
"基线对比方法",
|
||
], font_size=12, color=TEXT_MID, bullet_char="▸")
|
||
|
||
# Box 3: correction
|
||
b3 = add_shape_fill(slide, x0 + 2 * (box_w + gap), y_top, box_w, box_h, RGBColor(0xFC, 0xE4, 0xEC))
|
||
add_text_box(slide, x0 + 2 * (box_w + gap) + Inches(0.15), y_top + Inches(0.1),
|
||
box_w - Inches(0.3), Inches(0.4),
|
||
"correction_label (GNN 目标)", font_size=14, color=ACCENT_RED, bold=True)
|
||
add_bullet_list(slide, x0 + 2 * (box_w + gap) + Inches(0.15), y_top + Inches(0.55),
|
||
box_w - Inches(0.3), Inches(2.0), [
|
||
"= teacher_mark − physics_mark",
|
||
"+1:teacher 独有(应加密)",
|
||
" 0:两者一致",
|
||
"−1:physics 独有(不应加密)",
|
||
], font_size=12, color=TEXT_MID, bullet_char="▸")
|
||
|
||
# Bottom takeaway
|
||
add_takeaway_strip(slide,
|
||
"GNN 的训练目标:学习 teacher 与 physics 之间的差异,使标记策略超越纯物理先验")
|
||
|
||
|
||
def slide_05_pipeline(prs):
|
||
"""Full pipeline overview."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"全流程概览:5 步流水线",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_BLUE)
|
||
|
||
# Pipeline steps as connected boxes
|
||
steps = [
|
||
("Step 1", "数据生成", "gen.py", "残差驱动 AMR\n保存逐步数据 + 标签", ACCENT_BLUE),
|
||
("Step 2", "数据校验", "check.py", "字段完整性\n统计趋势\n空间可视化", ACCENT_GREEN),
|
||
("Step 3", "训练", "train.py", "features + physics_score\n→ GNN → sigmoid\n→ 二分类", ACCENT_RED),
|
||
("Step 4", "评估", "test/eval.py", "离线指标 + Rollout\n三种方法对比", ACCENT_ORANGE),
|
||
("Step 5", "可视化", "viz.py", "端到端 AMR\n单步对比", RGBColor(0x9C, 0x27, 0xB0)),
|
||
]
|
||
|
||
box_w = Inches(2.2)
|
||
box_h = Inches(3.2)
|
||
x_start = Inches(0.5)
|
||
y_top = Inches(1.6)
|
||
gap = Inches(0.3)
|
||
|
||
for i, (step_num, title, script, desc, color) in enumerate(steps):
|
||
x = x_start + i * (box_w + gap)
|
||
|
||
# Step number circle
|
||
circle = slide.shapes.add_shape(MSO_SHAPE.OVAL, x + Inches(0.8), y_top, Inches(0.6), Inches(0.6))
|
||
circle.fill.solid()
|
||
circle.fill.fore_color.rgb = color
|
||
circle.line.fill.background()
|
||
tf = circle.text_frame
|
||
tf.paragraphs[0].text = str(i + 1)
|
||
tf.paragraphs[0].font.size = Pt(18)
|
||
tf.paragraphs[0].font.color.rgb = WHITE
|
||
tf.paragraphs[0].font.bold = True
|
||
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
|
||
tf.word_wrap = False
|
||
|
||
# Title
|
||
add_text_box(slide, x, y_top + Inches(0.7), box_w, Inches(0.4),
|
||
title, font_size=16, color=color, bold=True, align=PP_ALIGN.CENTER)
|
||
|
||
# Script name
|
||
add_text_box(slide, x, y_top + Inches(1.1), box_w, Inches(0.3),
|
||
script, font_size=10, color=TEXT_LIGHT, align=PP_ALIGN.CENTER,
|
||
font_name=FONT_EN)
|
||
|
||
# Description
|
||
add_text_box(slide, x + Inches(0.1), y_top + Inches(1.5), box_w - Inches(0.2), Inches(1.8),
|
||
desc, font_size=11, color=TEXT_MID, align=PP_ALIGN.CENTER)
|
||
|
||
# Arrow between steps
|
||
if i < len(steps) - 1:
|
||
arrow_x = x + box_w + Inches(0.05)
|
||
arrow_y = y_top + Inches(0.25)
|
||
add_text_box(slide, arrow_x, arrow_y, Inches(0.2), Inches(0.3),
|
||
"→", font_size=20, color=TEXT_LIGHT, align=PP_ALIGN.CENTER)
|
||
|
||
# Bottom: key data flow
|
||
box = add_shape_fill(slide, Inches(0.5), Inches(5.3), Inches(12.3), Inches(1.5),
|
||
RGBColor(0xF5, 0xF5, 0xF5))
|
||
add_text_box(slide, Inches(0.7), Inches(5.4), Inches(11.8), Inches(0.4),
|
||
"数据流", font_size=14, color=ACCENT_BLUE, bold=True)
|
||
add_text_box(slide, Inches(0.7), Inches(5.85), Inches(11.8), Inches(0.8),
|
||
"PDE 参数采样 → 初始网格 → AMR 循环(FEM solve → η → marks → save → refine)→ "
|
||
"训练数据 (.npz) → GNN 训练 → checkpoint → 离线推理 / Rollout 评估 → 可视化",
|
||
font_size=12, color=TEXT_MID)
|
||
|
||
|
||
def slide_06_data(prs):
|
||
"""Data generation and marking strategy."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"Step 1:数据生成与标记策略",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_BLUE)
|
||
|
||
# Left: data generation flow
|
||
add_text_box(slide, Inches(0.6), Inches(1.5), Inches(5.5), Inches(0.5),
|
||
"残差驱动 AMR 循环", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(0.6), Inches(2.1), Inches(5.5), Inches(2.5), [
|
||
"100 个 PDE 样本,统一初始网格 32×32",
|
||
"每步:FEM solve → 残差 η → 三种标记 → 保存 → 加密",
|
||
"每步保存 .npz:15-dim 特征 + 边索引 + 标记",
|
||
"共 1236 个 step 文件,平均 ~12 步/样本",
|
||
"加密时使用安全过滤:面积 + 反向 Dörfler",
|
||
], font_size=13)
|
||
|
||
# Right: residual estimator
|
||
add_text_box(slide, Inches(6.8), Inches(1.5), Inches(5.8), Inches(0.5),
|
||
"残差估计器 η(Teacher Signal)",
|
||
font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(6.8), Inches(2.1), Inches(5.8), Inches(2.5), [
|
||
"η = √(r_int² + r_jump² + r_sbc²)",
|
||
"r_int:内部残差(PDE 残差 × h/k)",
|
||
"r_jump:梯度跳变(相邻单元边界)",
|
||
"r_sbc:Sommerfeld BC 边界残差",
|
||
"使用 k₀ 归一化,介质内短波残差自然放大",
|
||
], font_size=13)
|
||
|
||
# Bottom: data check visualization
|
||
add_image_safe(slide, OUTLOOK / "data_correction_check" / "sample0011_step002.png",
|
||
Inches(0.6), Inches(4.6), Inches(12.1), Inches(2.4))
|
||
add_source_label(slide, "Source: check_correction_data.py — 4 面板空间可视化")
|
||
|
||
|
||
def slide_07_features(prs):
|
||
"""Feature engineering."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"Step 1:15 维特征工程",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_BLUE)
|
||
|
||
# Feature table
|
||
features = [
|
||
("0, 1", "x, y", "单元中点坐标"),
|
||
("2", "area", "单元面积"),
|
||
("3", "dist_to_center", "到圆柱中心距离"),
|
||
("4", "signed_dist", "dist − radius(负值=介质内)"),
|
||
("5", "inside", "是否在圆柱内 (0/1)"),
|
||
("6", "k", "波数(全局常量)"),
|
||
("7", "eps_r", "介电常数(全局常量)"),
|
||
("8", "radius", "半径(全局常量)"),
|
||
("9, 10", "cx, cy", "圆柱中心(全局常量)"),
|
||
("11", "k_h", "k × √area(网格-波数耦合)"),
|
||
("12", "k_eps_h", "k × √eps_r × √area(介质内分辨率)"),
|
||
("13", "sin(k·x)", "入射波相位 sin 分量"),
|
||
("14", "cos(k·x)", "入射波相位 cos 分量"),
|
||
]
|
||
|
||
# Table header
|
||
y_start = Inches(1.5)
|
||
row_h = Inches(0.32)
|
||
col_widths = [Inches(0.8), Inches(2.0), Inches(5.5)]
|
||
x_start = Inches(0.6)
|
||
|
||
# Header
|
||
header_bg = add_shape_fill(slide, x_start, y_start,
|
||
sum(w for w in col_widths), row_h, ACCENT_BLUE)
|
||
headers = ["维度", "特征名", "说明"]
|
||
x = x_start
|
||
for j, (hdr, w) in enumerate(zip(headers, col_widths)):
|
||
add_text_box(slide, x, y_start + Inches(0.02), w, row_h,
|
||
hdr, font_size=11, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
|
||
x += w
|
||
|
||
# Rows
|
||
for i, (dim, name, desc) in enumerate(features):
|
||
y = y_start + (i + 1) * row_h
|
||
bg_color = RGBColor(0xF8, 0xF9, 0xFA) if i % 2 == 0 else WHITE
|
||
add_shape_fill(slide, x_start, y, sum(w for w in col_widths), row_h, bg_color)
|
||
|
||
x = x_start
|
||
vals = [dim, name, desc]
|
||
for j, (val, w) in enumerate(zip(vals, col_widths)):
|
||
font_name = FONT_EN if j < 2 else FONT_CN
|
||
add_text_box(slide, x, y + Inches(0.02), w, row_h,
|
||
val, font_size=10, color=TEXT_DARK,
|
||
align=PP_ALIGN.CENTER if j < 2 else PP_ALIGN.LEFT,
|
||
font_name=font_name)
|
||
x += w
|
||
|
||
# Right: physics score
|
||
box_x = Inches(9.2)
|
||
box = add_shape_fill(slide, box_x, Inches(1.5), Inches(3.5), Inches(3.5),
|
||
RGBColor(0xE8, 0xF0, 0xFE))
|
||
add_text_box(slide, box_x + Inches(0.15), Inches(1.6), Inches(3.2), Inches(0.4),
|
||
"Physics Score(第 16 维输入)", font_size=13, color=ACCENT_BLUE, bold=True)
|
||
add_text_box(slide, box_x + Inches(0.15), Inches(2.1), Inches(3.2), Inches(2.5),
|
||
"λ_eff = 2π/(k·√ε_r) 介质内\n"
|
||
" = 2π/k 介质外\n\n"
|
||
"h = √(2·area) 特征长度\n\n"
|
||
"physics_score = h / λ_eff\n\n"
|
||
"> 1 表示分辨率不足\n"
|
||
"→ 应优先加密",
|
||
font_size=11, color=TEXT_MID, font_name=FONT_EN)
|
||
|
||
# Bottom: normalization
|
||
add_takeaway_strip(slide,
|
||
"训练时 15-dim 特征 + physics_score = 16-dim 输入,z-score 归一化")
|
||
|
||
|
||
def slide_08_gnn(prs):
|
||
"""GNN architecture."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"Step 3:CorrectionGNN 架构",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_RED)
|
||
|
||
# Architecture diagram as text boxes
|
||
# Embedding layer
|
||
add_text_box(slide, Inches(0.6), Inches(1.5), Inches(5.5), Inches(0.5),
|
||
"模型结构", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
arch_lines = [
|
||
"CorrectionGNN(基于 DensityGNN 骨干)",
|
||
"├── node_embedding: Linear(16 → latent_dim)",
|
||
"├── edge_embedding: Linear(16 → latent_dim)",
|
||
"├── mp_steps × 3:",
|
||
"│ ├── EdgeModule: MLP([src|dst|edge]) → latent_dim",
|
||
"│ ├── NodeModule: MLP([node|scatter_mean]) → latent_dim",
|
||
"│ └── LayerNorm + Residual",
|
||
"├── GlobalVirtualNode: mean_pool → attn_gate → broadcast",
|
||
"└── head: Linear(64→64) → ReLU → Linear(64→1)",
|
||
]
|
||
|
||
y = Inches(2.1)
|
||
for line in arch_lines:
|
||
add_text_box(slide, Inches(0.6), y, Inches(5.5), Inches(0.3),
|
||
line, font_size=11, color=TEXT_DARK, font_name="Consolas")
|
||
y += Inches(0.28)
|
||
|
||
# Right: key design choices
|
||
add_text_box(slide, Inches(6.8), Inches(1.5), Inches(5.8), Inches(0.5),
|
||
"关键设计", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
designs = [
|
||
("消息传递", "每步含边更新 + 节点更新,2 层 MLP + LayerNorm + 残差"),
|
||
("GVN", "mean pooling → attention-gated broadcast,scale 可学习"),
|
||
("边特征", "|x[src] − x[dst]|(16 维绝对差),输入和 latent 各计算一次"),
|
||
("边丢弃", "训练时 edge_dropout=0.1,推理时 0.0"),
|
||
("输出头", "单 logit → BCEWithLogitsLoss 训练"),
|
||
]
|
||
|
||
y = Inches(2.1)
|
||
for title, desc in designs:
|
||
add_text_box(slide, Inches(6.8), y, Inches(5.8), Inches(0.3),
|
||
f"▸ {title}", font_size=13, color=ACCENT_BLUE, bold=True)
|
||
add_text_box(slide, Inches(7.1), y + Inches(0.32), Inches(5.5), Inches(0.4),
|
||
desc, font_size=11, color=TEXT_MID)
|
||
y += Inches(0.8)
|
||
|
||
# Bottom: training config table
|
||
box = add_shape_fill(slide, Inches(0.6), Inches(5.0), Inches(12.1), Inches(2.0),
|
||
RGBColor(0xF5, 0xF5, 0xF5))
|
||
add_text_box(slide, Inches(0.8), Inches(5.1), Inches(11.5), Inches(0.4),
|
||
"训练配置", font_size=14, color=ACCENT_RED, bold=True)
|
||
|
||
configs = [
|
||
"latent_dim=64", "mp_steps=3", "head_hidden=64", "edge_dropout=0.1",
|
||
"lr=1e-3", "weight_decay=1e-4", "batch_size=1", "epochs=100",
|
||
"optimizer=Adam", "scheduler=ReduceLROnPlateau", "AMP (GPU)",
|
||
]
|
||
add_text_box(slide, Inches(0.8), Inches(5.5), Inches(11.5), Inches(0.4),
|
||
" | ".join(configs), font_size=10, color=TEXT_MID,
|
||
font_name=FONT_EN)
|
||
|
||
# Loss function
|
||
add_text_box(slide, Inches(0.8), Inches(6.0), Inches(11.5), Inches(0.8),
|
||
"损失函数:BCEWithLogitsLoss + per-graph pos_weight = neg_count/pos_count,"
|
||
"解决正负样本不平衡。向量化实现,无 Python 循环。",
|
||
font_size=12, color=TEXT_MID)
|
||
|
||
|
||
def slide_09_training(prs):
|
||
"""Training results."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"Step 3:训练结果 — AUC 0.950,top-k 远超物理先验",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_GREEN)
|
||
|
||
# Key metrics as big numbers
|
||
metrics = [
|
||
("AUC", "0.950", "二分类判别能力"),
|
||
("GNN top-k", "46.6%", "与 η 标记的重叠率"),
|
||
("Physics top-k", "15.3%", "物理先验基线"),
|
||
("GNN/Physics", "3.0×", "GNN 优势倍数"),
|
||
]
|
||
|
||
box_w = Inches(2.8)
|
||
box_h = Inches(1.8)
|
||
x_start = Inches(0.6)
|
||
y_top = Inches(1.6)
|
||
gap = Inches(0.25)
|
||
|
||
for i, (label, value, desc) in enumerate(metrics):
|
||
x = x_start + i * (box_w + gap)
|
||
color = ACCENT_BLUE if i < 2 else (ACCENT_GREEN if i == 3 else TEXT_MID)
|
||
bg = RGBColor(0xE8, 0xF0, 0xFE) if i < 2 else (
|
||
RGBColor(0xE8, 0xF5, 0xE9) if i == 3 else RGBColor(0xF5, 0xF5, 0xF5))
|
||
|
||
add_shape_fill(slide, x, y_top, box_w, box_h, bg)
|
||
add_text_box(slide, x, y_top + Inches(0.15), box_w, Inches(0.35),
|
||
label, font_size=13, color=TEXT_LIGHT, align=PP_ALIGN.CENTER)
|
||
add_text_box(slide, x, y_top + Inches(0.5), box_w, Inches(0.8),
|
||
value, font_size=32, color=color, bold=True, align=PP_ALIGN.CENTER,
|
||
font_name=FONT_EN)
|
||
add_text_box(slide, x, y_top + Inches(1.3), box_w, Inches(0.35),
|
||
desc, font_size=10, color=TEXT_LIGHT, align=PP_ALIGN.CENTER)
|
||
|
||
# Training curve placeholder
|
||
add_text_box(slide, Inches(0.6), Inches(3.8), Inches(5.5), Inches(0.5),
|
||
"训练过程", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(0.6), Inches(4.3), Inches(5.5), Inches(2.5), [
|
||
"100 epochs,最佳 epoch=40",
|
||
"train_loss: 0.435 → val_loss: 0.533",
|
||
"val_topk_overlap: 0.466 (vs physics 0.153)",
|
||
"GNN 在验证集上 100% 样本优于 physics",
|
||
"使用 AMP 混合精度加速 GPU 训练",
|
||
], font_size=13)
|
||
|
||
# Right: what this means
|
||
add_text_box(slide, Inches(6.8), Inches(3.8), Inches(5.8), Inches(0.5),
|
||
"结果解读", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(6.8), Inches(4.3), Inches(5.8), Inches(2.5), [
|
||
"AUC=0.95 表明 GNN 能很好地区分应加密/不应加密单元",
|
||
"top-k 46.6% 意味着 GNN 预测的 top-k 中近半与 η 一致",
|
||
"物理先验仅 15.3% → GNN 学到了大量额外信息",
|
||
"GNN 从几何+波场相位中捕捉了 η 的关键模式",
|
||
], font_size=13)
|
||
|
||
add_takeaway_strip(slide,
|
||
"GNN 不仅记住了 η 的模式,还学到了从几何/物理特征推断加密优先级的能力")
|
||
|
||
|
||
def slide_10_eval(prs):
|
||
"""Offline evaluation."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"Step 4a:离线评估 — GNN vs Physics 对比",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_ORANGE)
|
||
|
||
# Test visualization image
|
||
add_image_safe(slide, OUTLOOK / "result" / "correction" / "vis" / "sample0000_step004_vis.png",
|
||
Inches(0.6), Inches(1.5), Inches(12.1), Inches(3.5))
|
||
add_source_label(slide, "Source: test_correction.py — 验证集逐图可视化 (2×2: teacher/GNN/physics/TP·FP·FN·TN)")
|
||
|
||
# Bottom: what the panels show
|
||
add_text_box(slide, Inches(0.6), Inches(5.2), Inches(5.5), Inches(0.5),
|
||
"评估指标", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(0.6), Inches(5.7), Inches(5.5), Inches(1.5), [
|
||
"top-k overlap:GNN/probability top-k 与 teacher_mark 交集",
|
||
"AUC:ROC-AUC(GNN vs physics baseline)",
|
||
"gnn_beats_physics_ratio:GNN 优于 physics 的样本比例",
|
||
], font_size=12)
|
||
|
||
add_text_box(slide, Inches(6.8), Inches(5.2), Inches(5.8), Inches(0.5),
|
||
"解读", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(6.8), Inches(5.7), Inches(5.8), Inches(1.5), [
|
||
"左上:True teacher_mark(η top-3% 标记)",
|
||
"右上:GNN 预测(top-k selection)",
|
||
"左下:Physics baseline(h/λ_eff top-k)",
|
||
"右下:TP/FP/FN/TN 误差分类图",
|
||
], font_size=12)
|
||
|
||
|
||
def slide_11_rollout(prs):
|
||
"""Rollout evaluation results."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"Step 4b:Rollout 评估 — 迭代加密到目标单元数",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_ORANGE)
|
||
|
||
# Rollout curve
|
||
add_image_safe(slide, OUTLOOK / "result" / "correction" / "rollout" / "aw_rel_vs_elements.png",
|
||
Inches(0.6), Inches(1.5), Inches(7), Inches(4.0))
|
||
add_source_label(slide, "Source: eval_correction.py — aw_rel vs actual elements")
|
||
|
||
# Right: three methods comparison
|
||
add_text_box(slide, Inches(8.0), Inches(1.5), Inches(4.8), Inches(0.5),
|
||
"三种评估方法", font_size=16, color=TEXT_MID, bold=True)
|
||
|
||
methods_data = [
|
||
("physics", "h/λ_eff", "纯物理先验基线"),
|
||
("neural", "GNN sigmoid", "纯 GNN 概率"),
|
||
("hybrid", "α·z(physics)+β·z(neural)", "z-score 混合"),
|
||
]
|
||
|
||
y = Inches(2.1)
|
||
for method, formula, desc in methods_data:
|
||
add_text_box(slide, Inches(8.0), y, Inches(4.8), Inches(0.3),
|
||
f"▸ {method}: {formula}", font_size=12, color=ACCENT_BLUE, bold=True)
|
||
add_text_box(slide, Inches(8.3), y + Inches(0.3), Inches(4.5), Inches(0.3),
|
||
desc, font_size=11, color=TEXT_MID)
|
||
y += Inches(0.7)
|
||
|
||
# Bottom: results table
|
||
box = add_shape_fill(slide, Inches(0.6), Inches(5.6), Inches(12.1), Inches(1.5),
|
||
RGBColor(0xF5, 0xF5, 0xF5))
|
||
add_text_box(slide, Inches(0.8), Inches(5.7), Inches(11.5), Inches(0.4),
|
||
"Rollout 结果(aw_rel,面积加权相对误差)",
|
||
font_size=13, color=ACCENT_ORANGE, bold=True)
|
||
|
||
add_text_box(slide, Inches(0.8), Inches(6.1), Inches(11.5), Inches(0.9),
|
||
"target=4000: physics 14.1% → neural 14.0% → hybrid 13.8% (↓1.9%)\n"
|
||
"target=8000: physics 13.3% → neural 13.0% → hybrid 13.0% (↓1.8%)\n"
|
||
"target=12000: physics 12.9% → neural 12.8% → hybrid 12.8% (↓0.2%)",
|
||
font_size=11, color=TEXT_MID, font_name=FONT_EN)
|
||
|
||
|
||
def slide_12_viz(prs):
|
||
"""Visualization: AMR results."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"Step 5:可视化 — 端到端 AMR 与单步对比",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), RGBColor(0x9C, 0x27, 0xB0))
|
||
|
||
# AMR overview
|
||
add_image_safe(slide, OUTLOOK / "result" / "correction" / "viz" / "amr_overview.png",
|
||
Inches(0.6), Inches(1.5), Inches(12.1), Inches(2.8))
|
||
add_source_label(slide, "Source: viz_correction.py — GNN 驱动 AMR 全流程:网格 + 场图总览")
|
||
|
||
# Bottom: two modes
|
||
add_text_box(slide, Inches(0.6), Inches(4.6), Inches(5.5), Inches(0.5),
|
||
"模式 1:端到端 AMR", font_size=16, color=RGBColor(0x9C, 0x27, 0xB0), bold=True)
|
||
add_bullet_list(slide, Inches(0.6), Inches(5.1), Inches(5.5), Inches(1.8), [
|
||
"GNN 驱动完整细化循环",
|
||
"每步做 FEM solve,展示网格和场的演变",
|
||
"输出:amr_overview.png + amr_steps/stepXX.png",
|
||
"支持自定义物理参数(--k, --eps-r 等)",
|
||
], font_size=12)
|
||
|
||
add_text_box(slide, Inches(6.8), Inches(4.6), Inches(5.8), Inches(0.5),
|
||
"模式 2:单步对比", font_size=16, color=RGBColor(0x9C, 0x27, 0xB0), bold=True)
|
||
add_bullet_list(slide, Inches(6.8), Inches(5.1), Inches(5.8), Inches(1.8), [
|
||
"重建指定 AMR 步的 mesh",
|
||
"对比 GNN / teacher / physics 三种标记",
|
||
"2×2 对比图 + 3 面板场图 + 误差图",
|
||
"支持 Physics vs GNN vs Eta 三方对比",
|
||
], font_size=12)
|
||
|
||
|
||
def slide_13_viz_detail(prs):
|
||
"""Visualization detail: marks comparison."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"单步对比:GNN vs Teacher vs Physics 标记",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), RGBColor(0x9C, 0x27, 0xB0))
|
||
|
||
# Marks comparison image
|
||
add_image_safe(slide, OUTLOOK / "result" / "correction" / "viz" / "marks_sample0005_step010.png",
|
||
Inches(0.6), Inches(1.5), Inches(7.5), Inches(5.5))
|
||
add_source_label(slide, "Source: viz_correction.py — marks_sample0005_step010.png")
|
||
|
||
# Right: interpretation
|
||
add_text_box(slide, Inches(8.5), Inches(1.5), Inches(4.3), Inches(0.5),
|
||
"解读", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(8.5), Inches(2.1), Inches(4.3), Inches(4.5), [
|
||
"左上:True teacher_mark(η top-3%)",
|
||
"右上:GNN 预测标记",
|
||
"左下:Physics baseline 标记",
|
||
"右下:误差分类",
|
||
"",
|
||
"TP(绿):GNN 正确预测的加密单元",
|
||
"FP(蓝):GNN 误判为应加密",
|
||
"FN(红):GNN 漏掉的应加密单元",
|
||
"TN(灰):正确预测的非加密单元",
|
||
], font_size=12)
|
||
|
||
add_takeaway_strip(slide,
|
||
"GNN 能有效捕捉散射体界面和波前附近的高残差区域")
|
||
|
||
|
||
def slide_14_robustness(prs):
|
||
"""Validation and robustness."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"验证与稳健性:数据质量保障",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_GREEN)
|
||
|
||
# Five checks
|
||
add_text_box(slide, Inches(0.6), Inches(1.5), Inches(5.5), Inches(0.5),
|
||
"数据校验 5 项检查", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
checks = [
|
||
("1", "字段完整性", "每个 .npz 包含所有必需字段、维度/类型正确"),
|
||
("2", "全局统计", "teacher/physics mark 比例、IoU、correction 正负比"),
|
||
("3", "逐步趋势", "correction 信号随 AMR 步数合理衰减"),
|
||
("4", "空间可视化", "随机 5 样本 4 面板图(score/eta/mark/correction)"),
|
||
("5", "最终判定", "PASS / WARNING / FAIL"),
|
||
]
|
||
|
||
y = Inches(2.1)
|
||
for num, title, desc in checks:
|
||
circle = slide.shapes.add_shape(MSO_SHAPE.OVAL, Inches(0.6), y, Inches(0.4), Inches(0.4))
|
||
circle.fill.solid()
|
||
circle.fill.fore_color.rgb = ACCENT_GREEN
|
||
circle.line.fill.background()
|
||
tf = circle.text_frame
|
||
tf.paragraphs[0].text = num
|
||
tf.paragraphs[0].font.size = Pt(12)
|
||
tf.paragraphs[0].font.color.rgb = WHITE
|
||
tf.paragraphs[0].font.bold = True
|
||
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
|
||
|
||
add_text_box(slide, Inches(1.1), y, Inches(1.5), Inches(0.35),
|
||
title, font_size=13, color=TEXT_DARK, bold=True)
|
||
add_text_box(slide, Inches(2.6), y, Inches(4), Inches(0.35),
|
||
desc, font_size=11, color=TEXT_MID)
|
||
y += Inches(0.5)
|
||
|
||
# Right: comparison visualization
|
||
add_text_box(slide, Inches(6.8), Inches(1.5), Inches(5.8), Inches(0.5),
|
||
"三方对比可视化", font_size=18, color=TEXT_MID, bold=True)
|
||
|
||
add_image_safe(slide, OUTLOOK / "result" / "correction" / "viz" / "compare_sample0005_step010.png",
|
||
Inches(6.8), Inches(2.1), Inches(5.8), Inches(3.0))
|
||
add_source_label(slide, "Source: viz_correction.py — Physics vs GNN vs Eta (2×3 field + error)")
|
||
|
||
# Bottom: robustness
|
||
box = add_shape_fill(slide, Inches(0.6), Inches(5.5), Inches(12.1), Inches(1.3),
|
||
RGBColor(0xE8, 0xF5, 0xE9))
|
||
add_text_box(slide, Inches(0.8), Inches(5.6), Inches(11.5), Inches(0.4),
|
||
"稳健性保障", font_size=14, color=ACCENT_GREEN, bold=True)
|
||
add_bullet_list(slide, Inches(0.8), Inches(6.0), Inches(11.5), Inches(0.7), [
|
||
"训练/验证按 sample ID 划分(无数据泄漏) | 统一初始网格消除网格差异 | "
|
||
"安全过滤防止退化单元 | 多目标单元数评估(2k/4k/8k/12k)",
|
||
], font_size=11, color=TEXT_MID)
|
||
|
||
|
||
def slide_15_innovation(prs):
|
||
"""Innovation and value."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"创新点与可复用价值",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_BLUE)
|
||
|
||
innovations = [
|
||
("Correction Learning 框架",
|
||
"不直接预测 η,而是学习 teacher 与 physics 之间的差异。"
|
||
"这使得 GNN 专注于物理先验无法捕捉的信息,而非重复已有知识。"),
|
||
("无需 FEM 的推理",
|
||
"推理时只需几何/物理特征(15-dim + physics_score),"
|
||
"无需 FEM solve,可离线部署,计算代价从 O(N_steps × FEM) 降至 O(N_steps × GNN forward)。"),
|
||
("三标记对比设计",
|
||
"teacher_mark / physics_mark / correction_label 的三重对比,"
|
||
"清晰量化了 GNN 相对于物理先验的增量价值。"),
|
||
("安全过滤机制",
|
||
"面积过滤 + 反向 Dörfler 过滤的两层安全机制,"
|
||
"防止对退化单元或误差极小单元做无意义加密。"),
|
||
("统一评估体系",
|
||
"离线指标(top-k/AUC)+ Rollout 评估(三种方法 × 多目标单元数)"
|
||
"+ 端到端可视化,全方位验证 GNN 的有效性。"),
|
||
]
|
||
|
||
y = Inches(1.5)
|
||
for i, (title, desc) in enumerate(innovations):
|
||
# Number
|
||
add_text_box(slide, Inches(0.6), y, Inches(0.5), Inches(0.4),
|
||
f"{i+1}.", font_size=16, color=ACCENT_BLUE, bold=True)
|
||
add_text_box(slide, Inches(1.1), y, Inches(4.5), Inches(0.4),
|
||
title, font_size=15, color=TEXT_DARK, bold=True)
|
||
add_text_box(slide, Inches(1.1), y + Inches(0.4), Inches(11), Inches(0.5),
|
||
desc, font_size=12, color=TEXT_MID)
|
||
y += Inches(1.0)
|
||
|
||
add_takeaway_strip(slide,
|
||
"Correction learning 框架可推广到其他 PDE 问题和 AMR 策略")
|
||
|
||
|
||
def slide_16_limitations(prs):
|
||
"""Limitations and open questions."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.4), Inches(12), Inches(0.7),
|
||
"局限性与未解决问题",
|
||
font_size=28, color=TEXT_DARK, bold=True)
|
||
|
||
add_shape_fill(slide, Inches(0.6), Inches(1.1), Inches(2), Inches(0.04), ACCENT_ORANGE)
|
||
|
||
# Current limitations
|
||
add_text_box(slide, Inches(0.6), Inches(1.5), Inches(5.5), Inches(0.5),
|
||
"当前局限", font_size=18, color=ACCENT_RED, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(0.6), Inches(2.1), Inches(5.5), Inches(3.5), [
|
||
"仅验证 2D Helmholtz 介质圆柱散射问题",
|
||
"参数空间有限:k∈[3,15],ε_r∈[2,8]",
|
||
"统一初始网格(32×32),未测试非均匀初始网格",
|
||
"Rollout 改善幅度有限(~2%),高预算下差异缩小",
|
||
"correction_label 依赖 η 的质量(teacher 信号的上限)",
|
||
"batch_size=1 的图级训练,大规模数据效率低",
|
||
], font_size=13)
|
||
|
||
# Open questions
|
||
add_text_box(slide, Inches(6.8), Inches(1.5), Inches(5.8), Inches(0.5),
|
||
"开放问题", font_size=18, color=ACCENT_BLUE, bold=True)
|
||
|
||
add_bullet_list(slide, Inches(6.8), Inches(2.1), Inches(5.8), Inches(3.5), [
|
||
"能否推广到 3D 问题和其他 PDE(Maxwell, elasticity)?",
|
||
"如何处理更复杂的散射体(多体、非圆柱)?",
|
||
"是否可以用 RL 替代 supervised correction learning?",
|
||
"如何自适应调整 mark_fraction?",
|
||
"GNN 推理速度 vs FEM solve 的实际加速比?",
|
||
"能否与 hp-AMR 或 spectral 方法结合?",
|
||
], font_size=13)
|
||
|
||
# Bottom: boundary conditions
|
||
box = add_shape_fill(slide, Inches(0.6), Inches(5.5), Inches(12.1), Inches(1.3),
|
||
RGBColor(0xFE, 0xF3, 0xE2))
|
||
add_text_box(slide, Inches(0.8), Inches(5.6), Inches(11.5), Inches(0.4),
|
||
"应用边界", font_size=14, color=ACCENT_ORANGE, bold=True)
|
||
add_text_box(slide, Inches(0.8), Inches(6.0), Inches(11.5), Inches(0.7),
|
||
"本方法的核心假设:(1) 物理先验 physics_score 提供了合理的基线;"
|
||
"(2) 残差估计器 η 是可信的 teacher signal;"
|
||
"(3) 网格拓扑变化可以通过图结构捕捉。"
|
||
"当这些假设不成立时(如高频率问题、复杂几何),方法的有效性需要重新验证。",
|
||
font_size=12, color=TEXT_MID)
|
||
|
||
|
||
def slide_17_summary(prs):
|
||
"""Summary."""
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
set_slide_bg(slide, WHITE)
|
||
|
||
# Top accent bar
|
||
add_shape_fill(slide, Inches(0), Inches(0), SLIDE_W, Inches(0.08), ACCENT_BLUE)
|
||
|
||
add_text_box(slide, Inches(0.6), Inches(0.6), Inches(12), Inches(0.7),
|
||
"总结",
|
||
font_size=32, color=TEXT_DARK, bold=True, align=PP_ALIGN.CENTER)
|
||
|
||
# Summary points as cards
|
||
cards = [
|
||
("问题", "2D Helmholtz 散射的 FEM 求解需要自适应网格加密,"
|
||
"但传统残差驱动 AMR 计算代价高"),
|
||
("方法", "Correction Learning:GNN 学习 teacher_mark 与 physics_mark 之间的差异,"
|
||
"推理时无需 FEM solve"),
|
||
("数据", "100 样本 × ~12 步 = 1236 个训练样本,15-dim 特征 + physics_score"),
|
||
("架构", "DensityGNN 骨干 + 二分类头,消息传递 + GVN + edge dropout"),
|
||
("结果", "AUC=0.950,top-k 46.6%(vs physics 15.3%),"
|
||
"Rollout aw_rel 改善 ~2%"),
|
||
("价值", "Correction learning 框架可推广到其他 PDE 问题,"
|
||
"推理代价从 FEM solve 降至 GNN forward"),
|
||
]
|
||
|
||
y = Inches(1.6)
|
||
for i, (label, text) in enumerate(cards):
|
||
bg = RGBColor(0xE8, 0xF0, 0xFE) if i % 2 == 0 else RGBColor(0xF5, 0xF5, 0xF5)
|
||
add_shape_fill(slide, Inches(1), y, Inches(11.3), Inches(0.7), bg)
|
||
add_text_box(slide, Inches(1.2), y + Inches(0.05), Inches(1.5), Inches(0.6),
|
||
label, font_size=14, color=ACCENT_BLUE, bold=True)
|
||
add_text_box(slide, Inches(2.8), y + Inches(0.05), Inches(9.2), Inches(0.6),
|
||
text, font_size=13, color=TEXT_MID)
|
||
y += Inches(0.8)
|
||
|
||
# Bottom
|
||
add_text_box(slide, Inches(1), Inches(6.5), Inches(11.3), Inches(0.5),
|
||
"谢谢!欢迎提问与讨论",
|
||
font_size=20, color=TEXT_DARK, bold=True, align=PP_ALIGN.CENTER)
|
||
|
||
|
||
# ============================================================================
|
||
# Main
|
||
# ============================================================================
|
||
|
||
|
||
def main():
|
||
prs = Presentation()
|
||
prs.slide_width = SLIDE_W
|
||
prs.slide_height = SLIDE_H
|
||
|
||
# Build all slides
|
||
slide_01_title(prs)
|
||
slide_02_background(prs)
|
||
slide_03_gap(prs)
|
||
slide_04_approach(prs)
|
||
slide_05_pipeline(prs)
|
||
slide_06_data(prs)
|
||
slide_07_features(prs)
|
||
slide_08_gnn(prs)
|
||
slide_09_training(prs)
|
||
slide_10_eval(prs)
|
||
slide_11_rollout(prs)
|
||
slide_12_viz(prs)
|
||
slide_13_viz_detail(prs)
|
||
slide_14_robustness(prs)
|
||
slide_15_innovation(prs)
|
||
slide_16_limitations(prs)
|
||
slide_17_summary(prs)
|
||
|
||
# Save
|
||
out_path = OUTPUT / "final_presentation_cn.pptx"
|
||
prs.save(str(out_path))
|
||
print(f"✓ Saved: {out_path}")
|
||
print(f" Slides: {len(prs.slides)}")
|
||
|
||
# Count images
|
||
img_count = 0
|
||
for slide in prs.slides:
|
||
for shape in slide.shapes:
|
||
if shape.shape_type == 13: # picture
|
||
img_count += 1
|
||
print(f" Images: {img_count}")
|
||
|
||
return out_path
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|