pycombinatorial 2.2.2__tar.gz → 2.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. {pycombinatorial-2.2.2/pycombinatorial.egg-info → pycombinatorial-2.2.3}/PKG-INFO +6 -2
  2. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/README.md +5 -1
  3. pycombinatorial-2.2.3/pyCombinatorial/__init__.py +5 -0
  4. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/__init__.py +1 -0
  5. pycombinatorial-2.2.3/pyCombinatorial/algorithm/ga_eax.py +591 -0
  6. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/__init__.py +53 -2
  7. pycombinatorial-2.2.3/pyCombinatorial/web/js/algorithms/ga_eax.js +615 -0
  8. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/index.js +2 -0
  9. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3/pycombinatorial.egg-info}/PKG-INFO +6 -2
  10. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/SOURCES.txt +2 -0
  11. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/setup.py +1 -1
  12. pycombinatorial-2.2.2/pyCombinatorial/__init__.py +0 -5
  13. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/LICENSE +0 -0
  14. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/MANIFEST.in +0 -0
  15. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/aco.py +0 -0
  16. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/alns.py +0 -0
  17. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/bb.py +0 -0
  18. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/bf.py +0 -0
  19. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/bhk.py +0 -0
  20. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/brkga.py +0 -0
  21. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/bt.py +0 -0
  22. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/christofides.py +0 -0
  23. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/conc_hull.py +0 -0
  24. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/conv_hull.py +0 -0
  25. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/cw.py +0 -0
  26. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/eln.py +0 -0
  27. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/eo.py +0 -0
  28. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/frnn.py +0 -0
  29. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ga.py +0 -0
  30. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/gksp.py +0 -0
  31. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/grasp.py +0 -0
  32. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/hpn.py +0 -0
  33. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ins_c.py +0 -0
  34. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ins_f.py +0 -0
  35. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ins_n.py +0 -0
  36. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ins_r.py +0 -0
  37. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ksp.py +0 -0
  38. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/lns.py +0 -0
  39. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/mf.py +0 -0
  40. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/nn.py +0 -0
  41. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_2.py +0 -0
  42. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_2_5.py +0 -0
  43. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_2_5s.py +0 -0
  44. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_2s.py +0 -0
  45. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_3.py +0 -0
  46. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_3s.py +0 -0
  47. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_4.py +0 -0
  48. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_4s.py +0 -0
  49. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_5.py +0 -0
  50. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_5s.py +0 -0
  51. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_or.py +0 -0
  52. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rl_double_ql.py +0 -0
  53. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rl_ql.py +0 -0
  54. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rl_sarsa.py +0 -0
  55. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rr.py +0 -0
  56. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rss.py +0 -0
  57. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rt.py +0 -0
  58. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_gui.py +0 -0
  59. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_itr.py +0 -0
  60. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_sct.py +0 -0
  61. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_shc.py +0 -0
  62. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_tabu.py +0 -0
  63. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_vns.py +0 -0
  64. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/sa.py +0 -0
  65. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/som.py +0 -0
  66. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/spfc_h.py +0 -0
  67. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/spfc_m.py +0 -0
  68. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/spfc_s.py +0 -0
  69. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ssi.py +0 -0
  70. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/swp.py +0 -0
  71. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/tat.py +0 -0
  72. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/tbb.py +0 -0
  73. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/zs.py +0 -0
  74. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/utils/__init__.py +0 -0
  75. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/utils/graphs.py +0 -0
  76. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/utils/util.py +0 -0
  77. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/__main__.py +0 -0
  78. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/css/style.css +0 -0
  79. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/index.html +0 -0
  80. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/_kopt.js +0 -0
  81. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/_kopt_trials.js +0 -0
  82. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/_ksp_helpers.js +0 -0
  83. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/_shared.js +0 -0
  84. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/aco.js +0 -0
  85. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/alns.js +0 -0
  86. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/bb.js +0 -0
  87. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/bf.js +0 -0
  88. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/bhk.js +0 -0
  89. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/brkga.js +0 -0
  90. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/bt.js +0 -0
  91. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/christofides.js +0 -0
  92. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/conc_hull.js +0 -0
  93. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/conv_hull.js +0 -0
  94. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/cw.js +0 -0
  95. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/eln.js +0 -0
  96. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/eo.js +0 -0
  97. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/frnn.js +0 -0
  98. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ga.js +0 -0
  99. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/gksp.js +0 -0
  100. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/grasp.js +0 -0
  101. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/hpn.js +0 -0
  102. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ins_c.js +0 -0
  103. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ins_f.js +0 -0
  104. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ins_n.js +0 -0
  105. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ins_r.js +0 -0
  106. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ksp.js +0 -0
  107. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/lns.js +0 -0
  108. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/mf.js +0 -0
  109. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/nn.js +0 -0
  110. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_2.js +0 -0
  111. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_2_5.js +0 -0
  112. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_2_5s.js +0 -0
  113. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_2s.js +0 -0
  114. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_3.js +0 -0
  115. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_3s.js +0 -0
  116. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_4.js +0 -0
  117. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_4s.js +0 -0
  118. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_5.js +0 -0
  119. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_5s.js +0 -0
  120. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_or.js +0 -0
  121. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rl_double_ql.js +0 -0
  122. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rl_ql.js +0 -0
  123. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rl_sarsa.js +0 -0
  124. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rr.js +0 -0
  125. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rss.js +0 -0
  126. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rt.js +0 -0
  127. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_gui.js +0 -0
  128. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_itr.js +0 -0
  129. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_sct.js +0 -0
  130. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_shc.js +0 -0
  131. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_tabu.js +0 -0
  132. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_vns.js +0 -0
  133. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/sa.js +0 -0
  134. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/som.js +0 -0
  135. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/spfc_h.js +0 -0
  136. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/spfc_m.js +0 -0
  137. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/spfc_s.js +0 -0
  138. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ssi.js +0 -0
  139. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/swp.js +0 -0
  140. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/tat.js +0 -0
  141. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/tbb.js +0 -0
  142. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/zs.js +0 -0
  143. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/dataset.js +0 -0
  144. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/distance.js +0 -0
  145. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/renderer.js +0 -0
  146. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/seed.js +0 -0
  147. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/stepper.js +0 -0
  148. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/twoopt_refiner.js +0 -0
  149. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/main.js +0 -0
  150. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/dependency_links.txt +0 -0
  151. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/entry_points.txt +0 -0
  152. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/requires.txt +0 -0
  153. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/top_level.txt +0 -0
  154. {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycombinatorial
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: A library to solve TSP (Travelling Salesman Problem) using Exact Algorithms, Heuristics, Metaheuristics and Reinforcement Learning
5
5
  Home-page: https://github.com/Valdecy/pyCombinatorial
6
6
  Author: Valdecy Pereira
@@ -19,6 +19,9 @@ import pyCombinatorial
19
19
  # Start the web service using:
20
20
  pyCombinatorial.web_app()
21
21
 
22
+ # Terminate the web service using:
23
+ pyCombinatorial.web_stop()
24
+
22
25
  ```
23
26
 
24
27
  <p align="center">
@@ -33,7 +36,7 @@ _This Google Colab Demo is intended for quick demos only. For the best experienc
33
36
 
34
37
  **pyCombinatorial** is a Python-based library designed to tackle the classic Traveling Salesman Problem (TSP) through a diverse set of **Exact Algorithms**, **Heuristics**, **Metaheuristics**, and **Reinforcement Learning**. It brings together well-established and cutting-edge methodologies, offering end users a flexible toolkit to generate high-quality solutions for TSP instances of varying sizes and complexities.
35
38
 
36
- Techniques: **2-opt**; **2.5-opt**; **3-opt**; **4-opt**; **5-opt**; **Or-opt**; **2-opt Stochastic**; **2.5-opt Stochastic**; **3-opt Stochastic**; **4-opt Stochastic**; **5-opt Stochastic**; **Ant Colony Optimization**; **Adaptive Large Neighborhood Search**; **Bellman-Held-Karp Exact Algorithm**; **Bitonic Tour**; **Branch & Bound**; **BRKGA** (Biased Random Key Genetic Algorithm); **Brute Force**; **Cheapest Insertion**; **Christofides Algorithm**; **Clarke & Wright** (Savings Heuristic); **Concave Hull Algorithm**; **Convex Hull Algorithm**; **Elastic Net**; **Extremal Optimization**; **Farthest Insertion**; **FRNN** (Fixed Radius Near Neighbor); **Genetic Algorithm**; **GRASP** (Greedy Randomized Adaptive Search Procedure); **Greedy Karp-Steele Patching**; **Guided Search**; **Hopfield Network**; **Iterated Search**; **Karp-Steele Patching**; **Large Neighborhood Search**; **Multifragment Heuristic**; **Nearest Insertion**; **Nearest Neighbour**; **Random Insertion**; **Random Tour**; **Randomized Spectral Seriation**; **RL Q-Learning**; **RL Double Q-Learning**; **RL S.A.R.S.A** (State Action Reward State Action); **Ruin & Recreate**; **Scatter Search**; **Simulated Annealing**; **SOM** (Self Organizing Maps); **Space Filling Curve** (Hilbert); **Space Filling Curve** (Morton); **Space Filling Curve** (Sierpinski); **Spectral Seriation Initializer**; **Stochastic Hill Climbing**; **Sweep**; **Tabu Search**; **Truncated Branch & Bound**; **Twice-Around the Tree Algorithm** (Double Tree Algorithm); **Variable Neighborhood Search**; **Zero Suffix Method**.
39
+ Techniques: **2-opt**; **2.5-opt**; **3-opt**; **4-opt**; **5-opt**; **Or-opt**; **2-opt Stochastic**; **2.5-opt Stochastic**; **3-opt Stochastic**; **4-opt Stochastic**; **5-opt Stochastic**; **Ant Colony Optimization**; **Adaptive Large Neighborhood Search**; **Bellman-Held-Karp Exact Algorithm**; **Bitonic Tour**; **Branch & Bound**; **BRKGA** (Biased Random Key Genetic Algorithm); **Brute Force**; **Cheapest Insertion**; **Christofides Algorithm**; **Clarke & Wright** (Savings Heuristic); **Concave Hull Algorithm**; **Convex Hull Algorithm**; **Elastic Net**; **Extremal Optimization**; **Farthest Insertion**; **FRNN** (Fixed Radius Near Neighbor); **Genetic Algorithm**; **GA-EAX (Genetic Algorithm with Edge Assembly Crossover)**; **GRASP** (Greedy Randomized Adaptive Search Procedure); **Greedy Karp-Steele Patching**; **Guided Search**; **Hopfield Network**; **Iterated Search**; **Karp-Steele Patching**; **Large Neighborhood Search**; **Multifragment Heuristic**; **Nearest Insertion**; **Nearest Neighbour**; **Random Insertion**; **Random Tour**; **Randomized Spectral Seriation**; **RL Q-Learning**; **RL Double Q-Learning**; **RL S.A.R.S.A** (State Action Reward State Action); **Ruin & Recreate**; **Scatter Search**; **Simulated Annealing**; **SOM** (Self Organizing Maps); **Space Filling Curve** (Hilbert); **Space Filling Curve** (Morton); **Space Filling Curve** (Sierpinski); **Spectral Seriation Initializer**; **Stochastic Hill Climbing**; **Sweep**; **Tabu Search**; **Truncated Branch & Bound**; **Twice-Around the Tree Algorithm** (Double Tree Algorithm); **Variable Neighborhood Search**; **Zero Suffix Method**.
37
40
 
38
41
  ## Usage
39
42
 
@@ -117,6 +120,7 @@ print('Total Distance: ', round(distance, 2))
117
120
  - Farthest Insertion ([ Colab Demo ](https://colab.research.google.com/drive/13pWiLL_dO9Y1lvQO0zD50MXk4mD0Tn1W?usp=sharing)) ( [ Paper ](https://disco.ethz.ch/courses/fs16/podc/readingAssignment/1.pdf))
118
121
  - FRNN (Fixed Radius Near Neighbor) ([ Colab Demo ](https://colab.research.google.com/drive/16GgUGA0_TyR6UOqg0TtndjjuZhQ0TTYT?usp=sharing)) ( [ Paper ](https://dl.acm.org/doi/pdf/10.5555/320176.320186))
119
122
  - Genetic Algorithm ([ Colab Demo ](https://colab.research.google.com/drive/1zO9rm-G6HOMeg1Q_ptMHJr48EpHcCAIS?usp=sharing)) ( [ Paper ](https://doi.org/10.1007/BF02125403))
123
+ - Genetic Algorithm with Edge Assembly Crossover([ Colab Demo ](https://colab.research.google.com/drive/1-G5RBXtJHNO4bDn9yhxQpWHbprLIZ_jd?usp=sharing)) ( [ Paper ](https://doi.org/10.1287/ijoc.1120.0506))
120
124
  - GRASP (Greedy Randomized Adaptive Search Procedure) ([ Colab Demo ](https://colab.research.google.com/drive/1OnRyCc6C_QL6wr6-l5RlQI4eGbMdwuhS?usp=sharing)) ( [ Paper ](https://doi.org/10.1007/BF01096763))
121
125
  - Greedy Karp-Steele Patching ([ Colab Demo ](https://colab.research.google.com/drive/1to3u45QWWQK8REj1_YiF5rUqUqNjB18q?usp=sharing)) ( [ Paper ](https://doi.org/10.1016/S0377-2217(99)00468-3))
122
126
  - Guided Search ([ Colab Demo ](https://colab.research.google.com/drive/1uT9mlDoo37Ni7hqziGNELEGQCGBKQ83o?usp=sharing)) ( [ Paper ](https://doi.org/10.1016/S0377-2217(98)00099-X))
@@ -8,6 +8,9 @@ import pyCombinatorial
8
8
  # Start the web service using:
9
9
  pyCombinatorial.web_app()
10
10
 
11
+ # Terminate the web service using:
12
+ pyCombinatorial.web_stop()
13
+
11
14
  ```
12
15
 
13
16
  <p align="center">
@@ -22,7 +25,7 @@ _This Google Colab Demo is intended for quick demos only. For the best experienc
22
25
 
23
26
  **pyCombinatorial** is a Python-based library designed to tackle the classic Traveling Salesman Problem (TSP) through a diverse set of **Exact Algorithms**, **Heuristics**, **Metaheuristics**, and **Reinforcement Learning**. It brings together well-established and cutting-edge methodologies, offering end users a flexible toolkit to generate high-quality solutions for TSP instances of varying sizes and complexities.
24
27
 
25
- Techniques: **2-opt**; **2.5-opt**; **3-opt**; **4-opt**; **5-opt**; **Or-opt**; **2-opt Stochastic**; **2.5-opt Stochastic**; **3-opt Stochastic**; **4-opt Stochastic**; **5-opt Stochastic**; **Ant Colony Optimization**; **Adaptive Large Neighborhood Search**; **Bellman-Held-Karp Exact Algorithm**; **Bitonic Tour**; **Branch & Bound**; **BRKGA** (Biased Random Key Genetic Algorithm); **Brute Force**; **Cheapest Insertion**; **Christofides Algorithm**; **Clarke & Wright** (Savings Heuristic); **Concave Hull Algorithm**; **Convex Hull Algorithm**; **Elastic Net**; **Extremal Optimization**; **Farthest Insertion**; **FRNN** (Fixed Radius Near Neighbor); **Genetic Algorithm**; **GRASP** (Greedy Randomized Adaptive Search Procedure); **Greedy Karp-Steele Patching**; **Guided Search**; **Hopfield Network**; **Iterated Search**; **Karp-Steele Patching**; **Large Neighborhood Search**; **Multifragment Heuristic**; **Nearest Insertion**; **Nearest Neighbour**; **Random Insertion**; **Random Tour**; **Randomized Spectral Seriation**; **RL Q-Learning**; **RL Double Q-Learning**; **RL S.A.R.S.A** (State Action Reward State Action); **Ruin & Recreate**; **Scatter Search**; **Simulated Annealing**; **SOM** (Self Organizing Maps); **Space Filling Curve** (Hilbert); **Space Filling Curve** (Morton); **Space Filling Curve** (Sierpinski); **Spectral Seriation Initializer**; **Stochastic Hill Climbing**; **Sweep**; **Tabu Search**; **Truncated Branch & Bound**; **Twice-Around the Tree Algorithm** (Double Tree Algorithm); **Variable Neighborhood Search**; **Zero Suffix Method**.
28
+ Techniques: **2-opt**; **2.5-opt**; **3-opt**; **4-opt**; **5-opt**; **Or-opt**; **2-opt Stochastic**; **2.5-opt Stochastic**; **3-opt Stochastic**; **4-opt Stochastic**; **5-opt Stochastic**; **Ant Colony Optimization**; **Adaptive Large Neighborhood Search**; **Bellman-Held-Karp Exact Algorithm**; **Bitonic Tour**; **Branch & Bound**; **BRKGA** (Biased Random Key Genetic Algorithm); **Brute Force**; **Cheapest Insertion**; **Christofides Algorithm**; **Clarke & Wright** (Savings Heuristic); **Concave Hull Algorithm**; **Convex Hull Algorithm**; **Elastic Net**; **Extremal Optimization**; **Farthest Insertion**; **FRNN** (Fixed Radius Near Neighbor); **Genetic Algorithm**; **GA-EAX (Genetic Algorithm with Edge Assembly Crossover)**; **GRASP** (Greedy Randomized Adaptive Search Procedure); **Greedy Karp-Steele Patching**; **Guided Search**; **Hopfield Network**; **Iterated Search**; **Karp-Steele Patching**; **Large Neighborhood Search**; **Multifragment Heuristic**; **Nearest Insertion**; **Nearest Neighbour**; **Random Insertion**; **Random Tour**; **Randomized Spectral Seriation**; **RL Q-Learning**; **RL Double Q-Learning**; **RL S.A.R.S.A** (State Action Reward State Action); **Ruin & Recreate**; **Scatter Search**; **Simulated Annealing**; **SOM** (Self Organizing Maps); **Space Filling Curve** (Hilbert); **Space Filling Curve** (Morton); **Space Filling Curve** (Sierpinski); **Spectral Seriation Initializer**; **Stochastic Hill Climbing**; **Sweep**; **Tabu Search**; **Truncated Branch & Bound**; **Twice-Around the Tree Algorithm** (Double Tree Algorithm); **Variable Neighborhood Search**; **Zero Suffix Method**.
26
29
 
27
30
  ## Usage
28
31
 
@@ -106,6 +109,7 @@ print('Total Distance: ', round(distance, 2))
106
109
  - Farthest Insertion ([ Colab Demo ](https://colab.research.google.com/drive/13pWiLL_dO9Y1lvQO0zD50MXk4mD0Tn1W?usp=sharing)) ( [ Paper ](https://disco.ethz.ch/courses/fs16/podc/readingAssignment/1.pdf))
107
110
  - FRNN (Fixed Radius Near Neighbor) ([ Colab Demo ](https://colab.research.google.com/drive/16GgUGA0_TyR6UOqg0TtndjjuZhQ0TTYT?usp=sharing)) ( [ Paper ](https://dl.acm.org/doi/pdf/10.5555/320176.320186))
108
111
  - Genetic Algorithm ([ Colab Demo ](https://colab.research.google.com/drive/1zO9rm-G6HOMeg1Q_ptMHJr48EpHcCAIS?usp=sharing)) ( [ Paper ](https://doi.org/10.1007/BF02125403))
112
+ - Genetic Algorithm with Edge Assembly Crossover([ Colab Demo ](https://colab.research.google.com/drive/1-G5RBXtJHNO4bDn9yhxQpWHbprLIZ_jd?usp=sharing)) ( [ Paper ](https://doi.org/10.1287/ijoc.1120.0506))
109
113
  - GRASP (Greedy Randomized Adaptive Search Procedure) ([ Colab Demo ](https://colab.research.google.com/drive/1OnRyCc6C_QL6wr6-l5RlQI4eGbMdwuhS?usp=sharing)) ( [ Paper ](https://doi.org/10.1007/BF01096763))
110
114
  - Greedy Karp-Steele Patching ([ Colab Demo ](https://colab.research.google.com/drive/1to3u45QWWQK8REj1_YiF5rUqUqNjB18q?usp=sharing)) ( [ Paper ](https://doi.org/10.1016/S0377-2217(99)00468-3))
111
115
  - Guided Search ([ Colab Demo ](https://colab.research.google.com/drive/1uT9mlDoo37Ni7hqziGNELEGQCGBKQ83o?usp=sharing)) ( [ Paper ](https://doi.org/10.1016/S0377-2217(98)00099-X))
@@ -0,0 +1,5 @@
1
+ """Top-level package for pyCombinatorial."""
2
+
3
+ from .web import web_app, web_stop
4
+
5
+ __all__ = ['web_app', 'web_stop']
@@ -13,6 +13,7 @@ from .eln import elastic_net_tsp
13
13
  from .eo import extremal_optimization
14
14
  from .frnn import fixed_radius_nn
15
15
  from .ga import genetic_algorithm
16
+ from .ga_eax import genetic_algorithm_edge_assembly_crossover
16
17
  from .grasp import greedy_randomized_adaptive_search_procedure
17
18
  from .gksp import greedy_karp_steele_patching
18
19
  from .hpn import hopfield_network_tsp
@@ -0,0 +1,591 @@
1
+ ############################################################################
2
+
3
+ # Created by: Prof. Valdecy Pereira, D.Sc.
4
+ # UFF - Universidade Federal Fluminense (Brazil)
5
+ # email: valdecy.pereira@gmail.com
6
+ # Lesson: Genetic Algorithm with Edge Assembly Crossover (GA-EAX)
7
+
8
+ # GitHub Repository: <https://github.com/Valdecy>
9
+
10
+ ############################################################################
11
+
12
+ # Required Libraries
13
+ import numpy as np
14
+ import math
15
+ import random
16
+
17
+ from collections import Counter
18
+
19
+ ############################################################################
20
+
21
+ # Function: Tour Distance
22
+ def distance_calc(distance_matrix, city_tour):
23
+ route = city_tour[0] if isinstance(city_tour, (list, tuple)) and len(city_tour) > 0 and isinstance(city_tour[0], (list, tuple, np.ndarray)) else city_tour
24
+ distance = 0.0
25
+ for k in range(0, len(route) - 1):
26
+ m = k + 1
27
+ distance = distance + distance_matrix[route[k] - 1, route[m] - 1]
28
+ return float(distance)
29
+
30
+ # Function: 2_opt
31
+ def local_search_2_opt(distance_matrix, city_tour, recursive_seeding = -1, verbose=True):
32
+ route = list(city_tour[0])
33
+ if len(route) > 1 and route[0] == route[-1]:
34
+ route = route[:-1]
35
+ n = len(route)
36
+ if n <= 3:
37
+ closed = route + [route[0]]
38
+ return closed, float(distance_calc(distance_matrix, [closed, 0]))
39
+
40
+ best_distance = float(city_tour[1]) if city_tour[1] not in (None, 0) else float(distance_calc(distance_matrix, [route + [route[0]], 0]))
41
+ iteration = 0
42
+
43
+ while True:
44
+ improved = False
45
+ if verbose:
46
+ print('Iteration = ', iteration, 'Distance = ', round(best_distance, 2))
47
+
48
+ for i in range(0, n - 1):
49
+ a = route[i - 1]
50
+ b = route[i]
51
+ for j in range(i + 1, n):
52
+ c = route[j]
53
+ d = route[(j + 1) % n]
54
+ if a == c or b == d:
55
+ continue
56
+ delta = distance_matrix[a - 1, c - 1] + distance_matrix[b - 1, d - 1] - distance_matrix[a - 1, b - 1] - distance_matrix[c - 1, d - 1]
57
+ if delta < -1e-12:
58
+ route[i:j + 1] = reversed(route[i:j + 1])
59
+ best_distance = float(best_distance + delta)
60
+ improved = True
61
+ break
62
+ if improved:
63
+ break
64
+
65
+ iteration = iteration + 1
66
+ if recursive_seeding >= 0:
67
+ if iteration > recursive_seeding:
68
+ break
69
+ if not improved:
70
+ break
71
+ else:
72
+ if not improved:
73
+ break
74
+
75
+ closed = route + [route[0]]
76
+ return closed, float(best_distance)
77
+
78
+ ############################################################################
79
+
80
+ # Internal Utilities
81
+ def _route_0_to_1(route_0):
82
+ return [node + 1 for node in route_0] + [route_0[0] + 1]
83
+
84
+
85
+ def _route_1_to_0(route_1):
86
+ route = list(route_1)
87
+ if len(route) > 1 and route[0] == route[-1]:
88
+ route = route[:-1]
89
+ return [node - 1 for node in route]
90
+
91
+ def _route_distance_0(distance_matrix, route_0):
92
+ distance = 0.0
93
+ n = len(route_0)
94
+ for i in range(n):
95
+ distance = distance + distance_matrix[route_0[i], route_0[(i + 1) % n]]
96
+ return float(distance)
97
+
98
+ def _normalize_edge(i, j):
99
+ return (i, j) if i <= j else (j, i)
100
+
101
+ def _edge_set_from_route(route_0):
102
+ return {_normalize_edge(route_0[i], route_0[(i + 1) % len(route_0)]) for i in range(len(route_0))}
103
+
104
+ def _adjacency_from_route(route_0):
105
+ n = len(route_0)
106
+ adj = [set() for _ in range(n)]
107
+ for i in range(n):
108
+ a = route_0[i]
109
+ b = route_0[(i + 1) % n]
110
+ adj[a].add(b)
111
+ adj[b].add(a)
112
+ return adj
113
+
114
+ def _route_from_adjacency(adj, start=0):
115
+ route = [start]
116
+ prev = -1
117
+ curr = start
118
+ while True:
119
+ nbrs = list(adj[curr])
120
+ if len(nbrs) != 2:
121
+ raise ValueError('Adjacency structure is not a Hamiltonian cycle')
122
+ nxt = nbrs[0] if nbrs[0] != prev else nbrs[1]
123
+ if nxt == start:
124
+ break
125
+ route.append(nxt)
126
+ prev, curr = curr, nxt
127
+ if len(route) > len(adj):
128
+ raise ValueError('Cycle reconstruction overflow')
129
+ if len(route) != len(adj):
130
+ raise ValueError('Reconstructed route does not include all nodes')
131
+ return route
132
+
133
+ def _valid_degrees(adj):
134
+ return all(len(nbrs) == 2 for nbrs in adj)
135
+
136
+ def _connected_components(adj):
137
+ visited = [False] * len(adj)
138
+ components = []
139
+ for s in range(len(adj)):
140
+ if visited[s]:
141
+ continue
142
+ stack = [s]
143
+ visited[s] = True
144
+ comp = []
145
+ while stack:
146
+ u = stack.pop()
147
+ comp.append(u)
148
+ for v in adj[u]:
149
+ if not visited[v]:
150
+ visited[v] = True
151
+ stack.append(v)
152
+ components.append(comp)
153
+ return components
154
+
155
+ def _cycle_route_from_component(adj, component):
156
+ start = component[0]
157
+ route = [start]
158
+ prev = -1
159
+ curr = start
160
+ while True:
161
+ nbrs = list(adj[curr])
162
+ nxt = nbrs[1] if nbrs[0] == prev else nbrs[0]
163
+ if nxt == start:
164
+ break
165
+ route.append(nxt)
166
+ prev, curr = curr, nxt
167
+ return route
168
+
169
+ def _cycle_edges_from_component(adj, component):
170
+ route = _cycle_route_from_component(adj, component)
171
+ return [_normalize_edge(route[i], route[(i + 1) % len(route)]) for i in range(len(route))]
172
+
173
+ def _nearest_city_list(distance_matrix):
174
+ n = distance_matrix.shape[0]
175
+ near = []
176
+ for i in range(n):
177
+ order = np.argsort(distance_matrix[i, :]).tolist()
178
+ near.append(order)
179
+ return near
180
+
181
+ def _random_route(n, rng, initial_location=-1):
182
+ route = list(range(n))
183
+ if initial_location != -1:
184
+ start = max(0, min(n - 1, int(initial_location) - 1))
185
+ rest = [i for i in route if i != start]
186
+ rng.shuffle(rest)
187
+ return [start] + rest
188
+ rng.shuffle(route)
189
+ return route
190
+
191
+ def _prepare_initial_route(distance_matrix, route, local_search = True):
192
+ route_0 = _route_1_to_0(route) if min(route) >= 1 else list(route)
193
+ closed_1 = _route_0_to_1(route_0)
194
+ value = distance_calc(distance_matrix, [closed_1, 0])
195
+ if local_search:
196
+ closed_1, value = local_search_2_opt(distance_matrix, [closed_1, value], recursive_seeding=-1, verbose=False)
197
+ route_0 = _route_1_to_0(closed_1)
198
+ return route_0, float(value)
199
+
200
+ def _make_population(distance_matrix, population_size, rng, local_search = True, initial_location = -1, initial_population = None):
201
+ population = []
202
+ n = distance_matrix.shape[0]
203
+ if initial_population is not None and len(initial_population) > 0:
204
+ for route in initial_population:
205
+ route_0, value = _prepare_initial_route(distance_matrix, route, local_search=local_search)
206
+ population.append({'route': route_0, 'distance': value})
207
+ if len(population) >= population_size:
208
+ break
209
+ while len(population) < population_size:
210
+ route_0 = _random_route(n, rng, initial_location=initial_location)
211
+ closed_1 = _route_0_to_1(route_0)
212
+ value = distance_calc(distance_matrix, [closed_1, 0])
213
+ if local_search:
214
+ closed_1, value = local_search_2_opt(distance_matrix, [closed_1, value], recursive_seeding=-1, verbose=False)
215
+ route_0 = _route_1_to_0(closed_1)
216
+ population.append({'route': route_0, 'distance': float(value)})
217
+ return population
218
+
219
+ def _compute_edge_frequency(population):
220
+ edge_freq = Counter()
221
+ for individual in population:
222
+ for edge in _edge_set_from_route(individual['route']):
223
+ edge_freq[edge] += 1
224
+ return edge_freq
225
+
226
+ def _fallback_recombine(parent_a, parent_b, distance_matrix, near_cities, rng):
227
+ n = len(parent_a)
228
+ adj_a = _adjacency_from_route(parent_a)
229
+ adj_b = _adjacency_from_route(parent_b)
230
+ start = rng.randrange(n)
231
+ route = [start]
232
+ visited = {start}
233
+ curr = start
234
+ while len(route) < n:
235
+ candidates = list((adj_a[curr] | adj_b[curr]) - visited)
236
+ if len(candidates) > 0:
237
+ nxt = min(candidates, key=lambda node: distance_matrix[curr, node])
238
+ else:
239
+ remaining = [node for node in near_cities[curr] if node not in visited]
240
+ if len(remaining) == 0:
241
+ remaining = [node for node in range(n) if node not in visited]
242
+ nxt = remaining[0]
243
+ route.append(nxt)
244
+ visited.add(nxt)
245
+ curr = nxt
246
+ return route
247
+
248
+ def _extract_ab_cycles(parent_a, parent_b, distance_matrix, rng):
249
+ n = len(parent_a)
250
+ edges_a = _edge_set_from_route(parent_a)
251
+ edges_b = _edge_set_from_route(parent_b)
252
+ only_a = set(edges_a - edges_b)
253
+ only_b = set(edges_b - edges_a)
254
+ adj_a = [[] for _ in range(n)]
255
+ adj_b = [[] for _ in range(n)]
256
+ for u, v in only_a:
257
+ adj_a[u].append(v)
258
+ adj_a[v].append(u)
259
+ for u, v in only_b:
260
+ adj_b[u].append(v)
261
+ adj_b[v].append(u)
262
+ cycles = []
263
+ max_steps = 4 * n + 10
264
+ while len(only_a) > 0:
265
+ start_edge = rng.choice(tuple(only_a))
266
+ u, v = start_edge
267
+ a_edges = [(u, v)]
268
+ b_edges = []
269
+ node_set = {u, v}
270
+ prev = u
271
+ curr = v
272
+ next_type = 'B'
273
+ ok = False
274
+ steps = 0
275
+ while steps < max_steps:
276
+ steps = steps + 1
277
+ candidates = list(adj_b[curr]) if next_type == 'B' else list(adj_a[curr])
278
+ if len(candidates) > 1 and prev in candidates:
279
+ candidates = [x for x in candidates if x != prev]
280
+ if len(candidates) == 0:
281
+ break
282
+ preferred = []
283
+ for nxt in candidates:
284
+ edge = _normalize_edge(curr, nxt)
285
+ if next_type == 'B' or edge in only_a:
286
+ preferred.append(nxt)
287
+ if len(preferred) > 0:
288
+ candidates = preferred
289
+ nxt = rng.choice(candidates)
290
+ edge = (curr, nxt)
291
+ if next_type == 'B':
292
+ b_edges.append(edge)
293
+ next_type = 'A'
294
+ else:
295
+ a_edges.append(edge)
296
+ next_type = 'B'
297
+ node_set.add(nxt)
298
+ prev, curr = curr, nxt
299
+ if curr == u and next_type == 'A':
300
+ ok = True
301
+ break
302
+ if ok and len(a_edges) == len(b_edges) and len(a_edges) > 0:
303
+ for edge in a_edges:
304
+ only_a.discard(_normalize_edge(edge[0], edge[1]))
305
+ gain = sum(distance_matrix[i, j] for i, j in a_edges) - sum(distance_matrix[i, j] for i, j in b_edges)
306
+ cycles.append({'a_edges': a_edges, 'b_edges': b_edges, 'gain': float(gain), 'node_set': set(node_set)})
307
+ else:
308
+ only_a.discard(start_edge)
309
+ return cycles
310
+
311
+ def _apply_cycle(adj, cycle):
312
+ for u, v in cycle['a_edges']:
313
+ adj[u].discard(v)
314
+ adj[v].discard(u)
315
+ for u, v in cycle['b_edges']:
316
+ adj[u].add(v)
317
+ adj[v].add(u)
318
+
319
+ def _reconnect_subtours(adj, distance_matrix, near_cities):
320
+ while True:
321
+ components = _connected_components(adj)
322
+ if len(components) <= 1:
323
+ return adj
324
+ comp_index = {}
325
+ for idx, comp in enumerate(components):
326
+ for node in comp:
327
+ comp_index[node] = idx
328
+ center = min(components, key=len)
329
+ center_nodes = set(center)
330
+ center_edges = _cycle_edges_from_component(adj, center)
331
+ best_move = None
332
+ best_gain = -float('inf')
333
+ for a, b in center_edges:
334
+ for anchor in (a, b):
335
+ for c in near_cities[anchor][1:]:
336
+ if c in center_nodes:
337
+ continue
338
+ for d in list(adj[c]):
339
+ if comp_index[d] != comp_index[c]:
340
+ continue
341
+ delta_1 = distance_matrix[a, b] + distance_matrix[c, d] - distance_matrix[a, c] - distance_matrix[b, d]
342
+ if delta_1 > best_gain:
343
+ best_gain = delta_1
344
+ best_move = (a, b, c, d, 0)
345
+ delta_2 = distance_matrix[a, b] + distance_matrix[c, d] - distance_matrix[a, d] - distance_matrix[b, c]
346
+ if delta_2 > best_gain:
347
+ best_gain = delta_2
348
+ best_move = (a, b, c, d, 1)
349
+ if best_move is None:
350
+ other_components = [comp for comp in components if comp is not center]
351
+ for a, b in center_edges:
352
+ for comp in other_components:
353
+ for c, d in _cycle_edges_from_component(adj, comp):
354
+ delta_1 = distance_matrix[a, b] + distance_matrix[c, d] - distance_matrix[a, c] - distance_matrix[b, d]
355
+ if delta_1 > best_gain:
356
+ best_gain = delta_1
357
+ best_move = (a, b, c, d, 0)
358
+ delta_2 = distance_matrix[a, b] + distance_matrix[c, d] - distance_matrix[a, d] - distance_matrix[b, c]
359
+ if delta_2 > best_gain:
360
+ best_gain = delta_2
361
+ best_move = (a, b, c, d, 1)
362
+ if best_move is None:
363
+ raise RuntimeError('Failed to reconnect subtours generated by EAX')
364
+ a, b, c, d, mode = best_move
365
+ adj[a].discard(b)
366
+ adj[b].discard(a)
367
+ adj[c].discard(d)
368
+ adj[d].discard(c)
369
+ if mode == 0:
370
+ adj[a].add(c)
371
+ adj[c].add(a)
372
+ adj[b].add(d)
373
+ adj[d].add(b)
374
+ else:
375
+ adj[a].add(d)
376
+ adj[d].add(a)
377
+ adj[b].add(c)
378
+ adj[c].add(b)
379
+
380
+ def _build_multi_cycle_set(cycles, center_idx, max_cycles = 3):
381
+ selected = [center_idx]
382
+ used = set(cycles[center_idx]['node_set'])
383
+ candidates = [idx for idx in range(len(cycles)) if idx != center_idx]
384
+ candidates = sorted(candidates, key = lambda idx: cycles[idx]['gain'], reverse = True)
385
+ for idx in candidates:
386
+ if len(selected) >= max_cycles:
387
+ break
388
+ overlap = len(used & cycles[idx]['node_set'])
389
+ if overlap <= max(1, len(cycles[idx]['node_set']) // 4):
390
+ selected.append(idx)
391
+ used |= cycles[idx]['node_set']
392
+ return selected
393
+
394
+ def _adp_loss(old_edges, new_edges, edge_freq):
395
+ removed = old_edges - new_edges
396
+ added = new_edges - old_edges
397
+ penalty = 0.0
398
+ for edge in removed:
399
+ penalty = penalty - max(edge_freq.get(edge, 0) - 1, 0)
400
+ for edge in added:
401
+ penalty = penalty + edge_freq.get(edge, 0)
402
+ return max(float(penalty), 1e-8)
403
+
404
+ def _entropy_loss(old_edges, new_edges, edge_freq, population_size):
405
+ def entropy_term(count):
406
+ if count <= 0 or population_size <= 0:
407
+ return 0.0
408
+ p = count / float(population_size)
409
+ return -p * math.log(p)
410
+ changed = (old_edges - new_edges) | (new_edges - old_edges)
411
+ delta = 0.0
412
+ for edge in changed:
413
+ before = edge_freq.get(edge, 0)
414
+ after = before
415
+ if edge in old_edges and edge not in new_edges:
416
+ after = after - 1
417
+ if edge in new_edges and edge not in old_edges:
418
+ after = after + 1
419
+ delta = delta + (entropy_term(after) - entropy_term(before))
420
+ return max(float(-delta), 1e-8)
421
+
422
+ def _eax_candidate_from_pair(parent_a, parent_b, distance_matrix, near_cities, edge_freq, population_size, offspring_size, stage_mode, diversity_mode, rng):
423
+ cycles = _extract_ab_cycles(parent_a, parent_b, distance_matrix, rng)
424
+ if len(cycles) == 0:
425
+ child = _fallback_recombine(parent_a, parent_b, distance_matrix, near_cities, rng)
426
+ return child, _route_distance_0(distance_matrix, child)
427
+ order = list(range(len(cycles)))
428
+ if stage_mode == 'single':
429
+ rng.shuffle(order)
430
+ else:
431
+ order = sorted(order, key=lambda idx: cycles[idx]['gain'], reverse=True)
432
+ old_edges = _edge_set_from_route(parent_a)
433
+ best_child = None
434
+ best_distance = float('inf')
435
+ best_point = -float('inf')
436
+ for idx in order[:min(offspring_size, len(order))]:
437
+ selected_cycles = [idx] if stage_mode == 'single' else _build_multi_cycle_set(cycles, idx)
438
+ adj = _adjacency_from_route(parent_a)
439
+ valid = True
440
+ for cycle_idx in selected_cycles:
441
+ _apply_cycle(adj, cycles[cycle_idx])
442
+ if not _valid_degrees(adj):
443
+ valid = False
444
+ break
445
+ if valid:
446
+ try:
447
+ _reconnect_subtours(adj, distance_matrix, near_cities)
448
+ if not _valid_degrees(adj):
449
+ valid = False
450
+ else:
451
+ child = _route_from_adjacency(adj, start=parent_a[0])
452
+ except Exception:
453
+ valid = False
454
+ if not valid:
455
+ child = _fallback_recombine(parent_a, parent_b, distance_matrix, near_cities, rng)
456
+ child_distance = _route_distance_0(distance_matrix, child)
457
+ gain = _route_distance_0(distance_matrix, parent_a) - child_distance
458
+ if gain <= 0:
459
+ continue
460
+ if child == parent_b:
461
+ continue
462
+ new_edges = _edge_set_from_route(child)
463
+ if diversity_mode == 'greedy':
464
+ loss = 1.0
465
+ elif diversity_mode == 'distance':
466
+ loss = _adp_loss(old_edges, new_edges, edge_freq)
467
+ else:
468
+ loss = _entropy_loss(old_edges, new_edges, edge_freq, population_size)
469
+ point = gain / max(loss, 1e-8)
470
+ if point > best_point:
471
+ best_point = point
472
+ best_child = child
473
+ best_distance = child_distance
474
+ if best_child is None:
475
+ child = _fallback_recombine(parent_a, parent_b, distance_matrix, near_cities, rng)
476
+ return child, _route_distance_0(distance_matrix, child)
477
+ return best_child, float(best_distance)
478
+
479
+ ############################################################################
480
+
481
+ # Function: Genetic Algorithm with Edge Assembly Crossover
482
+ def genetic_algorithm_edge_assembly_crossover(distance_matrix, population_size = 30, offspring_size = 30, generations = 500, local_search = True, verbose = True, stage_switch = True, diversity_mode = 'entropy'):
483
+ distance_matrix = np.array(distance_matrix, dtype = float)
484
+ if distance_matrix.ndim != 2 or distance_matrix.shape[0] != distance_matrix.shape[1]:
485
+ raise ValueError('distance_matrix must be a square matrix')
486
+ rng = random.Random(None)
487
+ near_cities = _nearest_city_list(distance_matrix)
488
+ population = _make_population(distance_matrix = distance_matrix,
489
+ population_size = population_size,
490
+ rng = rng,
491
+ local_search = True,
492
+ initial_location = -1,
493
+ initial_population = None)
494
+
495
+ edge_freq = _compute_edge_frequency(population)
496
+ best_individual = min(population, key=lambda item: item['distance'])
497
+ best_route = list(best_individual['route'])
498
+ best_distance = float(best_individual['distance'])
499
+ #history = [{'generation': 0, 'best': best_distance, 'average': float(np.mean([item['distance'] for item in population])), 'stage': 1}]
500
+ stage = 1
501
+ stage_mode = 'single'
502
+ no_improve = 0
503
+ stage_reference_generation = 0
504
+ max_stag_best = 0
505
+ for generation in range(1, generations + 1):
506
+ mating_order = list(range(population_size))
507
+ rng.shuffle(mating_order)
508
+ mating_order = mating_order + [mating_order[0]]
509
+ previous_best = best_distance
510
+ for s in range(population_size):
511
+ parent_index = mating_order[s]
512
+ mate_index = mating_order[s + 1]
513
+ parent_a = list(population[parent_index]['route'])
514
+ parent_b = list(population[mate_index]['route'])
515
+ child_route, child_distance = _eax_candidate_from_pair(parent_a = parent_a,
516
+ parent_b = parent_b,
517
+ distance_matrix = distance_matrix,
518
+ near_cities = near_cities,
519
+ edge_freq = edge_freq,
520
+ population_size = population_size,
521
+ offspring_size = offspring_size,
522
+ stage_mode = stage_mode,
523
+ diversity_mode = diversity_mode,
524
+ rng = rng)
525
+
526
+ if local_search:
527
+ child_closed = _route_0_to_1(child_route)
528
+ child_closed, child_distance = local_search_2_opt(distance_matrix, [child_closed, child_distance],
529
+ recursive_seeding = -1,
530
+ verbose = False)
531
+ child_route = _route_1_to_0(child_closed)
532
+
533
+ if child_distance < population[parent_index]['distance']:
534
+ old_edges = _edge_set_from_route(population[parent_index]['route'])
535
+ new_edges = _edge_set_from_route(child_route)
536
+ for edge in old_edges - new_edges:
537
+ edge_freq[edge] = max(0, edge_freq.get(edge, 0) - 1)
538
+ if edge_freq[edge] == 0:
539
+ del edge_freq[edge]
540
+ for edge in new_edges - old_edges:
541
+ edge_freq[edge] = edge_freq.get(edge, 0) + 1
542
+ population[parent_index] = {'route': child_route, 'distance': float(child_distance)}
543
+
544
+ current_best_individual = min(population, key=lambda item: item['distance'])
545
+ current_best_distance = float(current_best_individual['distance'])
546
+ current_best_route = list(current_best_individual['route'])
547
+ current_average = float(np.mean([item['distance'] for item in population]))
548
+
549
+ if current_best_distance < best_distance:
550
+ best_distance = current_best_distance
551
+ best_route = current_best_route
552
+ no_improve = 0
553
+ else:
554
+ no_improve = no_improve + 1
555
+
556
+ if stage_switch:
557
+ threshold = max(1, int(1500 / max(1, offspring_size)))
558
+ if stage == 1:
559
+ if no_improve == threshold and max_stag_best == 0:
560
+ max_stag_best = max(1, int(generation / 10))
561
+ elif max_stag_best != 0 and no_improve >= max_stag_best:
562
+ stage = 2
563
+ stage_mode = 'multi'
564
+ no_improve = 0
565
+ max_stag_best = 0
566
+ stage_reference_generation = generation
567
+ else:
568
+ if no_improve == threshold and max_stag_best == 0:
569
+ max_stag_best = max(1, int(max(1, generation - stage_reference_generation) / 10))
570
+ elif max_stag_best != 0 and no_improve >= max_stag_best:
571
+ #history.append({'generation': generation, 'best': best_distance, 'average': current_average, 'stage': stage})
572
+ break
573
+
574
+ #history.append({'generation': generation, 'best': best_distance, 'average': current_average, 'stage': stage})
575
+
576
+ if verbose:
577
+ print('Generation = ', generation,
578
+ 'Stage = ', stage,
579
+ 'Best = ', round(best_distance, 2),
580
+ 'Average = ', round(current_average, 2),
581
+ 'No Improve = ', no_improve)
582
+
583
+ if abs(current_average - current_best_distance) < 1e-10:
584
+ break
585
+ if current_best_distance >= previous_best and generation >= generations:
586
+ break
587
+
588
+ best_route_closed = _route_0_to_1(best_route)
589
+ return best_route_closed, best_distance
590
+
591
+ ############################################################################