如何用lmfit实现掩码数组拟合(原使用curve_fit的sigma参数)
问题描述
我之前用scipy.optimize.curve_fit结合sigma=weight参数实现了掩码数组拟合,现在切换到lmfit后遇到了实现难题。观测数据与模板均为500-550nm范围内的波长(x)-通量(y)数据,观测数据是两种不同参数光谱的混合。lambda_input为拟合所用的波长列表,拟合仅在这些波长±线宽(lw)的范围内执行。请问如何将观测数据和模板的波长掩码到lambda_input指定范围以完成拟合?
原代码
import pandas as pd import numpy as np from scipy import interpolate from scipy.optimize import curve_fit import lmfit i_run = pd.read_csv("run.txt",delimiter='\t') i_run.columns = ["wave","flux"] run_w = np.array(i_run['wave']) run_f = np.array(i_run['flux']) # observation data lambda_input = pd.read_excel("../lambda/weights.xlsx") #fitting wavelengths v1 = 5 # conversion factors v2 = 6 lw = 0.01 # linewidths (+/- fitting wavelength) weight = np.zeros(len(run_w)) for w in range (0,187): n = lambda_input.iat[w,1] weight += np.select([np.logical_and(run_w >= ((n *(1 + (v1)))-lw),run_w <= ((n *(1 + (v1)))+lw)),run_w>0],[1,0]) for w in range (0,187): n = lambda_input.iat[w,1] weight += np.select([np.logical_and(run_w >= ((n *(1 + (v2)))-lw),run_w <= ((n *(1 + (v2)))+lw)),run_w>0],[1,0]) weight = np.select([weight>0,weight==0],[1,1e6]) p1 = 750 p2 = 800 p3 = 1.0 p4 = 20.0 p5 = 30.0 p6 = 0.5 p7 = 0.5 p8 = 100 p9 = -100 p10 = 4.0 p11 = 4.0 p12 = 3.0 p13 = 3.0 def spec(run_w,t1,t2,r,v1,v2,m1,m2,r1,r2,l1,l2,mi1,mi2): # 原代码省略了混合光谱生成逻辑,此处为示意 return spectrum popt, pcov = curve_fit(spec, xdata=run_w,sigma=weight,ydata=run_f, p0=[p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13]) model = lmfit.Model(spec) #fitting: lmfit p = model.make_params(t1=p1,t2=p2,r=p3,v1=p4,v2=p5,m1=p6,m2=p7,r1=p8,r2=p9,l1=p10,l2=p11,mi1=p12,mi2=p13) fit = model.fit(data=run_f, params=p, run_w=run_w, method='nelder', nan_policy='omit') lmfit.report_fit(fit) fit.plot()
解决方案
在lmfit中实现指定波长范围的拟合,有两种更简洁的方案,无需依赖极端sigma值掩码,直接通过布尔筛选或权重参数实现:
1. 生成布尔掩码筛选目标波长范围
替换原weight生成逻辑,直接创建布尔数组标记需要保留的波长点,再筛选数据:
# 初始化掩码为全False mask = np.zeros(len(run_w), dtype=bool) # 获取lambda_input中的目标波长列表 target_waves = lambda_input.iloc[:,1].values # 遍历所有目标波长,标记符合条件的范围 for n in target_waves: # 第一种光谱的波长范围 wave_range1 = (n*(1+v1) - lw, n*(1+v1) + lw) mask |= np.logical_and(run_w >= wave_range1[0], run_w <= wave_range1[1]) # 第二种光谱的波长范围 wave_range2 = (n*(1+v2) - lw, n*(1+v2) + lw) mask |= np.logical_and(run_w >= wave_range2[0], run_w <= wave_range2[1]) # 筛选出需要拟合的波长和通量数据 filtered_w = run_w[mask] filtered_f = run_f[mask]
2. 使用筛选后的数据执行lmfit拟合
将筛选后的filtered_w和filtered_f传入拟合函数,lmfit仅在目标范围内计算拟合误差:
model = lmfit.Model(spec) p = model.make_params(t1=p1,t2=p2,r=p3,v1=p4,v2=p5,m1=p6,m2=p7,r1=p8,r2=p9,l1=p10,l2=p11,mi1=p12,mi2=p13) # 使用筛选后的数据拟合 fit = model.fit(data=filtered_f, params=p, run_w=filtered_w, method='nelder') lmfit.report_fit(fit) fit.plot()
3. 替代方案:沿用权重逻辑与scipy对齐
若想保留原weight控制权重的思路,可直接将weight转换为lmfit所需的weights参数(lmfit中weights = 1/sigma):
# 原weight是sigma,转换为lmfit要求的weights weights = 1 / weight fit = model.fit(data=run_f, params=p, run_w=run_w, method='nelder', weights=weights, nan_policy='omit')
关键注意事项
- 若
spec函数涉及模板光谱插值,需对模板数据应用相同掩码,确保模板在筛选后的波长点上有对应值; - 布尔掩码筛选比极端sigma值更高效,更符合lmfit设计逻辑,避免无效计算;
- 原代码中
spec函数的v1、v2参数与全局变量重名,建议修改为v1_factor、v2_factor避免混淆。
内容的提问来源于stack exchange,提问作者cosmic noise
相关产品推荐
相关产品推荐

