【Bug已解决】Lab based Mz magnetometer Automation and sesnitivity calculations 解决方案
原始需求线索:Lab based Mz magnetometer Automation and sesnitivity calculations(基于实验室的 Mz 磁强计自动化测量,以及灵敏度计算)。
一、背景:磁强计与「Mz」是什么
磁强计(magnetometer)测量磁场强度,单位通常是特斯拉(T)或高斯(G,1 G = 1e-4 T)。「Mz」指被测磁场在某一坐标轴(z 轴)上的分量。实验室里常见类型:
- 磁通门(fluxgate):测直流/低频磁场,分辨率可达 pT 级;
- SQUID:超导量子干涉,分辨率极高(fT 级);
- Hall / 各向异性磁阻(AMR / GMR):固态传感器,便宜但噪声大。 所谓「自动化测量」,就是把「手动调磁场、读数、记录」变成程序循环:设激励 → 等待稳定 → 采样 → 存储 → 下一个点。
二、灵敏度(sensitivity)的定义
灵敏度通常指仪器能可靠分辨的最小磁场变化,工程上用「噪声基底」估算:
sensitivity ≈ NEP ≈ 噪声标准差 × sqrt(测量带宽)或更直接:在零场(或无信号)下采集一段数据,其标准差 σ 即代表最小可探测信号量级。这是本文计算的核心。常见错误是「用单次读数当灵敏度」或「没扣除系统漂移」。
三、为什么自动化测量会出问题:根因
3.1 没等系统稳定就采样
磁场线圈 / 样品有弛豫时间,激励一施加立刻读数,得到的是过渡态而非稳态 → 数据失真。
3.2 用单次读数当灵敏度
灵敏度应从「大量重复采样的统计分布」得出,单次读数毫无统计意义。
3.3 没扣除漂移 / 温漂
长期采集中环境温度、仪器零点缓慢漂移,直接算 σ 会把漂移当噪声,灵敏度被高估。
3.4 采样率与带宽不匹配
奈奎斯特:采样率须 ≥ 2×信号最高频。过低混叠,过高引入更多高频噪声。
四、最小可运行复现(错误:单次读数当灵敏度)
下面演示「用单次读数估算灵敏度」为何荒谬,以及正确做法:
import numpy as np # 模拟零场背景采集 1000 个点(真实仪器会有噪声) rng = np.random.default_rng(42) noise = rng.normal(0.0, 1e-9, size=1000) # 标准差 1 nT 的噪声 # 错误:拿第一个点当灵敏度 wrong = noise[0] print("错误(单次读数):", wrong, "T <- 毫无统计意义") # 正确:用标准差估算噪声基底 std_est = np.std(noise, ddof=1) print("正确(噪声标准差):", std_est, "T") # 若测量带宽 1 Hz,灵敏度约 = std(此处简化) sensitivity = std_est print("估算灵敏度 ≈", sensitivity, "T")运行可见:单次读数在 ±几 nT 间随机跳,而std稳定给出 1 nT 的噪声基底——这才是灵敏度。
五、解决方案一:自动化采集循环(带稳定等待)
用程序驱动「设点 → 稳定 → 采样 → 记录」,关键在「等稳定」:
import time import numpy as np def read_sensor(): """示意:从仪器读一个 z 分量读数(真实环境走串口/DAQ/API)。""" return float(np.random.default_rng().normal(0, 1e-9)) def settle_and_sample(target_field, settle=2.0, n=200, fs=100): """施加目标场,等稳定,再采 n 个样本求平均。""" # 1) 设激励(示意) # set_coil_current(target_field) time.sleep(settle) # 关键:等系统弛豫 # 2) 采样 buf = np.empty(n) dt = 1.0 / fs for i in range(n): buf[i] = read_sensor() time.sleep(dt) return buf.mean(), buf.std(ddof=1) if __name__ == "__main__": # 扫描一系列 z 场点 for Bz in np.linspace(-1e-6, 1e-6, 5): # -1uT .. +1uT, 5 点 mean, std = settle_and_sample(Bz) print(f"Bz目标={Bz:.2e} -> 均值={mean:.3e} 样本噪声={std:.3e}")settle等待 + 多次采样平均,是自动化测量的基本盘,避免「过渡态数据」。
六、解决方案二:扣除漂移求真实噪声
长期采集中用「去趋势(detrend)」去掉缓慢漂移,再算噪声:
import numpy as np def noise_after_detrend(signal): """去掉线性漂移后估算噪声基底。""" t = np.arange(len(signal)) # 一阶去趋势:减去最小二乘拟合的直线 p = np.polyfit(t, signal, 1) detrended = signal - np.polyval(p, t) return np.std(detrended, ddof=1), detrended if __name__ == "__main__": t = np.linspace(0, 100, 1000) drift = 5e-12 * t # 缓慢温漂(斜率 5 pT/s) raw = drift + np.random.default_rng(0).normal(0, 1e-9, 1000) naive = np.std(raw, ddof=1) # 含漂移,偏大 cleaned, _ = noise_after_detrend(raw) # 去漂移后 print("含漂移噪声:", naive) print("去漂移噪声:", cleaned) # 接近真实 1 nT去趋势后cleaned回到真实噪声水平,灵敏度估算不再被漂移污染(解决根因 3.3)。
七、解决方案三:灵敏度与 Allan 方差(长期稳定性)
对高稳定仪器,常用Allan 方差看「平均时间越长,噪声是否更低 / 何时受漂移主导」:
import numpy as np def allan_variance(x, fs, taus=None): """计算 Allan 方差。x: 零场序列;fs: 采样率。""" if taus is None: taus = fs * np.logspace(0, 2, 10) # 平均时间候选 advar = [] for tau in taus: m = max(1, int(round(tau * fs))) if m >= len(x) // 2: advar.append(np.nan) continue # 分成不重叠的 m 点块,块均值,相邻块差的一半 n_blocks = len(x) // m y = x[:n_blocks * m].reshape(n_blocks, m).mean(axis=1) advar.append(np.sqrt(np.mean(np.diff(y) ** 2) / 2)) return np.array(taus), np.array(advar) if __name__ == "__main__": fs = 10 x = np.random.default_rng(1).normal(0, 1e-9, 2000) taus, av = allan_variance(x, fs) best = np.nanmin(av) print("Allan 最小方差对应灵敏度 ≈", best, "T")Allan 方差的最低点常作为「最佳平均时间下的可达灵敏度」——平均太短噪声大,太长漂移主导,中间有最优。
八、解决方案四:自动化脚本的健壮性
仪器自动化跑几小时,必须处理异常与断点续测:
import json, os class MeasurementCampaign: def __init__(self, log_path): self.log_path = log_path self.results = self._load_existing() def _load_existing(self): if os.path.exists(self.log_path): with open(self.log_path) as f: return json.load(f) return [] def run_point(self, Bz): try: mean, std = settle_and_sample(Bz) self.results.append({"Bz": Bz, "mean": mean, "std": std}) # 每点即存盘,崩溃也可续测 with open(self.log_path, "w") as f: json.dump(self.results, f) except Exception as e: print(f"点 {Bz} 失败: {e}") # 跳过而非中断整个 campaign if __name__ == "__main__": camp = MeasurementCampaign("/tmp/mag_campaign.json") for Bz in np.linspace(-1e-6, 1e-6, 11): camp.run_point(Bz) print("已完成点数:", len(camp.results))要点:每点即落盘 + 单点异常跳过,保证数小时测量不被一次偶发失败拖垮。
九、排查清单
磁强计自动化测量出问题,按下面排查:
- 是否等了稳定时间?激励后有无
settle等待; - 灵敏度是否基于统计?别用单次读数(第四节);
- 是否扣除漂移?长期测量做去趋势(第六节);
- 采样率是否满足奈奎斯特?是否混叠;
- 噪声带宽是否明确?灵敏度与带宽相关;
- 是否用 Allan 方差看最优平均时间(第七、八节);
- 脚本是否每点落盘 + 跳过异常?避免整轮重来;
- 单位是否统一?T / G / nT 换算别错(1 G = 1e-4 T)。
十、小结
「磁强计自动化测量与灵敏度计算」的通用工程要点:
- 自动化采集:设点 → 稳定等待 → 多次采样平均,避免过渡态数据(第五节);
- 灵敏度基于统计:用零场采集的标准差(噪声基底)估算,而非单次读数(第四节);
- 去漂移:长期测量先去趋势再算噪声,防止漂移污染灵敏度(第六节);
- Allan 方差:找最佳平均时间下的可达灵敏度,平衡噪声与漂移(第七节);
- 健壮性:每点落盘、单点异常跳过,保证长时测量可续跑。 一句话:灵敏度不是「读一个数」,而是「在受控条件下统计噪声基底」的结果;自动化不是「循环读传感器」,而是「受控激励 + 稳定等待 + 统计采样 + 持久化」的闭环。把测量当成带统计意义的实验而非简单轮询,数据才可信。这与第 87 篇状态一致性、第 97 篇配额重置一样,都强调「长时间运行的采集/计数任务必须可统计、可续跑、可校验」。