Skip to content
Snippets Groups Projects
gt_segmented_cf.py 12.5 KiB
Newer Older
#!/usr/bin/env python3

# A script that takes a segmented graph in the gt format and performs the 
Lionel GUEZ's avatar
Lionel GUEZ committed
# non-local cost function calculation where each edge will have a cost
# function assigned to it. 
# Inputs (to be changed within the script):
    # Orientation
    # node_id_param_file location and name
    # gt graph name and location
    # shapefiles names and location
# Output: a gt graph with cost functions and an xml graph with cost functions

import graph_tool
import time
import json
import math
from os import path
import shapefile
import datetime
from numpy import loadtxt
import report_graph

######################
# Utility functions: #
######################

def comp_ishape(d_init, ishape_last, days_1950, eddy_index): 
    k = days_1950 - d_init
    
    if k == 0:
        ishape = eddy_index - 1
    else:
        ishape = ishape_last[k - 1] + eddy_index
        
    return ishape 

#######################
# Necessary functions #
#######################

def calculate_radii_and_rossby(start, end, inc, segment, e_overestim, extremum,
                               max_speed, ishape_last):
    
    radii = 0 #[m]
    rossby = 0 #[1/s]
    
    days_modifier = 0
    
    for i in range(start, end, inc):
        current_eddy = report_graph.node_to_date_eddy(segment[i], e_overestim)
Lionel GUEZ's avatar
Lionel GUEZ committed
        date = datetime.date(1950, 1, 1) \
            + datetime.timedelta(current_eddy['date_index'])
        year = date.year
        
        # calculate the location in the shapefile
        d_init_1 = extremum[year].record(0)[1]
        location = comp_ishape(d_init_1, ishape_last[year], 
Lionel GUEZ's avatar
Lionel GUEZ committed
                               current_eddy['date_index'],
                               current_eddy['eddy_index'])
Lionel GUEZ's avatar
Lionel GUEZ committed
        # now that we have the location in the shapefiles, we need to
        # get the radius and the rossby number
        shapeRec = extremum[year].shapeRecord(location)
        lat_in_deg = shapeRec.shape.points[0][1]
        #[deg]
        f = 2*2*math.pi/(24*3600)*math.sin(math.radians(lat_in_deg)) # [1/s]
        
        V_max = shapeRec.record[4] #[m/s]
        R_Vmax = max_speed[year].record(location)['r_eq_area'] * 1000 #[m]
        
        
        if (V_max < 100):
            # calculate Ro and Delta_Ro
            Ro = V_max / (f * R_Vmax) #[]
        else:
            Ro = 0
            days_modifier += 1
        
        ####### RADII #######
        radii += R_Vmax # [m]
        ####### ROSSBY ######
        rossby += Ro # []
        
    return {"radii": radii, "rossby": rossby, "days_modifier": days_modifier}

###############################
# grab e_overestim
###############################

with open("node_id_param.json") as f: node_id_param = json.load(f)
# assign attributes to the whole graphs
e_overestim = node_id_param["e_overestim"]

################################################
# set some values needed for the cost function #
################################################

delta_cent_mean = 3.8481 # [km]
delta_cent_std = 8.0388

delta_ro_mean = -0.0025965 # []
delta_ro_std = 5.2168

delta_r_mean = -0.0094709 * 1000 #[m] 
delta_r_std = 8.6953 * 1000

orientation = input("Enter orientation (anti or cyclo): ") 

##########################
# load the edgelist file #
##########################

t0 = time.perf_counter()

print('Loading gt file...')
g = graph_tool.Graph()
g.load('segmented.gt')
t1 = time.perf_counter()
print(f'Loading done: {g}')
Lionel GUEZ's avatar
Lionel GUEZ committed
print(t1 - t0, "s")

g.set_fast_edge_removal()

##################################
# Calculating the Cost Function: #
##################################

#######################
# Load the shapefiles #
#######################

# setup the dicts for the shapefiles and the ishape_last files
extremum = {}
max_speed = {}
ishape_last = {}

