Skip to content

Commit

Permalink
Add new population type 'from_geojson' for genetic algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinPontius committed Jan 23, 2024
1 parent c857cd8 commit 8778ed1
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Optional variables (default values provided and don't need to be changed normall
- `GENETIC_NUMBER_GENERATIONS`: number of generations for genetic algorithm
- `GENETIC_NUMBER_OFFSPRINGS`: number of offsprings for genetic algorithm
- `GENETIC_POPULATION_SIZE`: population size for genetic algorithm
- `GENETIC_POPULATION_TYPE`: type for initial population (options: 'grid_based')
- `GENETIC_POPULATION_TYPE`: type for initial population (options: 'grid_based', 'from_geojson')
- `INTERMEDIATE_WAYPOINTS`: [[lat_one,lon_one], [lat_two,lon_two] ... ]
- `ISOCHRONE_MAX_ROUTING_STEPS`: maximum number of routing steps. Applies also if more than one route is searched!
- `ISOCHRONE_MINIMISATION_CRITERION`: options: 'dist', 'squareddist_over_disttodest'
Expand Down
11 changes: 6 additions & 5 deletions WeatherRoutingTool/algorithms/genetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@

class Genetic(RoutingAlg):
fig: matplotlib.figure
route_ensemble: list
route: np.array # temp

pop_size: int
n_offsprings: int

default_map: Map
weather_path: str
path_to_route_folder: str

def __init__(self, config) -> None:
super().__init__(config)

self.default_map = config.DEFAULT_MAP
self.weather_path = config.WEATHER_DATA
self.path_to_route_folder = config.ROUTE_PATH

self.ncount = config.GENETIC_NUMBER_GENERATIONS # ToDo: use better name than ncount?
self.count = 0
Expand All @@ -61,6 +61,7 @@ def execute_routing(self, boat: Boat, wt: WeatherCond, constraints_list: Constra
wave_height = wave_height[::lat_int, ::lon_int]
problem = RoutingProblem(departure_time=self.departure_time, boat=boat, constraint_list=constraints_list)
initial_population = PopulationFactory.get_population(self.population_type, self.start, self.finish,
path_to_route_folder=self.path_to_route_folder,
grid=wave_height)
mutation = MutationFactory.get_mutation(self.mutation_type, grid=wave_height)
crossover = CrossoverFactory.get_crossover()
Expand Down Expand Up @@ -107,13 +108,13 @@ def terminate(self, **kwargs):

waypoint_coors = RouteParams.get_per_waypoint_coords(lons, lats, self.departure_time, speed)
dists = waypoint_coors['dist']
bearings = waypoint_coors['courses']
courses = waypoint_coors['courses']
start_times = waypoint_coors['start_times']
travel_times = waypoint_coors['travel_times']
arrival_time = start_times[-1] + timedelta(seconds=dists[-1]/speed)

dists = np.append(dists, -99)
bearings = np.append(bearings, -99)
courses = np.append(courses, -99)
start_times = np.append(start_times, arrival_time)
travel_times = np.append(travel_times, -99)

Expand All @@ -125,7 +126,7 @@ def terminate(self, **kwargs):
time=travel_times,
lats_per_step=lats,
lons_per_step=lons,
azimuths_per_step=bearings,
course_per_step=courses,
dists_per_step=dists,
starttime_per_step=start_times,
ship_params_per_step=self.ship_params)
Expand Down
94 changes: 77 additions & 17 deletions WeatherRoutingTool/algorithms/genetic_utils.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import json
import logging
import os
import random
from math import ceil

import cartopy.crs as ccrs
import cartopy.feature as cf
import numpy as np
from matplotlib import pyplot as plt
from geographiclib.geodesic import Geodesic
from pymoo.core.crossover import Crossover
from pymoo.core.duplicate import ElementwiseDuplicateElimination
from pymoo.core.mutation import Mutation
from pymoo.core.problem import ElementwiseProblem
from pymoo.core.sampling import Sampling
from skimage.graph import route_through_array

import WeatherRoutingTool.utils.graphics as graphics
from WeatherRoutingTool.algorithms.data_utils import GridMixin
from WeatherRoutingTool.routeparams import RouteParams
from WeatherRoutingTool.utils.graphics import plot_genetic_algorithm_initial_population

logger = logging.getLogger('WRT.Genetic')

Expand Down Expand Up @@ -47,28 +47,88 @@ def _do(self, problem, n_samples, **kwargs):
_, _, route = self.index_to_coords(route)
routes[i][0] = np.array(route)

figure_path = graphics.get_figure_path()
if figure_path is not None:
plt.rcParams['font.size'] = graphics.get_standard('font_size')
fig, ax = plt.subplots(figsize=graphics.get_standard('fig_size'))
ax.remove()
fig, ax = graphics.generate_basemap(fig, None, self.src, self.dest, '', False)
for i in range(0, n_samples):
ax.plot(routes[i, 0][:, 1], routes[i, 0][:, 0], color="firebrick")
plt.savefig(os.path.join(figure_path, 'genetic_algorithm_initial_population.png'))
plot_genetic_algorithm_initial_population(self.src, self.dest, routes)
self.X = routes
return self.X


class FromGeojsonPopulation(Sampling):
"""
Make initial population for genetic algorithm based on the isofuel algorithm with a ConstantFuelBoat
"""
def __init__(self, src, dest, path_to_route_folder, var_type=np.float64):
super().__init__()
self.var_type = var_type
self.src = src
self.dest = dest
self.path_to_route_folder = path_to_route_folder

def _do(self, problem, n_samples, **kwargs):
routes = np.full((n_samples, 1), None, dtype=object)
# Routes have to be named route_1.json, route_2.json, etc.
# See method find_routes_reaching_destination_in_current_step in isobased.py
for i in range(n_samples):
route_file = os.path.join(self.path_to_route_folder, f'route_{i+1}.json')
try:
route = self.read_route_from_file(route_file)
routes[i][0] = np.array(route)
except FileNotFoundError:
logger.warning(f"File '{route_file}' couldn't be found. Use great circle route instead.")
route = self.get_great_circle_route()
routes[i][0] = np.array(route)

plot_genetic_algorithm_initial_population(self.src, self.dest, routes)
self.X = routes
return self.X

def get_great_circle_route(self, distance=100000):
"""
Get equidistant route along great circle in the form [[lat1, lon1], [lat12, lon2], ...]
:param distance: distance in m
:return: route as list of lat/lon points
"""
geod = Geodesic.WGS84
line = geod.InverseLine(self.src[0], self.src[1], self.dest[0], self.dest[1])
n = int(ceil(line.s13 / distance))
route = []
for i in range(n+1):
s = min(distance * i, line.s13)
g = line.Position(s, Geodesic.STANDARD | Geodesic.LONG_UNROLL)
route.append([g['lat2'], g['lon2']])
return route

def read_route_from_file(self, route_absolute_path):
"""
Read route from geojson file and return the coordinates in the form [[lat1, lon1], [lat12, lon2], ...]
:param route_absolute_path: absolute path to geojson file
:return: route as list of lat/lon points
"""
with open(route_absolute_path) as file:
rp_dict = json.load(file)
route = [[feature['geometry']['coordinates'][1], feature['geometry']['coordinates'][0]]
for feature in rp_dict['features']]
return route


class PopulationFactory:
def __init__(self):
pass

@staticmethod
def get_population(population_type, src, dest, grid=None):
def get_population(population_type, src, dest, path_to_route_folder=None, grid=None):
if population_type == 'grid_based':
if grid is None:
msg = f"For population type '{population_type}', a grid has to be provided!"
logger.error(msg)
raise ValueError(msg)
population = GridBasedPopulation(src, dest, grid)
elif population_type == 'from_geojson':
if (not path_to_route_folder or not os.path.isdir(path_to_route_folder) or
not os.access(path_to_route_folder, os.R_OK)):
msg = f"For population type '{population_type}', a valid route path has to be provided!"
logger.error(msg)
raise ValueError(msg)
population = FromGeojsonPopulation(src, dest, path_to_route_folder)
else:
msg = f"Population type '{population_type}' is invalid!"
logger.error(msg)
Expand Down Expand Up @@ -223,10 +283,10 @@ def get_constraints(self, route):

def get_power(self, route):
route_dict = RouteParams.get_per_waypoint_coords(route[:, 1], route[:, 0], self.departure_time,
self.boat.boat_speed_function())
self.boat.get_boat_speed())

