将scipy.optimize.minimize限制为整数值

时间:2016-08-30 20:49:44

标签: python optimization scipy minimize

我正在使用scipy.optimize.minimize来优化现实问题,答案只能是整数。我目前的代码如下:

from scipy.optimize import minimize

def f(x):
    return (481.79/(5+x[0]))+(412.04/(4+x[1]))+(365.54/(3+x[2]))+(375.88/(3+x[3]))+(379.75/(3+x[4]))+(632.92/(5+x[5]))+(127.89/(1+x[6]))+(835.71/(6+x[7]))+(200.21/(1+x[8]))

def con(x):
    return sum(x)-7

cons = {'type':'eq', 'fun': con}

print scipy.optimize.minimize(f, [1,1,1,1,1,1,1,0,0], constraints=cons, bounds=([0,7],[0,7],[0,7],[0,7],[0,7],[0,7],[0,7],[0,7],[0,7]))

这会产生:

x: array([  2.91950510e-16,   2.44504019e-01,   9.97850733e-01,
     1.05398840e+00,   1.07481251e+00,   2.60570253e-01,
     1.36470363e+00,   4.48527831e-02,   1.95871767e+00]

但是我想用整数值进行优化(将所有x四舍五入到最接近的整数并不总是给出最小值)。

有没有办法只使用scipy.optimize.minimize整数值?

(我想我可以创建一个包含x所有可能排列的数组,并为每个组合计算f(x),但这似乎不是一个非常优雅或快速的解决方案。)

4 个答案:

答案 0 :(得分:4)

纸浆溶液

经过一些研究,我不认为你的目标函数是线性的。我在Python pulp库中重新创建了这个问题但是纸浆不喜欢我们用浮点数和'LpAffineExpression'来划分。 This answer表明线性规划“不理解分歧”,但该评论是在添加约束的背景下,而不是目标函数。该评论向我指出了“Mixed Integer Linear Fractional Programming (MILFP)”和Wikipedia

如果它真的起作用(也许有人能弄明白为什么),你可以用纸浆做到这一点:

import pulp

data = [(481.79, 5), (412.04, 4), (365.54, 3)] #, (375.88, 3), (379.75, 3), (632.92, 5), (127.89, 1), (835.71, 6), (200.21, 1)]
x = pulp.LpVariable.dicts('x', range(len(data)), lowBound=0, upBound=7, cat=pulp.LpInteger)

numerator = dict((i,tup[0]) for i,tup in enumerate(data))
denom_int = dict((i,tup[1]) for i,tup in enumerate(data))

problem = pulp.LpProblem('Mixed Integer Linear Programming', sense=pulp.LpMinimize)

# objective function (doesn't work)
# TypeError: unsupported operand type(s) for /: 'float' and 'LpAffineExpression'
problem += sum([numerator[i] / (denom_int[i] + x[i]) for i in range(len(data))])

problem.solve()

for v in problem.variables():
  print(v.name, "=", v.varValue)

使用scipy.optimize的粗暴解决方案

您可以为函数中的每个brute使用slice和范围x s。如果你的函数中有3个x,那么你的范围元组中也会有3 slice个。所有这一切的关键是将1 slice(start, stop, step步骤大小添加到),以便slice(#, #, 1)

from scipy.optimize import brute
import itertools

def f(x):
  return (481.79/(5+x[0]))+(412.04/(4+x[1]))+(365.54/(3+x[2]))

ranges = (slice(0, 9, 1),) * 3
result = brute(f, ranges, disp=True, finish=None)
print(result)

itertools解决方案

或者您可以使用itertools生成所有组合:

combinations = list(itertools.product(*[[0,1,2,3,4,5,6,7,8]]*3))

values = []
for combination in combinations:
  values.append((combination, f(combination)))

best = [c for c,v in values if v == min([v for c,v in values])]
print(best)

注意:这是原始功能的缩小版本,例如用途。

答案 1 :(得分:2)

这是一种使用Python Gekko(我维护的软件包)解决混合整数非线性编程问题的方法:

from gekko import GEKKO

m = GEKKO(remote=False)
x = m.Array(m.Var,9,lb=0,ub=7,integer=True)

def f(x):
    return (481.79/(5+x[0]))+(412.04/(4+x[1]))\
           +(365.54/(3+x[2]))+(375.88/(3+x[3]))\
           +(379.75/(3+x[4]))+(632.92/(5+x[5]))\
           +(127.89/(1+x[6]))+(835.71/(6+x[7]))\
           +(200.21/(1+x[8]))

m.Minimize(f(x))
m.Equation(sum(x)==7)
m.options.SOLVER=1
m.solve()
print(x)

这提供了解决方案:

 ---------------------------------------------------
 Solver         :  APOPT (v1.0)
 Solution time  :  0.0529 sec
 Objective      :  859.5269999999999
 Successful solution
 ---------------------------------------------------


[[0.0] [0.0] [1.0] [1.0] [1.0] [0.0] [1.0] [0.0] [3.0]]

答案 2 :(得分:1)

可能有助于解决问题的一件事就是:

max([x-int(x)])=0

这不会完全解决你的问题,算法仍然会尝试欺骗,你会得到一些错误级别~±5e-10的值,它仍会尝试优化,只是因为scipy中的错误而且# 39; s算法,但它比没有好。

cons = ({'type':'eq', 'fun': con},
        {'type':'eq','fun': lambda x : max([x[i]-int(x[i]) for i in range(len(x))])})

在一些优化上测试了这个过程我知道解决方案,这个过程对初始值比对无约束搜索更敏感,它得到相当准确的答案但是解决方案可能实际上找不到真正的值,你基本上要求优化过程的大跳跃(用于确保它没有优化到局部最小值)来搜索样本空间,因为较小的增量通常不足以移动到下一个数字。

答案 3 :(得分:0)

强力解决方案的通用功能。有点比scipy更好的工作,因为scipy实际上使用浮点数运行函数,而不仅仅是整数,尽管范围明确地这样说,正如Jarad所述

def brute(func, arg_ranges, finish=min):
if isinstance(arg_ranges, dict):
    args = {k:np.unique(np.hstack([a for r in rs for a in r]) if isinstance(rs, list) else [a for a in rs]) for k,rs in arg_ranges.items()}
    print(args)
    return finish([(dict(zip(args.keys(), vs)), func(**dict(zip(args.keys(), vs)))) for vs in itertools.product(*args.values())], key=lambda x: x[1])
elif isinstance(arg_ranges, list):
    return finish([(i, func(i)) for r in arg_ranges for i in r], key=lambda x: x[1])
else:
    return finish([(i, func(i)) for i in arg_ranges], key=lambda x: x[1])

print(brute(lambda x,y: x / (y + 2), {'x':[range(1,5,2), range(0,6,1)], 'y':range(2,5,1)}))