Aller au contenu principal

La recherche locale

Ce que ce chapitre apporte7 points
  • Définir un mouvement, un voisin, un voisinage, et compter les voisins d'une solution.
  • Écrire les voisinages par échange, par insertion et par 2-opt sur une permutation, et dire ce que chacun garantit.
  • Expliquer pourquoi le 2-opt défait un croisement, et calculer son effet avec quatre distances au lieu de tout recalculer.
  • Écrire une descente, en distinguant le premier voisin qui améliore du meilleur voisin.
  • Reconnaître un optimum local, et dire pourquoi il dépend du voisinage et du point de départ.
  • Lancer plusieurs descentes depuis des départs différents et lire ce que la dispersion des résultats apprend.
  • Écrire le squelette chercher(initiale, voisin, cout, accepter, iterations) et l'appliquer à trois problèmes de Valdrome en ne changeant que la représentation, le voisin et le coût.
Chez Valdrome Mécanique, la règle du plus proche voisin a fourni une trajectoire de perçage pour la plaque P-217 : 692,06 mm, soit 23,1 secondes de déplacement. C'est correct, et c'est aussi une trajectoire dont un œil exercé voit tout de suite qu'elle revient sur ses pas. Le réflexe d'un opérateur n'est pas de tout recommencer, c'est de la retoucher : inverser un passage, permuter deux trous, et garder la retouche si elle raccourcit. Ce chapitre fait de ce réflexe une méthode. Il définit ce qu'est une retouche, le voisinage, montre comment on descend de retouche en retouche jusqu'à ne plus rien pouvoir améliorer, et pose la limite de la démarche : on s'arrête à un optimum local, qui n'est presque jamais l'optimum. Il livre aussi, en une vingtaine de lignes de Python, le squelette que le recuit, le tabou et les chapitres suivants reprendront en n'y changeant qu'une pièce.

Partir d'une solution et la retoucher

Le chapitre sur les heuristiques constructives a construit des solutions d'un seul jet : le plus proche voisin pour la plaque, le rangement par longueur décroissante pour les barres, une règle de priorité pour la machine. Chaque règle décide vite et ne revient jamais sur une décision. La recherche locale fait l'inverse : elle prend une solution déjà complète, quelle qu'en soit l'origine, et la modifie par de petits changements.

Sur la plaque P-217, le plus proche voisin donne l'ordre T1, T7, T10, T11, T5, T6, T12, T8, T3, T2, T9, T4. Les dernières étapes traversent la plaque pour aller chercher T2, puis reviennent vers T9 et T4 qu'on avait laissés derrière soi : la règle, qui ne regarde que le trou le plus proche, s'est acculée. Une retouche possible : retourner le passage T8, T3, T2, ce qui donne T1, T7, T10, T11, T5, T6, T12, T2, T3, T8, T9, T4.

Ce que change une retouche

  • 1.

    La retouche remplace deux trajets, T12 vers T8 (25 mm) et T2 vers T9, par T12 vers T2 et T8 vers T9. T12 est en (150 ; 60), T8 en (130 ; 45), T2 en (180 ; 105), T9 en (70 ; 60). Quelle est la longueur de T2 vers T9, en mm ?

  • 2.

    Quelle est la longueur de T12 vers T2, en mm ?

  • 3.

    Quelle est la longueur de T8 vers T9, en mm ?

  • 4.

    De combien la longueur de la tournée varie-t-elle, en mm (un nombre négatif est un gain) ?

Presque 28 mm de gagnés, en ne touchant que deux trajets. L'idée tient dans cette phrase : pour juger une retouche, il suffit de regarder ce qu'elle change.

Définitions

Une solution courante est la solution qu'on est en train de retoucher. Un mouvement est une modification élémentaire d'une solution : permuter deux trous, retourner un segment de la tournée. Un voisin de la solution courante est le résultat d'un mouvement. Le voisinage est l'ensemble de tous les voisins, et on parle de sa taille pour le nombre de ses éléments.

Une descente part d'une solution, prend un voisin qui est meilleur, s'y installe, et recommence, jusqu'à ce qu'aucun voisin ne soit meilleur. La solution où elle s'arrête est un optimum local : aucun de ses voisins ne fait mieux.

Un mouvement ne se définit pas sur « la solution » en général : il se définit sur sa représentation, celle qu'a posée le premier chapitre. Permuter deux éléments d'une permutation donne encore une permutation, donc encore une tournée valide. Changer un bit d'une sélection de commandes donne encore une sélection, mais pas toujours une sélection qui tient dans la capacité. Le voisinage est donc la moitié de la représentation : deux voisinages sur la même représentation donnent deux recherches différentes.

Le voisinage : trois façons de retoucher un ordre

Pour un ordre de perçage, trois mouvements reviennent partout.

Trois voisinages sur une permutation

