├── .gitignore
├── .idea
├── SI_1.iml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
├── other.xml
└── vcs.xml
├── .travis.yml
├── README.md
├── res
└── data
│ ├── had12.dat
│ ├── had14.dat
│ ├── had16.dat
│ ├── had18.dat
│ ├── had20.dat
│ └── test.dat
├── src
├── __init__.py
├── config.py
├── crossover.py
├── data_loading.py
├── fitness_function.py
├── generate_population.py
├── main.py
├── mutation.py
├── plot_drawer.py
├── selection.py
├── test_basicCrossover.py
├── test_basicMutation.py
├── test_fitness_score.py
├── test_generate_random_population.py
├── test_rouletteSelection.py
└── visualization_drawer.py
└── static
├── chart.png
└── visualization.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
2 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
3 |
4 | # User-specific stuff:
5 | .idea/**/workspace.xml
6 | .idea/**/tasks.xml
7 | .idea/dictionaries
8 |
9 | # Sensitive or high-churn files:
10 | .idea/**/dataSources/
11 | .idea/**/dataSources.ids
12 | .idea/**/dataSources.local.xml
13 | .idea/**/sqlDataSources.xml
14 | .idea/**/dynamic.xml
15 | .idea/**/uiDesigner.xml
16 |
17 | # Gradle:
18 | .idea/**/gradle.xml
19 | .idea/**/libraries
20 |
21 | # CMake
22 | cmake-build-debug/
23 | cmake-build-release/
24 |
25 | # Mongo Explorer plugin:
26 | .idea/**/mongoSettings.xml
27 |
28 | ## File-based project format:
29 | *.iws
30 |
31 | ## Plugin-specific files:
32 |
33 | # IntelliJ
34 | out/
35 |
36 | # mpeltonen/sbt-idea plugin
37 | .idea_modules/
38 |
39 | # JIRA plugin
40 | atlassian-ide-plugin.xml
41 |
42 | # Cursive Clojure plugin
43 | .idea/replstate.xml
44 |
45 | # Crashlytics plugin (for Android Studio and IntelliJ)
46 | com_crashlytics_export_strings.xml
47 | crashlytics.properties
48 | crashlytics-build.properties
49 | fabric.properties
50 |
51 | #Venv
52 | venv/
53 |
54 | #PyCache
55 | src/__pycache__
56 |
57 | #Coverage
58 | .coverage
59 | src/.coverage
--------------------------------------------------------------------------------
/.idea/SI_1.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/other.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: python
4 |
5 | python:
6 | - 3.6.4
7 |
8 | install:
9 | - pip install codecov
10 |
11 | script:
12 | - coverage run --source=. -m unittest discover .
13 |
14 | after_success:
15 | - codecov
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # QAP with GA 🐒 [](https://travis-ci.org/wzieba/QAP-Genetic-Algorithm) [](https://codecov.io/gh/wzieba/QAP-Genetic-Algorithm)
2 |
3 | Resolving Quadratic Assignment Problem with Genetic Algorithm
4 |
5 | ## 🚀 Getting Started
6 |
7 | To run clone repo, go to `src` folder and run
8 | ```
9 | python main.py
10 | ```
11 |
12 | ## 🔧 Config
13 |
14 | In `config.py` you can find following configuration options:
15 | ```
16 | INPUT_FILE = "had12.dat"
17 | CROSSOVER_PROBABILITY = 0.7
18 | MUTATION_PROBABILITY = 0.08
19 | POPULATION_SIZE = 100
20 | NUMBER_OF_GENERATIONS = 100
21 | DRAW_VISUALIZATION = True
22 | DRAW_CHART = True
23 | ```
24 | Feel free to experiment with them.
25 |
26 | ## 📈 Visualization
27 |
28 | ### Simulation
29 |
30 | 
31 |
32 | #### Legend (how to read)
33 |
34 | - Red color of line means long distance, green one - short
35 | - Thick line means big value of flow (aka cost), thin one - small
36 |
37 | Both values are in context of particular distance and flow matrices
38 |
39 | In short: thin green is better than thick red
40 |
41 | ### Charts
42 |
43 | 
44 |
45 | ## 🚚 Quadratic Assignment Problem
46 |
47 | The objective of the Quadratic Assignment Problem (QAP) is to assign n facilities to n locations in such a way as to minimize the assignment cost. The assignment cost is the sum, over all pairs, of the flow between a pair of facilities multiplied by the distance between their assigned locations.
48 |
49 | Source and more information: [neos-guide.org](https://neos-guide.org/content/quadratic-assignment-problem)
50 |
51 | ### Dataset
52 |
53 | Dataset available in `res/data` are taken from [http://anjos.mgi.polymtl.ca/qaplib/inst.html#HRW](http://anjos.mgi.polymtl.ca/qaplib/inst.html#HRW)
54 |
55 | Authors: S.W. Hadley, F. Rendl and H. Wolkowicz
56 |
57 | ## Genetic Algorithm
58 |
59 | In computer science and operations research, a genetic algorithm (GA) is a metaheuristic inspired by the process of natural selection. [More](https://en.wikipedia.org/wiki/Genetic_algorithm)
60 |
61 | ### Important note
62 |
63 | Some fragments of this implementation were inspired by code of mgr Filip Bachura from Wroclaw University of Science and Technology
--------------------------------------------------------------------------------
/res/data/had12.dat:
--------------------------------------------------------------------------------
1 | 12
2 |
3 | 0 1 2 2 3 4 4 5 3 5 6 7
4 | 1 0 1 1 2 3 3 4 2 4 5 6
5 | 2 1 0 2 1 2 2 3 1 3 4 5
6 | 2 1 2 0 1 2 2 3 3 3 4 5
7 | 3 2 1 1 0 1 1 2 2 2 3 4
8 | 4 3 2 2 1 0 2 3 3 1 2 3
9 | 4 3 2 2 1 2 0 1 3 1 2 3
10 | 5 4 3 3 2 3 1 0 4 2 1 2
11 | 3 2 1 3 2 3 3 4 0 4 5 6
12 | 5 4 3 3 2 1 1 2 4 0 1 2
13 | 6 5 4 4 3 2 2 1 5 1 0 1
14 | 7 6 5 5 4 3 3 2 6 2 1 0
15 |
16 | 0 3 4 6 8 5 6 6 5 1 4 6
17 | 3 0 6 3 7 9 9 2 2 7 4 7
18 | 4 6 0 2 6 4 4 4 2 6 3 6
19 | 6 3 2 0 5 5 3 3 9 4 3 6
20 | 8 7 6 5 0 4 3 4 5 7 6 7
21 | 5 9 4 5 4 0 8 5 5 5 7 5
22 | 6 9 4 3 3 8 0 6 8 4 6 7
23 | 6 2 4 3 4 5 6 0 1 5 5 3
24 | 5 2 2 9 5 5 8 1 0 4 5 2
25 | 1 7 6 4 7 5 4 5 4 0 7 7
26 | 4 4 3 3 6 7 6 5 5 7 0 9
27 | 6 7 6 6 7 5 7 3 2 7 9 0
--------------------------------------------------------------------------------
/res/data/had14.dat:
--------------------------------------------------------------------------------
1 | 14
2 |
3 | 0 1 2 2 3 4 4 5 3 5 6 7 8 9
4 | 1 0 1 1 2 3 3 4 2 4 5 6 7 8
5 | 2 1 0 2 1 2 2 3 1 3 4 5 6 7
6 | 2 1 2 0 1 2 2 3 3 3 4 5 6 7
7 | 3 2 1 1 0 1 1 2 2 2 3 4 5 6
8 | 4 3 2 2 1 0 2 3 3 1 2 3 4 5
9 | 4 3 2 2 1 2 0 1 3 1 2 3 4 5
10 | 5 4 3 3 2 3 1 0 4 2 1 2 3 4
11 | 3 2 1 3 2 3 3 4 0 4 5 6 7 8
12 | 5 4 3 3 2 1 1 2 4 0 1 2 3 4
13 | 6 5 4 4 3 2 2 1 5 1 0 1 2 3
14 | 7 6 5 5 4 3 3 2 6 2 1 0 1 2
15 | 8 7 6 6 5 4 4 3 7 3 2 1 0 1
16 | 9 8 7 7 6 5 5 4 8 4 3 2 1 0
17 |
18 | 0 3 4 6 8 5 6 6 5 1 4 6 1 5
19 | 3 0 6 3 7 9 9 2 2 7 4 7 9 6
20 | 4 6 0 2 6 4 4 4 2 6 3 6 5 6
21 | 6 3 2 0 5 5 3 3 9 4 3 6 3 4
22 | 8 7 6 5 0 4 3 4 5 7 6 7 7 3
23 | 5 9 4 5 4 0 8 5 5 5 7 5 1 8
24 | 6 9 4 3 3 8 0 6 8 4 6 7 1 8
25 | 6 2 4 3 4 5 6 0 1 5 5 3 7 5
26 | 5 2 2 9 5 5 8 1 0 4 5 2 4 5
27 | 1 7 6 4 7 5 4 5 4 0 7 7 5 6
28 | 4 4 3 3 6 7 6 5 5 7 0 9 6 5
29 | 6 7 6 6 7 5 7 3 2 7 9 0 6 5
30 | 1 9 5 3 7 1 1 7 4 5 6 6 0 5
31 | 5 6 6 4 3 8 8 5 5 6 5 5 5 0
--------------------------------------------------------------------------------
/res/data/had16.dat:
--------------------------------------------------------------------------------
1 | 16
2 |
3 | 0 1 2 2 3 4 4 5 3 5 6 7 8 9 7 8
4 | 1 0 1 1 2 3 3 4 2 4 5 6 7 8 6 7
5 | 2 1 0 2 1 2 2 3 1 3 4 5 6 7 5 6
6 | 2 1 2 0 1 2 2 3 3 3 4 5 6 7 5 6
7 | 3 2 1 1 0 1 1 2 2 2 3 4 5 6 4 5
8 | 4 3 2 2 1 0 2 3 3 1 2 3 4 5 3 4
9 | 4 3 2 2 1 2 0 1 3 1 2 3 4 5 3 4
10 | 5 4 3 3 2 3 1 0 4 2 1 2 3 4 2 3
11 | 3 2 1 3 2 3 3 4 0 4 5 6 7 8 6 7
12 | 5 4 3 3 2 1 1 2 4 0 1 2 3 4 2 3
13 | 6 5 4 4 3 2 2 1 5 1 0 1 2 3 1 2
14 | 7 6 5 5 4 3 3 2 6 2 1 0 1 2 2 3
15 | 8 7 6 6 5 4 4 3 7 3 2 1 0 1 1 2
16 | 9 8 7 7 6 5 5 4 8 4 3 2 1 0 2 1
17 | 7 6 5 5 4 3 3 2 6 2 1 2 1 2 0 1
18 | 8 7 6 6 5 4 4 3 7 3 2 3 2 1 1 0
19 |
20 | 0 3 4 6 8 5 6 6 5 1 4 6 1 5 4 5
21 | 3 0 6 3 7 9 9 2 2 7 4 7 9 6 3 2
22 | 4 6 0 2 6 4 4 4 2 6 3 6 5 6 2 6
23 | 6 3 2 0 5 5 3 3 9 4 3 6 3 4 7 8
24 | 8 7 6 5 0 4 3 4 5 7 6 7 7 3 3 3
25 | 5 9 4 5 4 0 8 5 5 5 7 5 1 8 5 4
26 | 6 9 4 3 3 8 0 6 8 4 6 7 1 8 5 6
27 | 6 2 4 3 4 5 6 0 1 5 5 3 7 5 9 4
28 | 5 2 2 9 5 5 8 1 0 4 5 2 4 5 4 5
29 | 1 7 6 4 7 5 4 5 4 0 7 7 5 6 5 5
30 | 4 4 3 3 6 7 6 5 5 7 0 9 6 5 1 8
31 | 6 7 6 6 7 5 7 3 2 7 9 0 6 5 4 5
32 | 1 9 5 3 7 1 1 7 4 5 6 6 0 5 7 4
33 | 5 6 6 4 3 8 8 5 5 6 5 5 5 0 5 3
34 | 4 3 2 7 3 5 5 9 4 5 1 4 7 5 0 8
35 | 5 2 6 8 3 4 6 4 5 5 8 5 4 3 8 0
--------------------------------------------------------------------------------
/res/data/had18.dat:
--------------------------------------------------------------------------------
1 | 18
2 |
3 | 0 1 2 2 3 4 4 5 3 5 6 7 8 9 7 8 4 5
4 | 1 0 1 1 2 3 3 4 2 4 5 6 7 8 6 7 3 4
5 | 2 1 0 2 1 2 2 3 1 3 4 5 6 7 5 6 2 3
6 | 2 1 2 0 1 2 2 3 3 3 4 5 6 7 5 6 4 5
7 | 3 2 1 1 0 1 1 2 2 2 3 4 5 6 4 5 3 4
8 | 4 3 2 2 1 0 2 3 3 1 2 3 4 5 3 4 4 5
9 | 4 3 2 2 1 2 0 1 3 1 2 3 4 5 3 4 4 5
10 | 5 4 3 3 2 3 1 0 4 2 1 2 3 4 2 3 5 6
11 | 3 2 1 3 2 3 3 4 0 4 5 6 7 8 6 7 1 2
12 | 5 4 3 3 2 1 1 2 4 0 1 2 3 4 2 3 5 6
13 | 6 5 4 4 3 2 2 1 5 1 0 1 2 3 1 2 6 7
14 | 7 6 5 5 4 3 3 2 6 2 1 0 1 2 2 3 7 8
15 | 8 7 6 6 5 4 4 3 7 3 2 1 0 1 1 2 8 9
16 | 9 8 7 7 6 5 5 4 8 4 3 2 1 0 2 1 9 10
17 | 7 6 5 5 4 3 3 2 6 2 1 2 1 2 0 1 7 8
18 | 8 7 6 6 5 4 4 3 7 3 2 3 2 1 1 0 8 9
19 | 4 3 2 4 3 4 4 5 1 5 6 7 8 9 7 8 0 1
20 | 5 4 3 5 4 5 5 6 2 6 7 8 9 10 8 9 1 0
21 |
22 | 0 3 4 6 8 5 6 6 5 1 4 6 1 5 4 5 6 8
23 | 3 0 6 3 7 9 9 2 2 7 4 7 9 6 3 2 6 6
24 | 4 6 0 2 6 4 4 4 2 6 3 6 5 6 2 6 5 7
25 | 6 3 2 0 5 5 3 3 9 4 3 6 3 4 7 8 3 2
26 | 8 7 6 5 0 4 3 4 5 7 6 7 7 3 3 3 4 4
27 | 5 9 4 5 4 0 8 5 5 5 7 5 1 8 5 4 3 3
28 | 6 9 4 3 3 8 0 6 8 4 6 7 1 8 5 6 7 6
29 | 6 2 4 3 4 5 6 0 1 5 5 3 7 5 9 4 4 4
30 | 5 2 2 9 5 5 8 1 0 4 5 2 4 5 4 5 4 7
31 | 1 7 6 4 7 5 4 5 4 0 7 7 5 6 5 5 6 10
32 | 4 4 3 3 6 7 6 5 5 7 0 9 6 5 1 8 5 3
33 | 6 7 6 6 7 5 7 3 2 7 9 0 6 5 4 5 4 6
34 | 1 9 5 3 7 1 1 7 4 5 6 6 0 5 7 4 5 2
35 | 5 6 6 4 3 8 8 5 5 6 5 5 5 0 5 3 2 4
36 | 4 3 2 7 3 5 5 9 4 5 1 4 7 5 0 8 5 6
37 | 5 2 6 8 3 4 6 4 5 5 8 5 4 3 8 0 6 8
38 | 6 6 5 3 4 3 7 4 4 6 5 4 5 2 5 6 0 3
39 | 8 6 7 2 4 3 6 4 7 10 3 6 2 4 6 8 3 0
--------------------------------------------------------------------------------
/res/data/had20.dat:
--------------------------------------------------------------------------------
1 | 20
2 |
3 | 0 1 2 2 3 4 4 5 3 5 6 7 8 9 7 8 4 5 3 10
4 | 1 0 1 1 2 3 3 4 2 4 5 6 7 8 6 7 3 4 2 9
5 | 2 1 0 2 1 2 2 3 1 3 4 5 6 7 5 6 2 3 1 8
6 | 2 1 2 0 1 2 2 3 3 3 4 5 6 7 5 6 4 5 3 8
7 | 3 2 1 1 0 1 1 2 2 2 3 4 5 6 4 5 3 4 2 7
8 | 4 3 2 2 1 0 2 3 3 1 2 3 4 5 3 4 4 5 3 6
9 | 4 3 2 2 1 2 0 1 3 1 2 3 4 5 3 4 4 5 1 6
10 | 5 4 3 3 2 3 1 0 4 2 1 2 3 4 2 3 5 6 2 5
11 | 3 2 1 3 2 3 3 4 0 4 5 6 7 8 6 7 1 2 2 9
12 | 5 4 3 3 2 1 1 2 4 0 1 2 3 4 2 3 5 6 2 5
13 | 6 5 4 4 3 2 2 1 5 1 0 1 2 3 1 2 6 7 3 4
14 | 7 6 5 5 4 3 3 2 6 2 1 0 1 2 2 3 7 8 4 3
15 | 8 7 6 6 5 4 4 3 7 3 2 1 0 1 1 2 8 9 5 2
16 | 9 8 7 7 6 5 5 4 8 4 3 2 1 0 2 1 9 10 6 1
17 | 7 6 5 5 4 3 3 2 6 2 1 2 1 2 0 1 7 8 4 3
18 | 8 7 6 6 5 4 4 3 7 3 2 3 2 1 1 0 8 9 5 2
19 | 4 3 2 4 3 4 4 5 1 5 6 7 8 9 7 8 0 1 3 10
20 | 5 4 3 5 4 5 5 6 2 6 7 8 9 10 8 9 1 0 4 11
21 | 3 2 1 3 2 3 1 2 2 2 3 4 5 6 4 5 3 4 0 7
22 | 10 9 8 8 7 6 6 5 9 5 4 3 2 1 3 2 10 11 7 0
23 |
24 | 0 3 4 6 8 5 6 6 5 1 4 6 1 5 4 5 6 8 9 4
25 | 3 0 6 3 7 9 9 2 2 7 4 7 9 6 3 2 6 6 5 6
26 | 4 6 0 2 6 4 4 4 2 6 3 6 5 6 2 6 5 7 6 5
27 | 6 3 2 0 5 5 3 3 9 4 3 6 3 4 7 8 3 2 5 5
28 | 8 7 6 5 0 4 3 4 5 7 6 7 7 3 3 3 4 4 5 5
29 | 5 9 4 5 4 0 8 5 5 5 7 5 1 8 5 4 3 3 6 4
30 | 6 9 4 3 3 8 0 6 8 4 6 7 1 8 5 6 7 6 3 9
31 | 6 2 4 3 4 5 6 0 1 5 5 3 7 5 9 4 4 4 5 2
32 | 5 2 2 9 5 5 8 1 0 4 5 2 4 5 4 5 4 7 5 3
33 | 1 7 6 4 7 5 4 5 4 0 7 7 5 6 5 5 6 10 6 7
34 | 4 4 3 3 6 7 6 5 5 7 0 9 6 5 1 8 5 3 4 6
35 | 6 7 6 6 7 5 7 3 2 7 9 0 6 5 4 5 4 6 8 2
36 | 1 9 5 3 7 1 1 7 4 5 6 6 0 5 7 4 5 2 3 7
37 | 5 6 6 4 3 8 8 5 5 6 5 5 5 0 5 3 2 4 8 3
38 | 4 3 2 7 3 5 5 9 4 5 1 4 7 5 0 8 5 6 7 1
39 | 5 2 6 8 3 4 6 4 5 5 8 5 4 3 8 0 6 8 7 3
40 | 6 6 5 3 4 3 7 4 4 6 5 4 5 2 5 6 0 3 7 7
41 | 8 6 7 2 4 3 6 4 7 10 3 6 2 4 6 8 3 0 5 6
42 | 9 5 6 5 5 6 3 5 5 6 4 8 3 8 7 7 7 5 0 4
43 | 4 6 5 5 5 4 9 2 3 7 6 2 7 3 1 3 7 6 4 0
--------------------------------------------------------------------------------
/res/data/test.dat:
--------------------------------------------------------------------------------
1 | 4
2 |
3 | 0 22 53 53
4 | 22 0 40 62
5 | 53 40 0 55
6 | 53 62 55 0
7 |
8 | 0 3 0 2
9 | 3 0 0 1
10 | 0 0 0 4
11 | 2 1 4 0
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzieba/QAP-Genetic-Algorithm/457aee0a83b18e129d5c46e1adf64dfa4203acd6/src/__init__.py
--------------------------------------------------------------------------------
/src/config.py:
--------------------------------------------------------------------------------
1 | INPUT_FILE = "had12.dat"
2 | CROSSOVER_PROBABILITY = 0.7
3 | MUTATION_PROBABILITY = 0.08
4 | POPULATION_SIZE = 100
5 | NUMBER_OF_GENERATIONS = 100
6 | DRAW_VISUALIZATION = True
7 | DRAW_CHART = True
8 |
--------------------------------------------------------------------------------
/src/crossover.py:
--------------------------------------------------------------------------------
1 | import copy
2 | import random
3 |
4 | from src.config import CROSSOVER_PROBABILITY
5 |
6 |
7 | class Crossover(object):
8 | def __init__(self, crossover_algorithm):
9 | self.crossover_algorithm = crossover_algorithm
10 |
11 | def crossover(self, population):
12 | return self.crossover_algorithm(population)
13 |
14 |
15 | class BasicCrossover(object):
16 |
17 | def __init__(self):
18 | pass
19 |
20 | def __call__(self, population):
21 | return self.perform_crossover(population)
22 |
23 | @staticmethod
24 | def perform_crossover(population):
25 | species_not_crossovered = []
26 | species_to_crossover = []
27 |
28 | BasicCrossover.choose_chromosomes_to_crossover(population, species_not_crossovered, species_to_crossover)
29 |
30 | crossover_tuples = BasicCrossover.create_crossover_tuples(species_not_crossovered, species_to_crossover)
31 |
32 | crossovered_species = BasicCrossover.crossover_population(crossover_tuples)
33 |
34 | return crossovered_species + species_not_crossovered
35 |
36 | @staticmethod
37 | def crossover_population(crossover_tuples):
38 | crossovered_species = []
39 | for crossover_tuple in crossover_tuples:
40 | child_a, child_b = BasicCrossover.crossover_chromosomes(
41 | crossover_tuple,
42 | point_of_crossover=random.randint(0, len(crossover_tuple) - 1)
43 | )
44 | crossovered_species.append(child_a)
45 | crossovered_species.append(child_b)
46 | return crossovered_species
47 |
48 | @staticmethod
49 | def choose_chromosomes_to_crossover(population, species_not_crossovered, species_to_crossover):
50 | for chromosome in population:
51 | if random.uniform(0, 1) < CROSSOVER_PROBABILITY:
52 | species_to_crossover.append(chromosome)
53 | else:
54 | species_not_crossovered.append(chromosome)
55 |
56 | @staticmethod
57 | def create_crossover_tuples(species_not_crossovered, species_to_crossover):
58 | crossover_tuples = []
59 | species_to_crossover = list(enumerate(species_to_crossover))
60 | while species_to_crossover:
61 | chromosome_to_crossover_index, chromosome_to_crossover = species_to_crossover.pop()
62 |
63 | if not species_to_crossover:
64 | species_not_crossovered.append(chromosome_to_crossover)
65 | break
66 |
67 | crossover_buddy_index, crossover_buddy = random.choice(species_to_crossover)
68 | species_to_crossover = list(filter(lambda value: value[0] != crossover_buddy_index, species_to_crossover))
69 | crossover_tuples.append((chromosome_to_crossover, crossover_buddy))
70 | return crossover_tuples
71 |
72 | @staticmethod
73 | def crossover_chromosomes(parents, point_of_crossover):
74 | father, mother = parents
75 | child_a, child_b = copy.copy(father), copy.copy(mother)
76 | # Change chromosome
77 | for index in range(point_of_crossover):
78 | # Values on index
79 | value_a = child_a[index]
80 | value_b = child_b[index]
81 | # Values for swap
82 | index_of_value_b_in_a = child_a.index(value_b)
83 | index_of_value_a_in_b = child_b.index(value_a)
84 | # Save values
85 | child_a[index_of_value_b_in_a] = value_a
86 | child_b[index_of_value_a_in_b] = value_b
87 | # Change values
88 | child_a[index] = value_b
89 | child_b[index] = value_a
90 | return child_a, child_b
91 |
--------------------------------------------------------------------------------
/src/data_loading.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import numpy as np
4 |
5 | from src.config import INPUT_FILE
6 |
7 |
8 | def read_square_matrix(matrix_file) -> np.ndarray:
9 | matrix = []
10 | row_counter = 0
11 | while row_counter < matrices_size:
12 | line = matrix_file.readline()
13 | assert isinstance(line, str)
14 | if not line.isspace():
15 | row_counter += 1
16 | matrix_row = [int(element) for element in line.split(sep=' ') if element != '']
17 | matrix.append(matrix_row)
18 | return np.array(matrix)
19 |
20 |
21 | with open(os.path.join('../res', 'data', INPUT_FILE), mode='r') as file:
22 | matrices_size = int(file.readline())
23 | distance_matrix = read_square_matrix(file)
24 | flow_matrix = read_square_matrix(file)
25 |
--------------------------------------------------------------------------------
/src/fitness_function.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 |
4 | # Summary: The objective of the Quadratic Assignment Problem (QAP) is to assign n facilities to n locations in such a
5 | # way as to minimize the assignment cost.
6 | #
7 | # The assignment cost is the sum, over all pairs, of the flow between a pair
8 | # of facilities multiplied by the distance between their assigned locations.
9 |
10 |
11 | def compute_fitness_scores_list(population, distance_matrix, flow_matrix):
12 | matrices_size = distance_matrix.shape[0]
13 | fitness_scores_list = []
14 | for chromosome in population:
15 | assert len(chromosome) == len(set(chromosome))
16 | chromosome_fitness_sum = 0
17 | for x in range(matrices_size):
18 | for y in range(matrices_size):
19 | chromosome_fitness_sum += distance_matrix[x, y] * flow_matrix[chromosome[x] - 1, chromosome[y] - 1]
20 | fitness_scores_list.append(chromosome_fitness_sum)
21 |
22 | return fitness_scores_list
23 |
24 |
25 | def get_normalized_result_of_fitness_function_scores_list(fitness_scores_list):
26 | map_to_minimization_problem = list(map(lambda value: 1. / value, fitness_scores_list))
27 | normalized_results = np.array(map_to_minimization_problem) / np.sum(map_to_minimization_problem)
28 | return normalized_results
29 |
--------------------------------------------------------------------------------
/src/generate_population.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 |
4 | def generate_random_population(number_of_objects, number_of_random_chromosomes):
5 | population_list = []
6 | for _ in range(number_of_random_chromosomes):
7 | rand_chromosome = list(range(number_of_objects))
8 | random.shuffle(rand_chromosome)
9 | population_list.append(rand_chromosome)
10 | return population_list
11 |
--------------------------------------------------------------------------------
/src/main.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os.path
3 |
4 | import time
5 |
6 | import numpy as np
7 |
8 | from src.plot_drawer import PlotDrawer
9 |
10 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
11 |
12 | from src.config import POPULATION_SIZE, NUMBER_OF_GENERATIONS, DRAW_VISUALIZATION, INPUT_FILE, DRAW_CHART
13 | from src.crossover import BasicCrossover, Crossover
14 | from src.data_loading import matrices_size, flow_matrix, distance_matrix
15 | from src.visualization_drawer import CustomDrawer
16 | from src.fitness_function import get_normalized_result_of_fitness_function_scores_list, compute_fitness_scores_list
17 | from src.generate_population import generate_random_population
18 | from src.mutation import Mutation, BasicMutation
19 | from src.selection import Selection, TournamentSelection, RouletteSelection
20 |
21 | selection_strategy = Selection(selection_algorithm=TournamentSelection())
22 | mutation_strategy = Mutation(mutation_algorithm=BasicMutation())
23 | crossover_strategy = Crossover(crossover_algorithm=BasicCrossover())
24 |
25 |
26 | def main():
27 | population = generate_random_population(matrices_size, POPULATION_SIZE)
28 |
29 | visualization_drawer = CustomDrawer()
30 | plot_drawer = PlotDrawer()
31 |
32 | generation_indicies = []
33 | average_results = []
34 | min_results = []
35 | max_results = []
36 | previous_max_chromosome = []
37 |
38 | def draw_visual_frame():
39 | visualization_drawer.draw_generation_frame(max_chromosome, epoch, max_fitness, max_chromosome, flow_matrix,
40 | distance_matrix)
41 | time.sleep(1)
42 | return
43 |
44 | def print_console_output():
45 | print("Epoch: \t\t\t\t{}\nMean fit.: \t\t\t{}\nMax score: \t\t\t{}\nMax chrom.: \t\t{}\n\n"
46 | .format(epoch, average_fitness, max_fitness, max_chromosome))
47 |
48 | for epoch in range(NUMBER_OF_GENERATIONS):
49 |
50 | fitness_scores = compute_fitness_scores_list(population, distance_matrix, flow_matrix)
51 | fitness_scores_normalized = get_normalized_result_of_fitness_function_scores_list(fitness_scores)
52 |
53 | # While it's not normalized yet, max means "the worst", therefor "min" for us.
54 | max_fitness = np.min(fitness_scores)
55 | min_fitness = np.max(fitness_scores)
56 | average_fitness = np.mean(fitness_scores)
57 |
58 | max_results.append(max_fitness)
59 | min_results.append(min_fitness)
60 | average_results.append(average_fitness)
61 | generation_indicies.append(epoch)
62 |
63 | max_chromosome = population[np.argmin(fitness_scores)]
64 | max_chromosome = list(map(lambda value: value + 1, max_chromosome))
65 |
66 | selected_chromosomes = selection_strategy.select(population, fitness_scores_normalized)
67 | crossed_chromosomes = crossover_strategy.crossover(selected_chromosomes)
68 | mutated_chromosomes = mutation_strategy.mutate(crossed_chromosomes)
69 |
70 | print_console_output()
71 | if DRAW_VISUALIZATION and previous_max_chromosome != max_chromosome:
72 | draw_visual_frame()
73 |
74 | previous_max_chromosome = max_chromosome
75 |
76 | population = mutated_chromosomes
77 |
78 | if DRAW_CHART:
79 | plot_drawer.drawPlot(INPUT_FILE, generation_indicies, average_results, max_results, min_results)
80 |
81 | visualization_drawer.screen.mainloop()
82 |
83 |
84 | if __name__ == "__main__":
85 | main()
86 |
--------------------------------------------------------------------------------
/src/mutation.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | from src.config import MUTATION_PROBABILITY
4 |
5 |
6 | class Mutation(object):
7 | def __init__(self, mutation_algorithm):
8 | self.mutation_algorithm = mutation_algorithm
9 |
10 | def mutate(self, population):
11 | return self.mutation_algorithm(population)
12 |
13 |
14 | class BasicMutation(object):
15 |
16 | def __init__(self):
17 | pass
18 |
19 | def __call__(self, population):
20 | return self.mutate_population(population)
21 |
22 | # In TSP and QAP problem mutation will have slightly different form. We will choose two genes and swap them.
23 | @staticmethod
24 | def mutate_population(population):
25 | chromosomes_with_mutated = []
26 |
27 | for chromosome in population:
28 | if 0 <= random.uniform(0, 1) <= MUTATION_PROBABILITY:
29 | mutated_chromosome = BasicMutation.mutate_chromosome(chromosome,
30 | BasicMutation.generate_random_gen_indexes(
31 | chromosome))
32 | chromosomes_with_mutated.append(mutated_chromosome)
33 | else:
34 | chromosomes_with_mutated.append(chromosome)
35 |
36 | return chromosomes_with_mutated
37 |
38 | @staticmethod
39 | def mutate_chromosome(chromosome, random_indexes):
40 | gen_a_index, gen_b_index = random_indexes
41 | chromosome[gen_a_index], chromosome[gen_b_index] = chromosome[gen_b_index], chromosome[gen_a_index]
42 | return chromosome
43 |
44 | @staticmethod
45 | def generate_random_gen_indexes(chromosome):
46 | return random.randint(0, len(chromosome) - 1), random.randint(0, len(chromosome) - 1)
47 |
--------------------------------------------------------------------------------
/src/plot_drawer.py:
--------------------------------------------------------------------------------
1 | import plotly
2 | from plotly.graph_objs import *
3 |
4 |
5 | class PlotDrawer:
6 |
7 | @staticmethod
8 | def drawPlot(plot_title, generations, average_results, max_results, min_results):
9 | generations_rev = generations[::-1]
10 |
11 | min_results = min_results[::-1]
12 |
13 | max_and_min_trace = Scatter(
14 | x=generations + generations_rev,
15 | y=max_results + min_results,
16 | fill='tozerox',
17 | fillcolor='rgba(0,100,80,0.2)',
18 | line=Line(color='transparent'),
19 | showlegend=True,
20 | name='Max and min',
21 | )
22 |
23 | average_trace = Scatter(
24 | x=generations,
25 | y=average_results,
26 | line=Line(color='rgb(0,100,80)'),
27 | mode='lines',
28 | name='Average',
29 | )
30 |
31 | data = Data([max_and_min_trace, average_trace])
32 |
33 | layout = Layout(
34 | title=plot_title,
35 | paper_bgcolor='rgb(255,255,255)',
36 | plot_bgcolor='rgb(229,229,229)',
37 | xaxis=XAxis(
38 | gridcolor='rgb(255,255,255)',
39 | showgrid=True,
40 | showline=False,
41 | showticklabels=True,
42 | tickcolor='rgb(127,127,127)',
43 | ticks='outside',
44 | zeroline=False
45 | ),
46 | yaxis=YAxis(
47 | gridcolor='rgb(255,255,255)',
48 | showgrid=True,
49 | showline=False,
50 | showticklabels=True,
51 | tickcolor='rgb(127,127,127)',
52 | ticks='outside',
53 | zeroline=False
54 | ),
55 | )
56 | fig = Figure(data=data, layout=layout)
57 | plotly.offline.plot(fig, filename='plot_' + plot_title + '.html')
58 |
--------------------------------------------------------------------------------
/src/selection.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import random
3 |
4 |
5 | class Selection(object):
6 | def __init__(self, selection_algorithm):
7 | self.selection_algorithm = selection_algorithm
8 |
9 | def select(self, population, fitness_scores):
10 | return self.selection_algorithm(population, fitness_scores)
11 |
12 |
13 | class RouletteSelection(object):
14 |
15 | def __init__(self):
16 | pass
17 |
18 | def __call__(self, population, fitness_score_list):
19 | return self.selection(population, fitness_score_list)
20 |
21 | # Chromosomes with bigger fitness will be selected more times
22 | @staticmethod
23 | def selection(population, fitness_scores_list):
24 | new_population = []
25 |
26 | # Remove maximum value from every element so roulette selection will rely on bigger difference
27 | worst_result = np.min(fitness_scores_list)
28 | fitness_scores_list = list(map(lambda value: value - worst_result, fitness_scores_list))
29 |
30 | cumulative_sum = np.cumsum(fitness_scores_list)
31 |
32 | for _ in range(len(population)):
33 | probability_of_choose = random.uniform(0, 1) * sum(fitness_scores_list)
34 | randomly_selected_member = RouletteSelection.select_chromosome(population,
35 | cumulative_sum,
36 | probability_of_choose)
37 | new_population.append(randomly_selected_member)
38 |
39 | return new_population
40 |
41 | @staticmethod
42 | def select_chromosome(population, cumulative_sum, randomly_generated_probability):
43 | for index, _ in enumerate(cumulative_sum):
44 | if randomly_generated_probability <= cumulative_sum[index]:
45 | return population[index]
46 |
47 |
48 | class TournamentSelection(object):
49 |
50 | def __init__(self):
51 | pass
52 |
53 | def __call__(self, population, fitness_score_list):
54 | return self.tournament_selection(population, fitness_score_list)
55 |
56 | @staticmethod
57 | def tournament_selection(population_list, fitness_scores_list, elitism=False): # Create new population
58 | new_species = []
59 | population_size = len(fitness_scores_list)
60 | population_size = population_size - 1 if elitism else population_size
61 | for _ in range(0, population_size):
62 | # Take best
63 | of_parent_idx = random.randint(0, len(fitness_scores_list) - 1)
64 | tf_parent_idx = random.randint(0, len(fitness_scores_list) - 1)
65 | if fitness_scores_list[of_parent_idx] > fitness_scores_list[tf_parent_idx]:
66 | ch_winner = population_list[of_parent_idx]
67 | else:
68 | ch_winner = population_list[tf_parent_idx]
69 | new_species.append(ch_winner)
70 | return new_species
71 |
--------------------------------------------------------------------------------
/src/test_basicCrossover.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 | from src.crossover import BasicCrossover
4 |
5 | TEST_POPULATION = [[1, 2, 3, 4, 5, 6], [6, 5, 4, 3, 2, 1]]
6 |
7 |
8 | class TestBasicCrossover(TestCase):
9 |
10 | def setUp(self):
11 | super().setUp()
12 | self.crossover_algorithm = BasicCrossover()
13 |
14 | def test_crossover_chromosomes(self):
15 | chromosome_a, chromosome_b = self.crossover_algorithm.crossover_chromosomes(
16 | (TEST_POPULATION[0],
17 | TEST_POPULATION[1]),
18 | point_of_crossover=2
19 | )
20 | self.assertEqual([6, 5, 3, 4, 2, 1], chromosome_a)
21 | self.assertEqual([1, 2, 4, 3, 5, 6], chromosome_b)
22 |
23 | def test_size_of_crossovered_population(self):
24 | self.assertGreaterEqual(len(self.crossover_algorithm.perform_crossover(TEST_POPULATION)), len(TEST_POPULATION))
25 |
--------------------------------------------------------------------------------
/src/test_basicMutation.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 | from src.mutation import BasicMutation
4 |
5 | TEST_CHROMOSOME_TO_MUTATE = [1, 2, 3, 4]
6 | TEST_POPULATION = [[1, 2, 3, 4], [3, 4, 1, 2], [4, 1, 3, 2]]
7 |
8 |
9 | class TestBasicMutation(TestCase):
10 |
11 | def setUp(self):
12 | super().setUp()
13 | self.basic_mutation = BasicMutation()
14 |
15 | def test_mutate_chromosome(self):
16 | self.assertEqual([1, 4, 3, 2], self.basic_mutation.mutate_chromosome(TEST_CHROMOSOME_TO_MUTATE, (1, 3)))
17 |
18 | def test_mutate_population(self):
19 | self.assertEqual(len(TEST_POPULATION), len(self.basic_mutation.mutate_population(TEST_POPULATION)))
20 |
--------------------------------------------------------------------------------
/src/test_fitness_score.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 | import numpy
4 | import numpy as np
5 |
6 | from src.fitness_function import compute_fitness_scores_list, get_normalized_result_of_fitness_function_scores_list
7 |
8 | TEST_POPULATION = [[1, 2, 3, 4], [3, 4, 1, 2], [4, 1, 3, 2], [4, 1, 2, 3], [3, 4, 2, 1]]
9 |
10 | DISTANCE_MATRIX = numpy.matrix([
11 | [0, 22, 53, 53],
12 | [22, 0, 40, 62],
13 | [53, 40, 0, 55],
14 | [53, 62, 55, 0]
15 | ])
16 |
17 | FLOW_MATRIX = numpy.matrix([
18 | [0, 3, 0, 2],
19 | [3, 0, 0, 1],
20 | [0, 0, 0, 4],
21 | [2, 1, 4, 0]
22 | ])
23 |
24 |
25 | class TestFitnessScore(TestCase):
26 |
27 | def test_fitness_scores_match(self):
28 | fitness_scores_list = compute_fitness_scores_list(TEST_POPULATION, DISTANCE_MATRIX, FLOW_MATRIX)
29 | self.assertEqual([908, 790, 990, 858, 834], fitness_scores_list)
30 |
31 | def test_normalized_fitness_scores(self):
32 | fitness_scores_list = compute_fitness_scores_list(TEST_POPULATION, DISTANCE_MATRIX, FLOW_MATRIX)
33 | normalized_fitness_scores_list = get_normalized_result_of_fitness_function_scores_list(fitness_scores_list)
34 | for normalized_fitness_score in normalized_fitness_scores_list:
35 | self.assertTrue(0 <= normalized_fitness_score <= 1)
36 |
37 | def test_invalid_parameters(self):
38 | with self.assertRaises(AssertionError):
39 | compute_fitness_scores_list([[2, 1, 1, 1]], DISTANCE_MATRIX, FLOW_MATRIX)
40 |
41 | def test_normalized_fitness_score_values(self):
42 | fitness_scores_list = compute_fitness_scores_list(TEST_POPULATION, DISTANCE_MATRIX, FLOW_MATRIX)
43 | scores_list = get_normalized_result_of_fitness_function_scores_list(fitness_scores_list)
44 | self.assertTrue(scores_list[2] < scores_list[0] < scores_list[3] < scores_list[1])
45 |
46 | def test_results_sum_to_one(self):
47 | fitness_scores_list = compute_fitness_scores_list(TEST_POPULATION, DISTANCE_MATRIX, FLOW_MATRIX)
48 | normalized_fitness_scores_list = get_normalized_result_of_fitness_function_scores_list(fitness_scores_list)
49 | self.assertTrue(abs(1.0 - sum(normalized_fitness_scores_list) <= 0.00002))
50 |
51 | def test_best_value_choose(self):
52 | fitness_scores_list = compute_fitness_scores_list(TEST_POPULATION, DISTANCE_MATRIX, FLOW_MATRIX)
53 | normalized_fitness_scores_list = get_normalized_result_of_fitness_function_scores_list(fitness_scores_list)
54 | max_chromosome = TEST_POPULATION[np.argmax(normalized_fitness_scores_list)]
55 | self.assertEqual([3, 4, 1, 2], max_chromosome)
56 |
57 | def test_mean_of_fitness_score(self):
58 | fitness_scores_list = compute_fitness_scores_list(TEST_POPULATION, DISTANCE_MATRIX, FLOW_MATRIX)
59 | normalized_fitness_scores_list = get_normalized_result_of_fitness_function_scores_list(fitness_scores_list)
60 | self.assertTrue(abs(0.2 - np.mean(normalized_fitness_scores_list) <= 0.00002))
61 |
--------------------------------------------------------------------------------
/src/test_generate_random_population.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 | from src.generate_population import generate_random_population
4 |
5 | TEST_NUMBER_OF_RANDOM_CHROMOSOMES = 5
6 | TEST_NUMBER_OF_OBJECTS = 10
7 |
8 |
9 | class TestGenerateRandomPopulation(TestCase):
10 | list_of_random_chromosomes = []
11 |
12 | def setUp(self):
13 | self.list_of_random_chromosomes = generate_random_population(TEST_NUMBER_OF_OBJECTS,
14 | TEST_NUMBER_OF_RANDOM_CHROMOSOMES)
15 |
16 | def testGenerateRandomPopulationSize(self):
17 | self.assertTrue(self.list_of_random_chromosomes.__len__() == TEST_NUMBER_OF_RANDOM_CHROMOSOMES)
18 |
19 | def testGenerateRandomPopulationGensInRange(self):
20 | for chromosome in self.list_of_random_chromosomes:
21 | for gen in chromosome:
22 | self.assertTrue(gen in range(TEST_NUMBER_OF_OBJECTS))
23 |
--------------------------------------------------------------------------------
/src/test_rouletteSelection.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 | from src.selection import RouletteSelection
4 |
5 | TEST_FITNESS_SCORES_LIST = [1, 2, 3]
6 | TEST_CUMULATIVE_SUM = [1, 3, 6]
7 | TEST_POPULATION = [["testA"], ["testB"], ["testC"]]
8 |
9 |
10 | class TestRouletteSelection(TestCase):
11 |
12 | def setUp(self):
13 | super().setUp()
14 | self.roulette_selection_algorithm = RouletteSelection()
15 |
16 | def test_select_proper_value(self):
17 | self.assertEqual(["testC"], self.selected_chromosome(3.5))
18 | self.assertEqual(["testA"], self.selected_chromosome(0))
19 | self.assertEqual(["testC"], self.selected_chromosome(6))
20 | self.assertEqual(["testA"], self.selected_chromosome(0.99))
21 |
22 | def test_size_of_generated_population(self):
23 | new_population = self.roulette_selection_algorithm.selection(TEST_POPULATION, TEST_FITNESS_SCORES_LIST)
24 | self.assertEqual(len(TEST_POPULATION), len(new_population))
25 |
26 | def test_validate_members_of_generated_population(self):
27 | new_population = self.roulette_selection_algorithm.selection(TEST_POPULATION, TEST_FITNESS_SCORES_LIST)
28 | for chromosome in new_population:
29 | self.assertIn(chromosome, TEST_POPULATION)
30 |
31 | def selected_chromosome(self, randomly_generated_probability):
32 | return self.roulette_selection_algorithm.select_chromosome(TEST_POPULATION,
33 | TEST_CUMULATIVE_SUM,
34 | randomly_generated_probability)
35 |
--------------------------------------------------------------------------------
/src/visualization_drawer.py:
--------------------------------------------------------------------------------
1 | import math
2 | import sys
3 | import turtle
4 |
5 | import numpy as np
6 |
7 |
8 | class CustomDrawer(turtle.Turtle):
9 | POINT_RADIUS = 20
10 | FONT_CONFIG = ["Arial", 14, "normal"]
11 | DRAWING_SPEED = "fastest"
12 | SHOW_TURTLE = False
13 | CAPTIONS_MARGIN = 50
14 | DISTANCE_BETWEEN_POINTS = 100
15 |
16 | coordinates_list = {}
17 |
18 | def __init__(self):
19 | turtle.Turtle.__init__(self, shape="classic")
20 | self.screen.tracer(0, 0)
21 | self.screen.colormode(255)
22 | self.speed(CustomDrawer.DRAWING_SPEED)
23 | if not CustomDrawer.SHOW_TURTLE:
24 | self.hideturtle()
25 |
26 | def draw_generation_frame(
27 | self,
28 | max_chromosome,
29 | generation_number,
30 | best_score,
31 | best_chromosome,
32 | flow_matrix,
33 | distance_matrix
34 | ):
35 | self.clear()
36 | self.reset()
37 | self.__draw_generation_info(generation_number, best_score, best_chromosome)
38 | self.draw_points(max_chromosome)
39 | self.__draw_connections_between_points(max_chromosome, flow_matrix, distance_matrix)
40 | self.screen.update()
41 |
42 | def move_to_circle_center(self, circle_radius, polygon_angle):
43 | self.penup()
44 | self.left(90)
45 | self.forward(circle_radius)
46 | self.right(90 - (polygon_angle / 2))
47 | self.pendown()
48 |
49 | def draw_points(self, best_chromosome):
50 | number_of_points = len(best_chromosome)
51 | circle_radius = CustomDrawer.DISTANCE_BETWEEN_POINTS / (2 * (math.sin(math.pi / number_of_points)))
52 | polygon_angle = 360 / number_of_points
53 | self.move_to_circle_center(circle_radius, polygon_angle)
54 | for draw_index in range(number_of_points):
55 | self.circle(CustomDrawer.POINT_RADIUS)
56 | self.coordinates_list[draw_index] = self.pos()
57 | self.penup()
58 | self.left(90)
59 | self.forward(20)
60 | self.write(best_chromosome[draw_index], font=CustomDrawer.FONT_CONFIG)
61 | self.backward(20)
62 | self.right(90)
63 | self.right((360 / number_of_points))
64 | self.forward(CustomDrawer.DISTANCE_BETWEEN_POINTS)
65 | self.pendown()
66 |
67 | def __draw_connections_between_points(self, max_chromosome, flow_matrix, distance_matrix):
68 | max_distance = np.max(distance_matrix)
69 | min_distance = np.min(distance_matrix[np.nonzero(distance_matrix)])
70 | for x in range(len(max_chromosome)):
71 | for y in range(len(max_chromosome)):
72 | flow_value = flow_matrix[max_chromosome[x] - 1, max_chromosome[y] - 1]
73 | distance_value = distance_matrix[x, y]
74 | if flow_value != 0:
75 | self.__draw_line(x, y,
76 | pen_color=self.__get_color_for_value(distance_value, min_distance, max_distance),
77 | pen_size=flow_value)
78 |
79 | def __draw_line(self, object_from, object_to, pen_color=(0, 0, 0), pen_size=1):
80 | position_from = self.coordinates_list[object_from]
81 | position_to = self.coordinates_list[object_to]
82 | self.penup()
83 | self.goto(position_from[0], position_from[1])
84 | self.pendown()
85 | self.pensize(pen_size)
86 | self.pencolor(pen_color)
87 | self.goto(position_to[0], position_to[1])
88 | self.pensize(1)
89 |
90 | def __draw_generation_info(self, generation_number, best_score, best_chromosome):
91 | start_position = self.pos()
92 | self.penup()
93 | self.goto(-self.screen.window_width() / 2 + CustomDrawer.CAPTIONS_MARGIN,
94 | self.screen.window_height() / 2 - CustomDrawer.CAPTIONS_MARGIN)
95 | self.write(f'Best chromosome: {best_chromosome}', font=CustomDrawer.FONT_CONFIG)
96 | self.right(90)
97 | self.forward(20)
98 | self.write(f'Generation number: {generation_number}', font=CustomDrawer.FONT_CONFIG)
99 | self.forward(20)
100 | self.write(f'Best score: {best_score}', font=CustomDrawer.FONT_CONFIG)
101 |
102 | self.left(90)
103 | self.goto(start_position[0], start_position[1])
104 | self.pendown()
105 |
106 | def __get_color_for_value(self, value, min_distance, max_distance):
107 | return self.convert_to_rgb(min_distance, max_distance, value)
108 |
109 | @staticmethod
110 | def convert_to_rgb(minval, maxval, val):
111 | colors = [(0, 255, 0), (255, 0, 0)]
112 | fi = float(val - minval) / float(maxval - minval) * (len(colors) - 1)
113 | i = int(fi)
114 | f = fi - i
115 | if f < sys.float_info.epsilon:
116 | return colors[i]
117 | else:
118 | (r1, g1, b1), (r2, g2, b2) = colors[i], colors[i + 1]
119 | return int(r1 + f * (r2 - r1)), int(g1 + f * (g2 - g1)), int(b1 + f * (b2 - b1))
120 |
--------------------------------------------------------------------------------
/static/chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzieba/QAP-Genetic-Algorithm/457aee0a83b18e129d5c46e1adf64dfa4203acd6/static/chart.png
--------------------------------------------------------------------------------
/static/visualization.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wzieba/QAP-Genetic-Algorithm/457aee0a83b18e129d5c46e1adf64dfa4203acd6/static/visualization.gif
--------------------------------------------------------------------------------