UI elements using the new structs #8
							
								
								
									
										164
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
				
			|||||||
 | 
					# osm-cache
 | 
				
			||||||
 | 
					cache/
 | 
				
			||||||
 | 
					# Byte-compiled / optimized / DLL files
 | 
				
			||||||
 | 
					__pycache__/
 | 
				
			||||||
 | 
					*.py[cod]
 | 
				
			||||||
 | 
					*$py.class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# C extensions
 | 
				
			||||||
 | 
					*.so
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Distribution / packaging
 | 
				
			||||||
 | 
					.Python
 | 
				
			||||||
 | 
					build/
 | 
				
			||||||
 | 
					develop-eggs/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
 | 
					downloads/
 | 
				
			||||||
 | 
					eggs/
 | 
				
			||||||
 | 
					.eggs/
 | 
				
			||||||
 | 
					lib/
 | 
				
			||||||
 | 
					lib64/
 | 
				
			||||||
 | 
					parts/
 | 
				
			||||||
 | 
					sdist/
 | 
				
			||||||
 | 
					var/
 | 
				
			||||||
 | 
					wheels/
 | 
				
			||||||
 | 
					share/python-wheels/
 | 
				
			||||||
 | 
					*.egg-info/
 | 
				
			||||||
 | 
					.installed.cfg
 | 
				
			||||||
 | 
					*.egg
 | 
				
			||||||
 | 
					MANIFEST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PyInstaller
 | 
				
			||||||
 | 
					#  Usually these files are written by a python script from a template
 | 
				
			||||||
 | 
					#  before PyInstaller builds the exe, so as to inject date/other infos into it.
 | 
				
			||||||
 | 
					*.manifest
 | 
				
			||||||
 | 
					*.spec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Installer logs
 | 
				
			||||||
 | 
					pip-log.txt
 | 
				
			||||||
 | 
					pip-delete-this-directory.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Unit test / coverage reports
 | 
				
			||||||
 | 
					htmlcov/
 | 
				
			||||||
 | 
					.tox/
 | 
				
			||||||
 | 
					.nox/
 | 
				
			||||||
 | 
					.coverage
 | 
				
			||||||
 | 
					.coverage.*
 | 
				
			||||||
 | 
					.cache
 | 
				
			||||||
 | 
					nosetests.xml
 | 
				
			||||||
 | 
					coverage.xml
 | 
				
			||||||
 | 
					*.cover
 | 
				
			||||||
 | 
					*.py,cover
 | 
				
			||||||
 | 
					.hypothesis/
 | 
				
			||||||
 | 
					.pytest_cache/
 | 
				
			||||||
 | 
					cover/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Translations
 | 
				
			||||||
 | 
					*.mo
 | 
				
			||||||
 | 
					*.pot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Django stuff:
 | 
				
			||||||
 | 
					*.log
 | 
				
			||||||
 | 
					local_settings.py
 | 
				
			||||||
 | 
					db.sqlite3
 | 
				
			||||||
 | 
					db.sqlite3-journal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Flask stuff:
 | 
				
			||||||
 | 
					instance/
 | 
				
			||||||
 | 
					.webassets-cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Scrapy stuff:
 | 
				
			||||||
 | 
					.scrapy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Sphinx documentation
 | 
				
			||||||
 | 
					docs/_build/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PyBuilder
 | 
				
			||||||
 | 
					.pybuilder/
 | 
				
			||||||
 | 
					target/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Jupyter Notebook
 | 
				
			||||||
 | 
					.ipynb_checkpoints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IPython
 | 
				
			||||||
 | 
					profile_default/
 | 
				
			||||||
 | 
					ipython_config.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# pyenv
 | 
				
			||||||
 | 
					#   For a library or package, you might want to ignore these files since the code is
 | 
				
			||||||
 | 
					#   intended to run in multiple environments; otherwise, check them in:
 | 
				
			||||||
 | 
					# .python-version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# pipenv
 | 
				
			||||||
 | 
					#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
 | 
				
			||||||
 | 
					#   However, in case of collaboration, if having platform-specific dependencies or dependencies
 | 
				
			||||||
 | 
					#   having no cross-platform support, pipenv may install dependencies that don't work, or not
 | 
				
			||||||
 | 
					#   install all needed dependencies.
 | 
				
			||||||
 | 
					#Pipfile.lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# poetry
 | 
				
			||||||
 | 
					#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
 | 
				
			||||||
 | 
					#   This is especially recommended for binary packages to ensure reproducibility, and is more
 | 
				
			||||||
 | 
					#   commonly ignored for libraries.
 | 
				
			||||||
 | 
					#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
 | 
				
			||||||
 | 
					#poetry.lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# pdm
 | 
				
			||||||
 | 
					#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
 | 
				
			||||||
 | 
					#pdm.lock
 | 
				
			||||||
 | 
					#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
 | 
				
			||||||
 | 
					#   in version control.
 | 
				
			||||||
 | 
					#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
 | 
				
			||||||
 | 
					.pdm.toml
 | 
				
			||||||
 | 
					.pdm-python
 | 
				
			||||||
 | 
					.pdm-build/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
 | 
				
			||||||
 | 
					__pypackages__/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Celery stuff
 | 
				
			||||||
 | 
					celerybeat-schedule
 | 
				
			||||||
 | 
					celerybeat.pid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SageMath parsed files
 | 
				
			||||||
 | 
					*.sage.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Environments
 | 
				
			||||||
 | 
					.env
 | 
				
			||||||
 | 
					.venv
 | 
				
			||||||
 | 
					env/
 | 
				
			||||||
 | 
					venv/
 | 
				
			||||||
 | 
					ENV/
 | 
				
			||||||
 | 
					env.bak/
 | 
				
			||||||
 | 
					venv.bak/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Spyder project settings
 | 
				
			||||||
 | 
					.spyderproject
 | 
				
			||||||
 | 
					.spyproject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Rope project settings
 | 
				
			||||||
 | 
					.ropeproject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# mkdocs documentation
 | 
				
			||||||
 | 
					/site
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# mypy
 | 
				
			||||||
 | 
					.mypy_cache/
 | 
				
			||||||
 | 
					.dmypy.json
 | 
				
			||||||
 | 
					dmypy.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Pyre type checker
 | 
				
			||||||
 | 
					.pyre/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# pytype static type analyzer
 | 
				
			||||||
 | 
					.pytype/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Cython debug symbols
 | 
				
			||||||
 | 
					cython_debug/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PyCharm
 | 
				
			||||||
 | 
					#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
 | 
				
			||||||
 | 
					#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
 | 
				
			||||||
 | 
					#  and can be added to the global gitignore or merged into this file.  For a more nuclear
 | 
				
			||||||
 | 
					#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
 | 
				
			||||||
 | 
					#.idea/
 | 
				
			||||||