L'échange permute deux éléments d'un ordre : les trous aux positions ii et jj se font mutuellement place.

L'insertion retire un élément et le replace ailleurs : le trou en position ii est sorti de l'ordre, puis réinséré pour finir en position jj.

Le 2-opt choisit deux positions i<ji < j et retourne le segment qui les sépare : les éléments de la position ii à la position jj sont lus à l'envers. Dans la tournée, cela remplace deux trajets par deux autres.

Un exemple sur cinq trous, l'ordre 1, 2, 3, 4, 5 :

MouvementRésultat
échange des positions 1 et 4 (les trous 2 et 5, positions numérotées à partir de 0)1, 5, 3, 4, 2
insertion du trou en position 1 pour qu'il finisse en position 31, 3, 4, 2, 5
2-opt sur les positions 1 à 3 (le segment 2, 3, 4)1, 4, 3, 2, 5

Les trois mouvements ne se valent pas. L'échange change jusqu'à quatre trajets, l'insertion trois, le 2-opt deux seulement. Un mouvement qui change peu de trajets change peu la longueur : c'est ce qu'on attend d'un « petit » changement, et ce qui rend le 2-opt bien adapté aux tournées.

Chaque voisinage a une taille, et elle se compte. Pour nn trous, il y a n(n−1)/2n(n-1)/2 paires de positions, donc n(n−1)/2n(n-1)/2 échanges. L'insertion offre (n−1)2(n-1)^2 mouvements, car déplacer un trou d'une position vers la suivante revient à déplacer le suivant d'une position vers la précédente, et on ne le compte qu'une fois. Le 2-opt offre n(n−1)/2−1n(n-1)/2 - 1 mouvements : retourner tout l'ordre redonne la même tournée parcourue à l'envers, ce n'est pas un voisin. Le code les énumère et les compte.

main.py
Sortie
>_ Prêt à exécuter…

Pour les douze trous de P-217, 66 échanges, 121 insertions et 65 mouvements 2-opt : quelques centaines de voisins au plus, qu'on examine en un clin d'œil. Pour une plaque de soixante trous, il y en a 1 770, 3 481 et 1 769. Le voisinage grossit comme le carré du nombre de trous, alors que l'espace des solutions grossit comme sa factorielle : on explore toujours une infime partie de l'espace, mais on l'explore là où la solution courante a des chances d'être améliorée.

Les autres problèmes de Valdrome ont les leurs, et le tableau suivant les met côte à côte. Dans chaque cas, le voisinage se lit sur la représentation posée au premier chapitre.

