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.)
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 slice
s pour chaque x
dans votre fonction. Si vous avez 3 x
s dans votre fonction, vous aurez également 3 slice
s 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.
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.
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)}))