Skip to content

Commit

Permalink
Feature/route fix (#2)
Browse files Browse the repository at this point in the history
* [FIX] router solver

* [FIX] deploy method
  • Loading branch information
lavi02 authored Jan 15, 2024
1 parent 47fc583 commit 3767513
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 51 deletions.
Binary file added .coverage
Binary file not shown.
3 changes: 2 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install httpx
- name: Run tests
run: pytest src/tests/*
Expand Down Expand Up @@ -56,4 +57,4 @@ jobs:
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}
3 changes: 3 additions & 0 deletions .github/workflows/sonarcloud-analyze.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,8 @@ jobs:
args:
-Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }}
-Dsonar.organization=f-lab-edu-1
- name:
run: |
echo "SonarCloud Analysis Completed"
9 changes: 9 additions & 0 deletions Tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[tox]
envlist = py38
skipsdist = True

[testenv]
deps =
pytest
pytest-cov
commands = pytest src/tests/* --cov
22 changes: 18 additions & 4 deletions example/src/router/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,30 @@


class Waypoints(BaseModel):
stop_fix_type: str = "Departure"
waypoints: Dict[str, Tuple[float, float]]


class RouteOptimizer:
def __init__(self, waypoints: Dict[str, Tuple[float, float]]):
def __init__(self, waypoints: Waypoints):
self.stop_fix_type = "Departure"
self.waypoints = waypoints

def get_stop_fix_type(self):
if self.stop_fix_type == "Departure":
if self.stop_fix_type == "Arrival":
return [True, True]
else:
return [True, False]
else:
if self.stop_fix_type == "Arrival":
return [False, True]
else:
return [False, False]

async def optimize_route(self) -> Dict[str, Tuple[float, float]]:
try:
optimized_waypoints = run(self.waypoints)
optimized_waypoints = run(self.waypoints.waypoints, self.get_stop_fix_type()[0], self.get_stop_fix_type()[1])
return optimized_waypoints
except Exception as e:
raise e
Expand All @@ -44,7 +58,7 @@ def optimize_waypoints_test(self):
@self.router.post("/optimize_test")
async def _(waypoints: Waypoints):
try:
optimizer = RouteOptimizer(waypoints.waypoints)
optimizer = RouteOptimizer(waypoints)
optimized_route = await optimizer.optimize_route()
return JSONResponse(status_code=200, content=optimized_route)
except Exception as e:
Expand All @@ -54,7 +68,7 @@ def optimize_waypoints(self):
@self.router.post("/optimize")
async def _(waypoints: Waypoints, current_user: str = Depends(JWTAuthenticator.get_current_user)):
try:
optimizer = RouteOptimizer(waypoints.waypoints)
optimizer = RouteOptimizer(waypoints)
optimized_route = optimizer.optimize_route()
return JSONResponse(content=optimized_route)
except Exception as e:
Expand Down
20 changes: 20 additions & 0 deletions k8s/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-deployment
spec:
replicas: 2
selector:
matchLabels:
app: fastapi
template:
metadata:
labels:
app: fastapi
spec:
containers:
- name: fastapi
image: ghcr.io/lavi02/halo:main
ports:
- containerPort: 8000
17 changes: 17 additions & 0 deletions k8s/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: fastapi-ingress
spec:
rules:
- host: 127.0.0.1
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: fastapi-service
port:
number: 80
11 changes: 11 additions & 0 deletions k8s/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: fastapi-service
spec:
selector:
app: fastapi
ports:
- protocol: TCP
port: 80
targetPort: 8000
11 changes: 11 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ aiohttp==3.9.1
aiosignal==1.3.1
annotated-types==0.6.0
anyio==3.7.1
astroid==3.0.2
async-timeout==4.0.3
attrs==23.1.0
autopep8==2.0.4
Expand All @@ -17,6 +18,7 @@ click-plugins==1.1.1
cligj==0.7.2
colorama==0.4.6
contourpy==1.0.7
coverage==7.4.0
cryptography==41.0.7
cycler==0.12.1
dependency-injector==4.41.0
Expand All @@ -34,6 +36,7 @@ dephell-shells==0.1.5
dephell-specifier==0.3.0
dephell-venvs==0.1.18
dephell-versioning==0.1.2
dill==0.3.7
docutils==0.20.1
ecdsa==0.18.0
exceptiongroup==1.2.0
Expand All @@ -44,11 +47,14 @@ frozenlist==1.4.0
geopandas==0.13.2
greenlet==3.0.1
h11==0.14.0
httpcore==1.0.2
httptools==0.6.1
httpx==0.26.0
idna==3.6
importlib-metadata==6.9.0
importlib-resources==6.1.1
iniconfig==2.0.0
isort==5.13.2
jaraco.classes==3.3.0
jeepney==0.8.0
Jinja2==3.1.2
Expand All @@ -59,6 +65,7 @@ m2r==0.3.1
markdown-it-py==3.0.0
MarkupSafe==2.1.3
matplotlib==3.7.4
mccabe==0.7.0
mdurl==0.1.2
mistune==0.8.4
more-itertools==10.1.0
Expand All @@ -74,6 +81,7 @@ passlib==1.7.4
pexpect==4.9.0
Pillow==10.1.0
pkginfo==1.9.6
platformdirs==4.1.0
pluggy==1.3.0
protobuf==4.25.1
ptyprocess==0.7.0
Expand All @@ -84,14 +92,17 @@ pydantic==2.5.2
pydantic-core==2.14.5
pygments==2.17.2
PyJWT==2.8.0
pylint==3.0.3
PyMySQL==1.1.0
pyparsing==3.1.1
pyproj==3.5.0
pyproject-hooks==1.0.0
pytest==7.4.4
pytest-cov==4.1.0
python-dateutil==2.8.2
python-dotenv==1.0.0
python-jose==3.3.0
python-multipart==0.0.6
pytz==2023.3.post1
PyYAML==6.0.1
readme-renderer==42.0
Expand Down
2 changes: 1 addition & 1 deletion src/repo/log/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def debug(self, message):
def warning(self, message):
self.logger.warning(message)

def error(self, message):
def error(self, message) -> None:
self.logger.error(message)

def fatal(self, message):
Expand Down
77 changes: 45 additions & 32 deletions src/service/tsp/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
import networkx as nx
from typing import Tuple, List

from src.repo.log.__init__ import handler
from src.repo.log.__init__ import handler, Log
from src.service.tsp.a_star import *
from src.service.tsp.q import *


class RouteOptimizer(ABC):
@abstractmethod
def optimize_route(self, matrix) -> Tuple[int, List[int]]:
def optimize_route(self, matrix, start_fixed=False, end_fixed=False) -> Tuple[int, List[int]]:
pass


Expand All @@ -24,11 +25,12 @@ def create_data_model(matrix):

return data


class OrToolsRouteOptimizer(RouteOptimizer):
def __init__(self, logger):
self.logger = logger
self.logger = logger if logger else handler

def optimize_route(self, matrix) -> tuple:
def optimize_route(self, matrix, start_fixed=False, end_fixed=False) -> tuple:
"""
Args:
matrix: The distance matrix
Expand All @@ -37,10 +39,15 @@ def optimize_route(self, matrix) -> tuple:
The objective value and the solution
"""
try:
# Instantiate the data problem
data = create_data_model(matrix)
manager = pywrapcp.RoutingIndexManager(
len(data['distance_matrix']), data['num_vehicles'], data['depot'])

start_index = 0 if start_fixed else data['depot']
end_index = len(matrix) - 1 if end_fixed else data['depot']

manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
data['num_vehicles'],
[start_index],
[end_index])

routing = pywrapcp.RoutingModel(manager)

Expand All @@ -51,7 +58,6 @@ def distance_callback(from_index, to_index):

transit_callback_index = routing.RegisterTransitCallback(
distance_callback)

routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

search_parameters = pywrapcp.DefaultRoutingSearchParameters()
Expand All @@ -61,8 +67,9 @@ def distance_callback(from_index, to_index):
solution = routing.SolveWithParameters(search_parameters)

if solution:
handler.log.info("Objective: %s" % solution.ObjectiveValue())
handler.log.info("Route: ")
self.logger.log.info("Objective: %s" %
solution.ObjectiveValue()) # type: ignore
self.logger.log.info("Route: ") # type: ignore

objective_value = solution.ObjectiveValue()
path = []
Expand All @@ -76,19 +83,18 @@ def distance_callback(from_index, to_index):

return objective_value, path
else:
# Tuple[int, List[int]
return None, None

except Exception as e:
handler.log.error("Ortools error: %s" % e)
self.logger.log.error("Ortools error: %s" % e) # type: ignore
return None, None


class QLearningRouteOptimizer(RouteOptimizer):
def __init__(self, logger):
self.logger = logger
self.logger = logger if logger else handler

def optimize_route(self, matrix) -> tuple:
def optimize_route(self, matrix, start_fixed=False, end_fixed=False) -> tuple:
"""
Args:
matrix: The distance matrix
Expand All @@ -98,8 +104,14 @@ def optimize_route(self, matrix) -> tuple:
"""
try:
graph = nx.from_numpy_array(np.array(matrix))
origin_node = 0
destination_node = len(matrix) - 1

origin_node = 0 if start_fixed else None
destination_node = len(matrix) - 1 if end_fixed else None

if origin_node is None:
origin_node = 0
if destination_node is None:
destination_node = len(matrix) - 1

env = RouteEnv(graph, origin_node, destination_node)
agent = QLearningAgent(env)
Expand All @@ -122,14 +134,15 @@ def optimize_route(self, matrix) -> tuple:
return objective_value, path

except Exception as e:
handler.log.error("Q error: %s" % e)
self.logger.log.error("Q-Learning error: %s" % e)
return None, None



class AStarRouteOptimizer(RouteOptimizer):
def __init__(self, logger):
self.logger = logger
def optimize_route(self, matrix):
self.logger = logger if logger else handler

def optimize_route(self, matrix, start_fixed=False, end_fixed=False):
"""
Args:
matrix: The distance matrix
Expand All @@ -139,16 +152,17 @@ def optimize_route(self, matrix):
"""
try:
graph = nx.from_numpy_array(np.array(matrix))
origin_node = 0
destination_node = len(matrix) - 1

origin = graph.nodes[origin_node]['x'], graph.nodes[origin_node]['y']
destination = graph.nodes[destination_node]['x'], graph.nodes[destination_node]['y']
origin_node = 0 if start_fixed else None
destination_node = len(matrix) - 1 if end_fixed else None

agent = HeuristicAgent(graph)
path = agent.calculate_optimized_path(origin, destination)
if path is None:
return None, None
if origin_node is None:
origin_node = 0
if destination_node is None:
destination_node = len(matrix) - 1

path = nx.astar_path(graph, origin_node,
destination_node, weight='weight')

objective_value = 0
for i in range(len(path) - 1):
Expand All @@ -157,6 +171,5 @@ def optimize_route(self, matrix):
return objective_value, path

except Exception as e:
handler.log.error("A* error: %s" % e)
self.logger.log.error("A* error: %s" % e)
return None, None

Loading

0 comments on commit 3767513

Please sign in to comment.