@@ -1,14 +1,12 @@
 | 
				
			|||||||
[[source]]
 | 
					[[source]]
 | 
				
			||||||
url = "https://pypi.org/simple"
 | 
					url = "https://pypi.org/simple"
 | 
				
			||||||
verify_ssl = true
 | 
					verify_ssl = true
 | 
				
			||||||
name = "pypi"
 | 
					name = "pypi"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[packages]
 | 
					[packages]
 | 
				
			||||||
numpy = "*"
 | 
					numpy = "*"
 | 
				
			||||||
scipy = "*"
 | 
					scipy = "*"
 | 
				
			||||||
fastapi = "*"
 | 
					fastapi = "*"
 | 
				
			||||||
 | 
					osmpythontools = "*"
 | 
				
			||||||
[dev-packages]
 | 
					
 | 
				
			||||||
 | 
					[dev-packages]
 | 
				
			||||||
[requires]
 | 
					 | 
				
			||||||
python_version = "3.12"
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2369
									
								
								backend/Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2369
									
								
								backend/Pipfile.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										0
									
								
								backend/app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								backend/app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/app/__pycache__/__init__.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/app/__pycache__/__init__.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/app/__pycache__/main.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/app/__pycache__/main.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								backend/app/dependencies.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/app/dependencies.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					import app.src
 | 
				
			||||||
							
								
								
									
										34
									
								
								backend/app/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								backend/app/main.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					from .src.optimizer import solve_optimization
 | 
				
			||||||
 | 
					from .src.landmarks_manager import Landmark
 | 
				
			||||||
 | 
					from fastapi import FastAPI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app = FastAPI()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.get("/optimize/{max_steps}/{print_details}")
 | 
				
			||||||
 | 
					def main(max_steps: int, print_details: bool):
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # CONSTRAINT TO RESPECT MAX NUMBER OF STEPS
 | 
				
			||||||
 | 
					    #max_steps = 16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Initialize all landmarks (+ start and goal). Order matters here
 | 
				
			||||||
 | 
					    landmarks = []
 | 
				
			||||||
 | 
					    landmarks.append(Landmark("départ", -1, (0, 0)))
 | 
				
			||||||
 | 
					    landmarks.append(Landmark("tour eiffel", 99, (0,2)))                           # PUT IN JSON
 | 
				
			||||||
 | 
					    landmarks.append(Landmark("arc de triomphe", 99, (0,4)))
 | 
				
			||||||
 | 
					    landmarks.append(Landmark("louvre", 99, (0,6)))
 | 
				
			||||||
 | 
					    landmarks.append(Landmark("montmartre", 99, (0,10)))
 | 
				
			||||||
 | 
					    landmarks.append(Landmark("concorde", 99, (0,8)))
 | 
				
			||||||
 | 
					    landmarks.append(Landmark("arrivée", -1, (0, 0)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    visiting_order = solve_optimization(landmarks, max_steps, print_details)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #return visiting_order
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return("max steps :", max_steps, "\n", visiting_order)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""if __name__ == "__main__":
 | 
				
			||||||
 | 
					    main()"""
 | 
				
			||||||
							
								
								
									
										0
									
								
								backend/app/src/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								backend/app/src/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/app/src/__pycache__/__init__.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/app/src/__pycache__/__init__.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								backend/app/src/__pycache__/optimizer.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								backend/app/src/__pycache__/optimizer.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										57
									
								
								backend/app/src/landmarks_manager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								backend/app/src/landmarks_manager.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					from OSMPythonTools.api import Api
 | 
				
			||||||
 | 
					from OSMPythonTools.overpass import Overpass
 | 
				
			||||||
 | 
					from dataclasses import dataclass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Defines the landmark class (aka some place there is to visit)
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class Landmarkkkk :
 | 
				
			||||||
 | 
					    name : str
 | 
				
			||||||
 | 
					    attractiveness : int
 | 
				
			||||||
 | 
					    id : int
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class Landmark :
 | 
				
			||||||
 | 
					    name : str
 | 
				
			||||||
 | 
					    attractiveness : int
 | 
				
			||||||
 | 
					    loc : tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Converts a OSM id to a landmark
 | 
				
			||||||
 | 
					def add_from_id(id: int, score: int) :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try :
 | 
				
			||||||
 | 
					        s = 'way/' + str(id)           # prepare string for query
 | 
				
			||||||
 | 
					        obj =  api.query(s)                             # object to add
 | 
				
			||||||
 | 
					    except :
 | 
				
			||||||
 | 
					        s = 'relation/' + str(id)           # prepare string for query
 | 
				
			||||||
 | 
					        obj =  api.query(s)                             # object to add
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Landmarkkkk(obj.tag('name:fr'), score, id)      # create Landmark out of it
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# take a lsit of tuples (id, score) to generate a list of landmarks
 | 
				
			||||||
 | 
					def generate_landmarks(ids_and_scores: list) :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    L = []
 | 
				
			||||||
 | 
					    for tup in ids_and_scores :
 | 
				
			||||||
 | 
					        L.append(add_from_id(tup[0], tup[1]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return L
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					api = Api()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					l = (7515426, 70)
 | 
				
			||||||
 | 
					t = (5013364, 100)
 | 
				
			||||||
 | 
					n = (201611261, 99)
 | 
				
			||||||
 | 
					a = (226413508, 50)
 | 
				
			||||||
 | 
					m = (23762981, 30)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ids_and_scores = [t, l, n, a, m]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					landmarks = generate_landmarks(ids_and_scores)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for obj in landmarks :
 | 
				
			||||||
 | 
					    print(obj)
 | 
				
			||||||
@@ -1,23 +1,23 @@
 | 
				
			|||||||
import fastapi
 | 
					import fastapi
 | 
				
			||||||
from dataclasses import dataclass
 | 
					from dataclasses import dataclass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					@dataclass
 | 
				
			||||||
class Destination:
 | 
					class Destination:
 | 
				
			||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
    location: tuple
 | 
					    location: tuple
 | 
				
			||||||
    attractiveness: int
 | 
					    attractiveness: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
d = Destination()
 | 
					d = Destination()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_route() -> list[Destination]:
 | 
					def get_route() -> list[Destination]:
 | 
				
			||||||
    return {"route": "Hello World"}
 | 
					    return {"route": "Hello World"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
endpoint = ("/get_route", get_route)
 | 
					endpoint = ("/get_route", get_route)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    fastapi.run()
 | 
					    fastapi.run()
 | 
				
			||||||
							
								
								
									
										323
									
								
								backend/app/src/optimizer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								backend/app/src/optimizer.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,323 @@
 | 
				
			|||||||
 | 
					from scipy.optimize import linprog
 | 
				
			||||||
 | 
					import numpy as np
 | 
				
			||||||
 | 
					from scipy.linalg import block_diag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# landmarks = [Landmark_1, Landmark_2, ...]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Convert the solution of the optimization into the list of edges to follow. Order is taken into account
 | 
				
			||||||
 | 
					def untangle(resx: list) :
 | 
				
			||||||
 | 
					    N = len(resx)                   # length of res
 | 
				
			||||||
 | 
					    L = int(np.sqrt(N))             # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
 | 
				
			||||||
 | 
					    n_edges = resx.sum()      # number of edges
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    order = []
 | 
				
			||||||
 | 
					    nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nonzero_tup = np.unravel_index(nonzeroind, (L,L))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indx = nonzero_tup[0].tolist()
 | 
				
			||||||
 | 
					    indy = nonzero_tup[1].tolist()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    vert = (indx[0], indy[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    order.append(vert[0])
 | 
				
			||||||
 | 
					    order.append(vert[1])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    while len(order) < n_edges + 1 :
 | 
				
			||||||
 | 
					        ind = indx.index(vert[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        vert = (indx[ind], indy[ind])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        order.append(vert[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return order
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Just to print the result
 | 
				
			||||||
 | 
					def print_res(res, landmarks: list, P) :
 | 
				
			||||||
 | 
					    X = abs(res.x)
 | 
				
			||||||
 | 
					    order = untangle(X)
 | 
				
			||||||
 | 
					    things = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """N = int(np.sqrt(len(X)))
 | 
				
			||||||
 | 
					    for i in range(N):
 | 
				
			||||||
 | 
					        print(X[i*N:i*N+N])
 | 
				
			||||||
 | 
					    print("Optimal value:", -res.fun)  # Minimization, so we negate to get the maximum
 | 
				
			||||||
 | 
					    print("Optimal point:", res.x)
 | 
				
			||||||
 | 
					    for i,x in enumerate(X) : X[i] = round(x,0)
 | 
				
			||||||
 | 
					    print(order)"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (X.sum()+1)**2 == len(X) : 
 | 
				
			||||||
 | 
					        print('\nAll landmarks can be visited within max_steps, the following order is suggested : ')
 | 
				
			||||||
 | 
					    else :
 | 
				
			||||||
 | 
					        print('Could not visit all the landmarks, the following order is suggested : ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for idx in order : 
 | 
				
			||||||
 | 
					        print('- ' + landmarks[idx].name)
 | 
				
			||||||
 | 
					        things.append(landmarks[idx].name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    steps = path_length(P, abs(res.x))
 | 
				
			||||||
 | 
					    print("\nSteps walked : " + str(steps))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return things
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Checks for cases of circular symmetry in the result
 | 
				
			||||||
 | 
					def has_circle(resx: list) :
 | 
				
			||||||
 | 
					    N = len(resx)                   # length of res
 | 
				
			||||||
 | 
					    L = int(np.sqrt(N))             # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
 | 
				
			||||||
 | 
					    n_edges = resx.sum()      # number of edges
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    nonzeroind = np.nonzero(resx)[0] # the return is a little funny so I use the [0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nonzero_tup = np.unravel_index(nonzeroind, (L,L))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indx = nonzero_tup[0].tolist()
 | 
				
			||||||
 | 
					    indy = nonzero_tup[1].tolist()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    verts = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i, x in enumerate(indx) :
 | 
				
			||||||
 | 
					        verts.append((x, indy[i]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for vert in verts :
 | 
				
			||||||
 | 
					        visited = []
 | 
				
			||||||
 | 
					        visited.append(vert)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while len(visited) < n_edges + 1 :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try : 
 | 
				
			||||||
 | 
					                ind = indx.index(vert[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                vert = (indx[ind], indy[ind])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if vert in visited :
 | 
				
			||||||
 | 
					                    return visited
 | 
				
			||||||
 | 
					                else :
 | 
				
			||||||
 | 
					                    visited.append(vert)
 | 
				
			||||||
 | 
					            except :
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Constraint to not have d14 and d41 simultaneously. Does not prevent circular symmetry with more elements
 | 
				
			||||||
 | 
					def break_sym(landmarks, A_ub, b_ub):
 | 
				
			||||||
 | 
					    L = len(landmarks)
 | 
				
			||||||
 | 
					    upper_ind = np.triu_indices(L,0,L)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    up_ind_x = upper_ind[0]
 | 
				
			||||||
 | 
					    up_ind_y = upper_ind[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i, _ in enumerate(up_ind_x) :
 | 
				
			||||||
 | 
					        l = [0]*L*L
 | 
				
			||||||
 | 
					        if up_ind_x[i] != up_ind_y[i] :
 | 
				
			||||||
 | 
					            l[up_ind_x[i]*L + up_ind_y[i]] = 1
 | 
				
			||||||
 | 
					            l[up_ind_y[i]*L + up_ind_x[i]] = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            A_ub = np.vstack((A_ub,l))
 | 
				
			||||||
 | 
					            b_ub.append(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            """for i in range(7):
 | 
				
			||||||
 | 
					                print(l[i*7:i*7+7])
 | 
				
			||||||
 | 
					            print("\n")"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return A_ub, b_ub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Constraint to not have circular paths. Want to go from start -> finish without unconnected loops
 | 
				
			||||||
 | 
					def break_circle(landmarks, A_ub, b_ub, circle) :
 | 
				
			||||||
 | 
					    N = len(landmarks)
 | 
				
			||||||
 | 
					    l = [0]*N*N
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for index in circle :
 | 
				
			||||||
 | 
					        x = index[0]
 | 
				
			||||||
 | 
					        y = index[1]
 | 
				
			||||||
 | 
					        l[x*N+y] = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    A_ub = np.vstack((A_ub,l))
 | 
				
			||||||
 | 
					    b_ub.append(len(circle)-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """print("\n\nPREVENT CIRCLE")
 | 
				
			||||||
 | 
					    for i in range(7):
 | 
				
			||||||
 | 
					        print(l[i*7:i*7+7])
 | 
				
			||||||
 | 
					    print("\n")"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return A_ub, b_ub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Constraint to respect max number of travels
 | 
				
			||||||
 | 
					def respect_number(landmarks, A_ub, b_ub):
 | 
				
			||||||
 | 
					    h = []
 | 
				
			||||||
 | 
					    for i in range(len(landmarks)) : h.append([1]*len(landmarks))
 | 
				
			||||||
 | 
					    T = block_diag(*h)
 | 
				
			||||||
 | 
					    """for l in T :
 | 
				
			||||||
 | 
					        for i in range(7):
 | 
				
			||||||
 | 
					            print(l[i*7:i*7+7])
 | 
				
			||||||
 | 
					        print("\n")"""
 | 
				
			||||||
 | 
					    return np.vstack((A_ub, T)), b_ub + [1]*len(landmarks)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Constraint to tie the problem together. Necessary but not sufficient to avoid circles
 | 
				
			||||||
 | 
					def respect_order(landmarks: list, A_eq, b_eq): 
 | 
				
			||||||
 | 
					    N = len(landmarks)
 | 
				
			||||||
 | 
					    for i in range(N-1) :     # Prevent stacked ones
 | 
				
			||||||
 | 
					        if i == 0 :
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        else : 
 | 
				
			||||||
 | 
					            l = [0]*N
 | 
				
			||||||
 | 
					            l[i] = -1
 | 
				
			||||||
 | 
					            l = l*N
 | 
				
			||||||
 | 
					            for j in range(N) :
 | 
				
			||||||
 | 
					                l[i*N + j] = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            A_eq = np.vstack((A_eq,l))
 | 
				
			||||||
 | 
					            b_eq.append(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            """for i in range(7):
 | 
				
			||||||
 | 
					                print(l[i*7:i*7+7])
 | 
				
			||||||
 | 
					            print("\n")"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return A_eq, b_eq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Compute manhattan distance between 2 locations
 | 
				
			||||||
 | 
					def manhattan_distance(loc1: tuple, loc2: tuple):
 | 
				
			||||||
 | 
					    x1, y1 = loc1
 | 
				
			||||||
 | 
					    x2, y2 = loc2
 | 
				
			||||||
 | 
					    return abs(x1 - x2) + abs(y1 - y2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Constraint to not stay in position
 | 
				
			||||||
 | 
					def init_eq_not_stay(landmarks): 
 | 
				
			||||||
 | 
					    L = len(landmarks)
 | 
				
			||||||
 | 
					    l = [0]*L*L
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i in range(L) :
 | 
				
			||||||
 | 
					        for j in range(L) :
 | 
				
			||||||
 | 
					            if j == i :
 | 
				
			||||||
 | 
					                l[j + i*L] = 1
 | 
				
			||||||
 | 
					    l[L-1] = 1      # cannot skip from start to finish
 | 
				
			||||||
 | 
					    #A_eq = np.array([np.array(xi) for xi in A_eq])                  # Must convert A_eq into an np array
 | 
				
			||||||
 | 
					    l = np.array(np.array(l))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """for i in range(7):
 | 
				
			||||||
 | 
					        print(l[i*7:i*7+7])"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return [l], [0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Initialize A and c. Compute the distances from all landmarks to each other and store attractiveness
 | 
				
			||||||
 | 
					# We want to maximize the sightseeing :  max(c) st. A*x < b   and   A_eq*x = b_eq
 | 
				
			||||||
 | 
					def init_ub_dist(landmarks: list, max_steps: int):
 | 
				
			||||||
 | 
					    # Objective function coefficients. a*x1 + b*x2 + c*x3 + ...
 | 
				
			||||||
 | 
					    c = []
 | 
				
			||||||
 | 
					    # Coefficients of inequality constraints (left-hand side)
 | 
				
			||||||
 | 
					    A = []
 | 
				
			||||||
 | 
					    for i, spot1 in enumerate(landmarks) :
 | 
				
			||||||
 | 
					        dist_table = [0]*len(landmarks)
 | 
				
			||||||
 | 
					        c.append(-spot1.attractiveness)
 | 
				
			||||||
 | 
					        for j, spot2 in enumerate(landmarks) :
 | 
				
			||||||
 | 
					            dist_table[j] = manhattan_distance(spot1.loc, spot2.loc)
 | 
				
			||||||
 | 
					        A.append(dist_table)
 | 
				
			||||||
 | 
					    c = c*len(landmarks)
 | 
				
			||||||
 | 
					    A_ub = []
 | 
				
			||||||
 | 
					    for line in A :
 | 
				
			||||||
 | 
					        #print(line)
 | 
				
			||||||
 | 
					        A_ub += line
 | 
				
			||||||
 | 
					    return c, A_ub, [max_steps]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Go through the landmarks and force the optimizer to use landmarks where attractiveness is set to -1
 | 
				
			||||||
 | 
					def respect_user_mustsee(landmarks: list, A_eq: list, b_eq: list) :
 | 
				
			||||||
 | 
					    L = len(landmarks)
 | 
				
			||||||
 | 
					    H = 0       # sort of heuristic to get an idea of the number of steps needed
 | 
				
			||||||
 | 
					    for i in landmarks : 
 | 
				
			||||||
 | 
					        if i.name == "départ" : elem_prev = i              # list of all matches
 | 
				
			||||||
 | 
					    for i, elem in enumerate(landmarks) :
 | 
				
			||||||
 | 
					        if elem.attractiveness == -1 :
 | 
				
			||||||
 | 
					            l = [0]*L*L
 | 
				
			||||||
 | 
					            if elem.name != "arrivée" :
 | 
				
			||||||
 | 
					                for j in range(L) :
 | 
				
			||||||
 | 
					                    l[j +i*L] = 1
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					            else :                          # This ensures we go to goal
 | 
				
			||||||
 | 
					                for k in range(L-1) :
 | 
				
			||||||
 | 
					                        l[k*L+L-1] = 1  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            H += manhattan_distance(elem.loc, elem_prev.loc)
 | 
				
			||||||
 | 
					            elem_prev = elem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            """for i in range(7):
 | 
				
			||||||
 | 
					                print(l[i*7:i*7+7])
 | 
				
			||||||
 | 
					            print("\n")"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            A_eq = np.vstack((A_eq,l))
 | 
				
			||||||
 | 
					            b_eq.append(1)
 | 
				
			||||||
 | 
					    return A_eq, b_eq, H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Computes the path length given path matrix (dist_table) and a result
 | 
				
			||||||
 | 
					def path_length(P: list, resx: list) :
 | 
				
			||||||
 | 
					    return np.dot(P, resx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Main optimization pipeline
 | 
				
			||||||
 | 
					def solve_optimization (landmarks, max_steps, printing_details) :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # SET CONSTRAINTS FOR INEQUALITY
 | 
				
			||||||
 | 
					    c, A_ub, b_ub = init_ub_dist(landmarks, max_steps)              # Add the distances from each landmark to the other
 | 
				
			||||||
 | 
					    P = A_ub                                                        # store the paths for later. Needed to compute path length
 | 
				
			||||||
 | 
					    A_ub, b_ub = respect_number(landmarks, A_ub, b_ub)              # Respect max number of visits. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO : Problems with circular symmetry
 | 
				
			||||||
 | 
					    A_ub, b_ub = break_sym(landmarks, A_ub, b_ub)                  # break the symmetry. Only use the upper diagonal values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # SET CONSTRAINTS FOR EQUALITY
 | 
				
			||||||
 | 
					    A_eq, b_eq = init_eq_not_stay(landmarks)                       # Force solution not to stay in same place
 | 
				
			||||||
 | 
					    A_eq, b_eq, H = respect_user_mustsee(landmarks, A_eq, b_eq)       # Check if there are user_defined must_see. Also takes care of start/goal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    A_eq, b_eq = respect_order(landmarks, A_eq, b_eq)              # Respect order of visit (only works when max_steps is limiting factor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Bounds for variables (x can only be 0 or 1)
 | 
				
			||||||
 | 
					    x_bounds = [(0, 1)] * len(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Solve linear programming problem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Raise error if no solution is found
 | 
				
			||||||
 | 
					    if not res.success :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Override the max_steps using the heuristic
 | 
				
			||||||
 | 
					        for i, val in enumerate(b_ub) :
 | 
				
			||||||
 | 
					            if val == max_steps : b_ub[i] = H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Solve problem again :
 | 
				
			||||||
 | 
					        res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not res.success :
 | 
				
			||||||
 | 
					            s = "No solution could be found, even when increasing max_steps using the heuristic"
 | 
				
			||||||
 | 
					            return s
 | 
				
			||||||
 | 
					            #raise ValueError("No solution could be found, even when increasing max_steps using the heuristic")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # If there is a solution, we're good to go, just check for
 | 
				
			||||||
 | 
					    else :
 | 
				
			||||||
 | 
					        circle = has_circle(res.x)
 | 
				
			||||||
 | 
					        i = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Break the circular symmetry if needed
 | 
				
			||||||
 | 
					        while len(circle) != 0 :
 | 
				
			||||||
 | 
					            A_ub, b_ub = break_circle(landmarks, A_ub, b_ub, circle)
 | 
				
			||||||
 | 
					            res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
 | 
				
			||||||
 | 
					            circle = has_circle(res.x)
 | 
				
			||||||
 | 
					            i += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if printing_details is True :
 | 
				
			||||||
 | 
					            if i != 0 :
 | 
				
			||||||
 | 
					                print(f"Neded to recompute paths {i} times because of unconnected loops...")
 | 
				
			||||||
 | 
					            X = print_res(res, landmarks, P)
 | 
				
			||||||
 | 
					            return X
 | 
				
			||||||
 | 
					        else :
 | 
				
			||||||
 | 
					            return untangle(res.x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,206 +0,0 @@
 | 
				
			|||||||
from scipy.optimize import linprog
 | 
					 | 
				
			||||||
import numpy as np
 | 
					 | 
				
			||||||
from scipy.linalg import block_diag
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Defines the landmark class (aka some place there is to visit)
 | 
					 | 
				
			||||||
class landmark :
 | 
					 | 
				
			||||||
    def __init__(self, name: str, attractiveness: int, loc: tuple):
 | 
					 | 
				
			||||||
        self.name = name
 | 
					 | 
				
			||||||
        self.attractiveness = attractiveness
 | 
					 | 
				
			||||||
        self.loc = loc
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Convert the result (edges from j to k like d_25 = edge between vertex 2 and vertex 5) into the list of indices corresponding to the landmarks
 | 
					 | 
				
			||||||
def untangle(res: list) :
 | 
					 | 
				
			||||||
    N = len(res)                # length of res
 | 
					 | 
				
			||||||
    L = int(np.sqrt(N))         # number of landmarks. CAST INTO INT but should not be a problem because N = L**2 by def.
 | 
					 | 
				
			||||||
    n_landmarks = res.sum()     # number of visited landmarks
 | 
					 | 
				
			||||||
    visit_order = []
 | 
					 | 
				
			||||||
    cnt = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if n_landmarks % 2 == 1 :                                     # if odd number of visited checkpoints
 | 
					 | 
				
			||||||
        for i in range(L) :
 | 
					 | 
				
			||||||
            for j in range(L) :
 | 
					 | 
				
			||||||
                if res[i*L + j] == 1 :              # if index is 1
 | 
					 | 
				
			||||||
                    cnt += 1                        # increment counter
 | 
					 | 
				
			||||||
                    if cnt % 2 == 1 :               # if counter odd
 | 
					 | 
				
			||||||
                        visit_order.append(i)
 | 
					 | 
				
			||||||
                        visit_order.append(j)
 | 
					 | 
				
			||||||
    else :                                   # if even number of ones
 | 
					 | 
				
			||||||
        for i in range(L) :
 | 
					 | 
				
			||||||
            for j in range(L) :
 | 
					 | 
				
			||||||
                if res[i*L + j] == 1 :              # if index is one
 | 
					 | 
				
			||||||
                    cnt += 1                        # increment counter
 | 
					 | 
				
			||||||
                    if j % (L-1) == 0 :             # if last node
 | 
					 | 
				
			||||||
                        visit_order.append(j)       # append only the last index
 | 
					 | 
				
			||||||
                        return visit_order          # return
 | 
					 | 
				
			||||||
                    if cnt % 2 == 1 : 
 | 
					 | 
				
			||||||
                        visit_order.append(i)
 | 
					 | 
				
			||||||
                        visit_order.append(j)
 | 
					 | 
				
			||||||
    return visit_order
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Just to print the result
 | 
					 | 
				
			||||||
def print_res(res: list, P) :
 | 
					 | 
				
			||||||
    X = abs(res.x)
 | 
					 | 
				
			||||||
    order = untangle(X)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # print("Optimal value:", -res.fun)  # Minimization, so we negate to get the maximum
 | 
					 | 
				
			||||||
    # print("Optimal point:", res.x)
 | 
					 | 
				
			||||||
    # N = int(np.sqrt(len(X)))
 | 
					 | 
				
			||||||
    # for i in range(N):
 | 
					 | 
				
			||||||
    #     print(X[i*N:i*N+N])
 | 
					 | 
				
			||||||
    # print(order)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (X.sum()+1)**2 == len(X) : 
 | 
					 | 
				
			||||||
        print('\nAll landmarks can be visited within max_steps, the following order is most likely not the fastest')
 | 
					 | 
				
			||||||
    else :
 | 
					 | 
				
			||||||
        print('Could not visit all the landmarks, the following order could be the fastest but not sure')
 | 
					 | 
				
			||||||
    print("Order of visit :")
 | 
					 | 
				
			||||||
    for i, elem in enumerate(landmarks) : 
 | 
					 | 
				
			||||||
        if i in order : print('- ' + elem.name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    steps = path_length(P, abs(res.x))
 | 
					 | 
				
			||||||
    print("\nSteps walked : " + str(steps))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Constraint to use only the upper triangular indices for travel
 | 
					 | 
				
			||||||
def break_sym(landmarks, A_eq, b_eq):
 | 
					 | 
				
			||||||
    L = len(landmarks)
 | 
					 | 
				
			||||||
    l = [0]*L*L
 | 
					 | 
				
			||||||
    for i in range(L) :
 | 
					 | 
				
			||||||
        for j in range(L) :
 | 
					 | 
				
			||||||
            if i >= j :
 | 
					 | 
				
			||||||
                l[j+i*L] = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    A_eq = np.vstack((A_eq,l))
 | 
					 | 
				
			||||||
    b_eq.append(0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return A_eq, b_eq
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Constraint to respect max number of travels
 | 
					 | 
				
			||||||
def respect_number(landmarks, A_ub, b_ub):
 | 
					 | 
				
			||||||
    h = []
 | 
					 | 
				
			||||||
    for i in range(len(landmarks)) : h.append([1]*len(landmarks))
 | 
					 | 
				
			||||||
    T = block_diag(*h)
 | 
					 | 
				
			||||||
    return np.vstack((A_ub, T)), b_ub + [1]*len(landmarks)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Constraint to tie the problem together and have a connected path
 | 
					 | 
				
			||||||
def respect_order(landmarks: list, A_eq, b_eq): 
 | 
					 | 
				
			||||||
    N = len(landmarks)
 | 
					 | 
				
			||||||
    for i in range(N-1) :     # Prevent stacked ones
 | 
					 | 
				
			||||||
        if i == 0 :
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
        else : 
 | 
					 | 
				
			||||||
            l = [0]*N
 | 
					 | 
				
			||||||
            l[i] = -1
 | 
					 | 
				
			||||||
            l = l*N
 | 
					 | 
				
			||||||
            for j in range(N) :
 | 
					 | 
				
			||||||
                l[i*N + j] = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            A_eq = np.vstack((A_eq,l))
 | 
					 | 
				
			||||||
            b_eq.append(0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return A_eq, b_eq
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Compute manhattan distance between 2 locations
 | 
					 | 
				
			||||||
def manhattan_distance(loc1: tuple, loc2: tuple):
 | 
					 | 
				
			||||||
    x1, y1 = loc1
 | 
					 | 
				
			||||||
    x2, y2 = loc2
 | 
					 | 
				
			||||||
    return abs(x1 - x2) + abs(y1 - y2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Constraint to not stay in position
 | 
					 | 
				
			||||||
def init_eq_not_stay(landmarks): 
 | 
					 | 
				
			||||||
    L = len(landmarks)
 | 
					 | 
				
			||||||
    l = [0]*L*L
 | 
					 | 
				
			||||||
    for i in range(L) :
 | 
					 | 
				
			||||||
        for j in range(L) :
 | 
					 | 
				
			||||||
            if j == i :
 | 
					 | 
				
			||||||
                l[j + i*L] = 1
 | 
					 | 
				
			||||||
    #A_eq = np.array([np.array(xi) for xi in A_eq])                  # Must convert A_eq into an np array
 | 
					 | 
				
			||||||
    l = np.array(np.array(l))
 | 
					 | 
				
			||||||
    return [l], [0]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Initialize A and c. Compute the distances from all landmarks to each other and store attractiveness
 | 
					 | 
				
			||||||
# We want to maximize the sightseeing :  max(c) st. A*x < b   and   A_eq*x = b_eq
 | 
					 | 
				
			||||||
def init_ub_dist(landmarks: list, max_steps: int):
 | 
					 | 
				
			||||||
    # Objective function coefficients. a*x1 + b*x2 + c*x3 + ...
 | 
					 | 
				
			||||||
    c = []
 | 
					 | 
				
			||||||
    # Coefficients of inequality constraints (left-hand side)
 | 
					 | 
				
			||||||
    A = []
 | 
					 | 
				
			||||||
    for i, spot1 in enumerate(landmarks) :
 | 
					 | 
				
			||||||
        dist_table = [0]*len(landmarks)
 | 
					 | 
				
			||||||
        c.append(-spot1.attractiveness)
 | 
					 | 
				
			||||||
        for j, spot2 in enumerate(landmarks) :
 | 
					 | 
				
			||||||
            dist_table[j] = manhattan_distance(spot1.loc, spot2.loc)
 | 
					 | 
				
			||||||
        A.append(dist_table)
 | 
					 | 
				
			||||||
    c = c*len(landmarks)
 | 
					 | 
				
			||||||
    A_ub = []
 | 
					 | 
				
			||||||
    for line in A :
 | 
					 | 
				
			||||||
        A_ub += line
 | 
					 | 
				
			||||||
    return c, A_ub, [max_steps]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Go through the landmarks and force the optimizer to use landmarks where attractiveness is set to -1
 | 
					 | 
				
			||||||
def respect_user_mustsee(landmarks: list, A_eq: list, b_eq: list) :
 | 
					 | 
				
			||||||
    L = len(landmarks)
 | 
					 | 
				
			||||||
    for i, elem in enumerate(landmarks) :
 | 
					 | 
				
			||||||
        if elem.attractiveness == -1 :
 | 
					 | 
				
			||||||
            l = [0]*L*L
 | 
					 | 
				
			||||||
            if elem.name != "arrivée" :
 | 
					 | 
				
			||||||
                for j in range(L) :
 | 
					 | 
				
			||||||
                    l[j +i*L] = 1
 | 
					 | 
				
			||||||
            else :                          # This ensures we go to goal
 | 
					 | 
				
			||||||
                for k in range(L-1) :
 | 
					 | 
				
			||||||
                        l[k*L+L-1] = 1
 | 
					 | 
				
			||||||
            A_eq = np.vstack((A_eq,l))
 | 
					 | 
				
			||||||
            b_eq.append(1)
 | 
					 | 
				
			||||||
    return A_eq, b_eq
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Computes the path length given path matrix (dist_table) and a result
 | 
					 | 
				
			||||||
def path_length(P: list, resx: list) :
 | 
					 | 
				
			||||||
    return np.dot(P, resx)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Initialize all landmarks (+ start and goal). Order matters here
 | 
					 | 
				
			||||||
landmarks = []
 | 
					 | 
				
			||||||
landmarks.append(landmark("départ", -1, (0, 0)))
 | 
					 | 
				
			||||||
landmarks.append(landmark("concorde", -1, (5,5)))
 | 
					 | 
				
			||||||
landmarks.append(landmark("tour eiffel", 99, (1,1)))                           # PUT IN JSON
 | 
					 | 
				
			||||||
landmarks.append(landmark("arc de triomphe", 99, (2,3)))
 | 
					 | 
				
			||||||
landmarks.append(landmark("louvre", 70, (4,2)))
 | 
					 | 
				
			||||||
landmarks.append(landmark("montmartre", 20, (0,2)))
 | 
					 | 
				
			||||||
landmarks.append(landmark("arrivée", -1, (0, 0)))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# CONSTRAINT TO RESPECT MAX NUMBER OF STEPS
 | 
					 | 
				
			||||||
max_steps = 25
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# SET CONSTRAINTS FOR INEQUALITY
 | 
					 | 
				
			||||||
c, A_ub, b_ub = init_ub_dist(landmarks, max_steps)              # Add the distances from each landmark to the other
 | 
					 | 
				
			||||||
P = A_ub                                                        # store the paths for later. Needed to compute path length
 | 
					 | 
				
			||||||
A_ub, b_ub = respect_number(landmarks, A_ub, b_ub)              # Respect max number of visits. 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# SET CONSTRAINTS FOR EQUALITY
 | 
					 | 
				
			||||||
A_eq, b_eq = init_eq_not_stay(landmarks)                       # Force solution not to stay in same place
 | 
					 | 
				
			||||||
A_eq, b_eq = respect_user_mustsee(landmarks, A_eq, b_eq)       # Check if there are user_defined must_see. Also takes care of start/goal
 | 
					 | 
				
			||||||
A_eq, b_eq = break_sym(landmarks, A_eq, b_eq)                  # break the symmetry. Only use the upper diagonal values
 | 
					 | 
				
			||||||
A_eq, b_eq = respect_order(landmarks, A_eq, b_eq)              # Respect order of visit (only works when max_steps is limiting factor)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Bounds for variables (x can only be 0 or 1)
 | 
					 | 
				
			||||||
x_bounds = [(0, 1)] * len(c)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Solve linear programming problem
 | 
					 | 
				
			||||||
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq = b_eq, bounds=x_bounds, method='highs', integrality=3)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Raise error if no solution is found
 | 
					 | 
				
			||||||
if not res.success :
 | 
					 | 
				
			||||||
    raise ValueError("No solution has been found, please adapt your max steps")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Print result
 | 
					 | 
				
			||||||
print_res(res, P)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user