web-dev-qa-db-fra.com

Restreindre scipy.optimize.minimize aux valeurs entières

J'utilise scipy.optimize.minimize pour optimiser un problème du monde réel pour lequel les réponses ne peuvent être que des nombres entiers. Mon code actuel ressemble à ceci:

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]))

Cela donne:

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]

Mais je veux qu'il soit optimisé avec des valeurs entières (arrondir tout x au nombre entier le plus proche ne donne pas toujours le minimum).

Existe-t-il un moyen d'utiliser scipy.optimize.minimize avec uniquement des valeurs entières?

(Je suppose que je pourrais créer un tableau avec toutes les permutations possibles de x et évaluer f(x) pour chaque combinaison, mais cela ne semble pas très élégant ou rapide Solution.)

15
nicmet

solution de pâte

Après quelques recherches, je ne pense pas que votre fonction objective soit linéaire. J'ai recréé le problème dans la bibliothèque Python pulp mais pulp n'aime pas que nous divisions par un flottant et 'LpAffineExpression'. Cette réponse suggère que la programmation linéaire "ne comprend pas les divisions" mais que ce commentaire est dans le contexte de l'ajout de contraintes, pas de la fonction objective. Ce commentaire m'a montré " programmation fractionnée linéaire mixte en nombres entiers (MILFP) "et sur Wikipedia .

Voici comment vous pourriez le faire en pâte si cela fonctionnait (peut-être que quelqu'un peut comprendre pourquoi):

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)

solution brute avec scipy.optimize

Vous pouvez utiliser brute et des plages de slices pour chaque x dans votre fonction. Si vous avez 3 xs dans votre fonction, vous aurez également 3 slices dans votre plage Tuple. La clé de tout cela est d'ajouter la taille de l'étape de 1 Au slice(start, stop,step) donc 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)

solution itertools

Ou vous pouvez utiliser itertools pour générer toutes les combinaisons:

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)

Remarque: il s'agit d'une version réduite de votre fonction d'origine à titre d'exemple.

4
Jarad

Une chose qui pourrait aider votre problème, vous pourriez avoir une contrainte comme:

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

Cela ne résoudra pas complètement votre problème, l'algorithme essaiera toujours de tricher et vous obtiendrez des valeurs avec un certain niveau d'erreur ~±5e-10 qu'il essaiera toujours d'optimiser juste par l'erreur dans l'algorithme de scipy mais c'est mieux que rien.

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

après avoir testé ce processus sur certaines optimisations, je connais la solution, ce processus est plus sensible aux valeurs initiales que la recherche sans contrainte, il obtient des réponses assez précises mais la solution peut en fait ne pas trouver la vraie valeur, vous avez essentiellement besoin du grand saut du processus d'optimisation (ce qu'il utilise pour s'assurer qu'il n'optimise pas au minimum local) pour rechercher l'espace d'échantillonnage car les incréments plus petits ne sont généralement pas assez forts pour passer au nombre suivant.

1
Simon Jones

Fonction générique pour la solution bruteforce. Fait un peu mieux que brute dans scipy, car scipy exécute en fait une fonction avec des nombres flottants, pas uniquement des entiers, bien que la plage le dise explicitement, comme l'a déclaré 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)}))
0