Scipy minimize多线性约束不满足问题求助
问题分析与解决
核心问题:闭包变量捕获陷阱
你定义子约束的lambda函数犯了Python闭包的常见错误——循环中定义的lambda不会捕获当前循环的变量值,而是会引用循环结束时变量的最终值。这就导致所有子总和约束实际上都在检查最后一组的和是否等于0.2,完全不符合你的约束需求,自然得不到正确结果。
解决方案
1. 修复约束定义
有两种可靠的方式避免闭包陷阱:
方式一:用默认参数绑定循环变量
每次循环时,把当前的分组和目标值绑定到lambda的默认参数中:
cons = [] # 注意:z_group总和是0.25+0.55+0.2=1.0,总和约束是冗余的,直接去掉 for select, target_z in zip(groups, z_group): cons.append({'type': 'eq', 'fun': lambda x, s=select, z=target_z: z - x[s].sum()})
方式二:用functools.partial绑定参数
把约束逻辑抽成独立函数,再用partial绑定参数,可读性更强:
from functools import partial def sub_sum_constraint(x, select, target_z): return target_z - x[select].sum() cons = [] for select, target_z in zip(groups, z_group): cons.append({'type': 'eq', 'fun': partial(sub_sum_constraint, select=select, target_z=target_z)})
2. 移除冗余约束
z_group的总和恰好是1.0,子总和约束已经隐含了x.sum()=1.0的要求,保留总和约束会造成冗余,可能干扰求解器收敛,建议直接删除。
3. 优化初始点(可选)
你原来的初始点x0=np.zeros(10,)不在可行域内(所有x_i为0,不满足子总和约束),可以换成一个满足约束的初始点,帮助求解器更快收敛:
x0 = np.zeros(10,) for select, z in zip(groups, z_group): x0[select] = z / len(select) # 组内均匀分配
完整修正代码
import numpy as np from scipy.optimize import minimize from functools import partial utility_vector = np.array([0.10, 0.08, 0.05, 0.075, 0.32, 0.21, 0.18, 0.05, 0.03, 0.12]) groups = [[0, 1, 2, 3], [4, 5], [6, 7, 8, 9]] z_group = [0.25, 0.55, 0.2] # 生成满足约束的初始点 x0 = np.zeros(10,) for select, z in zip(groups, z_group): x0[select] = z / len(select) def opt_func(x, u, target): utility = (x * u).sum() return (utility - target)**2 # 定义子总和约束函数 def sub_sum_constraint(x, select, target_z): return target_z - x[select].sum() cons = [] # 添加子总和约束 for select, target_z in zip(groups, z_group): cons.append({'type': 'eq', 'fun': partial(sub_sum_constraint, select=select, target_z=target_z)}) bnds = tuple((0, None) for i in range(10)) res = minimize(fun=opt_func, x0=x0, method='trust-constr', bounds=bnds, constraints=tuple(cons), args=(utility_vector, 0.16), tol=1e-6) # 适当提高精度以满足约束 print(res) print(f'\nTotal allocation sum {res.x.sum()}') # 检查子总和约束满足情况 for idx, select in enumerate(groups): diff = z_group[idx] - res.x[select].sum() print(f'{select} fields difference {diff:.6f}')
错误原因复盘
Python的lambda是闭包结构,它不会在定义时复制循环变量的值,而是在调用时去读取变量的当前值。当循环结束后,所有lambda里的idx和select都指向最后一次循环的结果,导致前两个约束完全失效——这是Python循环中定义闭包的典型坑点。
内容的提问来源于stack exchange,提问作者Will_E
相关产品推荐
相关产品推荐

