问题描述
我正在通过将 Cplex 作为库导入 Python 来求解整数编程模型。假设优化问题具有以下形式的约束 (Ax = b)
:
x0+x1+x1+x3 = 1
此约束中 x 变量的索引为 0、1、1 和 3。它们存储在一个列表中:indices=[0,1,3]
这些变量的系数也存储在另一个列表coeff = [1,1]
Cplex 不能接受重复的索引,所以约束应该是这样的:
x0+2x1+x3 = 1
所以这两个列表应该像这样更新:
indices=[0,3]
coeff = [1,2,1]
我有这个函数,它将索引和系数作为两个参数,并为我提供操作列表:
def manipulate(indices,coeff):
u = np.unique(indices)
sums = { ui:np.sum([ coeff[c[0]] for c in np.argwhere(indices == ui) ]) for ui in u }
return list(sums.keys()),list(sums.values())
因此,manipulate([0,3],[1,1])
返回 ([0,1])
。
我的问题是当我有这么多变量时,列表的长度可能是一百万,而我有数百万个这样的约束。当我使用 cplex 解决我的优化问题时,程序变得非常慢。我跟踪了每个函数所花费的时间,并意识到我的代码中最耗时的部分是这些计算,我猜这是因为使用了 numpy。我需要让这个函数更有效率,希望能减少执行时间。你能和我分享关于如何改变函数操作的任何想法和建议吗?
非常感谢,
解决方法
如果不使用基于本机代码的扩展,可能总会有妥协:
-
Numpy / Vectorization 方法会遗漏基于哈希的算法,恕我直言,将遭受算法复杂性的缺点(例如,需要排序;需要进行多次传递……)
-
基于 Python 的基于哈希的方法会受到缓慢循环的影响。
尽管如此,我确实认为,您的方法在某种程度上接近两全其美,并且您可能会有所收获。
一些代码
from time import perf_counter as pc
from collections import defaultdict
import numpy as np
np.random.seed(0)
def manipulate(indices,coeff):
u = np.unique(indices)
sums = { ui:np.sum([ coeff[c[0]] for c in np.argwhere(indices == ui) ]) for ui in u }
return list(sums.keys()),list(sums.values())
# this assumes pre-sorted indices
def manipulate_alt(indices,coeff):
unique,indices = np.unique(indices,return_index=True)
cum_coeffs = np.cumsum(coeff)
bla = cum_coeffs[indices-1]
blub = np.roll(bla,-1)
bluab = np.ediff1d(blub,to_begin=blub[0])
return unique.tolist(),bluab.tolist()
def manipulate_pure_py(indices,coeff):
final = defaultdict(int)
n = len(indices)
for i in range(n):
final[indices[i]] += coeff[i]
return list(final.keys()),list(final.values())
# BAD NON-SCIENTIFIC BENCHMARK
# ----------------------------
ITERATIONS = 10
SIZE = 1000000
accu_time_manipulate = 0.0
accu_time_manipulate_alt = 0.0
accu_time_manipulate_py = 0.0
for i in range(ITERATIONS):
indices = np.sort(np.random.randint(0,10000,size=SIZE))
coeffs = np.random.randint(1,100,size=SIZE)
start = pc()
sol = manipulate(indices,coeffs)
end = pc()
accu_time_manipulate += (end-start)
start = pc()
sol_alt = manipulate_alt(indices,coeffs)
end = pc()
accu_time_manipulate_alt += (end-start)
start = pc()
sol_py = manipulate_pure_py(indices,coeffs)
end = pc()
accu_time_manipulate_py += (end-start)
assert sol[0] == sol_alt[0]
assert sol[1] == sol_alt[1]
assert sol[0] == sol_py[0]
assert sol[1] == sol_py[1]
print(accu_time_manipulate)
print(accu_time_manipulate_alt)
print(accu_time_manipulate_py)
时间
164.34614480000005
0.24998690000001744
8.751806900000059
备注
- 基准很糟糕
- 我 100% 不相信基于 numpy 的 hack
- 只需使用简单的基于字典的方法(或走本机代码路线)
- (取决于建模任务:您也可以在构建模型时结合此合并:直接进行,不要延迟到后期处理)