
简介mx3tools 是一套面向微磁学仿真研究者的 Python 工具集合专为配合 Mumax3 运行与分析仿真而设计适合已掌握 Python 基础、正在开展自旋动力学或磁畴模拟的科研人员与研究生使用。资源包共 20 个文件以 12 个 py 脚本为核心辅以 ovf 数据样例、yaml 与 cfg 配置、md 说明文档及 license 等压缩包约 8.55MB结构紧凑、便于直接安装调用。其中 ovftools 模块可将 simulation.out 目录下的 .ovf 文件批量读入 numpy 数组输出形状为 (n_files, nz, ny, nx, 3) 的数据方便后续对磁化分量做统计与可视化ioutil、plotutil、statutil、datautil、simrun 等模块则覆盖数据读写、绘图、统计与仿真调度等环节。目前已有 1521 人学习下载可作为微磁仿真后处理的实用脚本参考帮助读者省去重复编写解析代码的时间快速搭建从仿真输出到数据分析的完整流程。1. 从脚本堆到工具链mx3tools 到底解决什么问题做微磁学仿真的人大多经历过这个阶段手头攒了十几个 Python 脚本有的负责生成 Mumax3 的.mx3输入文件有的负责批量提交任务有的负责把table.txt读出来画图。每个脚本单看都能跑但拼在一起就出问题——路径写死了、参数散落在各处、换一台机器就得重来一遍。mx3tools 这个方向要解决的就是把「一堆能跑的脚本」变成「一条能复现的仿真流水线」。它面向的是用 Mumax3 做微磁学仿真、又习惯用 Python 做前后处理的研究生和工程师。核心价值不在算法而在工程化把仿真参数、输入生成、任务调度、结果解析、可视化这几步串成可维护的流程。如果你现在还在手动改.mx3文件里的数字、手动scp结果、手动对着table.txt调列号那这套东西值得花时间搭起来。下面按「先立住概念、再动手复现、最后讲坑」的顺序展开。2. 用 Python 驱动 Mumax3输入生成与任务提交的最小闭环Mumax3 本身是个独立的 GPU 加速微磁学求解器输入是纯文本的.mx3脚本输出是table.txt、*.ovf等文件。它不依赖 Python但正因为输入输出都是文本Python 才有介入空间。mx3tools 这类工具的本质就是用 Python 做「参数管理 模板渲染 进程调度 结果解析」四件事。2.1 为什么不用手写 .mx3 而要模板化手写.mx3在单次仿真时没问题但一旦要做参数扫描——比如扫交换刚度Aex、DMI 强度Dind、外加磁场B_ext——手写就会失控。常见做法是用 Python 的字符串模板或string.Template生成脚本把可变参数抽出来。下面是一个最小可用的模板渲染函数from string import Template import os # .mx3 模板$ 开头的变量会被替换 MX3_TEMPLATE Template( setgridsize($nx, $ny, $nz) setcellsize($cx, $cy, $cz) Msat $msat Aex $aex Dind $dind B_ext vector($bx, $by, $bz) m uniform(1, 0, 0) relax() save(m) tableautosave(1e-12) run($runtime) ) def render_mx3(params: dict, out_path: str): 把参数字典渲染成 .mx3 文件 content MX3_TEMPLATE.substitute(**params) with open(out_path, w) as f: f.write(content) return out_path逻辑说明Template.substitute做的是严格替换缺任何一个变量都会抛KeyError这比str.format更安全——参数扫描时最怕的就是某个参数忘了传结果用了默认值还浑然不觉。参数说明nx/ny/nz是网格点数cx/cy/cz是单胞尺寸米msat是饱和磁化强度A/maex是交换刚度J/mdind是 DMI 常数J/m²runtime是仿真时长秒。这些单位在 Mumax3 里是固定的写错量级不会报错只会算出离谱结果这是后面避坑章要重点讲的。2.2 批量提交与并发控制参数扫描动辄几十上百个任务串行跑太慢全并发又会把 GPU 显存打爆。常见做法是用concurrent.futures控制并发数每个任务一个独立工作目录import subprocess from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path def run_mumax3(mx3_path: Path, workdir: Path, mumax3_binmumax3): 在指定目录运行一个 .mx3 文件 workdir.mkdir(parentsTrue, exist_okTrue) # -f 覆盖输出-o 指定输出目录 cmd [mumax3_bin, -f, -o, str(workdir), str(mx3_path)] proc subprocess.run(cmd, capture_outputTrue, textTrue) return { mx3: str(mx3_path), returncode: proc.returncode, stderr: proc.stderr[-500:], # 只留尾部避免日志爆炸 } def batch_run(tasks, max_workers2): tasks: [(mx3_path, workdir), ...] results [] with ThreadPoolExecutor(max_workersmax_workers) as ex: futures {ex.submit(run_mumax3, m, w): m for m, w in tasks} for fut in as_completed(futures): results.append(fut.result()) return results逻辑说明max_workers是关键参数。Mumax3 吃 GPU同一张卡上并发两个任务通常已经是上限再多会因显存不足直接崩而且崩的时候不一定有清晰报错。参数说明-f表示覆盖已有输出-o指定输出目录这两个参数建议始终显式写否则 Mumax3 默认输出到当前目录批量跑时会互相覆盖。stderr只截尾部 500 字符是因为 Mumax3 的日志很长全存下来内存吃不消。2.3 结果解析table.txt 的列名陷阱Mumax3 的table.txt第一行是列名但列的数量和顺序会随仿真里调用了哪些量而变化。比如你调了ext_topologicalcharge()就会多一列。硬编码列号是血泪教训的重灾区。稳妥做法是按列名取import pandas as pd def load_table(path): 读取 Mumax3 table.txt按列名访问 # 跳过以 # 开头的注释行第一行有效行是表头 df pd.read_csv(path, sepr\s, comment#, header0) df.columns [c.strip() for c in df.columns] return df def get_series(df, col): 按列名取数据列不存在时给出明确报错 if col not in df.columns: raise KeyError(f列 {col} 不存在实际列: {list(df.columns)}) return df[col]逻辑说明comment#让 pandas 跳过注释行sepr\s处理不定长空白分隔。参数说明Mumax3 的table.txt用空白对齐不是标准 CSV所以必须用正则分隔符。按列名取而不是按位置取是这套工具链里最省后悔药的一个决定——换一次仿真设置列顺序就可能变。3. 参数扫描与数据组织让几十组仿真不打架单次仿真跑通只是起点真正的工作量在参数扫描和结果管理。这一章讲怎么把几十上百组仿真组织得清清楚楚以及为什么目录结构比代码更重要。3.1 目录结构决定你能不能复现我一般会按「扫描维度 参数值」组织目录而不是按时间戳。时间戳目录跑完一周后你根本不知道哪个是哪个。推荐结构层级示例作用根目录runs/所有仿真扫描维度runs/Aex_1e-11/按某个参数分组参数组合runs/Aex_1e-11/Dind_1e-3/具体一组参数输出.../table.txt、.../*.ovfMumax3 产物元数据.../params.json记录这组参数的完整来源params.json是这套结构里最容易被忽略、但最该保留的东西。它记录的不只是扫描参数还有网格、单胞尺寸、材料常数这些「固定」参数——因为半年后你一定会怀疑当时到底用的哪个值。import json from pathlib import Path def make_run_dir(root: Path, params: dict) - Path: 按参数生成目录并写入元数据 # 用参数名和值拼目录值做科学计数法简化 parts [f{k}_{v:.0e} if isinstance(v, float) else f{k}_{v} for k, v in sorted(params.items())] run_dir root.joinpath(*parts) run_dir.mkdir(parentsTrue, exist_okTrue) with open(run_dir / params.json, w) as f: json.dump(params, f, indent2) return run_dir逻辑说明sorted(params.items())保证目录名顺序稳定不会因为字典顺序变化导致同一组参数生成两个目录。参数说明:.0e把浮点数转成科学计数法避免1e-11被写成0.00000000001这种又长又容易看错的形式。3.2 扫描参数怎么设才不浪费机时参数扫描最怕两件事范围设太宽大部分点落在物理上无意义的区域步长设太密算了一堆趋势相同的点。我的经验是先做「粗扫三点」——取参数的下限、中值、上限各跑一次看结果有没有明显变化。如果三点结果几乎一样说明这个参数在当前区间不敏感没必要细扫。具体到微磁学几个常见参数的扫描建议交换刚度Aex典型值在1e-12到2e-11J/m 量级粗扫取1e-12、5e-12、2e-11。DMI 常数Dind从 0 开始按1e-4、1e-3、5e-3J/m² 递增观察是否出现 Néel 型畴壁。外加磁场B_ext按饱和场的 0.1、0.5、0.9 倍取点重点看磁化翻转路径。提示粗扫阶段把runtime设短一些比如只跑到系统初步弛豫能省下大量机时。确认参数区间有意义后再用完整时长重跑。3.3 用 pandas 做批量后处理几十组仿真跑完后手动一组组看table.txt不现实。常见做法是把所有table.txt读进一个带参数标签的大表import pandas as pd from pathlib import Path def collect_tables(root: Path, tag_cols(Aex, Dind)): 遍历 runs 目录把所有 table.txt 汇总成一张表 frames [] for params_file in root.rglob(params.json): run_dir params_file.parent table run_dir / table.txt if not table.exists(): continue df pd.read_csv(table, sepr\s, comment#, header0) # 把参数作为新列附加 import json params json.loads(params_file.read_text()) for col in tag_cols: df[col] params.get(col) frames.append(df) return pd.concat(frames, ignore_indexTrue)逻辑说明rglob(params.json)递归找所有参数文件以它为锚点定位table.txt这样即使目录结构变了也能找到。参数说明tag_cols指定要附加到结果表里的参数列按你实际扫描的维度改。汇总后就能用groupby按参数分组画图比一组组手动画快得多。4. 避坑与排查Mumax3 Python 工具链的五个翻车现场这一章全是踩过的坑按「现象 → 原因 → 解决」写。有些坑不涉及代码但比代码坑更耗时间。4.1 仿真跑完但 table.txt 是空的现象任务返回码是 0目录里也有table.txt但打开只有表头没有数据。原因通常是tableautosave没设或者设的时间间隔大于runtime导致仿真结束前一次都没触发保存。解决在.mx3里显式写tableautosave(1e-12)并确认runtime至少是它的几十倍。另一个可能是run()之前就出错了但 Mumax3 把错误写进了log.txt而不是 stderr所以返回码仍是 0。排查时先看log.txt尾部。4.2 并发跑两个任务后 GPU 报显存不足现象单任务正常max_workers2时其中一个崩报显存分配失败。原因Mumax3 会按网格大小预分配显存两个任务叠加超过卡容量。解决把max_workers降到 1或者减小网格。别指望 Mumax3 自己排队它不做显存调度。如果一定要并发先算清单个任务的显存占用大致是网格点数 × 每个分量字节数 × 若干中间数组留 20% 余量。4.3 参数扫描结果里混进了上一次的旧数据现象汇总表里某些参数组合的数据明显不对像是别的参数下的结果。原因-f没加Mumax3 没覆盖旧输出而table.txt是追加模式新旧数据混在一起。解决始终加-f并且在run_mumax3里先检查工作目录是否为空非空就报错或清空。我一般会在params.json里记一个run_id汇总时按它去重。4.4 单位量级写错导致结果离谱但不报错现象仿真能跑完但磁化强度、畴壁宽度这些量比预期差好几个数量级。原因Mumax3 不做单位检查Msat写成8e5还是8e6它都接受但物理结果完全不同。解决在params.json里把单位写进键名比如Msat_A_per_m、Aex_J_per_m渲染模板时再映射回 Mumax3 的变量名。多一层映射少一次翻车。4.5 用 ThreadPoolExecutor 跑 GPU 任务反而更慢现象并发数从 1 提到 2总耗时没降反升。原因Mumax3 本身已经吃满 GPU两个进程在 GPU 上争抢上下文切换开销大于并行收益。解决GPU 任务用进程级并发ProcessPoolExecutor而不是线程或者干脆串行。真正值得并行的是 CPU 侧的预处理和后处理不是仿真本身。5. 进阶把 mx3tools 做成可复用的仿真流水线前面几章把「生成输入 → 提交任务 → 解析结果」这条链路走通了。这一章讲怎么把它从「能跑」推到「可复用」以及一个我常用的验证技巧。5.1 用配置文件驱动整条流水线硬编码参数是复用的最大障碍。常见做法是把扫描配置写成一个 YAML 或 JSON 文件Python 只负责读配置、展开参数组合、调度任务import itertools, yaml def expand_sweep(config_path): 从配置文件展开参数组合 cfg yaml.safe_load(open(config_path)) keys list(cfg[sweep].keys()) values [cfg[sweep][k] for k in keys] combos [] for combo in itertools.product(*values): params dict(cfg[fixed]) # 固定参数 params.update(dict(zip(keys, combo))) combos.append(params) return combos逻辑说明cfg[fixed]放不扫描的参数cfg[sweep]放要扫描的维度itertools.product做笛卡尔积。参数说明配置文件里每个扫描维度是一个列表列表长度决定该维度的点数。这样换一个扫描方案只改配置文件不动代码。5.2 验证方法用解析解对一遍工具链搭好后怎么确认它算得对我的习惯是拿一个已知解析解的场景做基准。比如一维畴壁的宽度解析式是Δ sqrt(Aex / K)K 为有效各向异性在 Mumax3 里设一个纯交换 单轴各向异性的系统弛豫后量畴壁宽度和解析式对比。如果差在 10% 以内说明网格、单位、参数传递这条链路是通的。这个验证不花多少机时但能挡住大部分「参数传错但没人发现」的问题。5.3 一个具体技巧把 OVF 转成可分析的数组table.txt只有标量时间序列要看空间分布得读.ovf。Mumax3 的 OVF 是文本头 二进制数据自己解析容易出错。常见做法是用discretisedfield这类库读但如果不想引入额外依赖可以只读文本头拿到网格信息再按偏移量读二进制import numpy as np def read_ovf_magnetization(path): 读取 OVF 1.0 的磁化矢量场 with open(path, rb) as f: header {} while True: line f.readline().decode(ascii).strip() if line.startswith(# Begin: Data): break if : in line: k, v line.lstrip(# ).split(:, 1) header[k.strip()] v.strip() # 数据段跳过 Data binary 8 行 f.readline() nx, ny, nz (int(header[f{k}nodes]) for k in (x, y, z)) data np.fromfile(f, dtypef4, countnx * ny * nz * 3) return data.reshape(nz, ny, nx, 3)逻辑说明OVF 头是 ASCII逐行读到# Begin: Data为止之后是二进制。参数说明f4是小端 32 位浮点这是 Mumax3 默认输出格式reshape(nz, ny, nx, 3)的顺序是 z 最外层和 Mumax3 的网格约定一致。读出来后就能算拓扑荷、做切片图接进前面的 pandas 流程。这套东西值不值得做取决于你打算跑多少次仿真。如果只是偶尔跑一两个手写.mx3更快但只要进入参数扫描阶段把 mx3tools 这条链路搭起来省下的时间远超搭建成本。我自己的习惯是任何要跑超过十次的仿真先花半天把模板和目录结构定下来后面每次改参数都只是改一个配置文件。希望帮到你。本文还有配套的精品资源点击获取