基于openpyxl与scipy求解Pi衰减器电阻网络非线性方程组问题
问题描述
我正在用数据科学工具实现Excel自动化,处理Pi衰减器的真实阻值计算:该元件端子3短接R1和R2共地,无法直接用万用表测真实阻值。我已通过手动输入测量值,用scipy的fsolve求解出真实阻值(等效R1、R2无短接的情况),验证代码正常。
方程组由欧姆表跨接引脚(1,3)、(2,3)、(1,2)的串并联阻值推导,确认正确,属于非线性方程组(变量在分母)。
现在需要批量处理数百组Excel测试数据,适配不同衰减器型号。已将Excel数据读取为numpy数组,但传入fsolve时出现大量小数位输出,以及RuntimeWarning(double_scalars溢出、除零错误),警告出现在方程组赋值环节。
目前用单行数据调试,尝试过强制numpy数组为float64/float32、在函数内创建新数组、转换参数类型、四舍五入等方法,均未解决问题。需要基于numpy、fsolve和openpyxl的解决方案。
核心问题分析
你的代码里方程组的定义完全错误:
- 手动测试时,方程逻辑是
1/(1/x+1/(y+w)) - 测量值 = 0(x/y/w是真实阻值,测量值是固定输入数据) - 但现在代码中写成了
1/(1/z[0]+1/(z[1]+z[2])) - z[0],把待求解的真实阻值z[0]当成了测量值,直接导致方程组逻辑混乱,fsolve迭代时出现除零、溢出。
此外还有两个次要问题:
- 初始猜测值
zGuess用固定的[1,1,1],和实际电阻值差距过大,容易导致迭代发散 - 未处理Excel数据可能存在的非数值情况(比如空单元格)
解决方案
1. 修正方程组逻辑
将测量值作为参数传入convert_measured函数,用functools.partial绑定每组的测量值,避免混淆待求解变量和输入数据。
2. 优化初始猜测值
用当前行的测量值作为初始猜测,真实阻值和测量值不会相差过大,能提升fsolve的收敛速度和稳定性。
3. 数据类型校验与处理
确保从Excel读取的数值是float类型,处理空单元格避免程序崩溃。
完整修正代码
import numpy as np from scipy.optimize import fsolve import openpyxl from functools import partial wb = openpyxl.load_workbook("datafile.xlsx") ws = wb['Group A'] # 测量数据区域:I15-K34(列9-11,行15-34) MeasColBegin = 9 MeasColEnd = 11 MeasRowBegin = 15 MeasRowEnd = 15 # 调试时只用1行,实际改为34 # 计算结果写入区域:I39-K58(列9-11,行39-58) CalcColBegin = 9 CalcColEnd = 11 CalcRowBegin = 39 def convert_measured(z, measured_vals): """ z: 待求解的真实阻值数组 [R1_real, R2_real, R3_real] measured_vals: 测量得到的等效阻值数组 [R_13, R_23, R_12] """ x, y, w = z R13, R23, R12 = measured_vals F = np.empty(3, dtype=np.float64) # 修正后的方程组:等效测量值等于计算值 F[0] = 1/(1/x + 1/(y + w)) - R13 F[1] = 1/(1/y + 1/(x + w)) - R23 F[2] = 1/(1/w + 1/(x + y)) - R12 return F def main(): # 遍历测量数据行 for idx, row in enumerate(ws.iter_rows(min_row=MeasRowBegin, max_row=MeasRowEnd, min_col=MeasColBegin, max_col=MeasColEnd), start=0): # 提取测量值,转为float,跳过空值 measured_vals = [] for cell in row: val = cell.value if val is None: print(f"第{MeasRowBegin+idx}行存在空值,跳过") measured_vals = None break measured_vals.append(float(val)) if measured_vals is None: continue measured_vals = np.array(measured_vals, dtype=np.float64) print(f"测量值: {measured_vals}") # 用测量值作为初始猜测,提升收敛性 z_guess = measured_vals.copy() # 用partial绑定当前组的测量值,传递给fsolve solve_func = partial(convert_measured, measured_vals=measured_vals) z_sol, infodict, ier, mesg = fsolve(solve_func, z_guess, full_output=True) # 检查求解是否成功(ier=1表示成功) if ier == 1: print(f"真实阻值: {np.round(z_sol, 4)}") # 将结果写入Excel对应行 calc_row = CalcRowBegin + idx for col_offset, val in enumerate(z_sol): ws.cell(row=calc_row, column=CalcColBegin + col_offset, value=np.round(val, 4)) else: print(f"第{MeasRowBegin+idx}行求解失败: {mesg}") # 保存修改后的Excel文件 wb.save("datafile_processed.xlsx") print("计算完成,结果已保存到datafile_processed.xlsx") if __name__ == "__main__": main()
关键说明
- 使用
functools.partial将每组的测量值绑定到目标函数,符合fsolve的参数要求(仅接受待求解变量作为第一个参数) - 初始猜测值用测量值复制,保证迭代起点接近真实解,减少发散概率
- 增加求解结果状态判断(
ier参数),及时发现求解失败的情况 - 结果保留4位小数写入Excel,避免过多无效小数位
- 处理Excel空单元格,防止程序崩溃
内容的提问来源于stack exchange,提问作者Kritischer
相关产品推荐
相关产品推荐

