#!/usr/bin/env python3 """A script that takes the graph of segments without cost functions and computes the non-local cost functions applied to edges. Input: - "node_id_param.json", expected to be in the current directory - the graph of segments without cost functions, "segments.gt" or "segments.graphml", expected to be in the current directory - 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 import util_eddies import bisect 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 for prop in properties: f = 2 * Omega * math.sin(math.radians(prop["pos"][1])) # in s-1 radius = prop["radius"] * 1000 # in m if abs(prop["speed"]) < 100: avg_Rossby += prop["speed"] / (f * radius) n_valid_Rossby += 1 avg_rad += radius # in m 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): 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 t0 = time.perf_counter() timings = open("timings.txt", "w") parser = argparse.ArgumentParser() parser.add_argument("SHPC_dir", nargs='+') parser.add_argument("--graphml", action = "store_true", help = "save to graphml format") args = parser.parse_args() # Grab e_overestim: with open("node_id_param.json") as f: node_id_param = json.load(f) e_overestim = node_id_param["e_overestim"] # Set some values needed for the cost function: delta_cent_mean = 3.8481 # [in km] delta_cent_std = 8.0388 delta_ro_mean = -0.0025965 delta_ro_std = 5.2168 delta_r_mean = -0.0094709 * 1000 # [in m] delta_r_std = 8.6953 * 1000 # Load the graph_tool file: print('Loading graph...') try: g = graph_tool.load_graph('segments.gt') except FileNotFoundError: g = graph_tool.load_graph('segments.graphml') 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() t1 = time.perf_counter() timings.write(f"loading: {t1 - t0:.0f} s\n") 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: 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 print("Iterating on vertices...") for n in g.vertices(): if n.in_degree() != 0 or n.out_degree() != 0: len_seg = len(g.vp.inst_eddies[n]) if len_seg > n_days_avg: # The segment is longer than the number of days over which # to average # First 7 days calculation 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 # Last 7 days calculation: properties = 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) g.vp.pos_last[n] = properties[- 1]["pos"] # in degrees else: # 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. properties = node_to_prop(g.vp.inst_eddies[n], e_overestim, array_d_init, handlers) g.vp.first_av_rad[n], g.vp.first_av_ros[n] \ = calculate_radii_rossby(properties) g.vp.last_av_rad[n] = g.vp.first_av_rad[n] g.vp.last_av_ros[n] = g.vp.first_av_ros[n] g.vp.pos_first[n] = properties[0]["pos"] # in degrees g.vp.pos_last[n] = properties[- 1]["pos"] # in degrees t1 = time.perf_counter() timings.write(f"iterating on vertices: {t1 - t0:.0f} s\n") t0 = t1 print("Iterating on edges...") 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 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: # 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.cost_function[edge] = cf t1 = time.perf_counter() timings.write(f"iterating on edges: {t1 - t0:.0f} s\n") t0 = t1 print("Saving...") if args.graphml: g.save('segments_cost_functions.graphml') else: g.save('segments_cost_functions.gt') print('All done') t1 = time.perf_counter() timings.write(f"saving: {t1 - t0:.0f} s\n") timings.close()