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

Lionel GUEZ's avatar
Lionel GUEZ committed
"""A script that takes the graph of segments without cost functions
Lionel GUEZ's avatar
Lionel GUEZ committed
and computes the non-local cost functions applied to edges.
Lionel GUEZ's avatar
Lionel GUEZ committed

Input:

- "node_id_param.json", expected to be in the current directory

Lionel GUEZ's avatar
Lionel GUEZ committed
- the graph of segments without cost functions, "segments.gt" or
  "segments.graphml", expected to be in the current directory
Lionel GUEZ's avatar
Lionel GUEZ committed

- shapefiles, specified as command line arguments

Output: the graph of segments with cost functions,
'segments_cost_functions.gt'

"""

import graph_tool
import time
import json
import math
import report_graph
Lionel GUEZ's avatar
Lionel GUEZ committed
import util_eddies
import bisect
Lionel GUEZ's avatar
Lionel GUEZ committed
import argparse
Omega = 2 * math.pi / 86164.

def calculate_radii_rossby(properties):
    """Compute average on some instantaneous eddies of Rossby number and
    radius of maximum speed contour. The required properties for each
    eddy are position, radius and speed. "properties" is a list of
    dictionaries. Each dictionary in the list contains the three properties.
    avg_rad = 0 # in m
    avg_Rossby = 0
    n_valid_Rossby = 0
Lionel GUEZ's avatar
Lionel GUEZ committed

    for prop in properties:
        f = 2 * Omega * math.sin(math.radians(prop["pos"][1])) # in s-1
        radius = prop["radius"] * 1000 # in m
Lionel GUEZ's avatar
Lionel GUEZ committed

        if abs(prop["speed"]) < 100:
            avg_Rossby += prop["speed"] / (f * radius)
            n_valid_Rossby += 1
Lionel GUEZ's avatar
Lionel GUEZ committed

Lionel GUEZ's avatar
Lionel GUEZ committed
        avg_rad += radius # in m
Lionel GUEZ's avatar
Lionel GUEZ committed

    avg_rad /= len(properties)
    if n_valid_Rossby != 0: avg_Rossby /= n_valid_Rossby
    return avg_rad, avg_Rossby
def get_SHPC(array_d_ini, date_index):
Lionel GUEZ's avatar
Lionel GUEZ committed
    i_SHPC = bisect.bisect(array_d_init, date_index) - 1
    assert i_SHPC >= 0
    return i_SHPC
def node_to_prop(node_list, e_overestim, array_d_init, handlers):
    """node_list is a list of node identification numbers for
    instantaneous eddies. This function returns some properties of the
    eddies, read from shapefiles: position of extremum, radius of
    outermost contour or maximum speed contour, and speed. The three
    properties are in a dictionary, for each eddy.

    """

    properties = []

    for n in node_list:
        date_index, eddy_index = report_graph.node_to_date_eddy(n, e_overestim)
        i_SHPC = get_SHPC(array_d_init, date_index)
        ishape = util_eddies.comp_ishape(handlers[i_SHPC], date_index,
                                         eddy_index)
        shapeRec = handlers[i_SHPC]["readers"]["extremum"].shapeRecord(ishape)
        prop = {"pos": shapeRec.shape.points[0], "speed": shapeRec.record.speed}
        prop["radius"] = handlers[i_SHPC]["readers"]["max_speed_contour"]\
            .record(ishape).r_eq_area
        properties.append(prop)

    return properties

Lionel GUEZ's avatar
Lionel GUEZ committed
t0 = time.perf_counter()
timings = open("timings.txt", "w")
Lionel GUEZ's avatar
Lionel GUEZ committed
parser = argparse.ArgumentParser()
parser.add_argument("SHPC_dir", nargs='+')
parser.add_argument("--graphml", action = "store_true",
                    help = "save to graphml format")
Lionel GUEZ's avatar
Lionel GUEZ committed
args = parser.parse_args()
Lionel GUEZ's avatar
Lionel GUEZ committed
# Grab e_overestim:
with open("node_id_param.json") as f: node_id_param = json.load(f)
e_overestim = node_id_param["e_overestim"]

Lionel GUEZ's avatar
Lionel GUEZ committed
# Set some values needed for the cost function:
Lionel GUEZ's avatar
Lionel GUEZ committed
delta_cent_mean = 3.8481 # [in km]
delta_cent_std = 8.0388

Lionel GUEZ's avatar
Lionel GUEZ committed
delta_ro_mean = -0.0025965
delta_ro_std = 5.2168

Lionel GUEZ's avatar
Lionel GUEZ committed
delta_r_mean = -0.0094709 * 1000 # [in m]
delta_r_std = 8.6953 * 1000

Lionel GUEZ's avatar
Lionel GUEZ committed
# Load the graph_tool file:
Lionel GUEZ's avatar
Lionel GUEZ committed

Lionel GUEZ's avatar
Lionel GUEZ committed
print('Loading graph...')
Lionel GUEZ's avatar
Lionel GUEZ committed
    g = graph_tool.load_graph('segments.gt')
except FileNotFoundError:
Lionel GUEZ's avatar
Lionel GUEZ committed
    g = graph_tool.load_graph('segments.graphml')
Lionel GUEZ's avatar
Lionel GUEZ committed
print('Loading done...')
print("Input graph:")
print("Number of vertices:", g.num_vertices())
print("Number of edges:", g.num_edges())
print("Internal properties:")
g.list_properties()
Lionel GUEZ's avatar
Lionel GUEZ committed
t1 = time.perf_counter()
Lionel GUEZ's avatar
Lionel GUEZ committed
timings.write(f"loading: {t1 - t0:.0f} s\n")
Lionel GUEZ's avatar
Lionel GUEZ committed
t0 = t1
g.vp['pos_first'] = g.new_vp('vector<double>')
g.vp['pos_last'] = g.new_vp('vector<double>')
g.vp['first_av_rad'] = g.new_vp('float')
g.vp['first_av_ros'] = g.new_vp('float')
g.vp['last_av_rad'] = g.new_vp('float')
g.vp['last_av_ros'] = g.new_vp('float')
g.ep['cost_function'] = g.new_ep('float')
# Set up the list of SHPC:
Lionel GUEZ's avatar
Lionel GUEZ committed
handlers = [util_eddies.open_shpc(shpc_dir) for shpc_dir in args.SHPC_dir]

array_d_init = [handler["d_init"] for handler in handlers]
# (create the list once and for all)
n_days_avg = 7 # number of days to average
Lionel GUEZ's avatar
Lionel GUEZ committed
print("Iterating on vertices...")

Lionel GUEZ's avatar
Lionel GUEZ committed
for n in g.vertices():
    if n.in_degree() != 0:
        # Define properties for beginning of the segment:
        properties = node_to_prop(g.vp.inst_eddies[n][:n_days_avg], e_overestim,
                                  array_d_init, handlers)
        g.vp.first_av_rad[n], g.vp.first_av_ros[n] \
            = calculate_radii_rossby(properties)
        g.vp.pos_first[n] = properties[0]["pos"] # in degrees
    if n.out_degree() != 0:
        # Define properties for end of the segment:
        len_seg = len(g.vp.inst_eddies[n])

        if n.in_degree() == 0 or len_seg > n_days_avg:
            # We have to read more from the shapefiles and redefine
            # properties.

            if n.in_degree() == 0 or len_seg > 2 * n_days_avg:
                # We cannot use part of properties from the beginning
                # of the segment.
                properties = node_to_prop(g.vp.inst_eddies[n][- n_days_avg:],
                                          e_overestim, array_d_init, handlers)
            else:
                # assertion: n.in_degree() != 0 and n_days_avg <
                # len_seg < 2 * n_days_avg

                # We can use part of the properties from the beginning
                # of the segment.
                properties = properties[len_seg - n_days_avg:] \
                    + node_to_prop(g.vp.inst_eddies[n][n_days_avg:],
                                   e_overestim, array_d_init, handlers)
            g.vp.last_av_rad[n], g.vp.last_av_ros[n] \
                = calculate_radii_rossby(properties)
Lionel GUEZ's avatar
Lionel GUEZ committed
            # The number of eddies in the segment is lower than or
            # equal to the number of days over which to average. The
            # values for the end of the segment will be the same as
            # for the begining, except for the position.
            g.vp.last_av_rad[n] = g.vp.first_av_rad[n]
Lionel GUEZ's avatar
Lionel GUEZ committed
            g.vp.last_av_ros[n] = g.vp.first_av_ros[n]

        g.vp.pos_last[n] = properties[- 1]["pos"] # in degrees
Lionel GUEZ's avatar
Lionel GUEZ committed
t1 = time.perf_counter()
Lionel GUEZ's avatar
Lionel GUEZ committed
timings.write(f"iterating on vertices: {t1 - t0:.0f} s\n")
Lionel GUEZ's avatar
Lionel GUEZ committed
t0 = t1
Lionel GUEZ's avatar
Lionel GUEZ committed
print("Iterating on edges...")

for edge in g.edges():
    source_node = edge.source()
    target_node = edge.target()
Lionel GUEZ's avatar
Lionel GUEZ committed

Lionel GUEZ's avatar
Lionel GUEZ committed
    lat_for_conv = (g.vp.pos_last[source_node][1] +
Lionel GUEZ's avatar
Lionel GUEZ committed
                    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
Lionel GUEZ's avatar
Lionel GUEZ committed

Lionel GUEZ's avatar
Lionel GUEZ committed
    # 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

Lionel GUEZ's avatar
Lionel GUEZ committed
    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)
Lionel GUEZ's avatar
Lionel GUEZ committed

    # calculate the first term
    first_term = ((Delta_Cent - delta_cent_mean)/delta_cent_std) ** 2
Lionel GUEZ's avatar
Lionel GUEZ committed

    # Rossbies:
    if (g.vp.first_av_ros[target_node] and g.vp.last_av_ros[source_node]):
Lionel GUEZ's avatar
Lionel GUEZ committed
        Delta_Ro = g.vp.last_av_ros[source_node] \
            - g.vp.first_av_ros[target_node]
Lionel GUEZ's avatar
Lionel GUEZ committed
        # At least one of the rossbies is invalid.
        # Delta_Ro = delta_ro_mean
        Delta_Ro = 0
Lionel GUEZ's avatar
Lionel GUEZ committed

    # Calculate the second term
    second_term = ((Delta_Ro - delta_ro_mean)/delta_ro_std ) ** 2
Lionel GUEZ's avatar
Lionel GUEZ committed

    # R_Vmax 1 and 2 already exist, just get the delta
Lionel GUEZ's avatar
Lionel GUEZ committed

Lionel GUEZ's avatar
Lionel GUEZ committed
    Delta_R_Vmax = g.vp.last_av_rad[source_node] \
        - g.vp.first_av_rad[target_node]
Lionel GUEZ's avatar
Lionel GUEZ committed

    # Calculate the third term
    third_term = ((Delta_R_Vmax - delta_r_mean)/delta_r_std) ** 2
Lionel GUEZ's avatar
Lionel GUEZ committed

    #############################
    # calculate the cost function
    #############################
Lionel GUEZ's avatar
Lionel GUEZ committed

    cf = math.sqrt(first_term + second_term + third_term)
Lionel GUEZ's avatar
Lionel GUEZ committed

    # assign as weight to the edge
    g.ep.cost_function[edge] = cf
Lionel GUEZ's avatar
Lionel GUEZ committed
t1 = time.perf_counter()
Lionel GUEZ's avatar
Lionel GUEZ committed
timings.write(f"iterating on edges: {t1 - t0:.0f} s\n")
Lionel GUEZ's avatar
Lionel GUEZ committed
t0 = t1
Lionel GUEZ's avatar
Lionel GUEZ committed
print("Saving...")

if args.graphml:
    g.save('segments_cost_functions.graphml')
else:
    g.save('segments_cost_functions.gt')

print('All done')
Lionel GUEZ's avatar
Lionel GUEZ committed
t1 = time.perf_counter()
Lionel GUEZ's avatar
Lionel GUEZ committed
timings.write(f"saving: {t1 - t0:.0f} s\n")
Lionel GUEZ's avatar
Lionel GUEZ committed
timings.close()