shipparams = self.boat.get_fuel_per_time_netCDF(route_dict['courses'], route_dict['start_lats'],
route_dict['start_lons'], route_dict['start_times'])
shipparams = self.boat.get_ship_parameters(route_dict['courses'], route_dict['start_lats'],
route_dict['start_lons'], route_dict['start_times'], [])
fuel = shipparams.get_fuel()
fuel = (fuel / 3600) * route_dict['travel_times']
return np.sum(fuel), shipparams
Expand Down
2 changes: 1 addition & 1 deletion WeatherRoutingTool/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(self, init_mode='from_json', file_name=None, config_dict=None):
self.GENETIC_NUMBER_GENERATIONS = None # number of generations for genetic algorithm
self.GENETIC_NUMBER_OFFSPRINGS = None # number of offsprings for genetic algorithm
self.GENETIC_POPULATION_SIZE = None # population size for genetic algorithm
self.GENETIC_POPULATION_TYPE = None # type for initial population (options: 'grid_based')
self.GENETIC_POPULATION_TYPE = None # type for initial population (options: 'grid_based', 'from_geojson')
self.INTERMEDIATE_WAYPOINTS = None # [[lat_one,lon_one], [lat_two,lon_two] ... ]
self.ISOCHRONE_MAX_ROUTING_STEPS = None # maximum number of routing steps
self.ISOCHRONE_MINIMISATION_CRITERION = None # options: 'dist', 'squareddist_over_disttodest'
Expand Down
12 changes: 12 additions & 0 deletions WeatherRoutingTool/utils/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,15 @@ def generate_basemap(fig, depth, start=None, finish=None, title='', show_depth=T
plt.title(title)

return fig, ax


def plot_genetic_algorithm_initial_population(src, dest, routes):
figure_path = get_figure_path()
if figure_path is not None:
plt.rcParams['font.size'] = get_standard('font_size')
fig, ax = plt.subplots(figsize=get_standard('fig_size'))
ax.remove()
fig, ax = generate_basemap(fig, None, src, dest, '', False)
for i in range(0, len(routes)):
ax.plot(routes[i, 0][:, 1], routes[i, 0][:, 0], color="firebrick")
plt.savefig(os.path.join(figure_path, 'genetic_algorithm_initial_population.png'))

0 comments on commit 8778ed1

Please sign in to comment.