当前说明对应 v0.1.3。
epochpost 是一个基于 sdf_xarray 的 EPOCH PIC 通用后处理脚本。核心思路是:代码只维护一份,日常只修改 epochpost_input.json 来描述要画什么图。
仓库建议保持以下结构:
epochpost/
├── epochpost.py
├── epochpost_input.json
├── README.md
└── Assets/ # 可选:本地字体资源
└── fonts/ # 可放有授权的 Arial .ttf/.otf/.ttc
最常用的运行方式:
python epochpost.py epochpost_input.json如果配置文件就在当前目录且名称为 epochpost_input.json,也可以直接:
python epochpost.py本程序用于 EPOCH/PIC 的 sdf 文件后处理,底层使用 sdf_xarray。当前版本已经把程序逻辑和每个算例的输入参数分开:
epochpost.py:通用后处理模块,原则上不再为每个 run 修改。epochpost_input.json:每个算例自己的后处理输入,只描述“数据在哪里、要画什么图”。
建议以后每个 PIC 算例目录只保存一份自己的 epochpost_input.json。
例如:
run7/
├── input.deck
├── epochpost_input.json
├── Data/
│ ├── sourcexyspecies0001.sdf
│ ├── sourcexyspecies0002.sdf
│ ├── ...
│ ├── foilparticles0001.sdf
│ └── ...
└── Data/postprocess/ # 程序自动创建
而通用 Python 程序可以放在统一位置,例如:
~/python_lib/
├── epochpost.py
└── Assets/ # 可选
└── fonts/
└── Arial.ttf # 示例;仅放你有权使用的字体
这样不同 run 之间只需要复制和修改 epochpost_input.json。
在算例目录中:
python ~/python_lib/epochpost.py epochpost_input.json如果当前目录存在 epochpost_input.json,也可以直接:
python ~/python_lib/epochpost.py程序会依次尝试:
epochpost_input.jsonpost_input.json(兼容旧版本)input.json(兼容旧版本)
也可以传入任意其他 JSON 文件名:
python ~/python_lib/epochpost.py run7_post.jsonfrom epochpost import run_config
run_config("epochpost_input.json")这也是以后打包成 Python library 后推荐保留的接口。
如果你的数据就在 Data/,激光波长为 800 nm,那么 settings 可以完全省略,最简输入可以只有:
{
"tasks": [
{
"type": "spatial",
"name": "density_10fs",
"prefix": "sourcexyspecies",
"variable": {
"contains": ["Number_Density", "Electron_foil"]
},
"axes": ["x", "y"],
"cuts": {"z": 0.0},
"select": {"mode": "time", "time_fs": 10.0},
"animate": false,
"normalization": {
"kind": "density_nc"
},
"scale": "log"
}
]
}任务中的 enabled 如果省略,默认视为 true。
推荐配置为:
"settings": {
"data_dir": "Data",
"out_dir": null,
"laser_wavelength_nm": 800.0,
"space_unit": "nm",
"space_factor": 1000000000.0,
"time_unit": "fs",
"time_factor": 1000000000000000.0,
"plot": {
"format": "png",
"dpi": 300,
"fig_single": [3.4, 2.7],
"fig_double": [7.0, 4.8],
"font_family": "Arial",
"mathtext_fontset": "custom",
"font_label": 10,
"font_tick": 8,
"font_legend": 8,
"font_panel": 11,
"font_annotation": 8,
"line_width": 1.5,
"axis_width": 1.0,
"bbox_inches": "tight"
}
}这些值已经写成 epochpost.py 的默认值,因此如果你满意这些论文绘图规范,settings.plot 可以整个省略。
| 参数 | 含义 | 默认值 |
|---|---|---|
data_dir |
SDF 文件目录 | Data |
out_dir |
输出目录;null 时为 <data_dir>/postprocess |
null |
laser_wavelength_nm |
激光波长,用于计算临界密度 nc |
800 |
space_unit |
图上的空间单位标签 | nm |
space_factor |
SDF 中 m 转换到绘图单位的乘数 | 1e9 |
time_unit |
时间单位标签 | fs |
time_factor |
SDF 中 s 转换到绘图单位的乘数 | 1e15 |
plot.format |
静态图默认格式 | png |
plot.dpi |
位图输出分辨率 | 300 |
plot.fig_single |
单栏图尺寸,单位 inch | [3.4, 2.7] |
plot.fig_double |
双栏图尺寸,单位 inch | [7.0, 4.8] |
plot.font_family |
普通文本字体 | Arial |
plot.mathtext_fontset |
兼容字段;v0.1.2 中数学文本会自动跟随最终选中的普通字体 | custom |
plot.font_label |
坐标轴标签字号 | 10 |
plot.font_tick |
刻度字号 | 8 |
plot.font_legend |
图例字号 | 8 |
plot.font_panel |
标题/Panel 字号 | 11 |
plot.font_annotation |
普通 annotation 默认字号 | 8 |
plot.line_width |
普通曲线默认线宽 | 1.5 |
plot.axis_width |
坐标轴和 tick 线宽 | 1.0 |
plot.bbox_inches |
保存时边界裁切 | tight |
旧版本的:
"dpi": 180仍然兼容,但新版本推荐统一写到 settings.plot.dpi 中。
默认就是:
300 dpi PNG
因此 task 中完全不需要额外指定保存格式。
如果希望整个算例的静态图都输出成 PDF:
"plot": {
"format": "pdf",
"bbox_inches": "tight"
}PDF 是矢量输出,dpi 对其中的矢量元素不重要,但程序仍会保留该参数以兼容可能包含的 raster 元素。
默认普通字体为 Arial。程序启动时会先尝试注册 epochpost.py 同目录下的:
Assets/
Assets/fonts/
其中的 .ttf、.otf 和 .ttc 字体文件。字体选择优先级为:
settings.plot.font_family
→ Arial
→ Helvetica
→ DejaVu Sans
→ Matplotlib 默认 sans-serif
因此,没有 Assets 文件夹也不会报错。如果系统没有 Arial,程序会自动回退并打印提示。
如果你希望本地、集群和其他机器的论文图字体尽量一致,可以把自己有权使用的 Arial 字体放到:
Assets/fonts/
例如:
Assets/fonts/Arial.ttf
Assets/fonts/Arial Italic.ttf
Assets/fonts/Arial Bold.ttf
注意:Arial 属于受许可字体。公开 GitHub 仓库或 release 中不要直接分发无授权的 Arial 字体文件;可以只保留空的
Assets/fonts/和说明文档。
v0.1.2 的自动 colorbar label 会让单位字母保持普通正体;像 m^-3 中的指数只局部交给 Matplotlib mathtext 排版,从而避免依赖字体是否包含 Unicode 上标负号。
绝大多数情况下不用写 plot。如果某一张图需要双栏宽度:
{
"type": "xt",
"name": "foil_Jy_xt",
"plot": {
"figure_size": "double"
}
}支持:
"figure_size": "single""figure_size": "double"或者直接指定尺寸:
"figure_size": [5.0, 3.2]也可以只让某一张图输出 PDF:
"plot": {
"format": "pdf",
"figure_size": "single",
"bbox_inches": "tight"
}或者单独覆盖 dpi:
"plot": {
"dpi": 600
}多 panel 对比图默认使用双栏宽度 7.0 inch。如果需要强制指定总尺寸,可写:
"plot": {
"overview_figure_size": [7.0, 6.0]
}**重要:**相对路径以 epochpost_input.json 所在目录为基准,而不是以 Python 库文件所在目录为基准。
"select": {
"mode": "latest"
}"select": {
"mode": "number",
"number": 78
}"select": {
"mode": "number",
"number": [20, 40, 60, 78]
}JSON 不能直接写 np.arange(),因此程序提供等价写法:
"select": {
"mode": "number",
"number": {
"arange": [10, 79, 5]
}
}等价于 Python:
np.arange(10, 79, 5)即选择:
10, 15, 20, ..., 75
arange 的 stop 不包含在结果中,与 NumPy 完全一致。
"select": {
"mode": "time",
"time_fs": 10.0
}程序会自动寻找与 10 fs 最接近的 SDF 文件。
"select": {
"mode": "time",
"time_fs": [2.0, 4.0, 6.0, 8.0, 10.0]
}"select": {
"mode": "time",
"time_fs": {
"arange": [2.0, 10.1, 2.0]
}
}等价于:
np.arange(2.0, 10.1, 2.0)"time_fs": {
"linspace": [2.0, 10.0, 5]
}等价于:
np.linspace(2.0, 10.0, 5)结果为:
2, 4, 6, 8, 10 fs
linspace 包含终点。
"number": {
"range": [10, 79, 5]
}等价于:
range(10, 79, 5)只适合整数编号。
用于某一时刻或多个时刻的二维场、密度、电流等空间分布。
例如:
{
"type": "spatial",
"name": "foil_density_xy",
"prefix": "sourcexyspecies",
"variable": {
"contains": ["Number_Density", "Electron_foil"]
},
"axes": ["x", "y"],
"cuts": {
"z": 0.0
},
"select": {
"mode": "time",
"time_fs": [2, 4, 6, 8, 10]
},
"animate": false,
"xlim": [320, 760],
"ylim": [-750, 750],
"normalization": {
"kind": "density_nc"
},
"scale": "log",
"vmin": 0.01,
"vmax": 200,
"cmap": "magma",
"save_data": true
}v0.1.2 推荐让内置 normalization 自动生成 colorbar label;
density_nc、current_j0、momentum_mec通常只需要写kind,不必手动写label。
多个 time_fs 或多个 number 时,程序会:
- 每个时刻/编号分别保存一张 PNG;
- 再生成一张统一 colorbar 的多面板比较图;
save_data=true时把所有结果保存到一个压缩.npz中。
v0.1.2 的时刻标题统一使用:
time=12.34 fs
即实际时刻保留两位小数,并在数值与 fs 之间保留一个空格。
如果 task 写:
"title": null则不会显示 None,也不会自动把 task 名称作为标题。单图/子图只显示:
time=12.34 fs
如果显式设置:
"title": "Electron density"则显示:
Electron density, time=12.34 fs
多 panel 图在 title: null 时也不会额外显示总标题。
文件名仍使用适合文件系统的紧凑形式,例如
time=12.34fs;这里只改变图上的显示格式。
{
"type": "spatial",
"name": "density_animation",
"prefix": "sourcexyspecies",
"variable": {"contains": ["Number_Density", "Electron_foil"]},
"axes": ["x", "y"],
"cuts": {"z": 0.0},
"animate": true,
"time_range_fs": [0.0, null],
"stride": 5,
"max_frames": 100,
"fps": 12,
"normalization": {"kind": "density_nc"},
"scale": "log"
}其中:
time_range_fs: [0, null]:从 0 fs 到最后一个文件;stride: 5:每隔 5 个文件取一帧;max_frames:最多保留多少帧;fps:GIF 帧率。
适合从不同 SDF 文件中提取同一条空间线,例如:
J_y(x,t)J_x(x,t)- 密度
n_e(x,t) - 某个固定
y,z截面上的场随时间演化
示例:
{
"type": "xt",
"name": "foil_Jy_xt_y0",
"prefix": "sourcexyspecies",
"variable": {
"contains": ["Jy", "Electron_foil"]
},
"line_axis": "x",
"cuts": {
"y": 0.0,
"z": 0.0
},
"time_range_fs": [0.0, null],
"stride": 1,
"space_range": [320, 760],
"normalization": {
"kind": "current_j0"
},
"scale": "symlog",
"linthresh": 0.02,
"cmap": "RdBu_r",
"save_data": true
}xt 本身就是跨多个文件组成一张图,因此使用 time_range_fs + stride,不使用 select。
例如电子的 x-px:
{
"type": "particle_phase",
"name": "foil_x_px",
"prefix": "foilparticles",
"x_variable": {
"contains": ["Grid_Particles"],
"component": "x"
},
"y_variable": {
"contains": ["Particles_Px"]
},
"weight_variable": {
"contains": ["Particles_Weight"]
},
"x_normalization": {
"factor": 1000000000.0,
"label": "x (nm)"
},
"y_normalization": {
"kind": "momentum_mec"
},
"select": {
"mode": "number",
"number": [60, 65, 70, 75, 78]
},
"animate": false,
"xlim": [360, 720],
"ylim": [-20, 40],
"bins": [400, 400],
"scale": "log",
"save_data": true
}与 spatial 一样,particle_phase + animate=false 支持单个或多个 time_fs/number。
示例:
{
"type": "particle_xt",
"name": "foil_particle_xt",
"prefix": "foilparticles",
"position_variable": {
"contains": ["Grid_Particles"],
"component": "x"
},
"weight_variable": {
"contains": ["Particles_Weight"]
},
"position_normalization": {
"factor": 1000000000.0,
"label": "x (nm)"
},
"time_range_fs": [0.0, null],
"stride": 1,
"space_range": [360, 720],
"bins": 500,
"scale": "log",
"save_data": true
}如果已经知道完整变量名:
"variable": {
"name": "完整的_sdf_xarray_变量名"
}推荐通常使用:
"variable": {
"contains": ["Number_Density", "Electron_foil"]
}程序要求这些关键词最终只能唯一匹配一个变量。如果匹配到 0 个或多个,程序会打印候选变量并报错。
把:
"inspect": {
"enabled": true,
"prefix": "foilparticles",
"particle_file": true,
"select": {
"mode": "number",
"number": 78
}
}设置为 true 后运行程序。
程序会打印这个文件中的:
- 变量名称;
- dimensions;
- shape。
确认变量名之后,再把 inspect.enabled 改回 false。
对于粒子 SDF,必须:
"particle_file": true这样 sdf_xarray 会使用 keep_particles=True。
v0.1.2 的内置归一化不仅负责数值变换,也会根据实际匹配到的变量自动生成论文式 label。通常不再需要在每个 task 中手动填写 normalization.label。
自动 label 的共同排版规则是:
- 主物理量、单位字母使用当前选中的普通字体(默认 Arial);
- 下标只局部使用 Matplotlib mathtext 排版;
- 指数只局部使用 mathtext 上标排版;
- 不把整个 label 放进数学模式;
- 因此
m、A、V、T、kg、s等 SI 单位保持正体; - 不依赖字体是否提供 Unicode 上标负号,因此不同环境下更稳定;
x / y / z分量会根据已经选中的变量自动识别。
可以完全省略 normalization,或者写:
"normalization": {
"kind": "none"
}程序会根据实际匹配到的 EPOCH/sdf_xarray 变量自动生成简洁的物理量名称与 SI 单位,不再直接显示很长的 SDF 变量名。
典型结果:
| 实际变量 | 自动 label |
|---|---|
| electron number density | n_e (m^-3) |
| ion number density | n_i (m^-3) |
Current_Jx / Current_Jy / Current_Jz |
J_x (A m^-2) / J_y (A m^-2) / J_z (A m^-2) |
Electric_Field_Ex/Ey/Ez |
E_x (V m^-1) / E_y (V m^-1) / E_z (V m^-1) |
Magnetic_Field_Bx/By/Bz |
B_x (T) / B_y (T) / B_z (T) |
particle momentum Px/Py/Pz |
p_x (kg m s^-1) 等 |
这里表格为了 Markdown 可读性写成 m^-3;实际图片中 -3、-2、-1 会正确显示为上标,而单位字母保持正体。
例如:
{
"type": "spatial",
"name": "nxz",
"prefix": "sourcexzspecies",
"variable": {
"contains": ["Number_Density", "Electron_foil", "source_xz"]
},
"axes": ["x", "z"],
"cuts": {"y": 0.0},
"select": {"mode": "time", "time_fs": 10.0},
"normalization": {
"kind": "none"
},
"scale": "log",
"cmap": "gray",
"title": null
}colorbar 会自动得到类似:
n_e (m^-3)
其中指数在图片里为真正上标。
"normalization": {
"kind": "density_nc"
}数值对应:
n / nc
其中 nc 根据 laser_wavelength_nm 自动计算。
label 也会根据已经匹配到的粒子种类自动生成。例如 electron number density:
n_e / n_c
如果匹配的是 ion number density,则会相应使用 n_i。
推荐直接写:
"normalization": {
"kind": "density_nc"
}不必再额外写:
"label": "$n_e/n_c$""normalization": {
"kind": "current_j0"
}数值对应:
J / (e nc c)
程序会根据实际选中的电流分量自动生成 label:
J_x / (e n_c c)
J_y / (e n_c c)
J_z / (e n_c c)
例如变量选择:
"variable": {
"contains": ["Jy", "Electron_foil"]
}配合:
"normalization": {
"kind": "current_j0"
}会自动得到 J_y/(e n_c c) 风格的 colorbar label。
"y_normalization": {
"kind": "momentum_mec"
}数值对应:
p / (me c)
程序会自动识别粒子动量分量:
p_x / (m_e c)
p_y / (m_e c)
p_z / (m_e c)
因此相空间任务中通常可以直接写:
"y_variable": {
"contains": ["Particles_Px"]
},
"y_normalization": {
"kind": "momentum_mec"
}无需再手写 p_x/(m_e c)。
自动 label 是默认推荐方式,但仍然保留完整的手动接口。
只要显式设置:
"normalization": {
"kind": "current_j0",
"label": "My custom label"
}程序就会优先使用你写的 label。
优先级为:
手动 normalization.label
↓
内置 normalization 自动 label
↓
kind="none" 的物理量 + SI 单位自动 label
也就是说,已有旧 JSON 中的手动 label 不会失效。
例如 m → nm:
"x_normalization": {
"factor": 1000000000.0,
"label": "x (nm)"
}如果 kind: "none" 同时指定了非 1 的自定义 factor,程序不会擅自继续标原始 SI 单位,因为缩放后单位可能已经改变。
因此这种情况建议显式给出:
"label": "..."以保证单位正确。
自动 label 与 v0.1.2 的字体 fallback 配合工作:
Assets / Assets/fonts 中的指定字体
→ 系统 Arial
→ Helvetica
→ DejaVu Sans
→ Matplotlib 默认 sans-serif
没有 Assets 文件夹也不会影响 label 的数值逻辑或上下标排版。
对于 kind="none":
J_y (A m^-2)
单位字母 A、m 是普通正体,-2 只局部作为上标排版。
对于归一化量:
J_y / (e n_c c)
J、e、n、c 等主体同样跟随当前普通字体,下标只局部排版,因此和 kind="none" 使用的是同一套视觉规则。
cmap 直接使用 Matplotlib 的 colormap 名称,名称需要与 Matplotlib 支持的名称一致。
例如:
"cmap": "gray"或:
"cmap": "Greys"都是有效的;但:
"cmap": "Grey"不是有效名称,会由 Matplotlib 报错。因此推荐直接使用标准名称,例如 viridis、magma、gray、Greys、RdBu_r。
"scale": "linear"适合密度、粒子计数等正值:
"scale": "log"可指定:
"vmin": 0.01,
"vmax": 200适合正负电流、电场:
"scale": "symlog",
"linthresh": 0.02"scale": "linear",
"symmetric": true如果不写 vmin/vmax,程序使用 percentile 自动估计。默认:
"percentile": 99.5每个 task 独立输出到:
<out_dir>/<task.name>/
例如:
Data/postprocess/foil_density_xy/
可能包含:
.png:静态图;.gif:动画;.npz:save_data=true时保存的后处理数据。
多时刻/多编号任务会额外输出一张 multiple_time 或 multiple_number 对比图,并使用同一个 colorbar 范围,便于直接比较。
多时刻任务的单图文件名会包含实际时刻,例如:
density_time=12.34fs.png
图内标题则显示为:
time=12.34 fs
二者的空格规则不同是有意设计:图内符合物理量排版习惯,文件名保持紧凑。
以后不建议再复制很多不同版本的 Python 后处理脚本。建议:
统一程序:
~/python_lib/epochpost.py
每个算例:
run1/epochpost_input.json
run2/epochpost_input.json
run5/epochpost_input.json
run7/epochpost_input.json
...
每次后处理时只修改当前 run 的:
epochpost_input.json
如果一个 run 有很多不同分析,可以让 tasks 中同时包含多个任务:
"tasks": [
{"type": "spatial", "name": "density", "...": "..."},
{"type": "spatial", "name": "Jx", "...": "..."},
{"type": "xt", "name": "Jy_xt", "...": "..."},
{"type": "particle_phase", "name": "x_px", "...": "..."}
]这样一份 JSON 本身也相当于这个算例的“后处理记录”,以后回头看 run7 时可以直接知道当时画过什么、用了什么范围和归一化。
| type | 用途 | 单/多时刻 | 多编号 | 动图 |
|---|---|---|---|---|
spatial |
2D 空间分布 | ✓ | ✓ | ✓ |
xt |
空间-时间图 | 通过时间范围 | 不使用 | — |
particle_phase |
粒子相空间 | ✓ | ✓ | ✓ |
particle_xt |
粒子位置-时间图 | 通过时间范围 | 不使用 | — |
v0.1.2 已包含论文式默认绘图样式、字体 fallback、none/density_nc/current_j0/momentum_mec 统一的自动 colorbar label,以及统一的时间标题格式。这套结构已经适合作为后续 Python library 的基础:绘图算法集中在模块里,算例差异全部留在 JSON 配置中。
建议 GitHub 仓库只保存程序、示例输入和说明文档,不要提交大型 SDF 数据或后处理结果。
第一次上传一个新的本地仓库时,可以在 GitHub 新建一个空仓库 epochpost,然后在本地执行:
mkdir epochpost
cd epochpost
# 把这三个文件复制到当前目录:
# epochpost.py
# epochpost_input.json
# README.md
git init
git branch -M main
git add epochpost.py epochpost_input.json README.md
git commit -m "Initial release of epochpost"
git remote add origin https://github.com/YOUR_USERNAME/epochpost.git
git push -u origin main之后每次更新只需要:
git add epochpost.py epochpost_input.json README.md
git commit -m "Update epochpost"
git push对于实际 PIC 算例,建议把 Data/、*.sdf、postprocess/ 等大型数据加入 .gitignore,避免误上传模拟数据。
v0.1.2 - 2026 年 8 月 9 日
在上一版本的基础上,新增功能:colormap 自动加label;并且输出图片的格式尽可能满足论文要求,也可以用户自定义。
v0.1.3 - 2026年 8 月 13 日
bugfix: epochpost_input.json 文件中当同时写入多个 task并且均为 true 时,存在多个task 先后多次读取同一 sdf 文件的情况,会导致一些问题,v0.1.3 修正这一问题并通过了测试。
bugfix: json 文件中针对变量名的输入,当选择'contains'时,将'Ex','Ey','Ez'等作为整体进行变量名称匹配,避免匹配到多个变量。