ProblèmeReprésentationUn mouvementNombre de voisins
Tournée de 12 trousun ordre des 12 trousretourner un segment (2-opt)65
Machine, 10 ordres de fabricationun ordre de passagepermuter deux ordres45
Affectation, 8 opérateursposte_de[i], une permutationéchanger les postes de deux opérateurs28
Commandes, 10 commandesdix zéros et unschanger un bit10
Un voisinage doit rester dans l'espace des solutions
Un mouvement sur une permutation en donne une autre, sans test à ajouter. Sur dix zéros et uns, changer un bit peut sortir de la capacité : le voisin est alors irréalisable, et il faut le rejeter, le réparer ou le pénaliser, comme au premier chapitre. Un mouvement qui écrit deux fois le même trou casse la représentation : c'est une erreur du voisinage, pas une trouvaille.
Grand voisinage, petit voisinage
Un voisinage étendu (l'insertion, ou le 2-opt sur une grande plaque) contient plus de retouches possibles : la solution où l'on s'arrête est plus difficile à améliorer, donc en général meilleure. Il coûte aussi plus à examiner : chaque pas de la descente évalue plus de voisins. Un voisinage minuscule est rapide et se bloque tout de suite. Mais le nombre de voisins n'est pas tout : la nature du mouvement compte autant. Sur P-217, l'échange compte 66 voisins, un de plus que le 2-opt, et ses descentes finissent pourtant plus mal (les redémarrages, plus bas, le mesurent) : un échange de deux trous éloignés change quatre trajets d'un coup, un saut plutôt qu'une retouche. Il n'y a pas de bon choix universel, il y a un compromis à mesurer.

Le 2-opt : défaire un croisement

Le mouvement de la tournée, c'est le 2-opt. Il a une propriété géométrique qui fait son succès.

Prenons deux trajets d'une tournée qui se croisent, l'un de aa à bb, l'autre de cc à dd. Retirer ces deux trajets sépare la tournée en deux morceaux ; on peut les recoller de deux manières, et une seule redonne une tournée d'un seul tenant : aa vers cc et bb vers dd, ce qui retourne le segment entre bb et cc. Dans le plan, en distance euclidienne, la somme des deux nouveaux trajets est strictement plus courte que celle des deux anciens, par l'inégalité triangulaire : les deux trajets qui se croisent sont les diagonales d'un quadrilatère convexe, et la somme des diagonales dépasse celle de deux côtés opposés. Une tournée où deux trajets se croisent n'est jamais optimale, et le 2-opt sait la raccourcir.

La réciproque est fausse : une tournée peut n'avoir aucun croisement et se laisser encore améliorer, ou l'être par un mouvement qui ne défait aucun croisement. Le 2-opt est un voisinage, pas seulement une chasse aux croisements.

Le calcul de la longueur du voisin est plus économique qu'il n'y paraît. Retourner le segment de la position ii à la position jj ne change que deux trajets, de aa à bb et de cc à dd, où aa est le point avant la position ii, bb celui en position ii, cc celui en position jj et dd celui qui suit. Le reste de la tournée est lu à l'envers ou non, la distance est la même dans les deux sens : rien d'autre ne bouge. La variation de longueur vaut

Δ=dist(a,c)+dist(b,d)−dist(a,b)−dist(c,d)\Delta = \mathrm{dist}(a, c) + \mathrm{dist}(b, d) - \mathrm{dist}(a, b) - \mathrm{dist}(c, d)

où dist\mathrm{dist} est la distance entre deux points (le dist du code qui suit) : une tournée de soixante trous se juge ainsi en quatre distances au lieu de soixante et une. Le code vérifie que les deux calculs concordent sur le mouvement retouché plus haut.

main.py
Sortie
>_ Prêt à exécuter…

Le voisin est évalué en quatre distances, alors que le recalcul complet en demande treize. Sur une grande plaque l'écart devient considérable, et c'est ce qui rend le 2-opt praticable : une descente évalue des dizaines de milliers de voisins.

La figure suivante fait retoucher la tournée à la main. Elle part du plus proche voisin. Choisir deux arêtes de la tournée, en cliquant dessus, fait apparaître ce que le mouvement retirerait, ce qu'il ajouterait, et sa variation de longueur ; il ne s'applique que sur demande. On peut aussi changer de voisinage, ou lancer la descente entière.

Retoucher une tournée12 trous, voisinage 2-opt
Longueur
692,1 mm
Mouvements faits
0
Voisins (2-opt)
65
Qui raccourcissent
5
Départ
Voisinage
Descente
départT1T2T3T4T5T6T7T8T9T10T11T12

Trait rouge pointillé : arête retirée. Trait noir en pointillés serrés : arête ajoutée. Trait orangé épais : arête qui en croise une autre. Départ : le losange.

  1. T1
  2. T7
  3. T10
  4. T11
  5. T5
  6. T6
  7. T12
  8. T8
  9. T3
  10. T2
  11. T9
  12. T4
Cliquer deux arêtes de la tournée (ou Tab puis Entrée) : le segment entre elles serait retourné.
La plaque P-217, retouchée à partir du plus proche voisin. Le losange est le départ de l'outil.
À manipuler
Les arêtes qui en croisent une autre sont tracées en orangé et épaisses. Le bouton « Montrer deux arêtes qui se croisent » prévisualise un mouvement qui défait l'un de ces croisements : les arêtes retirées sont rouges en pointillés, les arêtes ajoutées noires en pointillés serrés, et la variation s'affiche en millimètres. Appliquer, et regarder le segment se retourner. Ensuite choisir soi-même deux arêtes, en cherchant une paire qui raccourcit et une paire qui rallonge : une paire qui rallonge se voit tout de suite à la variation positive. Défaire est toujours possible, au bouton ou par Retour arrière. Enfin, cliquer « Un pas de descente » plusieurs fois, en comparant « Premier voisin qui raccourcit » et « Meilleur voisin » : le second va plus loin par pas, le premier va plus vite par pas, et le compteur « Qui raccourcissent » descend jusqu'à zéro.

La descente

Une descente répète un même geste : examiner les voisins de la solution courante, se déplacer vers un voisin meilleur, recommencer ; elle s'arrête quand aucun voisin n'est meilleur. Deux variantes se distinguent par la façon de choisir le voisin.

Premier voisin, meilleur voisin

La descente au premier voisin qui améliore parcourt le voisinage dans un ordre fixé et s'installe dès qu'elle en trouve un meilleur, sans regarder les suivants.

La descente au meilleur voisin parcourt tout le voisinage, puis s'installe sur le meilleur voisin trouvé.

La première fait des pas peu coûteux et souvent nombreux ; la seconde, des pas plus coûteux et moins nombreux. Aucune n'est plus juste que l'autre : elles s'arrêtent parfois sur des solutions différentes, et le code suivant les compare sur la plaque, depuis deux points de départ. Ses deux dernières parties servent aux deux sections suivantes.

main.py
Sortie
>_ Prêt à exécuter…

Le premier tableau qui s'affiche est riche, et il faut le lire avec méthode. Depuis le plus proche voisin, le 2-opt descend de 692,06 à 633,23 mm en trois pas au meilleur voisin (692,06, puis 664,14, puis 635,83, puis 633,23) et en cinq au premier voisin : les deux arrivent au même endroit. L'échange, lui, s'arrête à 661,53 mm, en deux pas. Depuis l'ordre d'écriture, tout change : le 2-opt s'arrête à 647,35 mm, l'échange à 724,18 mm au premier voisin et à 688,22 mm au meilleur, et l'insertion, selon la variante, à 633,23 ou à 647,35 mm. Le résultat d'une descente dépend de trois choses : le point de départ, le voisinage, et la variante.

La dernière colonne, « voisins évalués », compte le travail total, et elle ne donne pas toujours raison à la première variante : par pas, le premier voisin coûte moins, mais il faut plus de pas. Depuis l'ordre d'écriture, le 2-opt évalue 574 voisins au premier voisin contre 455 au meilleur, alors que l'insertion en évalue 524 contre 1 089 : au total, le premier voisin n'est pas toujours le moins cher.

La descente en une phrase
Tant qu'un voisin fait mieux, s'y installer ; s'arrêter quand aucun ne fait mieux. Elle ne finit jamais dans le vide : chaque pas raccourcit strictement, et il n'y a qu'un nombre fini de solutions, donc elle s'arrête forcément.

L'optimum local

Une descente s'arrête quand aucun voisin n'est meilleur. Ce n'est pas la même chose que « la solution est la meilleure ».

Optimum local

Une solution est un optimum local pour un voisinage quand aucun de ses voisins n'a un meilleur coût. Un optimum global est meilleur que toute autre solution de l'espace. Tout optimum global est un optimum local, pour n'importe quel voisinage ; la réciproque est fausse.

Le bloc de code de la section précédente le montre sur P-217, dans sa seconde partie, sous le titre « optimums locaux » : il fait descendre par le 2-opt depuis l'ordre d'écriture, puis compte, pour chacun des trois voisinages, les voisins qui raccourcissent encore la tournée obtenue. Rien à recopier : le préambule (plaque, voisinages, descente) est le même.

Deux constats. D'abord, la tournée T1, T7, T9, T11, T10, T8, T3, T12, T2, T6, T5, T4 mesure 647,35 mm : aucun voisin ne la raccourcit, quel que soit le voisinage, et pourtant la meilleure tournée de P-217 mesure 633,23 mm, deux pour cent de moins. On est piégé : pour la sortir de là, il faudrait passer par des tournées plus longues, ce qu'une descente refuse par construction. Ensuite, la tournée à 661,53 mm, optimum local pour l'échange, ne l'est pas pour le 2-opt : un seul mouvement de plus la ramène à 633,23 mm. Un optimum local n'est pas une propriété de la tournée, c'est une propriété de la tournée et du voisinage.

Un optimum local n'est pas un optimum
Quand une descente s'arrête, il est tentant de dire « c'est fini, c'est la meilleure ». Elle n'a fait que constater qu'aucune retouche simple ne l'améliore. La valeur qu'elle rend est le fond du creux où la descente est tombée, pas le minimum du problème : seule une borne, comme celles du chapitre sur les bornes, dit si l'on peut espérer mieux. Sans borne, la valeur trouvée est seulement « la meilleure connue », et il faut l'écrire ainsi.
Vérification rapideon peut se reprendre

1.Une descente 2-opt s'arrête sur une tournée : aucun des 65 voisins ne la raccourcit. Que peut-on affirmer ?

2.Deux descentes 2-opt partent de deux ordres différents et s'arrêtent à 633,23 mm et à 647,35 mm. Que peut-on en conclure ?

Sur une grande plaque

Douze trous se retouchent à la main. Pour éprouver la méthode, Valdrome a une plaque plus grande, P-600 : soixante trous sur une plaque de 600 par 400 mm, l'outil partant de l'origine et y revenant, à 30 mm par seconde comme sur P-217. Ses coordonnées, entières en millimètres, sont engendrées par une formule reproductible, un générateur congruentiel de graine 598 : chacun peut la regénérer, elle ne change jamais. La graine 598 est la première en dessous de 600 pour laquelle aucun trou n'a deux voisins exactement à la même distance, ce qui rendrait le plus proche voisin ambigu. La figure suivante fait retoucher cette plaque. Le nombre de voisins y est grand, et le compteur « Qui raccourcissent » montre l'état de la descente. Aucune référence n'est donnée avant l'essai.

Retoucher une tournée60 trous, voisinage 2-opt
Longueur
3588,9 mm
Mouvements faits
0
Voisins (2-opt)
1769
Qui raccourcissent
51
Départ
Voisinage
Descente
départ

Trait rouge pointillé : arête retirée. Trait noir en pointillés serrés : arête ajoutée. Trait orangé épais : arête qui en croise une autre. Départ : le losange.

Cliquer deux arêtes de la tournée (ou Tab puis Entrée) : le segment entre elles serait retourné.
La plaque P-600 : soixante trous engendrés par une formule, sur 600 par 400 mm. Départ : le plus proche voisin.
À manipuler
Lancer d'abord « Descendre jusqu'à l'optimum local » avec le 2-opt, en notant la longueur atteinte et le nombre de pas. Puis défaire, et essayer l'autre variante (premier voisin qui raccourcit, ou meilleur voisin) : les deux s'arrêtent à des longueurs différentes. Recommencer avec l'échange, puis avec l'insertion, depuis chacun des trois départs (écriture, plus proche voisin, hasard) : neuf essais, neuf longueurs, et la tournée au plus court n'est pas toujours celle qu'on attendait. Le départ « Au hasard » est tiré au moment du clic, propre à chaque lecteur : « Autre tirage » en donne un autre, et les longueurs obtenues depuis ces départs varient d'un lecteur à l'autre. Une fois arrivé à un optimum local, changer de voisinage relance la descente : voir de combien. Quand une longueur paraît satisfaisante, cliquer « Comparer à la meilleure connue » : la figure donne alors la référence et l'écart.

Le code suivant engendre la plaque, applique le plus proche voisin, puis le 2-opt au meilleur voisin avec la variation en quatre distances, et enfin cinquante descentes depuis des ordres tirés au hasard (graine 598). Sa dernière partie calcule deux bornes, commentées plus bas.

main.py
Sortie
>_ Prêt à exécuter…

Le plus proche voisin mesure 3 588,87 mm, le 2-opt le ramène à 2 944,01 mm en treize pas, soit 18 % de moins. Ce n'est plus une affaire de coup d'œil. Quelle est la distance à l'optimum ? Pour soixante trous, la force brute est hors de question, mais un solveur exact y parvient : milp, celui du chapitre 3, alimenté par une formulation de la tournée que ce parcours ne détaille pas (une variable 0 ou 1 par arête, exactement deux arêtes en chaque point, puis des coupes qui interdisent les tournées en plusieurs boucles, ajoutées au fur et à mesure tant que la solution en comporte plusieurs), démontre que la meilleure tournée de P-600 mesure 2 788,70 mm. Il faut quelques tours de résolution, de moins d'une seconde chacun : cela tient à la taille de cette instance, et le chapitre 3 a montré que l'exact explose quand elle grossit. Ce n'est pas une « meilleure connue » : c'est l'optimum, prouvé. Le plus proche voisin est donc à 28,7 % de l'optimum, le plus proche voisin suivi du 2-opt à 5,6 %, et la meilleure des cinquante descentes (graine 598) à 0,8 %.

Deux bornes simples, que la dernière partie du bloc précédent calcule avec networkx, encadrent l'optimum par en dessous sans rien résoudre. Une tournée privée d'une arête est un arbre couvrant, donc l'arbre couvrant minimal de la plaque, départ compris, est une borne inférieure. Le 1-arbre la resserre : l'arbre couvrant minimal des trous seuls, plus les deux plus courts trajets du départ, car la tournée arrive au départ et en repart.

L'arbre couvrant minimal vaut 2 286,36 mm et le 1-arbre 2 439,15 mm, soit 87,5 % de l'optimum. Ce sont des bornes lâches, comme celles du chapitre 3 avant la relaxation, mais elles s'obtiennent en une fraction de seconde et sans connaître l'optimum. Avec le 1-arbre pour seule référence, le 2-opt depuis le plus proche voisin (2 944,01 mm) est garanti à au plus 20,7 % de l'optimum, et la meilleure des cinquante descentes (2 810,44 mm) à au plus 15,2 % : l'écart garanti du chapitre 3, sans avoir résolu le problème.

Les redémarrages

Une descente s'arrête dans le premier creux qu'elle rencontre, et le creux dépend du point de départ. Il y en a plusieurs, plus ou moins profonds. L'idée des redémarrages multiples est de ne pas parier sur un seul départ : on lance plusieurs descentes, chacune depuis une solution de départ différente (typiquement tirée au hasard), et on garde le meilleur optimum local rencontré. C'est la plus simple des stratégies pour sortir d'un creux, et elle est étonnamment efficace quand la descente est rapide.

Le code de ces essais est déjà écrit : c'est la dernière partie du bloc de la section « La descente », sous le titre # ---- Suite : les redemarrages. Rien à recopier : la plaque, les trois voisinages et la descente sont ceux de plus haut. Pour chacun des trois voisinages, il lance cent descentes au meilleur voisin depuis cent ordres tirés au hasard, compte les optimums locaux distincts, puis détaille les plus fréquents pour le 2-opt.

Sur P-217, cent descentes 2-opt depuis cent ordres tirés au hasard (graine 217) se répartissent en sept optimums locaux différents, du meilleur (633,23 mm, atteint 38 fois) au pire (679,32 mm). Le voisinage compte autant que le nombre d'essais, et ce n'est pas sa taille qui décide : l'échange (66 voisins) est plus grand que le 2-opt (65), et pourtant ses descentes ne trouvent la meilleure tournée que 6 fois sur 100 et se dispersent sur 49 optimums locaux, jusqu'à 779,42 mm, parce qu'un échange de deux trous éloignés change quatre trajets d'un coup. L'insertion (121 voisins, trois trajets changés) la trouve 62 fois. Dans tous les cas, le meilleur des essais est meilleur que la moyenne des essais : c'est ce que les redémarrages achètent, en temps de calcul.

La figure suivante le fait sentir sur P-600, où les creux sont nombreux. Chaque descente est lancée depuis un ordre tiré au hasard, et son optimum local est rangé dans un histogramme.

Descentes depuis des départs différents60 trous, voisinage 2-opt
Descentes
0
Meilleure vue
aucune
Moyenne
aucune
Optimums distincts
0
Voisinage

Départs tirés au hasard : la série est choisie au premier lancement, et chaque lecteur a la sienne.

Chaque descente ajoutera un optimum local ici.

Chaque barre compte les descentes dont l'optimum local tombe dans son intervalle. Contour épais : la classe de la meilleure.

La meilleure tournée vue jusqu'ici. Départ : le losange.

Lancer des descentes : chacune part d'un ordre tiré au hasard et s'arrête à un optimum local.
Descentes depuis des ordres tirés au hasard sur P-600 : où s'arrêtent-elles ?
À manipuler
Chaque lecteur a ses propres tirages : la figure tire une série de départs au hasard au premier lancement, et l'affiche sous les compteurs (« Série n° ... »). Deux lecteurs ne voient donc pas la même chose, et rien ne garantit de voir l'optimum : il n'apparaît que si le hasard a envoyé une descente au bon endroit, ce qui est rare sur soixante trous. Lancer dix descentes avec le 2-opt, lire l'histogramme : les longueurs des optimums locaux s'étalent, et la meilleure vue n'est pas la moyenne. Lancer cinquante descentes de plus et surveiller la « Meilleure vue » : elle baisse par à-coups, de moins en moins souvent, alors que la moyenne ne bouge presque plus. Se demander combien de descentes il faudrait pour gagner encore quelques millimètres, et si cela vaut le temps de calcul. Puis recommencer avec l'échange et avec l'insertion, qui gardent la série : les mêmes départs, donc une comparaison juste, mais la forme de l'histogramme change, sa position aussi. « Nouvelle série » tire d'autres départs : refaire l'expérience montre ce qui tient à la méthode et ce qui tient au hasard. Lorsque le résultat paraît satisfaisant, cliquer « Comparer à la meilleure connue ».

Les chiffres du texte (meilleure, médiane et pire des cinquante descentes de P-600, plus haut) sont ceux d'une graine fixée, 598 : ils se rejouent en Python à l'identique, mais ceux de la figure varient d'un lecteur à l'autre, et d'une série à l'autre.

Les redémarrages ont un défaut : chaque descente repart de zéro, et ne profite pas de ce que les précédentes ont trouvé. Les chapitres suivants reprennent l'idée par un autre bout : ne plus s'arrêter dans un creux, mais se donner le moyen d'en sortir.

Le squelette commun

Presque toutes les méthodes de ce parcours ont la même charpente : une solution courante, un voisin tiré, un coût mesuré, une décision de s'y déplacer ou non, et le souvenir de la meilleure solution vue. Le tabou en garde l'esprit mais examine tout le voisinage à chaque pas (une seconde forme, au chapitre 7), et les algorithmes génétiques et les fourmis en sortent tout à fait. Écrire cette charpente une fois évite de la réécrire à chaque chapitre.

main.py
Sortie
>_ Prêt à exécuter…

chercher fait quatre choses, dans cet ordre, autant de fois que demandé : tirer un voisin de la solution courante, le mesurer, décider de l'accepter, et retenir la meilleure solution rencontrée. Rien d'autre. Trois pièces changent d'un problème à l'autre, et ce sont celles qu'on a apprises à poser :

PièceTournéeMachinePostes
représentation (initiale)ordre des 12 trousordre des 10 ordresposte_de, permutation de 0 à 7
voisinretourner un segmentpermuter deux ordrespermuter deux postes
coûtlongueursomme des retardstemps total

La quatrième pièce, accepter, est ici la plus simple qui soit : la descente est le cas où l'on n'accepte un voisin que s'il est strictement meilleur. C'est elle qui, avec un nombre suffisant d'itérations, s'arrête dans un optimum local. Le squelette tire un voisin au hasard plutôt que de parcourir tout le voisinage : la descente devient ainsi une suite de tirages en nombre fixé, et non une boucle qui s'arrête d'elle-même. Avec assez d'itérations, elle a essayé presque tous les voisins de la solution où elle s'est arrêtée, et la longue série de tirages sans progrès en est la signature.

Lire la dernière ligne du tableau affiché : la même charpente, sans y toucher, retouche une tournée, un planning de machine et une affectation. Les résultats varient d'une graine à l'autre, parce que chaque graine tire des voisins dans un autre ordre, et donc s'arrête dans un autre optimum local. Sur l'affectation, ils s'étalent de 99 à 112 minutes alors que la meilleure affectation en vaut 95 : la descente n'y trouve l'optimum sur aucune des cinq graines, ce qui rappelle à quoi servent les redémarrages.

Ce que les chapitres suivants ne changeront pas, et ce qu'ils changeront
Les quatre étapes de chercher restent celles de tout le parcours. Le recuit simulé (chapitre 6) ne changera que accepter : au lieu de refuser tout voisin moins bon, il l'accepte parfois, avec une probabilité qui décroît, pour sortir des optimums locaux. La recherche tabou (chapitre 7) ajoutera une mémoire : elle examinera les voisins, prendra le meilleur même s'il est moins bon, et s'interdira de revenir en arrière. Les algorithmes génétiques et les fourmis (chapitres 8 et 9) sortent de ce moule : ils ne retouchent pas une solution, ils en manient tout un ensemble, ou construisent des solutions nouvelles à partir de traces.
Une graine, pour rejouer
random.Random(graine) rend la recherche reproductible : la même graine donne le même résultat, et l'on peut comparer deux réglages sans que le hasard décide à leur place. Une seule graine ne suffit pourtant pas à comparer deux méthodes : la comparaison honnête, sur plusieurs graines, est posée au chapitre 6.

Les erreurs d'un débutant

Modifier la solution courante au lieu d'en construire une copie
Écrire voisin = ordre puis échanger deux éléments modifie la solution courante elle-même, et le voisin qu'on refuse reste appliqué. La recherche rend alors une solution qui n'est plus celle dont elle annonce le coût. Un voisin se construit toujours sur une copie (list(ordre), une tranche ordre[:], une nouvelle liste), et la solution courante n'est touchée que quand on accepte.
Accepter un voisin égal
Écrire nouveau <= actuel fait accepter les voisins de même coût. La solution se promène alors sur un palier, sans progrès : une descente qui boucle jusqu'à ne plus rien trouver ne s'arrête plus, et le squelette dépense ses iterations sur ce palier. Sur les nombres à virgule, une comparaison stricte pose un problème voisin : deux longueurs égales au millième d'unité se départagent par des erreurs d'arrondi. Comparer avec un petit écart (c < actuel - 1e-9) rend la descente robuste.
Juger une méthode sur un seul départ
Une descente qui tombe sur 633,23 mm depuis un départ en trouve 647,35 depuis un autre. Le premier résultat n'est pas celui de la méthode, c'est celui d'un départ. Lancer plusieurs départs, et rendre au moins le meilleur, la moyenne et le pire.
Oublier le retour au départ dans la variation
La variation d'un 2-opt se calcule avec la position qui précède la première et celle qui suit la dernière. Pour la première et la dernière position de l'ordre, ce sont le départ de l'outil : un chemin qui n'inclut pas le départ aux deux bouts donne des variations fausses sur les bords, et la descente « améliore » des tournées qu'elle rallonge. Comparer toujours la variation à un recalcul complet sur quelques mouvements, comme le fait le code plus haut.

Exercices type

Pourquoi le 2-opt évalue-t-il un voisin en quatre distances, alors que l'échange de deux trous éloignés en demande davantage ?

Retourner un segment ne change que deux trajets de la tournée : celui qui y entre et celui qui en sort. Les trajets à l'intérieur du segment sont parcourus à l'envers, et la distance est la même dans les deux sens. Un échange de deux trous éloignés change quatre trajets (les deux trajets voisins de chacun des deux trous), donc huit distances, et une insertion en change trois. Le 2-opt est le voisinage qui change le moins de trajets pour un ordre de perçage, ce qui explique son succès.

Combien de voisins un ordre de 40 trous a-t-il pour l'échange, l'insertion et le 2-opt ?

L'échange : 40×39/2=78040 \times 39 / 2 = 780. L'insertion : 392=1 52139^2 = 1\,521. Le 2-opt : 780−1=779780 - 1 = 779, car retourner tout l'ordre ne change pas la tournée. Passer de 12 à 40 trous multiplie par 12 le nombre d'échanges, alors que le nombre de tournées est multiplié par un nombre à quarante chiffres (environ 1,7×10391{,}7 \times 10^{39}).

Une descente s'arrête après avoir examiné tous les voisins sans en trouver de meilleur. La solution est-elle optimale ?

Non. Elle est un optimum local pour ce voisinage, et rien de plus. Pour dire si elle est proche de l'optimum, il faut une borne : avec le 1-arbre de P-600, l'écart garanti d'une tournée à 2 944,01 mm est de 20,7 %, ce qui ne dit pas si elle est à cinq pour cent de l'optimum ou à vingt. Un optimum local peut aussi être excellent : c'est la mesure qui décide, pas la méthode.

Pourquoi lancer cent descentes depuis cent départs différents plutôt qu'une seule descente cent fois plus longue ?

Une descente ne peut pas être « plus longue » : elle s'arrête d'elle-même dès qu'aucun voisin n'est meilleur, et laisser tourner davantage ne change rien. Le seul moyen de faire fructifier plus de temps de calcul est de repartir ailleurs, ce qui explore d'autres creux. Sur P-217, cent départs au hasard donnent sept optimums locaux différents en 2-opt, dont le meilleur est trouvé 38 fois sur 100.

Que change-t-on dans chercher pour passer de la tournée à l'affectation des opérateurs ?

Trois pièces, pas une ligne du squelette : la solution initiale (une permutation de 0 à 7 au lieu d'un ordre des trous), le voisin (permuter les postes de deux opérateurs) et le coût (le temps total de l'affectation au lieu de la longueur). La règle accepter, elle, ne bouge pas. Séparer ces quatre pièces rend la recherche réutilisable, et c'est le sens du squelette.

La méthode

  1. Choisir la représentation et vérifier qu'un mouvement lui reste fidèle : une permutation reste une permutation.
  2. Choisir un voisinage et compter ses voisins. Trop petit, la descente se bloque ; trop grand, chaque pas coûte trop.
  3. Écrire le coût du voisin par la variation seule quand c'est possible (quatre distances pour un 2-opt), et la vérifier contre un recalcul complet.
  4. Construire une solution de départ par une règle simple : plus proche voisin, rangement par longueur décroissante, priorité.
  5. Descendre : premier voisin ou meilleur voisin, jusqu'à l'optimum local. Compter les pas et les voisins évalués.
  6. Redémarrer depuis plusieurs départs, et rendre le meilleur, la moyenne et le pire.
  7. Juger le résultat par une borne ou par un optimum connu, et annoncer l'écart.
  8. Passer par le squelette chercher quand on veut changer la règle d'acceptation : la descente est le cas accepter = seulement si meilleur.

Synthèse

  • La recherche locale part d'une solution complète et la retouche par de petits mouvements ; les solutions obtenues sont ses voisins, et leur ensemble est le voisinage.
  • Un mouvement se définit sur la représentation. Sur une permutation : l'échange (n(n−1)/2n(n-1)/2 voisins), l'insertion ((n−1)2(n-1)^2) et le 2-opt (n(n−1)/2−1n(n-1)/2 - 1).
  • Le 2-opt retourne un segment et ne remplace que deux trajets : sa variation se calcule en quatre distances, et il défait tout croisement, car en distance euclidienne une tournée qui se croise n'est jamais optimale.
  • Une descente s'installe sur un voisin meilleur jusqu'à n'en plus trouver : au premier voisin qui améliore, ou au meilleur voisin.
  • Elle s'arrête sur un optimum local, qui n'est en général pas l'optimum : sur P-217, 647,35 mm contre 633,23 mm.
  • L'optimum local dépend du voisinage (un optimum d'échange n'en est pas un pour le 2-opt) et du point de départ.
  • Les redémarrages lancent plusieurs descentes depuis des départs différents et gardent le meilleur : sur P-600, la meilleure de cinquante est à 0,8 % de l'optimum démontré, 2 788,70 mm, alors que la médiane est à 5,3 %.
  • Le squelette chercher(initiale, voisin, cout, accepter, iterations) sert à tout problème : seuls la représentation, le voisin et le coût changent, et la descente est le cas accepter = seulement si meilleur.
  • Le recuit simulé ne changera que accepter ; le tabou ajoutera une mémoire ; le génétique et les fourmis sortiront du moule.

Et ensuite

La descente est rapide et se bloque. Le chapitre suivant garde le squelette et n'y change que la règle d'acceptation : accepter parfois un voisin moins bon, avec une probabilité qui décroît, c'est le recuit simulé. Pour situer ce que vaut une tournée sans connaître l'optimum, les bornes du chapitre 3 servent de règle ; pour la représentation et le coût, le chapitre 1.

Mettre en pratique