Defining algorithms¶
The quickstart generated a skeleton at src/templates/algorithm/tsp_algorithm.py.
We will add our two algorithms alongside it: one deterministic heuristic and one parameter-driven metaheuristic.
Nearest Neighbor¶
Create
nearest_neighbor.pyinsrc/templates/algorithm/:
Code
from typing import List
from algomancy_scenario import (
ScenarioResult,
BaseAlgorithm,
BaseParameterSet,
)
from data_handling.data_model.data_model import DataModel
from data_handling.data_model.location import Location
from data_handling.result_model.result_model import ResultModel
class NearestNeighborParameterSet(BaseParameterSet):
def __init__(self, name: str = "NearestNeighbor"):
super().__init__(name=name)
def validate(self):
pass
class NearestNeighborAlgorithm(BaseAlgorithm):
def __init__(self, params: NearestNeighborParameterSet):
super().__init__(name="NearestNeighbor", params=params)
@staticmethod
def initialize_parameters() -> NearestNeighborParameterSet:
return NearestNeighborParameterSet()
def run(self, data: DataModel) -> ResultModel:
nm = data.network_manager
# sort locations on ID
locations = nm.get_locations()
locations.sort(key=lambda loc: loc.id)
current_location = locations[0]
ordered_locations = [current_location]
tour = []
while len(ordered_locations) < len(locations):
reachable_locations = nm.get_reachable_locations(current_location.id)
candidate_locations = self._remove_visited_locations(reachable_locations, ordered_locations)
if len(candidate_locations) == 0:
break
next_location = min(
candidate_locations,
key=lambda next_loc: nm.get_route(current_location.id, next_loc.id).cost
)
ordered_locations += [next_location]
tour += [nm.get_route(current_location.id, next_location.id)]
current_location = next_location
rm = ResultModel(data_id=data.id)
rm.set_ordered_locations(ordered_locations=ordered_locations)
rm.set_tour(tour=tour)
return rm
@staticmethod
def _remove_visited_locations(
reachable_locations: List[Location],
visited_locations: List[Location]
) -> List[Location]:
visited_set = set(visited_locations)
return [loc for loc in reachable_locations if loc not in visited_set]
Create
__init__.pyinsrc/templates/algorithm/:
from templates.algorithm.nearest_neighbor import NearestNeighborAlgorithm
algorithms = {
"NearestNeighbor": NearestNeighborAlgorithm,
}
The dict key is the algorithm name shown in the dashboard; the value is the class.
Update
main.pyto use the algorithm templates. The quickstart already added analgorithmsargument — update the import and the dict:Add the algorithm template(s) to
CoreConfiginmain.py.
from src.templates.algorithm import algorithms
app_cfg = AppConfig(
core_config=CoreConfig(
...
algorithms=algorithms,
...
)
)
Start the application, load the data, create a new scenario, and run the NearestNeighbor algorithm. Verify that no errors occur.
Simulated Annealing¶
Create
simulated_annealing.pyinsrc/templates/algorithm/. The parameter set exposes configurable values that the user can set in the GUI dialog:
Code
import math
import random
from typing import List
from algomancy_scenario import BaseAlgorithm, BaseParameterSet
from algomancy_utils.baseparameterset import FloatParameter, IntegerParameter
from data_handling.data_model.data_model import DataModel
from data_handling.data_model.location import Location
from data_handling.data_model.network_manager import NetworkManager
from data_handling.result_model.result_model import ResultModel
class SimulatedAnnealingParameterSet(BaseParameterSet):
def __init__(self, name: str = "SimulatingAnnealing"):
super().__init__(name=name)
self.add_parameters(
[
FloatParameter(name="initial_temperature", value=1000),
FloatParameter(name="cooling_rate", value=0.995),
FloatParameter(name="min_temperature", value=1.0),
IntegerParameter(name="max_iterations", value=10000),
FloatParameter(name="seed", value=42),
]
)
@property
def initial_temperature(self) -> float:
return self._parameters["initial_temperature"].value
@property
def cooling_rate(self) -> float:
return self._parameters["cooling_rate"].value
@property
def min_temperature(self) -> float:
return self._parameters["min_temperature"].value
@property
def max_iterations(self) -> int:
return self._parameters["max_iterations"].value
@property
def seed(self) -> float:
return self._parameters["seed"].value
def validate(self):
pass
class SimulatedAnnealingAlgorithm(BaseAlgorithm):
def __init__(self, params: SimulatedAnnealingParameterSet):
super().__init__(name="SimulatingAnnealing", params=params)
@staticmethod
def initialize_parameters() -> SimulatedAnnealingParameterSet:
return SimulatedAnnealingParameterSet()
def run(self, data: DataModel) -> ResultModel:
nm = data.network_manager
locations = nm.get_locations()
random.shuffle(locations)
current_tour = locations
current_cost = self._tour_cost(current_tour, nm=nm)
best_tour = current_tour[:]
best_cost = current_cost
iteration = 0
temperature = self.params.initial_temperature
while temperature > self.params.min_temperature and iteration < self.params.max_iterations:
self.set_progress(iteration / self.params.max_iterations * 100.0)
candidate_tour = self._neighbor(current_tour)
candidate_cost = self._tour_cost(candidate_tour, nm=nm)
delta = candidate_cost - current_cost
if delta < 0 or random.random() < math.exp(-delta / temperature):
current_tour = candidate_tour
current_cost = candidate_cost
if current_cost < best_cost:
best_tour = current_tour[:]
best_cost = current_cost
temperature *= self.params.cooling_rate
iteration += 1
rm = ResultModel(data_id=data.id)
rm.set_ordered_locations(ordered_locations=best_tour)
tour = []
for i in range(len(best_tour) - 1):
from_location, to_location = best_tour[i], best_tour[i + 1]
tour += [nm.get_route(from_location.id, to_location.id)]
rm.set_tour(tour=tour)
return rm
@staticmethod
def _tour_cost(tour: List[Location], nm: NetworkManager) -> float:
cost = 0.0
for i in range(len(tour) - 1):
route = nm.get_route(tour[i].id, tour[i + 1].id)
cost += route.cost
return cost
@staticmethod
def _neighbor(tour: List[Location]) -> List[Location]:
new_tour = tour[:]
i, j = random.sample(range(len(new_tour)), 2)
new_tour[i], new_tour[j] = new_tour[j], new_tour[i]
return new_tour
Update
__init__.pyinsrc/templates/algorithm/:
from templates.algorithm.nearest_neighbor import NearestNeighborAlgorithm
from templates.algorithm.simulated_annealing import SimulatedAnnealingAlgorithm
algorithms = {
"NearestNeighbor": NearestNeighborAlgorithm,
"SimulatingAnnealing": SimulatedAnnealingAlgorithm,
}
Start the application, load the data, and run both algorithms on separate scenarios.
Open the Compare page, select NearestNeighbor as the left scenario and SimulatedAnnealing as the right.
Enable “Show KPI cards” in the top-right area and verify that the KPI delta is displayed.