def __ranking_metrics_heuristic(topology, od_pairs=None): """ Sort OD pairs of a topology according to the Ranking Metrics Heuristics method Parameters ---------- topology : Topology or DirectedTopology The topology od_pairs : list, optional The OD pairs to be ranked (must be a subset of the OD pairs of the topology). If None, then the heuristic is calculated for all the OD pairs of the topology Returns ------- od_pairs : list The sorted list of OD pairs """ # Ranking Metrics Heuristic if od_pairs is None: od_pairs = od_pairs_from_topology(topology) fan_in, fan_out = fan_in_out_capacities(topology) degree = topology.degree() min_capacity = {(u, v): min(fan_out[u], fan_in[v]) for u, v in od_pairs} min_degree = {(u, v): min(degree[u], degree[v]) for u, v in od_pairs} # NFUR calculation is expensive, so before calculating it, the code # checks if it is really needed, i.e. if there are ties after capacity # and degree sorting cap_deg_pairs = [(min_capacity[(u, v)], min_degree[(u, v)]) for u, v in od_pairs] nfur_required = any((val > 1 for val in Counter(cap_deg_pairs).values())) if not nfur_required: # Sort all OD_pairs return sorted(od_pairs, key=lambda od_pair: (min_capacity[od_pair], min_degree[od_pair])) # if NFUR is required we calculate it. If fast is True, this function # returns the betweenness centrality rather than the NFUR. # Use betweenness centrality instead of NFUR if the topology is not trivial # for scalability reasons. # The threshold of 300 is a conservative value which allows fast execution # on most machines. parallelize = (topology.number_of_edges() > 100) fast = (topology.number_of_edges() > 300) nfur = __calc_nfur(topology, fast, parallelize) # Note: here we use the opposite of max rather than the inverse of max # (which is the formulation of the paper) because we only need to rank # in reverse order the max of NFURs. Since all NFURs are >=0, # using the opposite yields the same results as the inverse, but there # is no risk of incurring in divisions by 0. max_inv_nfur = {(u, v): -max(nfur[u], nfur[v]) for u, v in od_pairs} # Sort all OD_pairs return sorted( od_pairs, key=lambda od_pair: (min_capacity[od_pair], min_degree[od_pair], max_inv_nfur[od_pair]))
def __ranking_metrics_heuristic(topology, od_pairs=None): """ Sort OD pairs of a topology according to the Ranking Metrics Heuristics method Parameters ---------- topology : Topology or DirectedTopology The topology od_pairs : list, optional The OD pairs to be ranked (must be a subset of the OD pairs of the topology). If None, then the heuristic is calculated for all the OD pairs of the topology Returns ------- od_pairs : list The sorted list of OD pairs """ # Ranking Metrics Heuristic if od_pairs is None: od_pairs = od_pairs_from_topology(topology) fan_in, fan_out = fan_in_out_capacities(topology) degree = topology.degree() min_capacity = {(u, v): min(fan_out[u], fan_in[v]) for u, v in od_pairs} min_degree = {(u, v): min(degree[u], degree[v]) for u, v in od_pairs} # NFUR calculation is expensive, so before calculating it, the code # checks if it is really needed, i.e. if there are ties after capacity # and degree sorting cap_deg_pairs = [(min_capacity[(u, v)], min_degree[(u, v)]) for u, v in od_pairs] nfur_required = any((val > 1 for val in Counter(cap_deg_pairs).values())) if not nfur_required: # Sort all OD_pairs return sorted(od_pairs, key=lambda od_pair: (min_capacity[od_pair], min_degree[od_pair])) # if NFUR is required we calculate it. If fast is True, this function # returns the betweenness centrality rather than the NFUR. # Use betweenness centrality instead of NFUR if the topology is not trivial # for scalability reasons. # The threshold of 300 is a conservative value which allows fast execution # on most machines. parallelize = (topology.number_of_edges() > 100) fast = (topology.number_of_edges() > 300) nfur = __calc_nfur(topology, fast, parallelize) # Note: here we use the opposite of max rather than the inverse of max # (which is the formulation of the paper) because we only need to rank # in reverse order the max of NFURs. Since all NFURs are >=0, # using the opposite yields the same results as the inverse, but there # is no risk of incurring in divisions by 0. max_inv_nfur = {(u, v):-max(nfur[u], nfur[v]) for u, v in od_pairs} # Sort all OD_pairs return sorted(od_pairs, key=lambda od_pair: (min_capacity[od_pair], min_degree[od_pair], max_inv_nfur[od_pair]))
def validate_traffic_matrix(topology, traffic_matrix, validate_load=False): """ Validate whether a given traffic matrix and given topology are compatible. Returns True if they are compatible, False otherwise This validation includes validating whether the origin-destination pairs of the traffic matrix are coincide with or are a subset of the origin-destination pairs of the topology. Optionally, this function can verify if the volumes of the traffic matrix are compatible too, i.e. if at any time, no link has an utilization greater than 1.0. Parameters ---------- topology : topology The topology agains which the traffic matrix is validated tm : TrafficMatrix or TrafficMatrixSequence The traffic matrix (or sequence of) to be validated validate_load : bool, optional Specify whether load compatibility has to be validated or not. Default value is False Returns ------- is_valid : bool True if the topology and the traffic matrix are compatible, False otherwise """ if isinstance(traffic_matrix, TrafficMatrix): matrices = [traffic_matrix] elif isinstance(traffic_matrix, TrafficMatrixSequence): matrices = traffic_matrix.matrix else: raise ValueError('tm must be either a TrafficMatrix or a '\ ' TrafficMatrixSequence object') topology = topology.copy() if topology.is_directed() \ else topology.to_directed() od_pairs_topology = od_pairs_from_topology(topology) if validate_load: shortest_path = nx.all_pairs_dijkstra_path(topology, weight='weight') for matrix in matrices: od_pairs_tm = matrix.od_pairs() # verify that OD pairs in TM are equal or subset of topology if not all(((o, d) in od_pairs_topology for o, d in od_pairs_tm)): return False if validate_load: for u, v in topology.edges_iter(): topology.edge[u][v]['load'] = 0 capacity_unit = capacity_units[topology.graph['capacity_unit']] volume_unit = capacity_units[matrix.attrib['volume_unit']] norm_factor = float(volume_unit) / float(capacity_unit) for o, d in od_pairs_tm: path = shortest_path[o][d] if len(path) <= 1: continue for u, v in zip(path[:-1], path[1:]): topology.edge[u][v]['load'] += matrix.flow[o][d] max_u = max((norm_factor * float(topology.edge[u][v]['load']) \ / float(topology.edge[u][v]['capacity']) for u, v in topology.edges_iter())) if max_u > 1.0: return False return True
def static_traffic_matrix(topology, mean, stddev, max_u=0.9, origin_nodes=None, destination_nodes=None): """ Return a TrafficMatrix object, i.e. a single traffic matrix, representing the traffic volume exchanged over a network at a specific point in time This matrix is generated by assigning traffic volumes drawn from a lognormal distribution and assigned to specific origin-destination pairs using the Ranking Metrics Heuristic method proposed by Nucci et al. [1]_ Parameters ---------- topology : topology The topology for which the traffic matrix is calculated. This topology can either be directed or undirected. If it is undirected, this function assumes that all links are full-duplex. mean : float The mean volume of traffic among all origin-destination pairs stddev : float The standard deviation of volumes among all origin-destination pairs. max_u : float, optional Represent the max link utilization. If specified, traffic volumes are scaled so that the most utilized link of the network has an utilization equal to max_u. If None, volumes are not scaled, but in this case links may end up with an utilization factor greater than 1.0 origin_nodes : list, optional A list of all nodes which can be traffic sources. If not specified, all nodes of the topology are traffic sources destination_nodes : list, optional A list of all nodes which can be traffic destinations. If not specified, all nodes of the topology are traffic destinations Returns ------- tm : TrafficMatrix References ---------- .. [1] Nucci et al., The problem of synthetically generating IP traffic matrices: initial recommendations, ACM SIGCOMM Computer Communication Review, 35(3), 2005 """ try: mean = float(mean) stddev = float(stddev) except ValueError: raise ValueError('mean and stddev must be of type float') if mean < 0 or stddev < 0: raise ValueError('mean and stddev must be not negative') topology = topology.copy() if topology.is_directed() \ else topology.to_directed() volume_unit = topology.graph['capacity_unit'] mu = log(mean ** 2 / sqrt(stddev ** 2 + mean ** 2)) sigma = sqrt(log((stddev ** 2 / mean ** 2) + 1)) if origin_nodes is None and destination_nodes is None: od_pairs = od_pairs_from_topology(topology) else: all_nodes = topology.nodes() origins = origin_nodes or all_nodes destinations = destination_nodes or all_nodes od_pairs = [(o, d) for o in origins for d in destinations if o != d] nr_pairs = len(od_pairs) volumes = sorted(lognormal(mu, sigma, size=nr_pairs)) # volumes = sorted([lognormvariate(mu, sigma) for _ in range(nr_pairs)]) if any(isinf(vol) for vol in volumes): raise ValueError('Some volumes are too large to be handled by a '\ 'float type. Set a lower value of mu and try again.') sorted_od_pairs = __ranking_metrics_heuristic(topology, od_pairs) # check if the matrix matches and scale if needed assignments = dict(zip(sorted_od_pairs, volumes)) if max_u is not None: if origin_nodes is not None: shortest_path = dict( (node, nx.single_source_dijkstra_path(topology, node, weight='weight')) for node in origin_nodes) # remove OD pairs not connected for o, d in itertools.product(shortest_path, destinations): if o != d and d not in shortest_path[o]: od_pairs.remove((o, d)) else: shortest_path = nx.all_pairs_dijkstra_path(topology, weight='weight') for u, v in topology.edges_iter(): topology.edge[u][v]['load'] = 0.0 # Find max u for o, d in od_pairs: path = shortest_path[o][d] if len(path) > 1: for u, v in zip(path[:-1], path[1:]): topology.edge[u][v]['load'] += assignments[(o, d)] # Calculate scaling current_max_u = max((float(topology.edge[u][v]['load']) \ / float(topology.edge[u][v]['capacity']) for u, v in topology.edges_iter())) norm_factor = max_u / current_max_u for od_pair in assignments: assignments[od_pair] *= norm_factor # write to traffic matrix traffic_matrix = TrafficMatrix(volume_unit=volume_unit) for (o, d), flow in assignments.items(): traffic_matrix.add_flow(o, d, flow) return traffic_matrix
def validate_traffic_matrix(topology, traffic_matrix, validate_load=False): """ Validate whether a given traffic matrix and given topology are compatible. Returns True if they are compatible, False otherwise This validation includes validating whether the origin-destination pairs of the traffic matrix are coincide with or are a subset of the origin-destination pairs of the topology. Optionally, this function can verify if the volumes of the traffic matrix are compatible too, i.e. if at any time, no link has an utilization greater than 1.0. Parameters ---------- topology : topology The topology agains which the traffic matrix is validated tm : TrafficMatrix or TrafficMatrixSequence The traffic matrix (or sequence of) to be validated validate_load : bool, optional Specify whether load compatibility has to be validated or not. Default value is False Returns ------- is_valid : bool True if the topology and the traffic matrix are compatible, False otherwise """ if isinstance(traffic_matrix, TrafficMatrix): matrices = [traffic_matrix] elif isinstance(traffic_matrix, TrafficMatrixSequence): matrices = traffic_matrix.matrix else: raise ValueError('tm must be either a TrafficMatrix or a '\ ' TrafficMatrixSequence object') topology = topology.copy() if topology.is_directed() \ else topology.to_directed() od_pairs_topology = od_pairs_from_topology(topology) if validate_load: shortest_path = nx.all_pairs_dijkstra_path(topology, weight='weight') for matrix in matrices: od_pairs_tm = matrix.od_pairs() # verify that OD pairs in TM are equal or subset of topology if not all(((o, d) in od_pairs_topology for o, d in od_pairs_tm)): return False if validate_load: for u, v in topology.edges_iter(): topology.edge[u][v]['load'] = 0 capacity_unit = capacity_units[topology.graph['capacity_unit']] volume_unit = capacity_units[matrix.attrib['volume_unit']] norm_factor = float(capacity_unit)/float(volume_unit) for o, d in od_pairs_tm: path = shortest_path[o][d] if len(path) <= 1: continue for hop in range(len(path) - 1): topology.edge[path[hop]][path[hop + 1]]['load'] \ += matrix.flow[o][d] max_u = max((norm_factor * float(topology.edge[u][v]['load']) \ / float(topology.edge[u][v]['capacity']) for u, v in topology.edges_iter())) if max_u > 1.0: return False return True
def static_traffic_matrix(topology, mean, stddev, max_u=0.9, origin_nodes=None, destination_nodes=None): """ Return a TrafficMatrix object, i.e. a single traffic matrix, representing the traffic volume exchanged over a network at a specific point in time This matrix is generated by assigning traffic volumes drawn from a lognormal distribution and assigned to specific origin-destination pairs using the Ranking Metrics Heuristic method proposed by Nucci et al. [1]_ Parameters ---------- topology : topology The topology for which the traffic matrix is calculated. This topology can either be directed or undirected. If it is undirected, this function assumes that all links are full-duplex. mean : float The mean volume of traffic among all origin-destination pairs stddev : float The standard deviation of volumes among all origin-destination pairs. max_u : float, optional Represent the max link utilization. If specified, traffic volumes are scaled so that the most utilized link of the network has an utilization equal to max_u. If None, volumes are not scaled, but in this case links may end up with an utilization factor greater than 1.0 origin_nodes : list, optional A list of all nodes which can be traffic sources. If not specified, all nodes of the topology are traffic sources destination_nodes : list, optional A list of all nodes which can be traffic destinations. If not specified, all nodes of the topology are traffic destinations Returns ------- tm : TrafficMatrix References ---------- .. [1] Nucci et al., The problem of synthetically generating IP traffic matrices: initial recommendations, ACM SIGCOMM Computer Communication Review, 35(3), 2005 """ try: mean = float(mean) stddev = float(stddev) except ValueError: raise ValueError('mean and stddev must be of type float') if mean < 0 or stddev < 0: raise ValueError('mean and stddev must be not negative') topology = topology.copy() if topology.is_directed() \ else topology.to_directed() volume_unit = topology.graph['capacity_unit'] mu = log(mean**2/sqrt(stddev**2 + mean**2)) sigma = sqrt(log((stddev**2/mean**2) + 1)) if origin_nodes is None and destination_nodes is None: od_pairs = od_pairs_from_topology(topology) else: all_nodes = topology.nodes() origins = origin_nodes if origin_nodes is not None \ else all_nodes destinations = destination_nodes if destination_nodes is not None \ else all_nodes od_pairs = [(o, d) for o in origins for d in destinations if o != d] nr_pairs = len(od_pairs) volumes = sorted(lognormal(mu, sigma, size=nr_pairs)) #volumes = sorted([lognormvariate(mu, sigma) for _ in range(nr_pairs)]) if any(isinf(vol) for vol in volumes): raise ValueError('Some volumes are too large to be handled by a '\ 'float type. Set a lower value of mu and try again.') sorted_od_pairs = __ranking_metrics_heuristic(topology, od_pairs) # check if the matrix matches and scale if needed assignments = dict(zip(sorted_od_pairs, volumes)) if max_u is not None: if origin_nodes is not None: shortest_path = dict( (node, nx.single_source_dijkstra_path(topology, node, weight='weight')) for node in origin_nodes) # remove OD pairs not connected for o in shortest_path: for d in destinations: if o != d and d not in shortest_path[o]: od_pairs.remove((o, d)) else: shortest_path = nx.all_pairs_dijkstra_path(topology, weight='weight') for u, v in topology.edges_iter(): topology.edge[u][v]['load'] = 0.0 # Find max u for o, d in od_pairs: path = shortest_path[o][d] if len(path) > 1: for hop in range(len(path) - 1): topology.edge[path[hop]][path[hop + 1]]['load'] \ += assignments[(o, d)] # Calculate scaling current_max_u = max((float(topology.edge[u][v]['load']) \ /float(topology.edge[u][v]['capacity']) for u, v in topology.edges_iter())) norm_factor = max_u/current_max_u for od_pair in assignments: assignments[od_pair] *= norm_factor # write to traffic matrix traffic_matrix = TrafficMatrix(volume_unit=volume_unit) for (o, d), flow in assignments.items(): traffic_matrix.add_flow(o, d, flow) return traffic_matrix