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.
- {pycombinatorial-2.2.2/pycombinatorial.egg-info → pycombinatorial-2.2.3}/PKG-INFO +6 -2
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/README.md +5 -1
- pycombinatorial-2.2.3/pyCombinatorial/__init__.py +5 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/__init__.py +1 -0
- pycombinatorial-2.2.3/pyCombinatorial/algorithm/ga_eax.py +591 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/__init__.py +53 -2
- pycombinatorial-2.2.3/pyCombinatorial/web/js/algorithms/ga_eax.js +615 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/index.js +2 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3/pycombinatorial.egg-info}/PKG-INFO +6 -2
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/SOURCES.txt +2 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/setup.py +1 -1
- pycombinatorial-2.2.2/pyCombinatorial/__init__.py +0 -5
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/LICENSE +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/MANIFEST.in +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/aco.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/alns.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/bb.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/bf.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/bhk.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/brkga.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/bt.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/christofides.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/conc_hull.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/conv_hull.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/cw.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/eln.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/eo.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/frnn.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ga.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/gksp.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/grasp.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/hpn.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ins_c.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ins_f.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ins_n.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ins_r.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ksp.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/lns.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/mf.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/nn.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_2.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_2_5.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_2_5s.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_2s.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_3.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_3s.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_4.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_4s.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_5.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_5s.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/opt_or.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rl_double_ql.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rl_ql.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rl_sarsa.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rr.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rss.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/rt.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_gui.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_itr.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_sct.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_shc.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_tabu.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/s_vns.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/sa.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/som.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/spfc_h.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/spfc_m.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/spfc_s.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/ssi.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/swp.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/tat.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/tbb.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/algorithm/zs.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/utils/__init__.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/utils/graphs.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/utils/util.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/__main__.py +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/css/style.css +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/index.html +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/_kopt.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/_kopt_trials.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/_ksp_helpers.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/_shared.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/aco.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/alns.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/bb.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/bf.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/bhk.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/brkga.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/bt.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/christofides.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/conc_hull.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/conv_hull.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/cw.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/eln.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/eo.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/frnn.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ga.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/gksp.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/grasp.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/hpn.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ins_c.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ins_f.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ins_n.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ins_r.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ksp.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/lns.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/mf.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/nn.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_2.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_2_5.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_2_5s.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_2s.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_3.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_3s.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_4.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_4s.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_5.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_5s.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/opt_or.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rl_double_ql.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rl_ql.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rl_sarsa.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rr.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rss.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/rt.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_gui.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_itr.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_sct.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_shc.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_tabu.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/s_vns.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/sa.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/som.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/spfc_h.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/spfc_m.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/spfc_s.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/ssi.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/swp.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/tat.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/tbb.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/algorithms/zs.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/dataset.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/distance.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/renderer.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/seed.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/stepper.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/core/twoopt_refiner.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pyCombinatorial/web/js/main.js +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/dependency_links.txt +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/entry_points.txt +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/requires.txt +0 -0
- {pycombinatorial-2.2.2 → pycombinatorial-2.2.3}/pycombinatorial.egg-info/top_level.txt +0 -0
- {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.
|
|
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))
|
|
@@ -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
|
+
############################################################################
|