SASfit实战:小角散射数据拟合的模型选型、参数约束与排错指南
发布时间:2026/10/18 13:40:06 锦皓数字建站

简介SASfit是一款用于小角度散射SAXS/SANS数据分析和结构模型拟合的开源程序面向材料、生物、胶体等领域从事结构表征的科研人员与工程师。软件可计算回转半径、散射不变性、Porod常数等整体结构参数并支持多种形状因子与尺寸分布拟合借助一组通用全局参数还能同时拟合多条散射曲线在对比变化实验或极化中子测量中能有效降低参数相关性提升结果确定性。压缩包内共208个文件、约54.19MB主要包含66个tbl表格数据、44个so动态库与44个头文件、18个gpf拟合脚本、17个rgb颜色映射表以及csv、txt、pdf说明和def示例等可辅助用户快速查看编译产物、依赖接口与使用示例也便于直接利用示例脚本开展二次开发和定制。目前已有1617人学习或下载适合希望深入理解模型拟合原理、基于开源代码扩展功能或将其集成到自身数据处理流程中的科研用户。1. 拟合小角散射数据不是玄学SASfit把这件事变成了可复现的工作流做小角散射的人手里都有一堆这样的数据样品的散射强度I(q)随着散射矢量q增大而快速衰减想要从中得到粒径、形状和尺寸分布就必须把合适的结构模型拟合上去。SASfit正是为此而生的开源程序它支持SAXS和SANS两类数据内置几十种结构模型从实心球、核壳球、椭球、圆柱到聚合物链、微乳液结构因子都有拟合完成直接输出参数、误差、chi²和残差曲线。它最大的价值在于把散射实验数据变成可以写进报告的结构结论——反推出半径、壳层厚度、多分散度、体积分数这些关键参数。这篇文章不是照着界面念说明书而是把模型选型、参数约束、数据预处理和排错方法串起来适合刚拿到第一条曲线的研究生也适合要批量处理几十条数据的工程师。2. 散射拟合的数学骨架SASfit到底在算哪个方程2.1 从I(q)到结构参数SASfit替你解的那个方程在稀释、无相互作用的近似下小角散射强度对q的依赖可以写成简洁的形式I(q) scale × P(q) background其中P(q)是形状因子描述单个粒子的几何对散射的贡献scale是强度缩放因子物理上它集中了粒子数密度N、粒子体积V的平方以及粒子与溶剂之间的散射长度密度差Δρ²background是与q无关的常数背景用来吸收实验中未能完全扣除的溶剂散射和杂散散射。SASfit的大多数模型都采用这种归一化约定P(q)在q趋近0时归一化到1也就是说只描述散射曲线的形状把强度绝对值全部交给scale。这个约定的实际含义是即使实验数据没有做绝对强度标定依然可以用任意单位的强度曲线拟合出尺寸参数只是不能从scale解读出粒子数密度和体积分数。如果体系里粒子间的相互作用不可忽略比如高体积分数的胶体、带电粒子体系公式变成I(q) scale × P(q) × S(q) backgroundS(q)是结构因子描述粒子间的空间关联。SASfit提供了硬球、带电硬球、黏性硬球等结构因子模型可以和任意的形状因子组合。以最简单的实心球模型为例P(q)的解析表达式是 [3(sin(qR) − qR·cos(qR)) / (qR)³]²其中R是球的半径qR很小时这一项趋近1。核壳球则在这个基础上叠加壳层与核心的散射长度密度差表达式里会出现两个不同半径的散射振幅之差因此核壳结构会在散射曲线上留下特征性的次级振荡峰。SASfit要做的就是用非线性最小二乘迭代找到一组参数使计算出的模型曲线与实验数据的加权残差平方和最小。默认使用Levenberg-Marquardt算法这个算法在参数初值离最优解不远时收敛非常快但初值离谱或者参数高度相关时很容易陷在局部极小值里。这就是后文里参数边界和初值设置成为整个拟合流程成败关键的根源。如果内置模型满足不了需求SASfit还允许用户自定义P(q)的表达式。自定义模型时最关键的是保持P(q)在q→0处归一化到1否则scale和几何参数之间会产生很强的相关性拟合结果会变得极不稳定。我在实际使用中遇到过几次自定义模型拟合发散最终都是因为归一化系数和scale揽在一起扯不清楚。2.2 模型库选型逻辑先从简单模型开始别让参数表吓住SASfit内置模型用几十种来描述绝不过分从简单几何体到复杂自组装结构都有。对新手来说真正的难点不是模型不够用而是不知道选哪个。我的习惯是遵循一条原则先根据实验特征缩小候选范围然后从参数最少的模型开始。实验曲线特征优先考虑的模型选型理由低q区平台明显中高q有振荡峰实心球、核壳球球体干涉峰明显核壳结构会引入次级峰低q区有-4的幂律下降界面平滑致密粒子的Porod型模型-4幂律是平滑界面的典型特征中q区有-2的幂律下降高斯链、排除体积链链状聚合物的典型散射特征整条曲线缓慢下降没有明显平台椭球、圆柱形状的各向异性使平台被抹平微乳液、双连续结构微乳液双连续结构模型这类模型专门描述双连续相的峰型选型之前建议先做两步快速分析而不是直接堆模型。第一步在低q区做回转半径近似得到整个散射体的回转半径Rg球体的话半径大约对应sqrt(5/3)·Rg第二步看高q区的幂律指数指数接近-4说明粒子界面平滑明显小于-4说明有表面粗糙或分形层次结构。这两步用任意数据处理软件都能做做完再回到SASfit选模型能省掉很多盲目试模型的时间。2.3 参数分类与约束机制不约束参数的拟合等于碰运气SASfit的模型参数看起来五花八门实际上可以分成四类。第一类是强度相关参数只有scale和background两个第二类是几何尺寸参数球形是半径圆柱是半径加长度核壳结构还要加壳厚第三类是分布参数即多分散度SASfit可以在几何参数上叠加Schulz、Gauss或者Lognormal分布描述样品中尺寸不完全均一的情况第四类是结构因子参数包括粒子的体积分数、硬球相互作用半径等。小角散射数据的信息量是有限的。一条q_max/q_min覆盖两个数量级的曲线能稳定确定的独立参数一般不要超过5到7个。超过这个数多出来的参数就会在样品数据里找解释空间结果看起来拟合得很漂亮换个数据集就完全不是那个结果。参数相关性是最常见的翻车源头scale和体积分数高度纠缠多分散度和背景参数也会互相补偿。在数据上它们各自变化却产生几乎相同的模型曲线拟合算法自然无法分辨。所以我在开始拟合前会做三步参数约束。第一步把物理上不可能的值封死半径必须大于0多分散度通常在0到0.5之间体积分数不能超过0.74这个球密堆上限。第二步根据回转半径分析的结果设置几何参数的量级初值取估算值的约50%作为上下限。第三步先固定所有分布参数和结构因子参数只让几何参数自由收敛之后再逐步放开其余参数。这个顺序能显著降低局部极小的风险尤其适合新手。分布函数的选择看起来是小事实际会对参数结果产生明显影响。Gauss分布对平均值对称适合宽度比较窄的样品Lognormal分布允许向大尺寸方向延伸长尾很多工业胶体和生物组装体系的长尾特征正是如此Schulz分布解析性最好计算快对中等多分散度体系是个稳妥默认项。同一个数据集用三种分布拟合出的平均半径可能存在几个纳米的偏差这并不代表拟合错误只是分布假设不同。报告时应当写明用了哪种分布换分布时不要直接对比参数。提示SASfit里每个参数都可以单独设置初始值、最小值和最大值还可以在拟合过程中固定住。把不打算拟合的参数锁定比删掉它更安全因为它仍然参与模型计算只是不参与优化。3. 用SASfit跑通第一次拟合数据格式、参数设置和结果检查3.1 准备一份SASfit能直接读的数据文件SASfit读取的散射数据通常是纯文本格式空格或Tab分隔三列分别是q、I(q)和误差。q的单位常见是Å⁻¹如果实验站给的是nm⁻¹换算时注意是10倍关系。误差这一列不能省拟合的权重完全依赖它误差缺失或者填0最小二乘的权重就会变成无穷大或非有限值结果不可信。从原始探测器文件到SASfit输入文件中间通常要经过辐射校正、容器扣除、样品浓度归一化等步骤。这些步骤各自的处理流程由实验站软件完成SASfit本身不负责背景扣除background参数只是拟合里的一个常数。我习惯把已经扣除过溶剂散射的数据送入SASfit残留的微小背景用background参数吸收。下面这段脚本是每次拿到新数据都会先跑一遍的清洗流程import numpy as np # 原始数据: 假设前三列是 q, I, dI, 其他列用不到 raw np.loadtxt(raw_saxs.dat, skiprows2) q raw[:, 0] I raw[:, 1] err raw[:, 2] # 去掉 q 过小、过大和误差非正的坏点 mask (q 2e-3) (q 0.8) (err 0) q, I, err q[mask], I[mask], err[mask] # 归一到最大强度让 scale 初值可以取 1 附近 norm I.max() I / norm err / norm np.savetxt(sasfit_input.dat, np.column_stack((q, I, err)), fmt%.6e, delimiter , headerq[1/A] I dI, comments#)脚本把强度归一化到最大值归一化后scale的初值直接设成1附近即可这是我的习惯做法。q的下限2e-3和上限0.8是按典型SAXS配置给的实际要根据数据覆盖范围和信噪比调整。下限附近常常是光束阻挡器边缘污染最严重的区域宁可多去掉一两个点也不要让低q区坏点主导拟合。高q端通常没问题但q太大导致计数很差时误差很大那些点权重本来就小留着也无妨。3.2 配置一次完整拟合从模型到参数边界打开SASfit载入刚生成的sasfit_input.dat之后操作流程一般如下。首先在模型列表中选择带尺寸分布的球模型Sphere with size distribution然后设置参数表的初始值scale填1background填0或一个很小的正数比如0.001半径填根据低q区回转半径估算的数值多分散度填0.1分布函数选Schulz。接着设置参数边界scale的上下限取0.1到10background取-0.01到0.1半径上下限取估算值的50%到200%多分散度限在0.01到0.5。最后运行拟合。这个配置里有两个细节值得说。一是background上限给到0.1是因为预处理已经把最大强度归一化成10.1已经是背景占比很高的上限给太小会把背景锁死在边界上。二是多分散度下限0.01而不是0这是为了让参数不落在边界值上避免某些拟合器对边界值的误差统计处理失真也保留了一定的优化空间。拟合结束之后界面通常同时显示模型曲线、实验数据和残差曲线。残差曲线是重点它显示每个数据点上实验值与拟合值的差除以误差后的结果。残差在0附近随机分布最理想如果残差呈现明显的弯曲或周期性的起伏意味着模型形状与实际散射形态不一致这时候要回到模型选择而不是继续调参数。如果一次拟合从不同初始值出发得到明显不同的参数结果说明模型存在参数退化性也就是参数组合不唯一。我一般会重复三次不同初值的拟合结果不一致就不把这次拟合当作可靠结论转而简化模型。例如把核壳球换成实心球或者先不加结构因子等简单模型跑通再逐层加复杂度。3.3 拟合质量不能只看chi²把参数误差和物理合理性一起检验SASfit输出里的chi²、参数估计和渐近标准误差都很有用但很多人只盯着chi²。chi²本身是不完善的指标它依赖误差估计的准确性。实验误差被低估chi²虚高误差被高估chi²虚低。我把chi²当作参考判断拟合品质主要看三件事残差分布、参数误差和物理合理性。下面的脚本可以快速完成前两种检查import numpy as np # 读取实验数据和SASfit导出的拟合曲线 exp np.loadtxt(sasfit_input.dat) fit np.loadtxt(fit_curve.dat) q exp[:, 0] I_exp exp[:, 1] err exp[:, 2] I_fit fit[:, 1] # 假设导出文件第二列是模型强度 res (I_exp - I_fit) / err print(chi2/dof , np.sum(res**2) / (len(res) - 5)) # 统计超过3倍误差点数 bad np.sum(np.abs(res) 3) print(residual outliers 3sigma:, bad, /, len(res)) # 检查残差是否在某一段系统性同号 for lo, hi in [(0, 5), (5, 10)]: seg res[int(len(res)*lo/20):int(len(res)*hi/20)] print(fresidual mean q bin {lo}-{hi}:, seg.mean())这里len(res)-5是自由度5是带尺寸分布的球模型的参数个数实际使用时要按模型的真实参数数调整。残差段均值如果显著偏离0说明这个q区间存在系统偏差即使整体chi²很好看也要警惕。物理合理性检查最简单但最容易犯。多分散度拟合出0.49而样品来自已知的窄分布单分散体系多半是模型选错了半径拟合出500 Å而动态光散射结果显示水合粒径15 nm尺寸尺度完全对不上。出现这类矛盾时不要强行解释先回头检查数据预处理和模型选择。4. 拟合翻车血泪排查SASfit常见问题和避坑指南4.1 低q区拟合曲线压不住残差在低角度整体上翘现象不管选什么模型拟合曲线在低q段都低于实验数据残差呈现明显的正峰而且随着q减小残差越来越大。原因低q区上翘通常意味着体系里有大尺度结构比如几十纳米的粒子聚集成几百纳米的团簇。单个粒子的模型里根本不存在这么大的空间尺度所以永远压不住。另一个常见原因是样品池边缘的寄生散射没有完全扣除这种杂散信号集中在低q区。解决先看数据本身把I对q做双对数图如果最低的几个点在斜率趋势上方突然抬头优先怀疑是聚集体或寄生散射。实验上对样品做一次离心或过滤重新测量空池背景并扣除。数据处理上可以从拟合区间剔除最低的几个点范围不用很大剔除后如果chi²明显下降说明问题就集中在低q区。4.2 拟合发散或参数漂移到边界模型曲线却看不出明显变化现象迭代不收敛或者半径、多分散度、scale等参数一路漂移到边界并被卡住。此时模型曲线和实验点之间可能看起来已经比较接近了。原因参数相关性强。多分散度和scale的组合可以产生非常相似的散射曲线大尺度因子配宽分布与较小尺度因子配窄分布在有限的数据区间内几乎无法区分。LM算法在这两个参数的组合谷里来回振荡最终被顶到边界上。解决先固定多分散度用一个合理的小值比如0.05只让半径和scale自由拟合拿到一个稳定的基础解。然后再放开多分散度边界不要给到0.5以上多数单峰样品不会超过0.3。如果放开之后多分散度又冲边界说明样品本身可能是双峰分布单峰模型根本描述不了它。4.3 chi²接近1但残差在中q区出现系统性正负振荡现象全局拟合指标很好残差在某个q区间内连续正负交替呈波浪状。单独看每一小段残差都不算大但整个区间明显不是随机分布。原因中q区的振荡往往对应粒子间的干涉峰或核壳结构的次级峰。硬球结构因子会在q≈2π/d的位置产生一个宽峰d是粒子间平均距离。如果模型里没有S(q)这个峰无法被描述只能被周围参数分摊吸收于是残差呈现波浪状。解决不要把这一现象当作统计噪声。在SASfit模型定义中加入硬球结构因子拟合粒子的体积分数和作用半径。如果加入S(q)后中q区残差立即平息说明粒子间关联确实存在。注意加入S(q)会让体积分数和scale产生相关性需要重新收紧边界通常先固定体积分数在实验预估浓度附近拟合收敛后再放开。4.4 多分散度拟合到接近0是不是样品真的单分散现象多分散度参数在每次拟合中都跑到0.01的下边界多次更换初值都是同样结果。原因低信噪比或者模型本身对多分散度不敏感时分布宽度无法被数据约束优化器总是选择更容易降低chi²的方向推参数。多分散度在数据里没有有效梯度信息被推到边界是统计学上的必然不能当作样品单分散的证据。解决把多分散度固定在几个不同的值分别拟合比如0、0.05、0.1、0.2比较各组的chi²。如果chi²随多分散度几乎没有变化说明数据不支持报告这个参数报告时应当写“多分散度未被数据约束”而不是写“样品单分散”。我习惯在写结论时把这种参数的固定值作为假设明确列出而不是混在拟合结果表里避免给合作方传递错误的确定性。5. 从能跑到能信参数敏感性和多模型比较给SASfit结果把关5.1 参数敏感性分析数据到底支持哪个参数SASfit输出的参数误差是基于协方差矩阵的渐近估计参数相关性强时这个误差会骗人。更可靠的做法是参数扫描选定一个参数把它固定在一系列取值上每次重新拟合其余参数记录chi²随固定值的变化。如果曲线是窄而深的单谷说明数据对这个参数有很强的约束力如果是大平底说明它基本是自由的。这个检查比协方差矩阵更直观也更方便写进报告。我用一组扫描结果做判断的脚本如下import numpy as np # scan.dat 两列: 固定半径值, 对应最优chi2 s np.loadtxt(scan.dat) i np.argmin(s[:, 1]) r_best, chi_min s[i] # 按 chi2 chi_min 1 划定近似的 1-sigma 区间 sel s[:, 1] chi_min 1 r_lo, r_hi s[sel, 0].min(), s[sel, 0].max() print(f最优半径 {r_best:.1f}, 近似不确定区间 {r_lo:.1f}-{r_hi:.1f}) if s[sel].shape[0] s.shape[0] * 0.8: print(警告: 参数未被有效约束)这里的chi²最小值加1是近似一个标准误的经典做法实际使用时如果误差列估计不准区间意义会打折扣但用于筛选哪些参数可信已经足够。5.2 多模型比较用惩罚项挡住过拟合参数多的模型chi²总是更小直接比较不公平。我习惯用BIC贝叶斯信息准则把参数数量惩罚进去数值越低越好。SASfit本身不计算BIC但拿到每次拟合的chi²、自由参数数和数据点数后几行代码就能算清楚import numpy as np def bic(chi2, k, n): return chi2 k * np.log(n) # 模型A: 实心球 4个自由参数, chi2320, 数据点200 print(实心球 BIC:, bic(320, 4, 200)) # 模型B: 核壳球 6个自由参数, chi2298 print(核壳球 BIC:, bic(298, 6, 200))核壳球chi²小了22但BIC只小了约12优势并不像chi²差距看起来那么大。实际使用时如果模型间BIC差异小于2我会视为没有决定性差异选择参数更少的那个作为最终结论。多模型对比要在同一批数据、同一套误差权重下进行换了一条误差列BIC的绝对数值没有可比性。我在一次批次样品分析中为了让拟合更完美叠加了核壳层、硬球结构因子和尺寸分布结果BIC排序表明最简洁的实心球模型反而是多数样品的最佳选择。从那以后我形成了一个习惯每个样品至少跑一个简单模型和一个复杂模型把BIC结果列入汇总表再决定哪些参数进入最后的讨论而不是挑一条最漂亮的拟合曲线交差。这个习惯帮我挡掉了不少拟合得很漂亮但一推就倒的结论。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。