├── .gitignore ├── README.md ├── advancedAlgorithm.ipynb ├── advanced_scripts ├── calculate_objectives.py ├── initial_population.py ├── lengthGridEW.npy ├── lengthGridNS.npy ├── prediction.py ├── runAlgorithm.py ├── spatial_crossover.py ├── spatial_extention_pymoo.py ├── spatial_mutation.py └── spatial_sampling.py ├── models └── DTR_model.joblib ├── preliminary_work ├── GA_trial.ipynb ├── GFS.ipynb ├── calculate_lengthGrid.ipynb ├── cmems.ipynb ├── firstCalculations.ipynb ├── mergeData.ipynb ├── mergedRoutes.html ├── model.ipynb └── simple_routing.ipynb ├── scripts ├── calculate_objectives.py ├── initial_population.py ├── lengthGridEW.npy ├── lengthGridNS.npy ├── prediction.py ├── runAlgorithm.py ├── spatial_crossover.py ├── spatial_extention_pymoo.py ├── spatial_mutation.py └── spatial_sampling.py └── simpleAlgorithm.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ship Routing Algorithms for Just-In-Time and Energy Efficient Voyages – Comparing Simple and Advanced Implementations of a Genetic Algorithm 2 | 3 | Rising fuel costs, the ever-increasing number of regulations and their tightening demonstrates the need to minimize fuel consumption in container shipping. With the development of an interactive Jupyter notebook, we want to meet this need. The developed script is intended to assist in planning fuel-saving routes and takes into account the engine power of the vessel, the condition of the weather and the sea, and also possible just-in-time deadlines. 4 | 5 | For this purpose, the engine power is first modeled in order to be able to make statements about which speed is possible under which weather conditions. By using a genetic algorithm, a multi-objective optimization is then performed, aiming at the lowest possible fuel consumption while at the same time meeting the scheduled deadlines. Thereby the algorithm is tested in two different specifications. One with a constant and over the route not changeable engine power and the other with a changeable engine power. With the first specification, a temporal window can only be considered up to a certain degree due to the constant engine power. The second specification with the variable engine power, however, should allow the calculation of just-in-time routes. In order to be able to make statements about the two specifications, they are compared concerning applicability, constraints and performance. 6 | 7 | You can find the both different specifications of the algorithm in the main path of this repository, or also in the Google Colab environment ([simple algorithm](https://colab.research.google.com/drive/1LCKjc_mqWN_DwWTHlsaOHLscdcrTY7OD?usp=sharing), [advanced algorithm](https://colab.research.google.com/drive/1J0TDFwOL1fKs_Nb9iuq2Sq-MrRiNu6DZ?usp=sharing)). The two scripts can be run there interactively, or are downloadable as part of the repository to run locally. 8 | In addition to these scripts, there are some more files in the repository, which are the basis for the algorithm, and some more notebooks for preparation. The notebooks used to merge the data and create the model are also available as interactive Google Colab ([model](https://colab.research.google.com/drive/1_0zxOrPO2eSQvSD5ovPS1mo_sS7NGl3j?usp=sharing), [mergeData](https://colab.research.google.com/drive/1yM4Ayj7l5cnjCb0NmpNzR_v2EOn1FDVj?usp=sharing)). 9 | 10 | This software was developed as part of the study project "Copernicus Marine Services for Energy optimized Cargo Ship routing" in the summerterm 2021 at ifgi at the University of Münster. 11 | -------------------------------------------------------------------------------- /advanced_scripts/calculate_objectives.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pickle 3 | from datetime import datetime, time, timedelta 4 | 5 | # Array of fuel cost per hour for the different engine powers [0.6, 0.7, 0.8] 6 | fuelArray=[152*33200, 154*33200, 156*33200] 7 | 8 | 9 | def calculateTime(route, timeGrids): 10 | """ 11 | calculate the time needed to go the specific route based on the time grids # 12 | 13 | Parameters 14 | ---------- 15 | route : array 16 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 17 | timeGrids : array 18 | arrays of the time needed for the grid cell, the first dimension are the three different speeds [0.8, 0.7, 0.6] 19 | the second dimension the direction of the gird [N, S, E, W]. 20 | The third and fourt dimension is then the grid for this combination of speed and bearing. 21 | """ 22 | sumTime = 0 23 | routeList = makeArrays(route) 24 | #calculate the bearing for each gird cell 25 | routeWithBearing = calculateBearing(routeList) 26 | 27 | for x in range(len(routeWithBearing)-1): 28 | coordinate = routeWithBearing[x] 29 | speed = coordinate[2] 30 | speedIndex = 0 31 | if(speed == 0.8): 32 | speedIndex = 0 33 | elif(speed == 0.7): 34 | speedIndex = 1 35 | else: 36 | speedIndex = 2 37 | 38 | bearing = coordinate[3] 39 | 40 | #select time grid for specific speed and bearing 41 | timeGrid = timeGrids[speedIndex][bearing] 42 | sumTime = sumTime + timeGrid[coordinate[0]][coordinate[1]] 43 | hours_added = timedelta(minutes = sumTime) 44 | return hours_added 45 | 46 | def calculateFuelUse(route, timeGrids): 47 | """ 48 | calculate the fuel use needed to go the specific route based on the time grids 49 | 50 | Parameters 51 | ---------- 52 | route : array 53 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 54 | timeGrids : array 55 | arrays of the time needed for the grid cell, the first dimension are the three different speeds [0.8, 0.7, 0.6] 56 | the second dimension the direction of the gird [N, S, E, W]. 57 | The third and fourt dimension is then the grid for this combination of speed and bearing. 58 | """ 59 | fuelUse = 0 60 | routeList = makeArrays(route) 61 | #calculate the bearing for each gird cell 62 | routeWithBearing = calculateBearing(routeList) 63 | for x in range(len(routeWithBearing)-1): 64 | coordinate = routeWithBearing[x] 65 | speed = coordinate[2] 66 | speedIndex = 0 67 | if(speed == 0.8): 68 | speedIndex = 0 69 | elif(speed == 0.7): 70 | speedIndex = 1 71 | else: 72 | speedIndex = 2 73 | 74 | 75 | bearing = coordinate[3] 76 | #select time grid for specific speed and bearing 77 | timeGrid = timeGrids[speedIndex][bearing] 78 | fuelUse = fuelUse + (timeGrid[coordinate[0]][coordinate[1]]/60 * fuelArray[speedIndex]) 79 | return fuelUse 80 | 81 | def makeArrays(route): 82 | """ 83 | the route returned by the muation is a array of tuples. It needs to be an array 84 | 85 | Parameters 86 | ---------- 87 | route : array 88 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 89 | """ 90 | routeNew = [] 91 | for x in route: 92 | routeNew.append(list(x)) 93 | return routeNew 94 | 95 | 96 | def calculateBearing(route): 97 | """ 98 | calculates the bearing for each grid cell 99 | 100 | Parameters 101 | ---------- 102 | route : array 103 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 104 | """ 105 | route 106 | for i in range(len(route)-1): 107 | if route[i][0]< route[i+1][0]: 108 | route[i].append(0) #up 109 | elif route[i][0] > route[i+1][0]: 110 | route[i].append(1) # down 111 | elif route[i][1] < route[i+1][1]: 112 | route[i].append(2) #right 113 | elif route[i][1] > route[i+1][1]: 114 | route[i].append(3) #left 115 | else: 116 | route[i].append("error") 117 | return route 118 | 119 | 120 | def calculate_time_differences(routes, startTime, endTime, timeGrids): 121 | """ 122 | functions calculates the time differences depending on the start and end time for each route in the population 123 | 124 | Parameters 125 | ---------- 126 | routes : array 127 | array of all routes that are in the population 128 | startTime : str, day.month.year hours:minutes 129 | start time of the ship 130 | endTime : str, day.month.year hours:minutes 131 | booked port time, the ship shlould arrive 132 | timeGrids : array 133 | arrays of the time needed for the grid cell, the first dimension are the three different speeds [0.8, 0.7, 0.6] 134 | the second dimension the direction of the gird [N, S, E, W]. 135 | The third and fourt dimension is then the grid for this combination of speed and bearing. 136 | 137 | """ 138 | 139 | timeDif= [] 140 | # loop over the individuals in the population 141 | for route in routes: 142 | startTime_object = datetime.strptime(startTime, "%d.%m.%Y %H:%M" ) 143 | endTime_object = datetime.strptime(endTime, "%d.%m.%Y %H:%M" ) 144 | duration = calculateTime(route[1], timeGrids ) 145 | eta = startTime_object + duration 146 | difference= endTime_object-eta 147 | total_seconds = difference.total_seconds() 148 | minutes = total_seconds/60 149 | timeDif.append(float(minutes) ** 2) 150 | return(np.array(timeDif)) 151 | 152 | 153 | 154 | def calculate_fuelUse(routes,timeGrids): 155 | """ 156 | functions calculates the fuel use for each route in the population 157 | 158 | Parameters 159 | ---------- 160 | routes : array 161 | array of all routes that are in the population 162 | timeGrids : array 163 | arrays of the time needed for the grid cell, the first dimension are the three different speeds [0.8, 0.7, 0.6] 164 | the second dimension the direction of the gird [N, S, E, W]. 165 | The third and fourt dimension is then the grid for this combination of speed and bearing. 166 | 167 | """ 168 | 169 | # loop over the individuals in the population 170 | all_fuel = [] 171 | for route in routes: 172 | fuelUse = calculateFuelUse(route[1], timeGrids) 173 | fuelUseT = fuelUse/1000000 174 | all_fuel.append(fuelUseT) 175 | 176 | return(np.array(all_fuel)) 177 | 178 | def calculate_MinDistance(routes,Grids): 179 | """ 180 | functions calculates the difference in km for each route in the population 181 | 182 | Parameters 183 | ---------- 184 | routes : array 185 | array of all routes that are in the population 186 | timeGrids : array 187 | arrays of the time needed for the grid cell, the first dimension are the three different speeds [0.8, 0.7, 0.6] 188 | the second dimension the direction of the gird [N, S, E, W]. 189 | The third and fourt dimension is then the grid for this combination of speed and bearing. 190 | 191 | """ 192 | all_KM = [] 193 | for route in routes: 194 | sumKM = 0 195 | routeList = makeArrays(route[1]) 196 | routeWithBearing = calculateBearing(routeList) 197 | for x in range(len(routeWithBearing)-1): 198 | coordinate = routeWithBearing[x] 199 | 200 | bearing = coordinate[3] 201 | 202 | timeGrid = Grids[bearing] 203 | sumKM = sumKM + timeGrid[coordinate[0]][coordinate[1]] 204 | all_KM.append(sumKM) 205 | 206 | return all_KM 207 | 208 | -------------------------------------------------------------------------------- /advanced_scripts/initial_population.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | import math 4 | import matplotlib.pyplot as plt 5 | from matplotlib.colors import ListedColormap 6 | import yaml 7 | from skimage.graph import route_through_array 8 | 9 | # array of differnet possible speeds 10 | speeds=[0.8, 0.7, 0.6] 11 | 12 | def makeArrays(route): 13 | """ 14 | the route returned by the muation is a array of tuples. It needs to be an array 15 | 16 | Parameters 17 | ---------- 18 | route : array 19 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 20 | """ 21 | routeNew = [] 22 | for x in route: 23 | routeNew.append(list(x)) 24 | return routeNew 25 | 26 | 27 | 28 | # make initial population for genetic algorithm 29 | def initialize_spatial(pop_size, startpoint, endpoint, timeGrid): 30 | """ 31 | creates a initial population by making a random configuration of routes 32 | 33 | Parameters 34 | ---------- 35 | pop_size : int 36 | the wanted population size 37 | startpoint: array, [cellX, cellY] 38 | the startpoint of the route 39 | endpoint: array, [cellX, cellY] 40 | the endpoint of the route 41 | timeGrid: array 42 | a grid containing the time for one specific context of bearing and speed 43 | 44 | """ 45 | all_routes = [] 46 | #calulate one route with the example time grid 47 | route, weight = route_through_array(timeGrid, startpoint, endpoint, fully_connected=False, geometric=True) 48 | route1= makeArrays(route) 49 | #assing the three different speeds to the route 50 | for i in range(len(route)): 51 | route1[i].append(speeds[0]) 52 | route2= makeArrays(route) 53 | for i in range(len(route)): 54 | route2[i].append(speeds[1]) 55 | route3= makeArrays(route) 56 | for i in range(len(route)): 57 | route3[i].append(speeds[2]) 58 | 59 | all_routes.append([1, route1]) 60 | all_routes.append([2, route2]) 61 | all_routes.append([3, route3]) 62 | 63 | 64 | # finde the middle point of the route 65 | middle = (startpoint[0] + endpoint[0])/2 66 | middle2 = (startpoint[1] + endpoint[1])/2 67 | 68 | middlePoint = (middle, middle2) 69 | 70 | 71 | 72 | 73 | 74 | # create routes for the wanted population size 75 | for i in range(1,math.floor(pop_size/2)): 76 | 77 | #create a time grid with random costs 78 | timeGridNew= [[random.random() for i in range(timeGrid.shape[1])] for j in range(timeGrid.shape[0])] 79 | timeGridNew = np.where(timeGrid >999, timeGrid, timeGridNew) 80 | change= (random.random() *150) 81 | # set a middle point and move it up and downwoard to create more different routes 82 | middlePointNew = (math.floor(middlePoint[0] + change), math.floor(middlePoint[1])) 83 | route1, weight = route_through_array(timeGridNew, startpoint, middlePointNew, fully_connected=False, geometric=True) 84 | route2, weight = route_through_array(timeGridNew, middlePointNew, endpoint, fully_connected=False, geometric=True) 85 | route= route1[:-1] + route2 86 | route= makeArrays(route) 87 | speed=speeds[math.floor(random.random()*3)] 88 | # append one speed for the complete route 89 | for j in range(len(route)): 90 | route[j].append(speed) 91 | all_routes.append([i, route]) 92 | middlePointNew = (math.floor(middlePoint[0] - change), math.floor(middlePoint[1])) 93 | route1, weight = route_through_array(timeGridNew, startpoint, middlePointNew, fully_connected=False, geometric=True) 94 | route2, weight = route_through_array(timeGridNew, middlePointNew, endpoint, fully_connected=False, geometric=True) 95 | route= route1[:-1] + route2 96 | route= makeArrays(route) 97 | speed=speeds[math.floor(random.random()*3)] 98 | # append one speed for the complete route 99 | for j in range(len(route)): 100 | route[j].append(speed) 101 | all_routes.append([i, route]) 102 | 103 | return all_routes 104 | -------------------------------------------------------------------------------- /advanced_scripts/lengthGridEW.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsten07/cms_routing/6dd1b14b849e03d72c7a857a2ece60dd19af31d5/advanced_scripts/lengthGridEW.npy -------------------------------------------------------------------------------- /advanced_scripts/lengthGridNS.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsten07/cms_routing/6dd1b14b849e03d72c7a857a2ece60dd19af31d5/advanced_scripts/lengthGridNS.npy -------------------------------------------------------------------------------- /advanced_scripts/prediction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """prediction.ipynb 3 | 4 | Automatically generated by Colaboratory. 5 | 6 | Original file is located at 7 | https://colab.research.google.com/drive/1lcvaJYf5k-Y0mmlCYAF8kWQlr2P-eTwr 8 | """ 9 | 10 | # Load model 11 | from joblib import dump, load 12 | 13 | # model_name = "DTR_model" 14 | # model = load('../models/' + model_name + '.joblib') 15 | # from datetime import datetime 16 | # # model = load("DTR_model.joblib") 17 | 18 | """# Load CMEMS data 19 | Try to use WMS or other more flexibel data retrieval 20 | """ 21 | 22 | import ftplib 23 | import os 24 | import numpy as np 25 | import netCDF4 as nc 26 | import pandas as pd 27 | from datetime import datetime 28 | 29 | 30 | def download(url, user, passwd, ftp_path, filename): 31 | with ftplib.FTP(url) as ftp: 32 | 33 | try: 34 | ftp.login(user, passwd) 35 | 36 | # Change directory 37 | ftp.cwd(ftp_path) 38 | 39 | # Download file (if there is not yet a local copy) 40 | if os.path.isfile(filename): 41 | print("There is already a local copy for this date ({})".format(filename)) 42 | else: 43 | with open(filename, 'wb') as fp: 44 | print("Downloading ... ({})".format(filename)) 45 | ftp.retrbinary('RETR {}'.format(filename), fp.write) 46 | 47 | except ftplib.all_errors as e: 48 | print('FTP error:', e) 49 | 50 | 51 | # Check contents 52 | 53 | """ 54 | with ftplib.FTP('nrt.cmems-du.eu') as ftp: 55 | 56 | try: 57 | ftp.login(UN_CMEMS, PW_CMEMS) 58 | 59 | # Change directory 60 | ftp.cwd('Core/GLOBAL_ANALYSIS_FORECAST_PHY_001_024/global-analysis-forecast-phy-001-024/2021/07') 61 | 62 | # List directory contents with additional information 63 | ftp.retrlines('LIST') 64 | 65 | # Get list of directory contents without additional information 66 | files = [] 67 | ftp.retrlines('NLST', files.append) 68 | print(files) 69 | 70 | # Check file size 71 | print("{} MB".format(ftp.size('mfwamglocep_2020120100_R20201202.nc')/1000000)) 72 | 73 | except ftplib.all_errors as e: 74 | print('FTP error:', e) 75 | """ 76 | 77 | 78 | def calc_relative_direction(ship_dir, ww_dir): 79 | """ 80 | determine relative wind direction for ships going north, east, south or west 81 | 82 | Parameters 83 | ---------- 84 | ship_dir : str, in ("N", "E", "S", "W") 85 | direction the ship is going 86 | ww_dir : array, float 87 | array of relative wind directions [0 - 360] 88 | """ 89 | if ship_dir not in ("N", "E", "S", "W"): 90 | raise Exception("Direction not accepted.") 91 | ww_360 = ww_dir 92 | ww_360[ww_360 < 0] = 360 + ww_dir[0] 93 | if ship_dir in ("N"): 94 | dir_4 = np.full((len(ww_dir), 1), 2) 95 | dir_4[(ww_dir < 45) | (ww_dir > 315)] = 1 96 | dir_4[(ww_dir > 135) & (ww_dir < 225)] = 3 97 | if ship_dir in ("E"): 98 | dir_4 = np.full((len(ww_dir), 1), 2) 99 | dir_4[(ww_dir > 45) & (ww_dir < 135)] = 1 100 | dir_4[(ww_dir > 225) & (ww_dir < 315)] = 3 101 | if ship_dir in ("W"): 102 | dir_4 = np.full((len(ww_dir), 1), 2) 103 | dir_4[(ww_dir > 45) & (ww_dir < 135)] = 3 104 | dir_4[(ww_dir > 225) & (ww_dir < 315)] = 1 105 | if ship_dir in ("S"): 106 | dir_4 = np.full((len(ww_dir), 1), 2) 107 | dir_4[(ww_dir < 45) | (ww_dir > 315)] = 3 108 | dir_4[(ww_dir > 135) & (ww_dir < 225)] = 1 109 | return dir_4 110 | 111 | 112 | def concatenate_cmems(cm_wave, cm_phy, ship_param, ship_dir): 113 | """ 114 | concatenate the variables from cmems wave and physics datasets 115 | 116 | Parameters 117 | ---------- 118 | cm_wave : net4CDF dataset 119 | netcdf file cmems wave 120 | cm_phy : net4CDF dataset 121 | netdcf file cmems physics 122 | ship_param : int 123 | ship variable that is used in model later (e.g. draft or length) 124 | ship_dir str, in ("N", "E", "S", "W") 125 | direction the ship is going 126 | """ 127 | array = (np.flipud(cm_wave["VHM0"][0, :, :]).data) # extract data from CMEMS 128 | dim = array.shape 129 | l = np.prod(dim) # get number of "pixel" 130 | 131 | # extract parameters from cmems dataset and reshape to array with dimension of 1 x number of pixel 132 | vhm = (np.flipud(cm_wave["VHM0"][0, :, :])).reshape(l, 1) 133 | vtm = (np.flipud(cm_wave["VTPK"][0, :, :])).reshape(l, 1) 134 | temp = (np.flipud(cm_phy["thetao"][0, 1, :, :])).reshape(l, 1) 135 | sal = (np.flipud(cm_phy["so"][0, 1, :, :])).reshape(l, 1) 136 | # create column for ship parameter 137 | ship = np.full((l, 1), ship_param) 138 | # calculate relative direction of wind depending on ship direction 139 | dir = calc_relative_direction(ship_dir, (np.flipud(cm_wave["VMDR_WW"][0, :, :])).reshape(l, 1)) 140 | 141 | # concatenate parameters 142 | a = np.concatenate((ship, vhm, vtm, temp, sal, dir), axis=1) 143 | 144 | # create pd df from array 145 | X_pred = pd.DataFrame(data=a, # values 146 | index=list(range(0, l)), # 1st column as index 147 | columns=["Draft", "VHM0", "VTPK", "thetao", "so", "dir_4"]) # 1st row as the column names 148 | return X_pred 149 | 150 | 151 | def prepare_grid(cm_wave, cm_phy, ship_param, ship_dir, model): 152 | """ 153 | prepare grid of SOGs 154 | 155 | Parameters 156 | ---------- 157 | cm_wave : net4CDF dataset 158 | netcdf file cmems wave 159 | cm_phy : net4CDF dataset 160 | netdcf file cmems physics 161 | ship_param : int 162 | ship variable that is used in model later (e.g. draft or length) 163 | ship_dir str, in ("N", "E", "S", "W") 164 | direction the ship is going 165 | """ 166 | 167 | X_pred = concatenate_cmems(cm_wave, cm_phy, ship_param, ship_dir) 168 | 169 | # extract shape from cmems data 170 | input = (np.flipud(cm_wave["VHM0"][0, :, :])) 171 | dim = input.shape 172 | 173 | # predict SOG 174 | # model = load('cms_routing/models/DTR_model.joblib') # import model 175 | SOG_pred = model.predict(X_pred) 176 | SOG_pred = SOG_pred.reshape(dim) # reshape to 'coordinates' 177 | SOG_pred[input < -30000] = -5 # -32767.0 # mask data with negative value 178 | 179 | return SOG_pred 180 | 181 | 182 | def calculateTimeGrid(SOG_E, SOG_N, SOG_S, SOG_W, AOI): 183 | kmGridEW = np.load("lengthGridEW.npy") 184 | kmGridEW = kmGridEW[AOI[2]:AOI[3], AOI[0]:AOI[1]] 185 | kmGridNS = np.load("lengthGridNS.npy") 186 | kmGridNS = kmGridNS[AOI[2]:AOI[3], AOI[0]:AOI[1]] 187 | 188 | timeGridE = SOG_E 189 | constE = 70 / np.power(timeGridE, 3) 190 | timeGridE80 = np.cbrt(80 / constE) 191 | timeGridE60 = np.cbrt(60 / constE) 192 | timeGridE = timeGridE[AOI[2]:AOI[3], AOI[0]:AOI[1]] 193 | timeGridE80 = timeGridE80[AOI[2]:AOI[3], AOI[0]:AOI[1]] 194 | timeGridE60 = timeGridE60[AOI[2]:AOI[3], AOI[0]:AOI[1]] 195 | timeGridE = np.where(timeGridE < 0, 10000, (kmGridEW * 1000) / (timeGridE * 30.87)) 196 | timeGridE80 = np.where(timeGridE80 < 0, 10000, (kmGridEW * 1000) / (timeGridE80 * 30.87)) 197 | timeGridE60 = np.where(timeGridE60 < 0, 10000, (kmGridEW * 1000) / (timeGridE60 * 30.87)) 198 | 199 | timeGridN = SOG_N 200 | constN = 70 / np.power(timeGridN, 3) 201 | timeGridN80 = np.cbrt(80 / constN) 202 | timeGridN60 = np.cbrt(60 / constN) 203 | timeGridN = timeGridN[AOI[2]:AOI[3], AOI[0]:AOI[1]] 204 | timeGridN80 = timeGridN80[AOI[2]:AOI[3], AOI[0]:AOI[1]] 205 | timeGridN60 = timeGridN60[AOI[2]:AOI[3], AOI[0]:AOI[1]] 206 | timeGridN = np.where(timeGridN < 0, 10000, (kmGridNS * 1000) / (timeGridN * 30.87)) 207 | timeGridN80 = np.where(timeGridN80 < 0, 10000, (kmGridNS * 1000) / (timeGridN80 * 30.87)) 208 | timeGridN60 = np.where(timeGridN60 < 0, 10000, (kmGridNS * 1000) / (timeGridN60 * 30.87)) 209 | 210 | timeGridS = SOG_S 211 | constS = 70 / np.power(timeGridS, 3) 212 | timeGridS80 = np.cbrt(80 / constS) 213 | timeGridS60 = np.cbrt(60 / constS) 214 | timeGridS = timeGridS[AOI[2]:AOI[3], AOI[0]:AOI[1]] 215 | timeGridS80 = timeGridS80[AOI[2]:AOI[3], AOI[0]:AOI[1]] 216 | timeGridS60 = timeGridS60[AOI[2]:AOI[3], AOI[0]:AOI[1]] 217 | timeGridS = np.where(timeGridS < 0, 10000, (kmGridNS * 1000) / (timeGridS * 30.87)) 218 | timeGridS80 = np.where(timeGridS80 < 0, 10000, (kmGridNS * 1000) / (timeGridS80 * 30.87)) 219 | timeGridS60 = np.where(timeGridS60 < 0, 10000, (kmGridNS * 1000) / (timeGridS60 * 30.87)) 220 | 221 | timeGridW = SOG_W 222 | constW = 70 / np.power(timeGridW, 3) 223 | timeGridW80 = np.cbrt(80 / constW) 224 | timeGridW60 = np.cbrt(60 / constW) 225 | timeGridW = timeGridW[AOI[2]:AOI[3], AOI[0]:AOI[1]] 226 | timeGridW80 = timeGridW80[AOI[2]:AOI[3], AOI[0]:AOI[1]] 227 | timeGridW60 = timeGridW60[AOI[2]:AOI[3], AOI[0]:AOI[1]] 228 | timeGridW = np.where(timeGridW < 0, 10000, (kmGridEW * 1000) / (timeGridW * 30.87)) 229 | timeGridW80 = np.where(timeGridW80 < 0, 10000, (kmGridEW * 1000) / (timeGridW80 * 30.87)) 230 | timeGridW60 = np.where(timeGridW60 < 0, 10000, (kmGridEW * 1000) / (timeGridW60 * 30.87)) 231 | 232 | timeGrids = [[timeGridN80, timeGridS80, timeGridE80, timeGridW80], [timeGridN, timeGridS, timeGridE, timeGridW], 233 | [timeGridN60, timeGridS60, timeGridE60, timeGridW60]] 234 | return timeGrids 235 | 236 | 237 | ''' 238 | # created masked array 239 | import numpy.ma as ma 240 | SOG_pred = np.ma.masked_where(np.flipud(np.ma.getmask(ds[parameter][0, :, :])), SOG_pred.reshape(dim)) 241 | SOG_pred.fill_value = -32767 242 | # SOG_pred =np.flipud(SOG_pred) 243 | ''' 244 | 245 | 246 | # # create actual grids for different ship directions 247 | # ship_param = 12 248 | # SOG_N = prepare_grid(model, ds, ds_p, ship_param, "N") 249 | # SOG_E = prepare_grid(model, ds, ds_p, ship_param, "E") 250 | # SOG_S = prepare_grid(model, ds, ds_p, ship_param, "S") 251 | # SOG_W = prepare_grid(model, ds, ds_p, ship_param, "W") 252 | 253 | # def cmems_paths(date): 254 | 255 | 256 | def get_cmems(date_start, date_end, UN_CMEMS, PW_CMEMS): 257 | date_s = datetime.strptime(date_start, "%d.%m.%Y %H:%M") 258 | date_e = datetime.strptime(date_end, "%d.%m.%Y %H:%M") 259 | 260 | date_m = date_s + (date_e - date_s) / 2 261 | date = date_m.strftime("%Y%m%d") 262 | today = datetime.now().strftime("%Y%m%d") 263 | 264 | path_date = date[0:4] + "/" + date[4:6] 265 | url = 'nrt.cmems-du.eu' 266 | path_w = 'Core/GLOBAL_ANALYSIS_FORECAST_WAV_001_027/global-analysis-forecast-wav-001-027/' + path_date 267 | path_p = 'Core/GLOBAL_ANALYSIS_FORECAST_PHY_001_024/global-analysis-forecast-phy-001-024/' + path_date 268 | 269 | with ftplib.FTP(url) as ftp: 270 | try: 271 | ftp.login(UN_CMEMS, PW_CMEMS) 272 | ftp.cwd(path_w) 273 | files = ftp.nlst() 274 | files = [i for i in files if date in i] 275 | filename_w = files[0] 276 | ftp.cwd('/') 277 | ftp.cwd(path_p) 278 | files = ftp.nlst() 279 | files = [i for i in files if date in i] 280 | filename_p = files[0] 281 | except ftplib.all_errors as e: 282 | print('FTP error:', e) 283 | 284 | download(url, UN_CMEMS, PW_CMEMS, path_w, filename_w) 285 | download(url, UN_CMEMS, PW_CMEMS, path_p, filename_p) 286 | 287 | ds_w = nc.Dataset(filename_w) 288 | ds_p = nc.Dataset(filename_p) 289 | return (ds_w, ds_p) 290 | 291 | 292 | """" 293 | # set CMEMS credentials 294 | UN_CMEMS = "jstenkamp" 295 | PW_CMEMS = "" 296 | 297 | # cmems wave data download 298 | url = 'nrt.cmems-du.eu' 299 | path = 'Core/GLOBAL_ANALYSIS_FORECAST_WAV_001_027/global-analysis-forecast-wav-001-027/2021/07' 300 | filename = 'mfwamglocep_2021070200_R20210703.nc' 301 | download(url, UN_CMEMS, PW_CMEMS, path, filename) 302 | 303 | # cmems physics download 304 | url = 'nrt.cmems-du.eu' 305 | path = 'Core/GLOBAL_ANALYSIS_FORECAST_PHY_001_024/global-analysis-forecast-phy-001-024/2021/07' 306 | filename_p = 'mercatorpsy4v3r1_gl12_mean_20210702_R20210703.nc' 307 | download(url, UN_CMEMS, PW_CMEMS, path, filename_p) 308 | 309 | 310 | 311 | # load files as netcdf dataset 312 | ds = nc.Dataset(filename) 313 | ds_p = nc.Dataset(filename_p) 314 | # ds 315 | """ 316 | -------------------------------------------------------------------------------- /advanced_scripts/runAlgorithm.py: -------------------------------------------------------------------------------- 1 | from pymoo import factory 2 | from pymoo.model.crossover import Crossover 3 | import spatial_extention_pymoo 4 | # add spatial functions to pymoo library 5 | factory.get_sampling_options = spatial_extention_pymoo._new_get_sampling_options 6 | factory.get_crossover_options = spatial_extention_pymoo._new_get_crossover_options 7 | factory.get_mutation_options = spatial_extention_pymoo._new_get_mutation_options 8 | Crossover.do = spatial_extention_pymoo._new_crossover_do 9 | 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | from matplotlib.colors import ListedColormap 13 | from pymoo.util.misc import stack 14 | from pymoo.model.problem import Problem 15 | 16 | from pymoo.algorithms.nsga2 import NSGA2 17 | from pymoo.factory import get_sampling, get_crossover, get_mutation 18 | from pymoo.factory import get_termination 19 | from pymoo.optimize import minimize 20 | from pymoo.model.problem import Problem 21 | 22 | 23 | from pymoo.optimize import minimize 24 | 25 | from calculate_objectives import calculate_time_differences 26 | from calculate_objectives import calculate_fuelUse 27 | from calculate_objectives import calculate_MinDistance 28 | 29 | 30 | import random 31 | 32 | 33 | def runAlgorithm(startpoint, endpoint, startTime, endTime, timeGrids, population, offspring, generations ): 34 | """ 35 | run the algorithm and return the result as a pymoo result object https://pymoo.org/interface/result.html 36 | 37 | Parameters 38 | ---------- 39 | startpoint: array, [cellX, cellY] 40 | the startpoint of the route 41 | endpoint: array, [cellX, cellY] 42 | the endpoint of the route 43 | startTime: str, day.month.year hours:minutes 44 | start time of the ship 45 | endTime: str, day.month.year hours:minutes 46 | booked port time, the ship shlould arrive 47 | population: int 48 | the wanted population, number of routes that will be returned 49 | offspirng: int 50 | number of new routes calculated with each iteration 51 | generations: int 52 | number of iterations 53 | """ 54 | timeGrid=timeGrids[0][0] 55 | 56 | class MyProblem(Problem): 57 | 58 | # define the number of variables etc. 59 | def __init__(self): 60 | super().__init__(n_var=2, # nr of variables 61 | n_obj=2, # nr of objectives 62 | n_constr=0, # nr of constraints 63 | xl=0.0, # lower boundaries 64 | xu=1.0) # upper boundaries 65 | 66 | # define the objective functions 67 | def _evaluate(self, X, out, *args, **kwargs): 68 | f1 = calculate_time_differences(X[:], startTime, endTime, timeGrids) 69 | f2 = calculate_fuelUse(X[:], timeGrids) 70 | out["F"] = np.column_stack([f1, f2]) 71 | 72 | problem = MyProblem() 73 | print(problem) 74 | # load own sampling, crossover and mutation method 75 | algorithm = NSGA2( 76 | pop_size=population, 77 | n_offsprings= offspring, 78 | sampling=get_sampling("spatial", startpoint= startpoint, endpoint=endpoint, timeGrid = timeGrid), 79 | crossover=get_crossover("spatial_one_point_crossover", timeGrid = timeGrid, n_points = 1.0), 80 | mutation=get_mutation("spatial_n_point_mutation", timeGrid=timeGrid, prob = 1.0), 81 | eliminate_duplicates=False 82 | ) 83 | termination = get_termination("n_gen", generations) 84 | 85 | # run algorithm itself 86 | res = minimize(problem, 87 | algorithm, 88 | termination, 89 | seed=1, 90 | save_history=True, 91 | verbose=True) 92 | return(res) 93 | -------------------------------------------------------------------------------- /advanced_scripts/spatial_crossover.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from skimage.graph import route_through_array 3 | import random 4 | import math 5 | 6 | from pymoo.model.crossover import Crossover 7 | 8 | 9 | 10 | speeds=[0.8, 0.7, 0.6] 11 | 12 | def makeArrays(route): 13 | """ 14 | the route returned by the muation is a array of tuples. It needs to be an array 15 | 16 | Parameters 17 | ---------- 18 | route : array 19 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 20 | """ 21 | routeNew = [] 22 | for x in route: 23 | routeNew.append(list(x)) 24 | return routeNew 25 | 26 | 27 | def closest_node(node, nodes): 28 | """ 29 | find the closest point to this point 30 | 31 | Parameters 32 | ---------- 33 | node : array 34 | one point where the nearest should be found 35 | nodes : array of node 36 | points where the nearest should be found from 37 | """ 38 | nodes = np.asarray(nodes) 39 | dist_2 = np.sum((nodes - node)**2, axis=1) 40 | return np.argmin(dist_2) 41 | 42 | def findDuplicate(node, nodes, index): 43 | """ 44 | proof if the ship goes through one point twice 45 | 46 | Parameters 47 | ---------- 48 | node : array 49 | one point which should be proofed 50 | nodes : array of node 51 | points which should be compared with node 52 | index : int 53 | number where to start in nodes 54 | """ 55 | nodes= nodes[index: len(nodes)] 56 | nodes = np.asarray(nodes) 57 | dist_2 = np.sum((nodes - node)**2, axis=1) 58 | return np.where(dist_2 == 0) 59 | 60 | def eleminateDuplicates(start, route): 61 | """ 62 | eleminate points, where the ship goes through twice 63 | 64 | Parameters 65 | ---------- 66 | start : int 67 | index where to start on the route 68 | route : array 69 | route which shcould be proofed on duplicates 70 | """ 71 | for i in range(start, len(route)): 72 | duplicate=findDuplicate(route[i], route, i) 73 | duplicate=duplicate[0] 74 | if len(duplicate) > 1: 75 | newRoute=route[:i] + route[i+duplicate[1]:] 76 | return eleminateDuplicates(i, newRoute) 77 | return route 78 | 79 | def crossover(route1, route2, timeGrid): 80 | """ 81 | combines two routes of the population to two new routes 82 | 83 | Parameters 84 | ---------- 85 | route1 : array 86 | first route which should be used 87 | route2 : array 88 | second route which schould be used 89 | timeGrid: array 90 | a grid containing the time for one specific context of bearing and speed 91 | """ 92 | timeGridNew= [[random.random() for i in range(timeGrid.shape[1])] for j in range(timeGrid.shape[0])] 93 | timeGridNew = np.where(timeGrid >999, timeGrid, timeGridNew) 94 | 95 | index1 = math.floor(random.random()*min(len(route1), len(route2))) 96 | index2 = min(len(route2) -1, len(route1)-1, (index1 + math.floor(random.random()*(len(route1) -index1)+ 10))) 97 | 98 | crossoverPoint1 = route1[index1] 99 | 100 | crossoverPoint2 = route2[index2] 101 | 102 | 103 | crossoverRoute1, weight = route_through_array(timeGridNew, crossoverPoint1[0:2], crossoverPoint2[0:2], fully_connected=False, geometric=True) 104 | crossoverRoute1= makeArrays(crossoverRoute1) 105 | 106 | 107 | crossoverPoint1 = route2[index1] 108 | #index= closest_node(crossoverPoint1, route2) 109 | 110 | crossoverPoint2 = route1[index2] 111 | crossoverRoute2, weight = route_through_array(timeGridNew, crossoverPoint1[0:2], crossoverPoint2[0:2], fully_connected=False, geometric=True) 112 | crossoverRoute2= makeArrays(crossoverRoute2) 113 | 114 | speed= speeds[math.floor(random.random()*3)] 115 | for j in range(len(crossoverRoute1)): 116 | crossoverRoute1[j].append(speed) 117 | 118 | for i in range(len(crossoverRoute2)): 119 | crossoverRoute2[i].append(speed) 120 | 121 | child1= [] 122 | child2= [] 123 | for i in range(index1-1): 124 | child1.append(route1[i]) 125 | child2.append(route2[i]) 126 | for x in crossoverRoute1: 127 | child1.append(x) 128 | for x in crossoverRoute2: 129 | child2.append(x) 130 | for i in range(index2 +1,len(route2)): 131 | child1.append(route2[i]) 132 | for i in range(index2 +1,len(route1)): 133 | child2.append(route1[i]) 134 | return[child1, child2, crossoverRoute1] 135 | 136 | 137 | class SpatialOnePointCrossover(Crossover): 138 | """ 139 | class to conduct the crossover 140 | 141 | """ 142 | 143 | 144 | 145 | def __init__(self, timeGrid, n_points, **kwargs): 146 | super().__init__(2, 2, 1.0) # (n_parents,n_offsprings,probability) 147 | self.n_points = n_points 148 | self.TimeGrid = timeGrid 149 | def _do(self, problem, X, **kwargs): 150 | 151 | _, n_matings= X.shape[0],X.shape[1] 152 | 153 | child_1 = [] 154 | child_2 = [] 155 | parent1_index = X[0][0][0] 156 | parent2_index = X[1][0][0] 157 | parent1 = X[0][0][1] 158 | parent2 = X[1][0][1] 159 | 160 | 161 | childs=crossover(parent1,parent2, self.TimeGrid) 162 | child_1= eleminateDuplicates(0, childs[0]) 163 | child_2= eleminateDuplicates(0, childs[1]) 164 | 165 | return np.array([[parent1_index, list(childs[0])], [parent2_index, list(childs[1])]]) -------------------------------------------------------------------------------- /advanced_scripts/spatial_extention_pymoo.py: -------------------------------------------------------------------------------- 1 | 2 | def _new_get_sampling_options(): 3 | from pymoo.operators.sampling.latin_hypercube_sampling import LatinHypercubeSampling 4 | from pymoo.operators.sampling.random_sampling import FloatRandomSampling 5 | from pymoo.operators.integer_from_float_operator import IntegerFromFloatSampling 6 | from pymoo.operators.sampling.random_sampling import BinaryRandomSampling 7 | from pymoo.operators.sampling.random_permutation_sampling import PermutationRandomSampling 8 | from spatial_sampling import SpatialSampling 9 | 10 | SAMPLING = [ 11 | ("real_random", FloatRandomSampling), 12 | ("real_lhs", LatinHypercubeSampling), 13 | ("bin_random", BinaryRandomSampling), 14 | ("int_random", IntegerFromFloatSampling, {'clazz': FloatRandomSampling}), 15 | ("int_lhs", IntegerFromFloatSampling, {'clazz': LatinHypercubeSampling}), 16 | ("perm_random", PermutationRandomSampling, dict(default_dir = None)), 17 | ("spatial", SpatialSampling) 18 | ] 19 | 20 | return SAMPLING 21 | 22 | def _new_get_crossover_options(): 23 | from pymoo.operators.crossover.differental_evolution_crossover import DifferentialEvolutionCrossover 24 | from pymoo.operators.crossover.exponential_crossover import ExponentialCrossover 25 | from pymoo.operators.crossover.half_uniform_crossover import HalfUniformCrossover 26 | from pymoo.operators.crossover.point_crossover import PointCrossover 27 | from pymoo.operators.crossover.simulated_binary_crossover import SimulatedBinaryCrossover 28 | from pymoo.operators.crossover.uniform_crossover import UniformCrossover 29 | from pymoo.operators.integer_from_float_operator import IntegerFromFloatCrossover 30 | from pymoo.operators.crossover.edge_recombination_crossover import EdgeRecombinationCrossover 31 | from pymoo.operators.crossover.order_crossover import OrderCrossover 32 | from spatial_crossover import SpatialOnePointCrossover 33 | #from spatial_crossover_constrained import SpatialOnePointCrossover 34 | CROSSOVER = [ 35 | ("real_sbx", SimulatedBinaryCrossover, dict(prob=0.9, eta=30)), 36 | ("int_sbx", IntegerFromFloatCrossover, dict(clazz=SimulatedBinaryCrossover, prob=0.9, eta=30)), 37 | ("real_de", DifferentialEvolutionCrossover), 38 | ("(real|bin|int)_ux", UniformCrossover), 39 | ("(bin|int)_hux", HalfUniformCrossover), 40 | ("(real|bin|int)_exp", ExponentialCrossover), 41 | ("(real|bin|int)_one_point", PointCrossover, {'n_points': 1}), 42 | ("(real|bin|int)_two_point", PointCrossover, {'n_points': 2}), 43 | ("(real|bin|int)_k_point", PointCrossover), 44 | ("perm_ox", OrderCrossover), 45 | ("perm_erx", EdgeRecombinationCrossover), 46 | ("spatial_one_point_crossover", SpatialOnePointCrossover) 47 | ] 48 | return CROSSOVER 49 | 50 | def _new_get_mutation_options(): 51 | from pymoo.operators.mutation.no_mutation import NoMutation 52 | from pymoo.operators.mutation.bitflip_mutation import BinaryBitflipMutation 53 | from pymoo.operators.mutation.polynomial_mutation import PolynomialMutation 54 | from pymoo.operators.integer_from_float_operator import IntegerFromFloatMutation 55 | from pymoo.operators.mutation.inversion_mutation import InversionMutation 56 | from spatial_mutation import SpatialNPointMutation 57 | 58 | MUTATION = [ 59 | ("none", NoMutation, {}), 60 | ("real_pm", PolynomialMutation, dict(eta=20)), 61 | ("int_pm", IntegerFromFloatMutation, dict(clazz=PolynomialMutation, eta=20)), 62 | ("bin_bitflip", BinaryBitflipMutation), 63 | ("perm_inv", InversionMutation), 64 | ("spatial_n_point_mutation", SpatialNPointMutation, dict(point_mutation_probability = 0.01)) 65 | ] 66 | 67 | return MUTATION 68 | 69 | import numpy as np 70 | from pymoo.model.population import Population 71 | 72 | offspring=2 73 | 74 | def _new_crossover_do(self, problem, pop, parents, **kwargs): 75 | """ 76 | 77 | This method executes the crossover on the parents. This class wraps the implementation of the class 78 | that implements the crossover. 79 | 80 | Parameters 81 | ---------- 82 | problem: class 83 | The problem to be solved. Provides information such as lower and upper bounds or feasibility 84 | conditions for custom crossovers. 85 | 86 | pop : Population 87 | The population as an object 88 | 89 | parents: numpy.array 90 | The select parents of the population for the crossover 91 | 92 | kwargs : dict 93 | Any additional data that might be necessary to perform the crossover. E.g. constants of an algorithm. 94 | 95 | Returns 96 | ------- 97 | offsprings : Population 98 | The off as a matrix. n_children rows and the number of columns is equal to the variable 99 | length of the problem. 100 | 101 | """ 102 | 103 | if self.n_parents != parents.shape[1]: 104 | raise ValueError('Exception during crossover: Number of parents differs from defined at crossover.') 105 | 106 | # get the design space matrix form the population and parents 107 | X = pop.get("X")[parents.T].copy() 108 | 109 | # now apply the crossover probability 110 | do_crossover = np.random.random(len(parents)) < self.prob 111 | 112 | # execute the crossover 113 | _X = self._do(problem, X, **kwargs) 114 | 115 | X = _X 116 | 117 | off = Population.new("X", X) 118 | 119 | return off 120 | -------------------------------------------------------------------------------- /advanced_scripts/spatial_mutation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from pymoo.model.mutation import Mutation 3 | import random 4 | import math 5 | from skimage.graph import route_through_array 6 | 7 | speeds=[0.8, 0.7, 0.6] 8 | 9 | 10 | def makeArrays(route): 11 | """ 12 | the route returned by the muation is a array of tuples. It needs to be an array 13 | 14 | Parameters 15 | ---------- 16 | route : array 17 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 18 | """ 19 | routeNew = [] 20 | for x in route: 21 | routeNew.append(list(x)) 22 | return routeNew 23 | 24 | 25 | 26 | # function to randomly change a certain patch 27 | 28 | def mutation(crossover_child, timeGrid): 29 | """ 30 | mutates one route, so changes a random part of it 31 | 32 | Parameters 33 | ---------- 34 | crossover_child : array 35 | one route the ship is going, returned by the crossover 36 | """ 37 | timeGridNew= [[random.random() for i in range(timeGrid.shape[1])] for j in range(timeGrid.shape[0])] 38 | timeGridNew = np.where(timeGrid >999, timeGrid, timeGridNew) 39 | crossover_child_split_list= [] 40 | 41 | crossover_child = makeArrays(crossover_child) 42 | #split crossover over child into 3 lists 43 | randomNumber = random.random()*len(crossover_child) 44 | startIndex= math.floor(randomNumber) 45 | startpoint = crossover_child[startIndex] 46 | endIndex= math.floor(startIndex + (random.random() * (len(crossover_child)-startIndex))) 47 | endpoint= crossover_child[endIndex] 48 | 49 | # recalculate route from end point of list 1 to sarting point of list 3 50 | route, weight = route_through_array(timeGridNew, startpoint[0:2], endpoint[0:2], 51 | fully_connected=False, geometric=True) 52 | route_list = makeArrays(route) 53 | speed= speeds[math.floor(random.random()*3)] 54 | for j in range(len(route_list)): 55 | route_list[j].append(speed) 56 | first_component = makeArrays(crossover_child[0:startIndex]) 57 | second_component = route_list[0:-1]#filter out starting point and endpoint from the new mid list 58 | third_component = makeArrays(crossover_child[endIndex:len(crossover_child)]) 59 | #combine all the sections to a final mutated route 60 | mutated_child = first_component + second_component + third_component 61 | #print(mutated_child) 62 | 63 | return mutated_child 64 | 65 | 66 | # class that performs the mutation 67 | class SpatialNPointMutation(Mutation): 68 | def __init__(self, timeGrid, prob=None,point_mutation_probability=0.01): 69 | super().__init__() 70 | self.prob = prob 71 | self.point_mutation_probability = point_mutation_probability 72 | self.TimeGrid= timeGrid 73 | def _do(self, problem, X, **kwargs): 74 | offspring=[] 75 | #print(X) 76 | 77 | # loop over individuals in population 78 | for i in X: 79 | # performe mutation with certain probability 80 | if np.random.uniform(0, 1) < self.prob: 81 | mutated_individual = mutation(i[1], self.TimeGrid) 82 | offspring.append([i[0], mutated_individual]) 83 | # if no mutation 84 | else: 85 | offspring.append([i[0], i]) 86 | offspring = np.array(offspring) 87 | return offspring -------------------------------------------------------------------------------- /advanced_scripts/spatial_sampling.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from pymoo.model.sampling import Sampling 3 | import initial_population 4 | 5 | 6 | 7 | class SpatialSampling(Sampling): 8 | def __init__(self, startpoint, endpoint, timeGrid, var_type=np.float, default_dir=None) -> None: 9 | super().__init__() 10 | self.var_type = var_type 11 | self.default_dir = default_dir 12 | self.startpoint = startpoint 13 | self.endpoint = endpoint 14 | self.timeGrid = timeGrid 15 | def _do(self, problem, n_samples, **kwargs): 16 | routes = initial_population.initialize_spatial(n_samples, self.startpoint, self.endpoint, self.timeGrid) 17 | return routes 18 | 19 | 20 | -------------------------------------------------------------------------------- /models/DTR_model.joblib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsten07/cms_routing/6dd1b14b849e03d72c7a857a2ece60dd19af31d5/models/DTR_model.joblib -------------------------------------------------------------------------------- /preliminary_work/calculate_lengthGrid.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "name": "calculate_lengthGrid.ipynb", 7 | "provenance": [] 8 | }, 9 | "kernelspec": { 10 | "name": "python3", 11 | "display_name": "Python 3" 12 | }, 13 | "language_info": { 14 | "name": "python" 15 | } 16 | }, 17 | "cells": [ 18 | { 19 | "cell_type": "code", 20 | "metadata": { 21 | "id": "-VvDvbniw-e3" 22 | }, 23 | "source": [ 24 | "import numpy as np\n", 25 | "\n", 26 | "arr = np.zeros([4, 3])" 27 | ], 28 | "execution_count": null, 29 | "outputs": [] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "metadata": { 34 | "colab": { 35 | "base_uri": "https://localhost:8080/" 36 | }, 37 | "id": "aM01OjlNxTix", 38 | "outputId": "c502e7ef-8eed-46a6-ac43-51424c9c6def" 39 | }, 40 | "source": [ 41 | "arr+" 42 | ], 43 | "execution_count": null, 44 | "outputs": [ 45 | { 46 | "output_type": "execute_result", 47 | "data": { 48 | "text/plain": [ 49 | "array([[0., 0., 0.],\n", 50 | " [0., 0., 0.],\n", 51 | " [0., 0., 0.],\n", 52 | " [0., 0., 0.]])" 53 | ] 54 | }, 55 | "metadata": { 56 | "tags": [] 57 | }, 58 | "execution_count": 4 59 | } 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "metadata": { 65 | "colab": { 66 | "base_uri": "https://localhost:8080/" 67 | }, 68 | "id": "B55WHRI1_NPI", 69 | "outputId": "6f62a051-2afd-473e-bd20-39f1e04e0b51" 70 | }, 71 | "source": [ 72 | "grid= np.load(\"SOG_W.npy\")\n", 73 | "print(grid)" 74 | ], 75 | "execution_count": null, 76 | "outputs": [ 77 | { 78 | "output_type": "stream", 79 | "text": [ 80 | "[[-5. -5. -5. ... -5. -5. -5.]\n", 81 | " [-5. -5. -5. ... -5. -5. -5.]\n", 82 | " [-5. -5. -5. ... -5. -5. -5.]\n", 83 | " ...\n", 84 | " [-5. -5. -5. ... -5. -5. -5.]\n", 85 | " [-5. -5. -5. ... -5. -5. -5.]\n", 86 | " [-5. -5. -5. ... -5. -5. -5.]]\n" 87 | ], 88 | "name": "stdout" 89 | } 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "metadata": { 95 | "colab": { 96 | "base_uri": "https://localhost:8080/" 97 | }, 98 | "id": "9HBTiiNj_cro", 99 | "outputId": "acd2bd11-477b-42c5-c900-480745ee639d" 100 | }, 101 | "source": [ 102 | "grid.shape" 103 | ], 104 | "execution_count": null, 105 | "outputs": [ 106 | { 107 | "output_type": "execute_result", 108 | "data": { 109 | "text/plain": [ 110 | "(2041, 4320)" 111 | ] 112 | }, 113 | "metadata": { 114 | "tags": [] 115 | }, 116 | "execution_count": 7 117 | } 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "metadata": { 123 | "id": "95TcVqUrAIR6" 124 | }, 125 | "source": [ 126 | "arr = np.zeros(grid.shape)" 127 | ], 128 | "execution_count": null, 129 | "outputs": [] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "metadata": { 134 | "colab": { 135 | "base_uri": "https://localhost:8080/" 136 | }, 137 | "id": "Y0fqCXjdALyC", 138 | "outputId": "4dc746d7-1169-4a45-e8ea-941e4a97ad6c" 139 | }, 140 | "source": [ 141 | "\n", 142 | "km=[110.71,109.45 ,107.35,104.43,100.72, 96.2, 91.04, 85.13, 78.58, 71.44, 63.74 ,55.57,\t46.97,38.01, 28.76, 19.30, 9.69]\n", 143 | "\n", 144 | "for i in range(len(km)):\n", 145 | " print(i)\n", 146 | " for x in range(60*i,60*(i+1)):\n", 147 | " for y in range(4320):\n", 148 | " arr[x][y] = km[i]*0.083\n", 149 | "\n", 150 | " for x in range(2040 - 60*(i+1),2040 - 60*i):\n", 151 | " for y in range(4320):\n", 152 | " arr[x][y] = km[i]*0.083" 153 | ], 154 | "execution_count": null, 155 | "outputs": [ 156 | { 157 | "output_type": "stream", 158 | "text": [ 159 | "0\n", 160 | "1\n", 161 | "2\n", 162 | "3\n", 163 | "4\n", 164 | "5\n", 165 | "6\n", 166 | "7\n", 167 | "8\n", 168 | "9\n", 169 | "10\n", 170 | "11\n", 171 | "12\n", 172 | "13\n", 173 | "14\n", 174 | "15\n", 175 | "16\n" 176 | ], 177 | "name": "stdout" 178 | } 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "metadata": { 184 | "id": "OL3e6h0gCy2K" 185 | }, 186 | "source": [ 187 | "for y in range(4320):\n", 188 | " arr[2040][y] = km[i]" 189 | ], 190 | "execution_count": null, 191 | "outputs": [] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "metadata": { 196 | "colab": { 197 | "base_uri": "https://localhost:8080/", 198 | "height": 227 199 | }, 200 | "id": "90Ag5992DSld", 201 | "outputId": "17b4bfb8-5616-473f-a98d-a091f001f546" 202 | }, 203 | "source": [ 204 | "import matplotlib.pyplot as plt\n", 205 | "\n", 206 | "plt.imshow(arr)" 207 | ], 208 | "execution_count": null, 209 | "outputs": [ 210 | { 211 | "output_type": "execute_result", 212 | "data": { 213 | "text/plain": [ 214 | "" 215 | ] 216 | }, 217 | "metadata": { 218 | "tags": [] 219 | }, 220 | "execution_count": 33 221 | }, 222 | { 223 | "output_type": "display_data", 224 | "data": { 225 | "image/png": "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\n", 226 | "text/plain": [ 227 | "
" 228 | ] 229 | }, 230 | "metadata": { 231 | "tags": [], 232 | "needs_background": "light" 233 | } 234 | } 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "metadata": { 240 | "id": "nTCYrrJ2dSI4" 241 | }, 242 | "source": [ 243 | "np.save(\"lengthGridEW.npy\", arr)" 244 | ], 245 | "execution_count": null, 246 | "outputs": [] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "metadata": { 251 | "id": "nFG-DV89-lYZ" 252 | }, 253 | "source": [ 254 | "Resources: \n", 255 | "http://www.iaktueller.de/exx.php\n", 256 | "https://de.wikipedia.org/wiki/Geographische_Breite" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "metadata": { 262 | "id": "icdE7ysb7bhm" 263 | }, 264 | "source": [ 265 | "arr2 = np.zeros(grid.shape)\n", 266 | "\n", 267 | "for x in range(2041):\n", 268 | " for y in range(4320):\n", 269 | " arr2[x][y] = 110*0.083\n" 270 | ], 271 | "execution_count": null, 272 | "outputs": [] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "metadata": { 277 | "colab": { 278 | "base_uri": "https://localhost:8080/", 279 | "height": 227 280 | }, 281 | "id": "dseeYD7uN7lW", 282 | "outputId": "885922ea-69e9-45a7-c595-013b74f9b494" 283 | }, 284 | "source": [ 285 | "import matplotlib.pyplot as plt\n", 286 | "\n", 287 | "plt.imshow(arr2)" 288 | ], 289 | "execution_count": null, 290 | "outputs": [ 291 | { 292 | "output_type": "execute_result", 293 | "data": { 294 | "text/plain": [ 295 | "" 296 | ] 297 | }, 298 | "metadata": { 299 | "tags": [] 300 | }, 301 | "execution_count": 7 302 | }, 303 | { 304 | "output_type": "display_data", 305 | "data": { 306 | "image/png": "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\n", 307 | "text/plain": [ 308 | "
" 309 | ] 310 | }, 311 | "metadata": { 312 | "tags": [], 313 | "needs_background": "light" 314 | } 315 | } 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "metadata": { 321 | "colab": { 322 | "base_uri": "https://localhost:8080/" 323 | }, 324 | "id": "zJR0ANjEOSw8", 325 | "outputId": "cff54a65-5c0c-4cc7-d789-1c4a340625de" 326 | }, 327 | "source": [ 328 | "print(arr2)" 329 | ], 330 | "execution_count": null, 331 | "outputs": [ 332 | { 333 | "output_type": "stream", 334 | "text": [ 335 | "[[9.13 9.13 9.13 ... 9.13 9.13 9.13]\n", 336 | " [9.13 9.13 9.13 ... 9.13 9.13 9.13]\n", 337 | " [9.13 9.13 9.13 ... 9.13 9.13 9.13]\n", 338 | " ...\n", 339 | " [9.13 9.13 9.13 ... 9.13 9.13 9.13]\n", 340 | " [9.13 9.13 9.13 ... 9.13 9.13 9.13]\n", 341 | " [0. 0. 0. ... 0. 0. 0. ]]\n" 342 | ], 343 | "name": "stdout" 344 | } 345 | ] 346 | }, 347 | { 348 | "cell_type": "code", 349 | "metadata": { 350 | "id": "cdG1X806-ksn" 351 | }, 352 | "source": [ 353 | "np.save(\"lengthGridNS.npy\", arr2)" 354 | ], 355 | "execution_count": null, 356 | "outputs": [] 357 | }, 358 | { 359 | "cell_type": "markdown", 360 | "metadata": { 361 | "id": "am45jbWcBf8a" 362 | }, 363 | "source": [ 364 | "1 5\n", 365 | "2 10\n", 366 | "3 15\n", 367 | "4 20\n", 368 | "5 25\n", 369 | "6 30\n", 370 | "7 35\n", 371 | "8 40\n", 372 | "9 45\n", 373 | "10 50\n", 374 | "11 55\n", 375 | "12 60\n", 376 | "13 65\n", 377 | "14 70\n", 378 | "15 75\n", 379 | "16 80\n", 380 | "17 85\n", 381 | "19 90\n" 382 | ] 383 | } 384 | ] 385 | } -------------------------------------------------------------------------------- /scripts/calculate_objectives.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pickle 3 | from datetime import datetime, timedelta 4 | 5 | def calculateBearing(route): 6 | """ 7 | calculates the bearing for each grid cell 8 | 9 | Parameters 10 | ---------- 11 | route : array 12 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 13 | """ 14 | for i in range(len(route)-1): 15 | if route[i][0]< route[i+1][0]: 16 | route[i].append(0) #up 17 | elif route[i][0] > route[i+1][0]: 18 | route[i].append(1) # down 19 | elif route[i][1] < route[i+1][1]: 20 | route[i].append(2) #right 21 | elif route[i][1] > route[i+1][1]: 22 | route[i].append(3) #left 23 | else: 24 | route[i].append("error") 25 | return route 26 | 27 | def calculateTime(route, timeGrids): 28 | """ 29 | calculate the time needed to go the specific route based on the time grids # 30 | 31 | Parameters 32 | ---------- 33 | route : array 34 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 35 | timeGrids : array 36 | arrays of the time needed for the grid cell 37 | the first dimension the direction of the gird [N, S, E, W]. 38 | The second and third dimension is then the grid for this combination of speed and bearing. 39 | """ 40 | sumTime = 0 41 | routeList = makeArrays(route) 42 | routeWithBearing = calculateBearing(routeList) 43 | for x in range(len(routeWithBearing)-1): 44 | coordinate = routeWithBearing[x] 45 | bearing = coordinate[2] 46 | timeGrid = timeGrids[bearing] 47 | sumTime = sumTime + timeGrid[coordinate[0]][coordinate[1]] 48 | hours_added = timedelta(minutes = sumTime) 49 | return hours_added 50 | 51 | def calculateFuelUse(route, timeGrids): 52 | """ 53 | calculate the fuel use needed to go the specific route based on the time grids 54 | 55 | Parameters 56 | ---------- 57 | route : array 58 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 59 | timeGrids : array 60 | arrays of the time needed for the grid cell 61 | the first dimension the direction of the gird [N, S, E, W]. 62 | The second and third dimension is then the grid for this combination of speed and bearing. 63 | """ 64 | fuelUse = 0 65 | routeList = makeArrays(route) 66 | routeWithBearing = calculateBearing(routeList) 67 | for x in range(len(routeWithBearing)-1): 68 | coordinate = routeWithBearing[x] 69 | bearing = coordinate[2] 70 | timeGrid = timeGrids[bearing] 71 | fuelUse = fuelUse + (timeGrid[coordinate[0]][coordinate[1]]/60 * 154*33200) 72 | return fuelUse 73 | 74 | def makeArrays(route): 75 | """ 76 | the route returned by the muation is a array of tuples. It needs to be an array 77 | 78 | Parameters 79 | ---------- 80 | route : array 81 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 82 | """ 83 | routeNew = [] 84 | for x in route: 85 | routeNew.append(list(x)) 86 | return routeNew 87 | 88 | def calculate_time_differences(routes, startTime, endTime, timeGrids): 89 | """ 90 | functions calculates the time differences depending on the start and end time for each route in the population 91 | 92 | Parameters 93 | ---------- 94 | routes : array 95 | array of all routes that are in the population 96 | startTime : str, day.month.year hours:minutes 97 | start time of the ship 98 | endTime : str, day.month.year hours:minutes 99 | booked port time, the ship shlould arrive 100 | timeGrids : array 101 | arrays of the time needed for the grid cell 102 | the first dimension the direction of the gird [N, S, E, W]. 103 | The second and third dimension is then the grid for this combination of speed and bearing. 104 | """ 105 | 106 | timeDif= [] 107 | # loop over the individuals in the population 108 | for route in routes: 109 | startTime_object = datetime.strptime(startTime, "%d.%m.%Y %H:%M" ) 110 | endTime_object = datetime.strptime(endTime, "%d.%m.%Y %H:%M" ) 111 | duration = calculateTime(route[1], timeGrids) 112 | eta = startTime_object + duration 113 | difference= endTime_object-eta 114 | total_seconds = difference.total_seconds() 115 | minutes = total_seconds/60 116 | timeDif.append(float(minutes) ** 2) 117 | return(np.array(timeDif)) 118 | 119 | 120 | 121 | def calculate_fuelUse(routes,timeGrids): 122 | """ 123 | functions calculates the fuel use for each route in the population 124 | 125 | Parameters 126 | ---------- 127 | routes : array 128 | array of all routes that are in the population 129 | timeGrids : array 130 | arrays of the time needed for the grid cell 131 | the first dimension the direction of the gird [N, S, E, W]. 132 | The second and third dimension is then the grid for this combination of speed and bearing. 133 | 134 | """ 135 | # loop over the individuals in the population 136 | all_fuel = [] 137 | for route in routes: 138 | fuelUse = calculateFuelUse(route[1], timeGrids) 139 | fuelUseT = fuelUse/1000000 140 | all_fuel.append(fuelUseT) 141 | 142 | return(np.array(all_fuel)) 143 | 144 | def calculate_MinDistance(routes,Grids): 145 | """ 146 | functions calculates the difference in km for each route in the population 147 | 148 | Parameters 149 | ---------- 150 | routes : array 151 | array of all routes that are in the population 152 | timeGrids : array 153 | arrays of the time needed for the grid cell, the first dimension are the three different speeds [0.8, 0.7, 0.6] 154 | the second dimension the direction of the gird [N, S, E, W]. 155 | The third and fourt dimension is then the grid for this combination of speed and bearing. 156 | 157 | """ 158 | 159 | all_KM = [] 160 | for route in routes: 161 | 162 | sumKM = 0 163 | routeList = makeArrays(route[1]) 164 | routeWithBearing = calculateBearing(routeList) 165 | for x in range(len(routeWithBearing)-1): 166 | coordinate = routeWithBearing[x] 167 | 168 | bearing = coordinate[3] 169 | timeGrid = Grids[bearing] 170 | sumKM = sumKM + timeGrid[coordinate[0]][coordinate[1]] 171 | all_KM.append(sumKM) 172 | 173 | return all_KM 174 | 175 | -------------------------------------------------------------------------------- /scripts/initial_population.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | import math 4 | import matplotlib.pyplot as plt 5 | from matplotlib.colors import ListedColormap 6 | import yaml 7 | from skimage.graph import route_through_array 8 | 9 | def makeArrays(route): 10 | """ 11 | the route returned by the muation is a array of tuples. It needs to be an array 12 | 13 | Parameters 14 | ---------- 15 | route : array 16 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 17 | """ 18 | routeNew = [] 19 | for x in route: 20 | routeNew.append(list(x)) 21 | return routeNew 22 | 23 | 24 | 25 | # make initial population for genetic algorithm 26 | def initialize_spatial(pop_size, startpoint, endpoint, timeGrid): 27 | """ 28 | creates a initial population by making a random configuration of routes 29 | 30 | Parameters 31 | ---------- 32 | pop_size : int 33 | the wanted population size 34 | startpoint: array, [cellX, cellY] 35 | the startpoint of the route 36 | endpoint: array, [cellX, cellY] 37 | the endpoint of the route 38 | timeGrid: array 39 | a grid containing the time for one specific context of bearing and speed 40 | 41 | """ 42 | all_routes = [] 43 | #calulate one route with the example time grid 44 | route, weight = route_through_array(timeGrid, startpoint, endpoint, fully_connected=False, geometric=True) 45 | route1= makeArrays(route) 46 | all_routes.append([1, route1]) 47 | 48 | middle = (startpoint[0] + endpoint[0])/2 49 | middle2 = (startpoint[1] + endpoint[1])/2 50 | # finde the middle point of the route 51 | middlePoint = (middle, middle2) 52 | 53 | # create routes for the wanted population size 54 | for i in range(1,math.floor(pop_size/2)+1): 55 | #create a time grid with random costs 56 | timeGridNew= [[random.random() for i in range(timeGrid.shape[1])] for j in range(timeGrid.shape[0])] 57 | timeGridNew = np.where(timeGrid >999, timeGrid, timeGridNew) 58 | change= (random.random() *150) 59 | # set a middle point and move it up and downwoard to create more different routes 60 | middlePointNew = (math.floor(middlePoint[0] + change), math.floor(middlePoint[1])) 61 | route1, weight = route_through_array(timeGridNew, startpoint, middlePointNew, fully_connected=False, geometric=True) 62 | route2, weight = route_through_array(timeGridNew, middlePointNew, endpoint, fully_connected=False, geometric=True) 63 | route= route1[:-1] + route2 64 | route= makeArrays(route) 65 | all_routes.append([i, route]) 66 | middlePointNew = (math.floor(middlePoint[0] - change), math.floor(middlePoint[1])) 67 | route1, weight = route_through_array(timeGridNew, startpoint, middlePointNew, fully_connected=False, geometric=True) 68 | route2, weight = route_through_array(timeGridNew, middlePointNew, endpoint, fully_connected=False, geometric=True) 69 | route= route1[:-1] + route2 70 | route= makeArrays(route) 71 | all_routes.append([i, route]) 72 | 73 | return all_routes 74 | 75 | -------------------------------------------------------------------------------- /scripts/lengthGridEW.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsten07/cms_routing/6dd1b14b849e03d72c7a857a2ece60dd19af31d5/scripts/lengthGridEW.npy -------------------------------------------------------------------------------- /scripts/lengthGridNS.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsten07/cms_routing/6dd1b14b849e03d72c7a857a2ece60dd19af31d5/scripts/lengthGridNS.npy -------------------------------------------------------------------------------- /scripts/prediction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """prediction.ipynb 3 | 4 | Automatically generated by Colaboratory. 5 | 6 | Original file is located at 7 | https://colab.research.google.com/drive/1lcvaJYf5k-Y0mmlCYAF8kWQlr2P-eTwr 8 | """ 9 | 10 | 11 | # Load model 12 | from joblib import dump, load 13 | # model_name = "DTR_model" 14 | # model = load('../models/' + model_name + '.joblib') 15 | # from datetime import datetime 16 | # # model = load("DTR_model.joblib") 17 | 18 | """# Load CMEMS data 19 | Try to use WMS or other more flexibel data retrieval 20 | """ 21 | 22 | import ftplib 23 | import os 24 | import numpy as np 25 | import netCDF4 as nc 26 | import pandas as pd 27 | from datetime import datetime 28 | 29 | def download(url, user, passwd, ftp_path, filename): 30 | 31 | with ftplib.FTP(url) as ftp: 32 | 33 | try: 34 | ftp.login(user, passwd) 35 | 36 | # Change directory 37 | ftp.cwd(ftp_path) 38 | 39 | # Download file (if there is not yet a local copy) 40 | if os.path.isfile(filename): 41 | print("There is already a local copy for this date ({})".format(filename)) 42 | else: 43 | with open(filename, 'wb') as fp: 44 | print("Downloading ... ({})".format(filename)) 45 | ftp.retrbinary('RETR {}'.format(filename), fp.write) 46 | 47 | except ftplib.all_errors as e: 48 | print('FTP error:', e) 49 | 50 | # Check contents 51 | 52 | """ 53 | with ftplib.FTP('nrt.cmems-du.eu') as ftp: 54 | 55 | try: 56 | ftp.login(UN_CMEMS, PW_CMEMS) 57 | 58 | # Change directory 59 | ftp.cwd('Core/GLOBAL_ANALYSIS_FORECAST_PHY_001_024/global-analysis-forecast-phy-001-024/2021/07') 60 | 61 | # List directory contents with additional information 62 | ftp.retrlines('LIST') 63 | 64 | # Get list of directory contents without additional information 65 | files = [] 66 | ftp.retrlines('NLST', files.append) 67 | print(files) 68 | 69 | # Check file size 70 | print("{} MB".format(ftp.size('mfwamglocep_2020120100_R20201202.nc')/1000000)) 71 | 72 | except ftplib.all_errors as e: 73 | print('FTP error:', e) 74 | """ 75 | 76 | 77 | def calc_relative_direction(ship_dir, ww_dir): 78 | """ 79 | determine relative wind direction for ships going north, east, south or west 80 | 81 | Parameters 82 | ---------- 83 | ship_dir : str, in ("N", "E", "S", "W") 84 | direction the ship is going 85 | ww_dir : array, float 86 | array of relative wind directions [0 - 360] 87 | """ 88 | if ship_dir not in ("N", "E", "S", "W"): 89 | raise Exception("Direction not accepted.") 90 | ww_360 = ww_dir 91 | ww_360[ww_360 < 0] = 360 + ww_dir[0] 92 | if ship_dir in ("N"): 93 | dir_4 = np.full((len(ww_dir), 1), 2) 94 | dir_4[(ww_dir < 45) | (ww_dir > 315)] = 1 95 | dir_4[(ww_dir > 135) & (ww_dir < 225)] = 3 96 | if ship_dir in ("E"): 97 | dir_4 = np.full((len(ww_dir), 1), 2) 98 | dir_4[(ww_dir > 45) & (ww_dir < 135)] = 1 99 | dir_4[(ww_dir > 225) & (ww_dir < 315)] = 3 100 | if ship_dir in ("W"): 101 | dir_4 = np.full((len(ww_dir), 1), 2) 102 | dir_4[(ww_dir > 45) & (ww_dir < 135)] = 3 103 | dir_4[(ww_dir > 225) & (ww_dir < 315)] = 1 104 | if ship_dir in ("S"): 105 | dir_4 = np.full((len(ww_dir), 1), 2) 106 | dir_4[(ww_dir < 45) | (ww_dir > 315)] = 3 107 | dir_4[(ww_dir > 135) & (ww_dir < 225)] = 1 108 | return dir_4 109 | 110 | def concatenate_cmems(cm_wave, cm_phy, ship_param, ship_dir): 111 | """ 112 | concatenate the variables from cmems wave and physics datasets 113 | 114 | Parameters 115 | ---------- 116 | cm_wave : net4CDF dataset 117 | netcdf file cmems wave 118 | cm_phy : net4CDF dataset 119 | netdcf file cmems physics 120 | ship_param : int 121 | ship variable that is used in model later (e.g. draft or length) 122 | ship_dir str, in ("N", "E", "S", "W") 123 | direction the ship is going 124 | """ 125 | array = (np.flipud(cm_wave["VHM0"][0, :, :]).data) # extract data from CMEMS 126 | dim = array.shape 127 | l = np.prod(dim) # get number of "pixel" 128 | 129 | # extract parameters from cmems dataset and reshape to array with dimension of 1 x number of pixel 130 | vhm = (np.flipud(cm_wave["VHM0"][0, :, :])).reshape(l, 1) 131 | vtm = (np.flipud(cm_wave["VTPK"][0, :, :])).reshape(l, 1) 132 | temp = (np.flipud(cm_phy["thetao"][0, 1, :, :])).reshape(l, 1) 133 | sal = (np.flipud(cm_phy["so"][0, 1, :, :])).reshape(l, 1) 134 | # create column for ship parameter 135 | ship = np.full((l, 1), ship_param) 136 | # calculate relative direction of wind depending on ship direction 137 | dir = calc_relative_direction(ship_dir, (np.flipud(cm_wave["VMDR_WW"][0, :, :])).reshape(l, 1)) 138 | 139 | # concatenate parameters 140 | a = np.concatenate((ship, vhm, vtm, temp, sal, dir), axis=1) 141 | 142 | # create pd df from array 143 | X_pred = pd.DataFrame(data=a, # values 144 | index=list(range(0, l)), # 1st column as index 145 | columns=["Draft", "VHM0", "VTPK", "thetao", "so", "dir_4"]) # 1st row as the column names 146 | return X_pred 147 | 148 | def prepare_grid(cm_wave, cm_phy, ship_param, ship_dir, model): 149 | """ 150 | prepare grid of SOGs 151 | 152 | Parameters 153 | ---------- 154 | cm_wave : net4CDF dataset 155 | netcdf file cmems wave 156 | cm_phy : net4CDF dataset 157 | netdcf file cmems physics 158 | ship_param : int 159 | ship variable that is used in model later (e.g. draft or length) 160 | ship_dir str, in ("N", "E", "S", "W") 161 | direction the ship is going 162 | """ 163 | 164 | X_pred = concatenate_cmems(cm_wave, cm_phy, ship_param, ship_dir) 165 | 166 | # extract shape from cmems data 167 | input = (np.flipud(cm_wave["VHM0"][0, :, :])) 168 | dim = input.shape 169 | 170 | # predict SOG 171 | # model = load('cms_routing/models/DTR_model.joblib') # import model 172 | SOG_pred = model.predict(X_pred) 173 | SOG_pred = SOG_pred.reshape(dim) # reshape to 'coordinates' 174 | SOG_pred[input < -30000] = -5 # -32767.0 # mask data with negative value 175 | 176 | return SOG_pred 177 | 178 | 179 | 180 | def calculateTimeGrid(SOG_E, SOG_N, SOG_S, SOG_W, AOI): 181 | kmGridEW= np.load("lengthGridEW.npy") 182 | kmGridEW = kmGridEW[AOI[2]:AOI[3], AOI[0]:AOI[1]] 183 | kmGridNS= np.load("lengthGridNS.npy") 184 | kmGridNS = kmGridNS[AOI[2]:AOI[3], AOI[0]:AOI[1]] 185 | 186 | timeGridE = SOG_E 187 | constE= 70/np.power(timeGridE, 3) 188 | timeGridE80= np.cbrt(80/constE) 189 | timeGridE60= np.cbrt(60/constE) 190 | timeGridE = timeGridE[AOI[2]:AOI[3], AOI[0]:AOI[1]] 191 | timeGridE80 = timeGridE80[AOI[2]:AOI[3], AOI[0]:AOI[1]] 192 | timeGridE60 = timeGridE60[AOI[2]:AOI[3], AOI[0]:AOI[1]] 193 | timeGridE = np.where(timeGridE < 0, 10000, (kmGridEW*1000)/(timeGridE*30.87)) 194 | timeGridE80 = np.where(timeGridE80 < 0, 10000, (kmGridEW*1000)/(timeGridE80*30.87)) 195 | timeGridE60 = np.where(timeGridE60 < 0, 10000, (kmGridEW*1000)/(timeGridE60*30.87)) 196 | 197 | 198 | 199 | timeGridN = SOG_N 200 | constN= 70/np.power(timeGridN, 3) 201 | timeGridN80= np.cbrt(80/constN) 202 | timeGridN60= np.cbrt(60/constN) 203 | timeGridN = timeGridN[AOI[2]:AOI[3], AOI[0]:AOI[1]] 204 | timeGridN80 = timeGridN80[AOI[2]:AOI[3], AOI[0]:AOI[1]] 205 | timeGridN60 = timeGridN60[AOI[2]:AOI[3], AOI[0]:AOI[1]] 206 | timeGridN = np.where(timeGridN < 0, 10000, (kmGridNS*1000)/(timeGridN*30.87)) 207 | timeGridN80 = np.where(timeGridN80 < 0, 10000, (kmGridNS*1000)/(timeGridN80*30.87)) 208 | timeGridN60 = np.where(timeGridN60 < 0, 10000, (kmGridNS*1000)/(timeGridN60*30.87)) 209 | 210 | 211 | timeGridS = SOG_S 212 | constS= 70/np.power(timeGridS, 3) 213 | timeGridS80= np.cbrt(80/constS) 214 | timeGridS60= np.cbrt(60/constS) 215 | timeGridS = timeGridS[AOI[2]:AOI[3], AOI[0]:AOI[1]] 216 | timeGridS80 = timeGridS80[AOI[2]:AOI[3], AOI[0]:AOI[1]] 217 | timeGridS60 = timeGridS60[AOI[2]:AOI[3], AOI[0]:AOI[1]] 218 | timeGridS = np.where(timeGridS < 0, 10000, (kmGridNS*1000)/(timeGridS*30.87)) 219 | timeGridS80 = np.where(timeGridS80 < 0, 10000, (kmGridNS*1000)/(timeGridS80*30.87)) 220 | timeGridS60 = np.where(timeGridS60 < 0, 10000, (kmGridNS*1000)/(timeGridS60*30.87)) 221 | 222 | 223 | 224 | 225 | timeGridW = SOG_W 226 | constW= 70/np.power(timeGridW, 3) 227 | timeGridW80= np.cbrt(80/constW) 228 | timeGridW60= np.cbrt(60/constW) 229 | timeGridW = timeGridW[AOI[2]:AOI[3], AOI[0]:AOI[1]] 230 | timeGridW80 = timeGridW80[AOI[2]:AOI[3], AOI[0]:AOI[1]] 231 | timeGridW60 = timeGridW60[AOI[2]:AOI[3], AOI[0]:AOI[1]] 232 | timeGridW = np.where(timeGridW < 0, 10000, (kmGridEW*1000)/(timeGridW*30.87)) 233 | timeGridW80 = np.where(timeGridW80 < 0, 10000, (kmGridEW*1000)/(timeGridW80*30.87)) 234 | timeGridW60 = np.where(timeGridW60 < 0, 10000, (kmGridEW*1000)/(timeGridW60*30.87)) 235 | 236 | 237 | timeGrids=[[timeGridN80, timeGridS80, timeGridE80, timeGridW80],[timeGridN, timeGridS, timeGridE, timeGridW],[timeGridN60, timeGridS60, timeGridE60, timeGridW60]] 238 | return timeGrids 239 | 240 | 241 | 242 | ''' 243 | # created masked array 244 | import numpy.ma as ma 245 | SOG_pred = np.ma.masked_where(np.flipud(np.ma.getmask(ds[parameter][0, :, :])), SOG_pred.reshape(dim)) 246 | SOG_pred.fill_value = -32767 247 | # SOG_pred =np.flipud(SOG_pred) 248 | ''' 249 | 250 | # # create actual grids for different ship directions 251 | # ship_param = 12 252 | # SOG_N = prepare_grid(model, ds, ds_p, ship_param, "N") 253 | # SOG_E = prepare_grid(model, ds, ds_p, ship_param, "E") 254 | # SOG_S = prepare_grid(model, ds, ds_p, ship_param, "S") 255 | # SOG_W = prepare_grid(model, ds, ds_p, ship_param, "W") 256 | 257 | # def cmems_paths(date): 258 | 259 | 260 | def get_cmems(date_start, date_end, UN_CMEMS, PW_CMEMS): 261 | date_s = datetime.strptime(date_start, "%d.%m.%Y %H:%M") 262 | date_e = datetime.strptime(date_end, "%d.%m.%Y %H:%M") 263 | 264 | date_m = date_s + (date_e - date_s) / 2 265 | date = date_m.strftime("%Y%m%d") 266 | today = datetime.now().strftime("%Y%m%d") 267 | 268 | path_date = date[0:4] + "/" + date[4:6] 269 | url = 'nrt.cmems-du.eu' 270 | path_w = 'Core/GLOBAL_ANALYSIS_FORECAST_WAV_001_027/global-analysis-forecast-wav-001-027/' + path_date 271 | path_p = 'Core/GLOBAL_ANALYSIS_FORECAST_PHY_001_024/global-analysis-forecast-phy-001-024/' + path_date 272 | 273 | with ftplib.FTP(url) as ftp: 274 | try: 275 | ftp.login(UN_CMEMS, PW_CMEMS) 276 | ftp.cwd(path_w) 277 | files = ftp.nlst() 278 | files = [i for i in files if date in i] 279 | filename_w = files[0] 280 | ftp.cwd('/') 281 | ftp.cwd(path_p) 282 | files = ftp.nlst() 283 | files = [i for i in files if date in i] 284 | filename_p = files[0] 285 | except ftplib.all_errors as e: 286 | print('FTP error:', e) 287 | 288 | download(url, UN_CMEMS, PW_CMEMS, path_w, filename_w) 289 | download(url, UN_CMEMS, PW_CMEMS, path_p, filename_p) 290 | 291 | ds_w = nc.Dataset(filename_w) 292 | ds_p = nc.Dataset(filename_p) 293 | return (ds_w, ds_p) 294 | 295 | 296 | """" 297 | # set CMEMS credentials 298 | UN_CMEMS = "jstenkamp" 299 | PW_CMEMS = "" 300 | 301 | # cmems wave data download 302 | url = 'nrt.cmems-du.eu' 303 | path = 'Core/GLOBAL_ANALYSIS_FORECAST_WAV_001_027/global-analysis-forecast-wav-001-027/2021/07' 304 | filename = 'mfwamglocep_2021070200_R20210703.nc' 305 | download(url, UN_CMEMS, PW_CMEMS, path, filename) 306 | 307 | # cmems physics download 308 | url = 'nrt.cmems-du.eu' 309 | path = 'Core/GLOBAL_ANALYSIS_FORECAST_PHY_001_024/global-analysis-forecast-phy-001-024/2021/07' 310 | filename_p = 'mercatorpsy4v3r1_gl12_mean_20210702_R20210703.nc' 311 | download(url, UN_CMEMS, PW_CMEMS, path, filename_p) 312 | 313 | 314 | 315 | # load files as netcdf dataset 316 | ds = nc.Dataset(filename) 317 | ds_p = nc.Dataset(filename_p) 318 | # ds 319 | """ 320 | -------------------------------------------------------------------------------- /scripts/runAlgorithm.py: -------------------------------------------------------------------------------- 1 | from pymoo import factory 2 | from pymoo.model.crossover import Crossover 3 | import spatial_extention_pymoo 4 | # add spatial functions to pymoo library 5 | factory.get_sampling_options = spatial_extention_pymoo._new_get_sampling_options 6 | factory.get_crossover_options = spatial_extention_pymoo._new_get_crossover_options 7 | factory.get_mutation_options = spatial_extention_pymoo._new_get_mutation_options 8 | Crossover.do = spatial_extention_pymoo._new_crossover_do 9 | 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | from matplotlib.colors import ListedColormap 13 | from pymoo.util.misc import stack 14 | from pymoo.model.problem import Problem 15 | 16 | from pymoo.algorithms.nsga2 import NSGA2 17 | from pymoo.factory import get_sampling, get_crossover, get_mutation 18 | from pymoo.factory import get_termination 19 | from pymoo.optimize import minimize 20 | from pymoo.model.problem import Problem 21 | 22 | 23 | from pymoo.optimize import minimize 24 | 25 | from calculate_objectives import calculate_time_differences 26 | from calculate_objectives import calculate_fuelUse 27 | from calculate_objectives import calculate_MinDistance 28 | 29 | 30 | import random 31 | 32 | 33 | def runAlgorithm(startpoint, endpoint, startTime, endTime, timeGrids, population, offspring, generations ): 34 | """ 35 | run the algorithm and return the result as a pymoo result object https://pymoo.org/interface/result.html 36 | 37 | Parameters 38 | ---------- 39 | startpoint: array, [cellX, cellY] 40 | the startpoint of the route 41 | endpoint: array, [cellX, cellY] 42 | the endpoint of the route 43 | startTime: str, day.month.year hours:minutes 44 | start time of the ship 45 | endTime: str, day.month.year hours:minutes 46 | booked port time, the ship shlould arrive 47 | population: int 48 | the wanted population, number of routes that will be returned 49 | offspirng: int 50 | number of new routes calculated with each iteration 51 | generations: int 52 | number of iterations 53 | """ 54 | timeGrid=timeGrids[0] 55 | 56 | class MyProblem(Problem): 57 | 58 | # define the number of variables etc. 59 | def __init__(self): 60 | super().__init__(n_var=2, # nr of variables 61 | n_obj=2, # nr of objectives 62 | n_constr=0, # nr of constraints 63 | xl=0.0, # lower boundaries 64 | xu=1.0) # upper boundaries 65 | 66 | # define the objective functions 67 | def _evaluate(self, X, out, *args, **kwargs): 68 | f1 = calculate_time_differences(X[:], startTime, endTime, timeGrids) 69 | f2 = calculate_fuelUse(X[:], timeGrids) 70 | out["F"] = np.column_stack([f1, f2]) 71 | 72 | problem = MyProblem() 73 | print(problem) 74 | # load own sampling, crossover and mutation method 75 | algorithm = NSGA2( 76 | pop_size=population, 77 | n_offsprings= offspring, 78 | sampling=get_sampling("spatial", startpoint= startpoint, endpoint=endpoint, timeGrid = timeGrid), 79 | crossover=get_crossover("spatial_one_point_crossover", timeGrid = timeGrid, n_points = 1.0), 80 | mutation=get_mutation("spatial_n_point_mutation", timeGrid=timeGrid, prob = 1.0), 81 | eliminate_duplicates=False 82 | ) 83 | termination = get_termination("n_gen", generations) 84 | 85 | # run algorithm itself 86 | res = minimize(problem, 87 | algorithm, 88 | termination, 89 | seed=1, 90 | save_history=True, 91 | verbose=True) 92 | 93 | return(res) 94 | -------------------------------------------------------------------------------- /scripts/spatial_crossover.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from skimage.graph import route_through_array 3 | import random 4 | import math 5 | 6 | from pymoo.model.crossover import Crossover 7 | 8 | def makeArrays(route): 9 | """ 10 | the route returned by the muation is a array of tuples. It needs to be an array 11 | 12 | Parameters 13 | ---------- 14 | route : array 15 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 16 | """ 17 | routeNew = [] 18 | for x in route: 19 | routeNew.append(list(x)) 20 | return routeNew 21 | 22 | 23 | def closest_node(node, nodes): 24 | """ 25 | find the closest point to this point 26 | 27 | Parameters 28 | ---------- 29 | node : array 30 | one point where the nearest should be found 31 | nodes : array of node 32 | points where the nearest should be found from 33 | """ 34 | nodes = np.asarray(nodes) 35 | dist_2 = np.sum((nodes - node)**2, axis=1) 36 | return np.argmin(dist_2) 37 | 38 | def findDuplicate(node, nodes, index): 39 | """ 40 | proof if the ship goes through one point twice 41 | 42 | Parameters 43 | ---------- 44 | node : array 45 | one point which should be proofed 46 | nodes : array of node 47 | points which should be compared with node 48 | index : int 49 | number where to start in nodes 50 | """ 51 | nodes= nodes[index: len(nodes)] 52 | nodes = np.asarray(nodes) 53 | dist_2 = np.sum((nodes - node)**2, axis=1) 54 | return np.where(dist_2 == 0) 55 | 56 | def eleminateDuplicates(start, route): 57 | """ 58 | eleminate points, where the ship goes through twice 59 | 60 | Parameters 61 | ---------- 62 | start : int 63 | index where to start on the route 64 | route : array 65 | route which shcould be proofed on duplicates 66 | """ 67 | for i in range(start, len(route)): 68 | duplicate=findDuplicate(route[i], route, i) 69 | duplicate=duplicate[0] 70 | if len(duplicate) > 1: 71 | #print("duplicate", duplicate) 72 | newRoute=route[:i] + route[i+duplicate[1]:] 73 | return eleminateDuplicates(i, newRoute) 74 | return route 75 | 76 | def crossover(route1, route2, timeGrid): 77 | """ 78 | combines two routes of the population to two new routes 79 | 80 | Parameters 81 | ---------- 82 | route1 : array 83 | first route which should be used 84 | route2 : array 85 | second route which schould be used 86 | timeGrid: array 87 | a grid containing the time for one specific context of bearing and speed 88 | """ 89 | timeGridNew= [[random.random() for i in range(timeGrid.shape[1])] for j in range(timeGrid.shape[0])] 90 | timeGridNew = np.where(timeGrid >999, timeGrid, timeGridNew) 91 | 92 | index1 = math.floor(random.random()*min(len(route1), len(route2))) 93 | index2 = min(len(route2) -1, len(route1)-1, (index1 + math.floor(random.random()*(len(route1) -index1)+ 10))) 94 | 95 | crossoverPoint1 = route1[index1] 96 | 97 | crossoverPoint2 = route2[index2] 98 | 99 | 100 | crossoverRoute1, weight = route_through_array(timeGridNew, crossoverPoint1[0:2], crossoverPoint2[0:2], fully_connected=False, geometric=True) 101 | crossoverRoute1= makeArrays(crossoverRoute1) 102 | 103 | 104 | crossoverPoint1 = route2[index1] 105 | 106 | crossoverPoint2 = route1[index2] 107 | crossoverRoute2, weight = route_through_array(timeGridNew, crossoverPoint1[0:2], crossoverPoint2[0:2], fully_connected=False, geometric=True) 108 | crossoverRoute2= makeArrays(crossoverRoute2) 109 | 110 | 111 | child1= [] 112 | child2= [] 113 | for i in range(index1-1): 114 | child1.append(route1[i]) 115 | child2.append(route2[i]) 116 | for x in crossoverRoute1: 117 | child1.append(x) 118 | for x in crossoverRoute2: 119 | child2.append(x) 120 | for i in range(index2 +1,len(route2)): 121 | child1.append(route2[i]) 122 | for i in range(index2 +1,len(route1)): 123 | child2.append(route1[i]) 124 | return[child1, child2, crossoverRoute1] 125 | 126 | #class to perform crossover 127 | class SpatialOnePointCrossover(Crossover): 128 | 129 | 130 | 131 | def __init__(self, timeGrid, n_points, **kwargs): 132 | super().__init__(2, 2, 1.0) # (n_parents,n_offsprings,probability) 133 | self.n_points = n_points 134 | self.TimeGrid = timeGrid 135 | def _do(self, problem, X, **kwargs): 136 | #print(X) 137 | _, n_matings= X.shape[0],X.shape[1] 138 | child_1 = [] 139 | child_2 = [] 140 | parent1_index = X[0][0][0] 141 | parent2_index = X[1][0][0] 142 | parent1 = X[0][0][1] 143 | parent2 = X[1][0][1] 144 | 145 | childs=crossover(parent1,parent2, self.TimeGrid) 146 | child_1= eleminateDuplicates(0, childs[0]) 147 | child_2= eleminateDuplicates(0, childs[1]) 148 | 149 | return np.array([[parent1_index, child_1], [parent2_index, child_2]]) -------------------------------------------------------------------------------- /scripts/spatial_extention_pymoo.py: -------------------------------------------------------------------------------- 1 | 2 | def _new_get_sampling_options(): 3 | from pymoo.operators.sampling.latin_hypercube_sampling import LatinHypercubeSampling 4 | from pymoo.operators.sampling.random_sampling import FloatRandomSampling 5 | from pymoo.operators.integer_from_float_operator import IntegerFromFloatSampling 6 | from pymoo.operators.sampling.random_sampling import BinaryRandomSampling 7 | from pymoo.operators.sampling.random_permutation_sampling import PermutationRandomSampling 8 | from spatial_sampling import SpatialSampling 9 | 10 | SAMPLING = [ 11 | ("real_random", FloatRandomSampling), 12 | ("real_lhs", LatinHypercubeSampling), 13 | ("bin_random", BinaryRandomSampling), 14 | ("int_random", IntegerFromFloatSampling, {'clazz': FloatRandomSampling}), 15 | ("int_lhs", IntegerFromFloatSampling, {'clazz': LatinHypercubeSampling}), 16 | ("perm_random", PermutationRandomSampling, dict(default_dir = None)), 17 | ("spatial", SpatialSampling) 18 | ] 19 | 20 | return SAMPLING 21 | 22 | def _new_get_crossover_options(): 23 | from pymoo.operators.crossover.differental_evolution_crossover import DifferentialEvolutionCrossover 24 | from pymoo.operators.crossover.exponential_crossover import ExponentialCrossover 25 | from pymoo.operators.crossover.half_uniform_crossover import HalfUniformCrossover 26 | from pymoo.operators.crossover.point_crossover import PointCrossover 27 | from pymoo.operators.crossover.simulated_binary_crossover import SimulatedBinaryCrossover 28 | from pymoo.operators.crossover.uniform_crossover import UniformCrossover 29 | from pymoo.operators.integer_from_float_operator import IntegerFromFloatCrossover 30 | from pymoo.operators.crossover.edge_recombination_crossover import EdgeRecombinationCrossover 31 | from pymoo.operators.crossover.order_crossover import OrderCrossover 32 | from spatial_crossover import SpatialOnePointCrossover 33 | #from spatial_crossover_constrained import SpatialOnePointCrossover 34 | CROSSOVER = [ 35 | ("real_sbx", SimulatedBinaryCrossover, dict(prob=0.9, eta=30)), 36 | ("int_sbx", IntegerFromFloatCrossover, dict(clazz=SimulatedBinaryCrossover, prob=0.9, eta=30)), 37 | ("real_de", DifferentialEvolutionCrossover), 38 | ("(real|bin|int)_ux", UniformCrossover), 39 | ("(bin|int)_hux", HalfUniformCrossover), 40 | ("(real|bin|int)_exp", ExponentialCrossover), 41 | ("(real|bin|int)_one_point", PointCrossover, {'n_points': 1}), 42 | ("(real|bin|int)_two_point", PointCrossover, {'n_points': 2}), 43 | ("(real|bin|int)_k_point", PointCrossover), 44 | ("perm_ox", OrderCrossover), 45 | ("perm_erx", EdgeRecombinationCrossover), 46 | ("spatial_one_point_crossover", SpatialOnePointCrossover) 47 | ] 48 | return CROSSOVER 49 | 50 | def _new_get_mutation_options(): 51 | from pymoo.operators.mutation.no_mutation import NoMutation 52 | from pymoo.operators.mutation.bitflip_mutation import BinaryBitflipMutation 53 | from pymoo.operators.mutation.polynomial_mutation import PolynomialMutation 54 | from pymoo.operators.integer_from_float_operator import IntegerFromFloatMutation 55 | from pymoo.operators.mutation.inversion_mutation import InversionMutation 56 | from spatial_mutation import SpatialNPointMutation 57 | 58 | MUTATION = [ 59 | ("none", NoMutation, {}), 60 | ("real_pm", PolynomialMutation, dict(eta=20)), 61 | ("int_pm", IntegerFromFloatMutation, dict(clazz=PolynomialMutation, eta=20)), 62 | ("bin_bitflip", BinaryBitflipMutation), 63 | ("perm_inv", InversionMutation), 64 | ("spatial_n_point_mutation", SpatialNPointMutation, dict(point_mutation_probability = 0.01)) 65 | ] 66 | 67 | return MUTATION 68 | 69 | import numpy as np 70 | from pymoo.model.population import Population 71 | 72 | offspring=2 73 | 74 | def _new_crossover_do(self, problem, pop, parents, **kwargs): 75 | """ 76 | 77 | This method executes the crossover on the parents. This class wraps the implementation of the class 78 | that implements the crossover. 79 | 80 | Parameters 81 | ---------- 82 | problem: class 83 | The problem to be solved. Provides information such as lower and upper bounds or feasibility 84 | conditions for custom crossovers. 85 | 86 | pop : Population 87 | The population as an object 88 | 89 | parents: numpy.array 90 | The select parents of the population for the crossover 91 | 92 | kwargs : dict 93 | Any additional data that might be necessary to perform the crossover. E.g. constants of an algorithm. 94 | 95 | Returns 96 | ------- 97 | offsprings : Population 98 | The off as a matrix. n_children rows and the number of columns is equal to the variable 99 | length of the problem. 100 | 101 | """ 102 | 103 | if self.n_parents != parents.shape[1]: 104 | raise ValueError('Exception during crossover: Number of parents differs from defined at crossover.') 105 | 106 | # get the design space matrix form the population and parents 107 | X = pop.get("X")[parents.T].copy() 108 | 109 | # now apply the crossover probability 110 | do_crossover = np.random.random(len(parents)) < self.prob 111 | 112 | # execute the crossover 113 | _X = self._do(problem, X, **kwargs) 114 | 115 | X = _X 116 | 117 | # create a population object 118 | off = Population.new("X", X) 119 | 120 | return off 121 | -------------------------------------------------------------------------------- /scripts/spatial_mutation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from pymoo.model.mutation import Mutation 3 | import random 4 | import math 5 | from skimage.graph import route_through_array 6 | 7 | 8 | 9 | def makeArrays(route): 10 | """ 11 | the route returned by the muation is a array of tuples. It needs to be an array 12 | 13 | Parameters 14 | ---------- 15 | route : array 16 | one route the ship is going. Containing array of [gridCooridinateX, gridCoordinateY, speed] 17 | """ 18 | routeNew = [] 19 | for x in route: 20 | routeNew.append(list(x)) 21 | return routeNew 22 | 23 | # function to randomly change a certain patch 24 | 25 | def mutation(crossover_child, timeGrid): 26 | """ 27 | mutates one route, so changes a random part of it 28 | 29 | Parameters 30 | ---------- 31 | crossover_child : array 32 | one route the ship is going, returned by the crossover 33 | """ 34 | timeGridNew= [[random.random() for i in range(timeGrid.shape[1])] for j in range(timeGrid.shape[0])] 35 | timeGridNew = np.where(timeGrid >999, timeGrid, timeGridNew) 36 | 37 | crossover_child_split_list= [] 38 | 39 | crossover_child = makeArrays(crossover_child) 40 | #split crossover over child into 3 lists 41 | randomNumber = random.random()*len(crossover_child) 42 | startIndex= math.floor(randomNumber) 43 | startpoint = crossover_child[startIndex] 44 | endIndex= math.floor(startIndex + (random.random() * (len(crossover_child)-startIndex))) 45 | endpoint= crossover_child[endIndex] 46 | 47 | # recalculate route from end point of list 1 to sarting point of list 3 48 | route, weight = route_through_array(timeGridNew, startpoint[0:2], endpoint[0:2], 49 | fully_connected=False, geometric=True) 50 | route_list = makeArrays(route) 51 | first_component = makeArrays(crossover_child[0:startIndex]) 52 | second_component = route_list[0:-1]#filter out starting point and endpoint from the new mid list 53 | third_component = makeArrays(crossover_child[endIndex:len(crossover_child)]) 54 | #combine all the sections to a final mutated route 55 | mutated_child = first_component + second_component + third_component 56 | #print(mutated_child) 57 | 58 | return mutated_child 59 | 60 | 61 | # class that performs the mutation 62 | class SpatialNPointMutation(Mutation): 63 | def __init__(self, timeGrid, prob=None,point_mutation_probability=0.01): 64 | super().__init__() 65 | self.prob = prob 66 | self.point_mutation_probability = point_mutation_probability 67 | self.TimeGrid= timeGrid 68 | def _do(self, problem, X, **kwargs): 69 | offspring=[] 70 | #print(X) 71 | 72 | # loop over individuals in population 73 | for i in X: 74 | # performe mutation with certain probability 75 | if np.random.uniform(0, 1) < self.prob: 76 | mutated_individual = mutation(i[1], self.TimeGrid) 77 | offspring.append([i[0], mutated_individual]) 78 | # if no mutation 79 | else: 80 | offspring.append([i[0], i]) 81 | offspring = np.array(offspring) 82 | return offspring -------------------------------------------------------------------------------- /scripts/spatial_sampling.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from pymoo.model.sampling import Sampling 3 | import initial_population 4 | 5 | import yaml 6 | 7 | 8 | class SpatialSampling(Sampling): 9 | def __init__(self, startpoint, endpoint, timeGrid, var_type=np.float, default_dir=None) -> None: 10 | super().__init__() 11 | self.var_type = var_type 12 | self.default_dir = default_dir 13 | self.startpoint = startpoint 14 | self.endpoint = endpoint 15 | self.timeGrid = timeGrid 16 | def _do(self, problem, n_samples, **kwargs): 17 | routes = initial_population.initialize_spatial(n_samples, self.startpoint, self.endpoint, self.timeGrid) 18 | return routes 19 | 20 | 21 | --------------------------------------------------------------------------------