如何自定义scipy.curve_fit拟合函数组合实现灵活峰解卷积?
实现方案
你可以通过函数工厂动态生成符合curve_fit参数要求的组合拟合函数,不需要每次手动写固定的Combined定义,具体实现步骤如下:
步骤1:建立拟合函数映射表
先把所有预定义的基线、峰函数都存入映射字典,方便通过字符串名称调用,同时可自动获取每个函数的拟合参数数量(第一个参数x不算入拟合参数):
import scipy.optimize as opt import numpy as np # 你已有的基线函数 def ZeroOrder(x,a): return a def Linear(x,a,b): return a+b*x def SecondOrder(x,a,b,c): return a+b*x+c*x**2 # 补充你用到的高斯峰定义(本地已有可跳过) def Gaussian(x, a, mu, sigma): return a * np.exp(-(x - mu)**2 / (2 * sigma**2)) # 函数映射表,后续新增洛伦兹、Voigt等峰函数直接往这里加即可 FUNC_MAP = { 'ZeroOrder': ZeroOrder, 'Linear': Linear, 'SecondOrder': SecondOrder, 'Gaussian': Gaussian }
步骤2:编写组合函数生成工厂
这个工厂函数会根据你传入的基线类型、峰类型列表,自动返回符合curve_fit要求的、签名为f(x, *params)的组合函数:
def generate_combined_func(baseline_type: str, peak_types: list): """ 生成符合curve_fit要求的组合拟合函数 :param baseline_type: 基线函数名称,对应FUNC_MAP的key :param peak_types: 峰函数名称列表,有几个峰就传几个元素 :return: 组合拟合函数、所需总拟合参数个数 """ # 读取基线函数和对应参数个数 baseline_func = FUNC_MAP[baseline_type] baseline_param_num = baseline_func.__code__.co_argcount - 1 # 读取所有峰函数和对应参数个数 peak_funcs = [FUNC_MAP[p] for p in peak_types] peak_param_nums = [f.__code__.co_argcount - 1 for f in peak_funcs] total_param_num = baseline_param_num + sum(peak_param_nums) def combined(x, *params): assert len(params) == total_param_num, f"参数个数不匹配,需要{total_param_num}个,实际传入{len(params)}个" # 计算基线 res = baseline_func(x, *params[:baseline_param_num]) # 逐个累加峰信号 ptr = baseline_param_num for func, param_num in zip(peak_funcs, peak_param_nums): res += func(x, *params[ptr:ptr+param_num]) ptr += param_num return res return combined, total_param_num
步骤3:使用示例
你需要调整基线/峰类型时,仅需要修改工厂函数的入参即可,不需要重写组合逻辑:
# 示例1:生成 线性基线 + 3个高斯峰 的组合函数,和你原来的逻辑完全对齐 combined_func, param_count = generate_combined_func( baseline_type='Linear', peak_types=['Gaussian', 'Gaussian', 'Gaussian'] ) # 初始参数p0长度需要等于返回的param_count,这里和你原参数长度一致(2+3*3=11) p0 = [0.1, 0.1, 115, 508.33, 7.1, 130, 508.33, 7.1, 165.84, 508.33, 7.1] # 直接传入curve_fit即可使用 popt, pcov = opt.curve_fit( combined_func, data[10][0], data[10][1], method='lm', check_finite=True, p0=p0 ) # 示例2:切换为 零阶基线 + 2个洛伦兹峰,仅需要修改入参(需要先把Lorentzian加入FUNC_MAP) # combined_func, param_count = generate_combined_func( # baseline_type='ZeroOrder', # peak_types=['Lorentzian', 'Lorentzian'] # ) # 对应调整p0长度为1+2*3=7即可使用
内容的提问来源于stack exchange,提问作者Jeroen
相关产品推荐
相关产品推荐

