def test_remove_edge(dag_object: pp.DAG): dag_object.remove_edge('b', 'f') dag_object.remove_edge('b', 'e') assert 'f' in dag_object.roots assert 'e' in dag_object.roots assert 'e' in dag_object.leafs assert 'e' in dag_object.isolate_nodes()
def test_route_to_node(dag_object: pp.DAG): route_i = dag_object.routes_to_node('i') assert route_i == [['h', 'i']] route_e = dag_object.routes_to_node('e') assert route_e == [['a', 'b', 'e'], ['a', 'c', 'b', 'e']] route_g = list(sorted(dag_object.routes_to_node('g'))) assert len(route_g) == 3 expected = list(sorted([['a', 'c', 'g'], ['a', 'b', 'f', 'g'], ['a', 'c', 'b', 'f', 'g']])) for rout, e_route in zip(route_g, expected): assert rout == e_route
def generate_causal_tree(dag, root, node_map): """ For a directed acyclic graph and a non-injective mapping of nodes, this method creates a *causal tree* for a given root node. This is useful for the extraction of causal paths in time-unfolded DAG representations of temporal networks. The nodes "{v}_{d}" in the resulting causal tree capture that - starting from the root node at step 0 - there is a causal path to node v at distance d from the root. Note that the same node can be represented by multiple nodes in the causal tree (at different distances d). """ causal_tree = DAG() causal_mapping = {} visited = defaultdict(lambda: False) queue = deque() # launch breadth-first-search at root of tree # root nodes are necessarily at depth 0 queue.append((root, 0)) edges = [] while queue: # take out left-most element from FIFO queue v, depth = queue.popleft() # x is the node ID of the node in the causal tree # the second component captures the distance from # the root of the causal tree. These IDs ensure # that the same physical nodes can occur at different # distances from the root x = '{0}_{1}'.format(node_map[v], depth) causal_mapping[x] = node_map[v] # process nodes at next level for w in dag.successors[v]: if (w, depth+1) not in queue: queue.append((w, depth+1)) # only consider nodes that have not already # been added to this level if not visited[node_map[w], depth+1]: # add edge to causal tree y = '{0}_{1}'.format(node_map[w], depth+1) edges.append((x, y)) visited[node_map[w], depth+1] = True causal_mapping[y] = node_map[w] # Adding all edges at once is more efficient! causal_tree.add_edges(edges) return causal_tree, causal_mapping
def test_add_edges(edges, types): roots, neither, leafs = types from pathpy import DAG D = DAG(edges=edges) assert D.leafs == leafs assert D.roots == roots assert neither not in leafs assert neither not in roots
def sample_paths_from_temporal_network_dag(tempnet, delta=1, num_roots=1, max_subpath_length=None): """ Estimates the frequency of causal paths in a temporal network assuming a maximum temporal distance of delta between consecutive time-stamped links on a path. This method first creates a directed and acyclic time-unfolded graph based on the given parameter delta. This directed acyclic graph is used to calculate causal paths for a given delta, randomly sampling num_roots roots in the time-unfolded DAG. Parameters ---------- tempnet : pathpy.TemporalNetwork TemporalNetwork to extract the time-respecting paths from delta : int Indicates the maximum temporal distance up to which time-stamped links will be considered to contribute to a causal path. For (u,v;3) and (v,w;7) a causal path (u,v,w) is generated for 0 < delta <= 4, while no causal path is generated for delta > 4. Every time-stamped edge is a causal path of length one. Default value is 1. num_roots : int The number of randomly chosen roots that will be used to estimate path statistics. Returns ------- Paths An instance of the class Paths, which can be used to generate higher- and multi-order models of causal paths in temporal networks. """ # generate a single time-unfolded DAG Log.add('Constructing time-unfolded DAG ...') dag, node_map = DAG.from_temporal_network(tempnet, delta) # dag.topsort() # assert dag.is_acyclic Log.add('finished.') print(dag) causal_paths = Paths() # For each root in the time-unfolded DAG, we generate a # causal tree and use it to count all causal paths # that originate at this root current_root = 1 Log.add('Generating causal trees for {0} root nodes ...'.format(num_roots)) import random for root in random.sample(dag.roots, num_roots): causal_tree, causal_mapping = generate_causal_tree(dag, root, node_map) if num_roots > 10: step = num_roots/10 if current_root % step == 0: Log.add('Analyzing tree {0}/{1} ...'.format(current_root, num_roots)) # elevate Logging level x = Log.min_severity Log.set_min_severity(Severity.WARNING) # calculate all unique longest path in causal tree causal_paths += paths_from_dag(causal_tree, causal_mapping, repetitions=False, max_subpath_length=max_subpath_length) current_root += 1 # restore log level Log.set_min_severity(x) Log.add('finished.') return causal_paths
def paths_from_temporal_network_dag(tempnet, delta=1, max_subpath_length=None): """ Calculates the frequency of causal paths in a temporal network assuming a maximum temporal distance of delta between consecutive time-stamped links on a path. This method first creates a directed and acyclic time-unfolded graph based on the given parameter delta. This directed acyclic graph is used to calculate all time-respecting paths for a given delta. I.e., for time-stamped links (a,b,1), (b,c,5), (b,d,7) and delta = 5 the time-respecting path (a,b,c) will be found. Parameters ---------- tempnet : pathpy.TemporalNetwork TemporalNetwork to extract the time-respecting paths from delta : int Indicates the maximum temporal distance up to which time-stamped links will be considered to contribute to a causal path. For (u,v;3) and (v,w;7) a causal path (u,v,w) is generated for 0 < delta <= 4, while no causal path is generated for delta > 4. Every time-stamped edge is a causal path of length one. Default value is 1. max_subpath_length : int Can be used to limit the calculation of sub path statistics to a given maximum length. This is useful as statistics of sub paths of length k are only needed to fit higher-order model with order k and larger. If model selection is limited to a maximum order K, we can set the maximum sub path length to K. Default is None, which means all subpaths are calculated. Returns ------- Paths An instance of the class Paths, which can be used to generate higher- and multi-order models of causal paths in temporal networks. Examples --------- >>> t = pp.TemporalNetwork() >>> t.add_edge('a', 'b', 1) >>> t.add_edge('b', 'a', 3) >>> t.add_edge('b', 'c', 3) >>> t.add_edge('d', 'c', 4) >>> t.add_edge('c', 'd', 5) >>> t.add_edge('c', 'b', 6) >>> >>>causal_paths = pp.path_extraction.paths_from_temporal_network_dag(t, delta=2) >>> [Severity.INFO] Constructing time-unfolded DAG ... >>> [Severity.INFO] finished. >>> [Severity.INFO] Generating causal trees for 2 root nodes ... >>> [Severity.INFO] finished. >>> print(causal_paths) >>> Total path count: 4.0 >>> [Unique / Sub paths / Total]: [4.0 / 24.0 / 28.0] >>> Nodes: 4 >>> Edges: 6 >>> Max. path length: 3 >>> Avg path length: 2.25 >>> Paths of length k = 0 0.0 [ 0.0 / 13.0 / 13.0 ] >>> Paths of length k = 1 0.0 [ 0.0 / 9.0 / 9.0 ] >>> Paths of length k = 2 3.0 [ 3.0 / 2.0 / 5.0 ] >>> Paths of length k = 3 1.0 [ 1.0 / 0.0 / 1.0 ] >>> The calculated (longest) causal paths in this example are: >>> (a, b, c, d), (d, c, b), (d, c, d), (a, b, a) """ # generate a single time-unfolded DAG Log.add('Constructing time-unfolded DAG ...') dag, node_map = DAG.from_temporal_network(tempnet, delta) Log.add('finished.') print(dag) causal_paths = Paths() # For each root in the time-unfolded DAG, we generate a # causal tree and use it to count all causal paths # that originate at this root num_roots = len(dag.roots) current_root = 1 Log.add('Generating causal trees for {0} root nodes ...'.format(num_roots)) for root in dag.roots: causal_tree, causal_mapping = generate_causal_tree(dag, root, node_map) if num_roots > 10: step = num_roots/10 if current_root % step == 0: Log.add('Analyzing tree {0}/{1} ...'.format(current_root, num_roots)) # elevate Logging level x = Log.min_severity Log.set_min_severity(Severity.WARNING) # calculate all unique longest path in causal tree causal_paths += paths_from_dag(causal_tree, causal_mapping, repetitions=False, max_subpath_length=max_subpath_length) current_root += 1 # restore log level Log.set_min_severity(x) Log.add('finished.') return causal_paths
def test_route_from_node(dag_object: pp.DAG): root = 'a' routes = list(sorted(dag_object.routes_from_node(root))) expected = [['a', 'b', 'e'], ['a', 'b', 'f', 'g'], ['a', 'c', 'b', 'e'], ['a', 'c', 'b', 'f', 'g'], ['a', 'c', 'g']] assert routes == expected
def test_dag_path_extraction_cyclic(dag_object: pp.DAG): dag_object.add_edge('g', 'a') # adds a cycle to the dag object with pytest.raises(ValueError): pp.path_extraction.paths_from_dag(dag_object)