J'écris une simulation de messagerie/logistique sur des cartes OpenStreetMap et j'ai réalisé que l'algorithme de base A * comme illustré ci-dessous ne sera pas assez rapide pour les grandes cartes (comme le Grand Londres).
Les nœuds verts correspondent à ceux qui ont été placés dans la file d'attente ouverte/prioritaire et en raison du nombre énorme (la carte entière est de l'ordre de 1-2 millions), il faut environ 5 secondes pour trouver l'itinéraire illustré. Malheureusement, 100 ms par trajet correspondent à ma limite absolue.
Actuellement, les nœuds sont stockés à la fois dans une liste de contiguïté et dans un tableau 2D spatial 100x100.
Je cherche des méthodes où je peux échanger le temps de prétraitement, l'espace et si besoin l'optimalité de l'itinéraire, pour des requêtes plus rapides. La formule Haversine linéaire pour le coût heuristique est la fonction la plus chère selon le profileur - j'ai optimisé autant que possible mon A * de base.
Par exemple, je pensais que si je choisissais un nœud arbitraire X dans chaque quadrant du tableau 2D et exécutais A * entre chacun, je pouvais stocker les routes vers le disque pour des simulations ultérieures. Lors de l'interrogation, je peux exécuter une recherche A * uniquement dans les quadrants, pour passer de la route précalculée au X.
Existe-t-il une version plus raffinée de ce que j'ai décrit ci-dessus ou peut-être une méthode différente que je devrais suivre. Merci beaucoup!
Pour mémoire, voici quelques résultats de référence pour pondérer arbitrairement le coût heuristique et calculer le chemin entre 10 paires de nœuds choisis au hasard:
Weight // AvgDist% // Time (ms)
1 1 1461.2
1.05 1 1327.2
1.1 1 900.7
1.2 1.019658848 196.4
1.3 1.027619169 53.6
1.4 1.044714394 33.6
1.5 1.063963413 25.5
1.6 1.071694171 24.1
1.7 1.084093229 24.3
1.8 1.092208509 22
1.9 1.109188175 22.5
2 1.122856792 18.2
2.2 1.131574742 16.9
2.4 1.139104895 15.4
2.6 1.140021962 16
2.8 1.14088128 15.5
3 1.156303676 16
4 1.20256964 13
5 1.19610861 12.9
Étonnamment, l'augmentation du coefficient à 1,1 a presque réduit de moitié le temps d'exécution tout en conservant le même itinéraire.
Vous devriez être en mesure de le rendre beaucoup plus rapide en échangeant l'optimalité. Voir Admissibilité et optimalité sur wikipedia.
L'idée est d'utiliser une valeur epsilon
qui conduira à une solution pas pire que 1 + epsilon
fois le chemin optimal, mais ce qui fera que moins de nœuds seront pris en compte par l'algorithme. Notez que cela ne signifie pas que la solution retournée sera toujours 1 + epsilon
fois le chemin optimal. Ce n'est que le pire des cas. Je ne sais pas exactement comment il se comporterait dans la pratique pour votre problème, mais je pense que cela vaut la peine d'être exploré.
Vous disposez d'un certain nombre d'algorithmes qui s'appuient sur cette idée sur wikipedia. Je crois que c'est votre meilleur pari pour améliorer l'algorithme et qu'il a le potentiel de fonctionner dans votre limite de temps tout en retournant de bons chemins.
Étant donné que votre algorithme traite des millions de nœuds en 5 secondes, je suppose que vous utilisez également des tas binaires pour la mise en œuvre, n'est-ce pas? Si vous les avez implémentés manuellement, assurez-vous qu'ils sont implémentés en tant que tableaux simples et qu'ils sont des tas binaires.
Il existe des algorithmes spécialisés pour ce problème qui font beaucoup de pré-calcul. De la mémoire, le pré-calcul ajoute des informations au graphique que A * utilise pour produire une heuristique beaucoup plus précise que la distance en ligne droite. Wikipedia donne les noms d'un certain nombre de méthodes à http://en.wikipedia.org/wiki/Shortest_path_problem#Road_networks et dit que Hub Labeling est le leader. Une recherche rapide à ce sujet apparaît http://research.Microsoft.com/pubs/142356/HL-TR.pdf . Un ancien, utilisant A *, se trouve à http://research.Microsoft.com/pubs/64505/goldberg-sp-wea07.pdf .
Avez-vous vraiment besoin d'utiliser Haversine? Pour couvrir Londres, j'aurais pensé que vous auriez pu supposer une terre plate et utiliser Pythagore, ou enregistrer la longueur de chaque lien dans le graphique.
Il y a un très bon article que Microsoft Research a écrit sur le sujet:
http://research.Microsoft.com/en-us/news/features/shortestpath-070709.aspx
Le document original est hébergé ici (PDF):
http://www.cc.gatech.edu/~thad/6601-gradAI-fall2012/02-search-Gutman04siam.pdf
Il y a essentiellement quelques choses que vous pouvez essayer:
GraphHopper fait deux choses de plus pour obtenir un routage rapide, non heuristique et flexible (note: je suis l'auteur et vous pouvez l'essayer en ligne ici )
Il devrait donc être possible de vous proposer des itinéraires rapides pour le Grand Londres.
De plus, le mode par défaut est le mode vitesse qui rend tout un ordre de grandeur plus rapide (par exemple 30 ms pour les routes à l'échelle européenne) mais moins flexible, car il nécessite un prétraitement ( Hiérarchies de contraction ). Si vous n'aimez pas cela, désactivez-le simplement et affinez davantage les rues incluses pour la voiture ou créez probablement un nouveau profil pour les camions, par exemple exclure les rues de service et les pistes qui devraient vous donner un coup de pouce supplémentaire de 30%. Et comme avec tout algorithme bidirectionnel, vous pouvez facilement implémenter une recherche parallèle.
Je pense que cela vaut la peine d'élaborer votre idée avec des "quadrants". Plus strictement, je l'appellerais une recherche d'itinéraire en basse résolution.
Vous pouvez choisir X nœuds connectés suffisamment proches et les traiter comme un seul nœud basse résolution. Divisez votre graphique entier en ces groupes et vous obtenez un graphique basse résolution. Il s'agit d'une étape de préparation.
Afin de calculer un itinéraire de la source à la cible, identifiez d'abord les nœuds basse résolution auxquels ils appartiennent et trouvez l'itinéraire à basse résolution. Améliorez ensuite votre résultat en trouvant l'itinéraire sur le graphique haute résolution, mais en restreignant l'algorithme uniquement aux nœuds qui appartiennent aux nœuds basse résolution de l'itinéraire basse résolution (en option, vous pouvez également envisager les nœuds voisins basse résolution jusqu'à une certaine profondeur ).
Cela peut également être généralisé à plusieurs résolutions, pas seulement à haut/bas.
À la fin, vous devriez obtenir un itinéraire suffisamment proche pour être optimal. Il est localement optimal, mais peut être quelque peu pire qu'optimal globalement dans une certaine mesure, ce qui dépend du saut de résolution (c'est-à-dire de l'approximation que vous faites lorsqu'un groupe de nœuds est défini comme un seul nœud).
J'ai travaillé dans une grande entreprise de navigation, je peux donc affirmer avec confiance que 100 ms devraient vous permettre d'obtenir un itinéraire de Londres à Athènes, même sur un appareil embarqué. Le Grand Londres serait une carte de test pour nous, car il est commodément petit (tient facilement dans RAM - ce n'est pas réellement nécessaire)
Tout d'abord, A * est entièrement obsolète. Son principal avantage est qu'il "techniquement" ne nécessite pas de prétraitement. Dans la pratique, vous devez de toute façon prétraiter une carte OSM, ce qui est un avantage inutile.
Les drapeaux d'arc sont la principale technique pour vous donner une augmentation de vitesse énorme. Si vous divisez la carte en sections disons 5x6, vous pouvez allouer une position de 1 bit dans un entier de 32 bits pour chaque section. Vous pouvez maintenant déterminer pour chaque Edge s'il est utile en voyage to section {X,Y}
d'une autre section. Très souvent, les routes sont bidirectionnelles, ce qui signifie qu'une seule des deux directions est utile. Ainsi, l'une des deux directions a ce bit défini et l'autre l'a effacé. Cela peut ne pas sembler être un réel avantage, mais cela signifie que sur de nombreuses intersections, vous réduisez le nombre de choix à considérer de 2 à seulement 1, et cela ne prend qu'une opération sur un seul bit.
Il existe des dizaines de variantes A * qui peuvent convenir ici. Vous devez cependant penser à vos cas d'utilisation.
Nous n'avons aucun moyen de connaître tous les détails dont vous et votre employeur avez connaissance. Votre premier arrêt devrait donc être CiteSeer ou Google Scholar: recherchez des articles qui traitent le pathfinding avec le même ensemble général de contraintes que vous.
Sélectionnez ensuite trois ou quatre algorithmes, effectuez le prototypage, testez comment ils évoluent et affinez-les. Vous devez garder à l'esprit que vous pouvez combiner différents algorithmes dans la même grande routine d'orientation en fonction de la distance entre les points, du temps restant ou de tout autre facteur.
Comme cela a déjà été dit, sur la base de la petite échelle de votre zone cible, la suppression de Haversine est probablement votre première étape pour gagner un temps précieux sur des évaluations de trigonométres coûteuses. REMARQUE: je ne recommande pas d'utiliser la distance euclidienne en coordonnées lat, lon - reprojetez votre carte dans un exemple Mercator transverse près du centre et utilisez les coordonnées cartésiennes en yards ou en mètres!
Le précalcul est le deuxième, et changer de compilateur peut être une troisième idée évidente (passer en C ou C++ - voir https://benchmarksgame.alioth.debian.org/ pour plus de détails).
Les étapes d'optimisation supplémentaires peuvent inclure l'élimination de l'allocation dynamique de la mémoire et l'utilisation d'une indexation efficace pour la recherche parmi les nœuds (pensez à l'arbre R et à ses dérivés/alternatives).
Habituellement, A * s'accompagne d'une consommation de mémoire excessive plutôt que de difficultés de temps.
Cependant, je pense qu'il pourrait être utile de commencer par calculer uniquement avec des nœuds qui font partie de "grandes rues", vous choisiriez généralement une autoroute plutôt qu'une petite ruelle.
Je suppose que vous pouvez déjà l'utiliser pour votre fonction de poids, mais vous pouvez être plus rapide si vous utilisez une file d'attente prioritaire pour décider du nœud à tester ensuite pour de nouveaux déplacements.
Vous pouvez également essayer de réduire le graphique aux seuls nœuds faisant partie des bords à faible coût, puis trouver un moyen de commencer/terminer au plus proche de ces nœuds. Vous avez donc 2 chemins du début à la "grande rue" et la "grande rue" à la fin. Vous pouvez maintenant calculer le meilleur chemin entre les deux nœuds qui font partie des "grandes rues" dans un graphique réduit.