├── .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 | 13 | 14 | 16 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 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 🐒 [![Build Status](https://travis-ci.org/wzieba/QAP-Genetic-Algorithm.svg?branch=master)](https://travis-ci.org/wzieba/QAP-Genetic-Algorithm) [![codecov](https://codecov.io/gh/wzieba/QAP-Genetic-Algorithm/branch/master/graph/badge.svg)](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 | ![Visualization](https://raw.githubusercontent.com/wzieba/QAP-Genetic-Algorithm/master/static/visualization.gif "Visualization") 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 | ![Chart](https://raw.githubusercontent.com/wzieba/QAP-Genetic-Algorithm/master/static/chart.png "Chart") 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 --------------------------------------------------------------------------------