Lionel GUEZ's avatar
Lionel GUEZ committed
shp_tr_dir = input('Directory containing %Y/SHPC_(anti|cyclo): ')
    
for year in range(1993,2019):
    shp_dir = path.join(shp_tr_dir, f'{year}/SHPC_{orientation}')
Lionel GUEZ's avatar
Lionel GUEZ committed
    extremum[year] = shapefile.Reader(path.join(shp_dir, 'extremum'))
    max_speed[year] = shapefile.Reader(path.join(shp_dir, 'max_speed_contour'))
    ishape_last[year] = loadtxt(path.join(shp_dir, 'ishape_last.txt'), 
                                dtype = int, delimiter = "\n", unpack = False) 

# change if there is a change over the number of days to average
num_of_days_to_avg = 7

# we iterate on the years because the segments are non sequential in time
for yr in range(1993, 2019):
    print(f'Current year: {yr}') 
    
    # iterate on the vertices 
    for n in g.vertices():
        # Get the segment and the number of days
        segment = g.vp.segment[n]      
        
        # calculate the indexes and dates
        first = report_graph.node_to_date_eddy(segment[0], e_overestim)
        
        # get days since 1950
        first_days = first['date_index']
        first_date = datetime.date(1950,1,1) + datetime.timedelta(first['date_index'])
        first_year = first_date.year
        
        #print(f'Segment size: {len(segment)}, first: {first_year}, last: {last_year}, yr: {yr}')        
        
        # chech if we are in the current year
        
        if (first_year == yr):
            num_of_days = len(segment)
            
            # start processing
            
            first_pos = []
            last_pos = []
            
            last = report_graph.node_to_date_eddy(segment[-1], e_overestim)
            last_days = last['date_index']
            last_date = datetime.date(1950,1,1) + datetime.timedelta(last['date_index'])
            last_year = last_date.year
            
            print(f'Segment size: {len(segment)}, first: {first_year}, last: {last_year}, yr: {yr}')
        
            # calculate the location in the shapefile
            
            d_init_1 = extremum[first_year].record(0)[1]
            d_init_2 = extremum[last_year].record(0)[1]
            first_loc = comp_ishape(d_init_1, ishape_last[first_year], first['date_index'], first['eddy_index'])
            last_loc = comp_ishape(d_init_2, ishape_last[last_year], last['date_index'], last['eddy_index'])
            first_pos = extremum[first_year].shape(first_loc).points[0]
            last_pos = extremum[last_year].shape(last_loc).points[0]
            
            ##### STORE POSITIONS IN THE VPS ######
            g.vp.pos_first[n] = first_pos # [deg, deg]
            g.vp.pos_last[n] = last_pos # [deg, deg]
        
            # if the segments are longer than the # of days over which to avg
        
            if (num_of_days > num_of_days_to_avg):
                first_radii = 0 # [m]
                last_radii = 0 # [m]
                
                first_rossby = 0 # []
                last_rossby = 0 # []
                
                # First 7 days calculation
                first_res = calculate_radii_and_rossby(0, num_of_days_to_avg,
                                                       1, segment, e_overestim,
                                                       extremum, max_speed,
                
                # average and assign radii
                first_radii = first_res['radii'] / num_of_days_to_avg
                g.vp.first_av_rad[n] = first_radii        
                
                # grab the days modifier
                modifier = first_res['days_modifier']
                
                if (num_of_days_to_avg - modifier > 0):
                    # Average and assign the rossbies:
                    first_rossby = first_res['rossby'] / (num_of_days_to_avg - modifier)
                    g.vp.first_av_ros[n] = first_rossby
                else:
                    # there is division by zero, average rossby is undefinied
                    pass
                    
                # Last 7 days calculation
                last_res = calculate_radii_and_rossby(len(segment) - 1, 
                                                      len(segment) - (num_of_days_to_avg + 1),
                                                      -1,
                                                      segment, e_overestim,
                                                      extremum,
                                                      max_speed, ishape_last)
                    
                # Average and assign the last radii
                last_radii = last_res['radii'] / num_of_days_to_avg
                g.vp.last_av_rad[n] = last_radii        
                
                
                # grab the days modifier
                modifier = last_res['days_modifier']
                
                if (num_of_days_to_avg - modifier > 0):
                    # Average and assign the rossbies:
                    last_rossby = last_res['rossby'] / (num_of_days_to_avg - modifier)
                    g.vp.last_av_ros[n] = last_rossby
                else:
                    # there is division by zero, average rossby is undefinied
                    pass
            # else, the number of eddies in a segment is lower than the # of 
            # days over which to average, the values will be the same except
            # for the positions
            else:
                res = calculate_radii_and_rossby(0, num_of_days, 1, segment,
                                                 e_overestim,
                                                 extremum, max_speed, ishape_last)
                
                # grab the days modifier
                modifier = res['days_modifier']
                
                if (num_of_days - modifier > 0):
                    # Average and assign the rossbies:
                    rossby = res['rossby'] / (num_of_days - modifier)
                    g.vp.first_av_ros[n] = rossby
                    g.vp.last_av_ros[n] = rossby
                else:
                    # there is division by zero, average rossby is undefinied
                    pass
                    
                # Average and assign the radii
                    
                radii = res['radii'] / num_of_days
                g.vp.first_av_rad[n] = radii
                g.vp.last_av_rad[n] = radii
            
###############################
# Calculate the cost function #
###############################

for edge in g.edges():
    source_node = edge.source()
    target_node = edge.target()
    
    cf = -10000

    lat_for_conv = (g.vp.pos_last[source_node][1] + 
                    g.vp.pos_first[target_node][1]) / 2 # latitude needed for conversion of degrees to kilometers
    lat_for_conv = math.radians(lat_for_conv) # need to convert to radians
    
    # because of the wrapping issue (360° wrapping incorrectly to 0°), we check for that here
    lon_diff = abs(g.vp.pos_last[source_node][0] - g.vp.pos_first[target_node][0])
    if (lon_diff > 300):
        lon_diff = 360 - lon_diff

    # calculate Delta_cent: numbers used for conversion obtained from:
        # https://stackoverflow.com/questions/1253499/simple-calculations-for-working-with-lat-lon-and-km-distance
    Delta_Cent = math.sqrt((  lon_diff * 111.32 * math.cos(lat_for_conv)   )**2 + 
                           (   (g.vp.pos_last[source_node][1] - g.vp.pos_first[target_node][1]) * 110.574  )**2)
    
    # calculate the first term
    first_term = ((Delta_Cent - delta_cent_mean)/delta_cent_std) ** 2
    
    # Rossbies: 
    if (g.vp.first_av_ros[target_node] and g.vp.last_av_ros[source_node]):
        Delta_Ro = g.vp.last_av_ros[source_node] - g.vp.first_av_ros[target_node]
    else:
        print("At least one of the rossbies is invalid.")
        #Delta_Ro = delta_ro_mean
        Delta_Ro = 0
    
    # Calculate the second term
    second_term = ((Delta_Ro - delta_ro_mean)/delta_ro_std ) ** 2
    
    # R_Vmax 1 and 2 already exist, just get the delta
    
    Delta_R_Vmax = g.vp.last_av_rad[source_node] - g.vp.first_av_rad[target_node]
    
    # Calculate the third term
    third_term = ((Delta_R_Vmax - delta_r_mean)/delta_r_std) ** 2
    
    #############################
    # calculate the cost function
    #############################
    
    cf = math.sqrt(first_term + second_term + third_term)
    
    # assign as weight to the edge
    g.ep.nl_cost_function[edge] = cf    


################################
# Writing to an GT Graph File #
################################

#g.save("segments_cyclo_gl_cf.xml")
#print("Done writing xml.")
g.save(f'segmented_{orientation}_cf.gt')
print("Done writing gt.")
g.save(f'segmented_{orientation}_cf.xml')
print("Done writing xml.")
#g.save(f'{orientation}_segmented_cf.gv', fmt = "dot")
#print("Done writing dot file.")

print('All done')