def test_notarborescence1(): # Not an arborescence due to not spanning. G = nx.MultiDiGraph() G.add_nodes_from(range(10)) G.add_edges_from([(0,1),(0,2),(1,3),(5,6)]) assert_true(nx.is_branching(G)) assert_false(nx.is_arborescence(G))
def test_notbranching1(): # Acyclic violation. G = nx.MultiDiGraph() G.add_nodes_from(range(10)) G.add_edges_from([(0,1),(1,0)]) assert_false(nx.is_branching(G)) assert_false(nx.is_arborescence(G))
def test_notbranching2(): # In-degree violation. G = nx.MultiDiGraph() G.add_nodes_from(range(10)) G.add_edges_from([(0,1),(0,2),(3,2)]) assert_false(nx.is_branching(G)) assert_false(nx.is_arborescence(G))
def test_notbranching1(): # Acyclic violation. G = nx.MultiDiGraph() G.add_nodes_from(range(10)) G.add_edges_from([(0, 1), (1, 0)]) assert not nx.is_branching(G) assert not nx.is_arborescence(G)
def test_notbranching2(): # In-degree violation. G = nx.MultiDiGraph() G.add_nodes_from(range(10)) G.add_edges_from([(0, 1), (0, 2), (3, 2)]) assert not nx.is_branching(G) assert not nx.is_arborescence(G)
def test_notarborescence1(): # Not an arborescence due to not spanning. G = nx.MultiDiGraph() G.add_nodes_from(range(10)) G.add_edges_from([(0, 1), (0, 2), (1, 3), (5, 6)]) assert nx.is_branching(G) assert not nx.is_arborescence(G)
def test_notarborescence2(): # Not an arborescence due to in-degree violation. G = nx.MultiDiGraph() nx.add_path(G, range(5)) G.add_edge(6, 4) assert not nx.is_branching(G) assert not nx.is_arborescence(G)
def test_notarborescence2(): # Not an arborescence due to in-degree violation. G = nx.MultiDiGraph() nx.add_path(G, range(5)) G.add_edge(6, 4) assert_false(nx.is_branching(G)) assert_false(nx.is_arborescence(G))
def test_multiple_roots(self): """Tests that a directed acyclic graph with multiple degree zero nodes creates an arborescence with multiple (weakly) connected components. """ G = nx.DiGraph([(0, 1), (0, 2), (1, 3), (2, 3), (5, 2)]) B = nx.dag_to_branching(G) expected = nx.DiGraph([(0, 1), (1, 3), (0, 2), (2, 4), (5, 6), (6, 7)]) assert_true(nx.is_branching(B)) assert_false(nx.is_arborescence(B)) assert_true(nx.is_isomorphic(B, expected))
def test_multiple_roots(self): """Tests that a directed acyclic graph with multiple degree zero nodes creates an arborescence with multiple (weakly) connected components. """ G = nx.DiGraph([(0, 1), (0, 2), (1, 3), (2, 3), (5, 2)]) B = nx.dag_to_branching(G) expected = nx.DiGraph([(0, 1), (1, 3), (0, 2), (2, 4), (5, 6), (6, 7)]) assert nx.is_branching(B) assert not nx.is_arborescence(B) assert nx.is_isomorphic(B, expected)
def tree(self): rslt = {} rslt['is_tree'] = nx.is_tree(self.graph) rslt['is_forest'] = nx.is_forest(self.graph) if self.directed == 'directed': rslt['is_arborescence'] = nx.is_arborescence(self.graph) rslt['is_branching'] = nx.is_branching(self.graph) fname_tree = self.DIR + '/tree.json' with open(fname_tree, "w") as f: json.dump(rslt, f, cls=SetEncoder, indent=2) print(fname_tree)
def analysis_lsde(file_path,address,flag): #read gpickle G = nx.DiGraph(directed=True) G = nx.read_gpickle(file_path) important_nodes=[address]# import important nodes ##########################for nodes(addresses) take_value_nodes=fl.fun_nodes(G) #nodes_info,clu_coe_filter,avg_coe,cen_in_filter,cen_out_filter,degree_sort nodes_info=take_value_nodes[0] #print(take_value_nodes[1]) ########################for edges(transaction records) take_value_edges=fl.fun_edges(G,important_nodes) #edgesinfo,(important nodes info, important nodes first time&last time&time difference),t-value(total)dataframe,polynomial parameters edges_info=take_value_edges[0] #print(take_value_edges[1][1]) #print(take_value_edges[1][0]) #print(edges_info) ########################for graph characteristics graph_charac=fl.graph_characteristic(G,nodes_info,edges_info) #addr_total_number,trans_total_number,value_max,value_min,value_average,value_median,value_onefourth,value_threefourth ########################for specific nodes specific_node_charac = fl.analy_specific_node(important_nodes[0],nodes_info,edges_info) #somenode,somenode_as_from,somenode_as_to,connected addr info(as from),connected addr info2(as to) ########################for subgraph #G2=fl.subgraph_total(G)#subgraph ########################summary&analyze feature_dic = {} addr_total_number=graph_charac[0] trans_total_number=graph_charac[1] #show in dic feature_dic['Total addresses'] = addr_total_number feature_dic['Total Transaction'] = trans_total_number feature_dic['Normality check(p-value)'] = stats.shapiro(edges_info.Value)[1]#if p > 0.001, transaction value meet the normality requirements feature_dic['Significance test(p-value)'] = stats.pearsonr(edges_info.Time,edges_info.Value)[1] #if p>0.01, time and value has the high significance relationship, i.e. time and value have high relativity print('24/35 successful!') feature_dic['Percentage of cluster coefficient>0 addresses'] = 100*(take_value_nodes[1].shape[0])/addr_total_number feature_dic['Average cluster coefficient of the whole graph'] = take_value_nodes[2] feature_dic['Percentage of in centrality>0.01 addresses']= 100*(take_value_nodes[3].shape[0])/addr_total_number feature_dic['Percentage of out centrality>0.01 addresses']= 100*(take_value_nodes[4].shape[0])/addr_total_number feature_dic['Max value']=graph_charac[2] feature_dic['Min value']=graph_charac[3] feature_dic['Average value']=graph_charac[4] feature_dic['Median value']=graph_charac[5] feature_dic['One fourth value']=graph_charac[6] feature_dic['Three fourth value']=graph_charac[7] feature_dic['The max degree of all addresses'] = len(take_value_nodes[5])-1 # feature_dic['Polynomial parameters']=take_value_edges[3] ttt1 = len(take_value_edges[1][0][0]) feature_dic['Important nodes information'] = ttt1 feature_dic['Important nodes start-time']=take_value_edges[1][1][0][0] feature_dic['Important nodes end-time']=take_value_edges[1][1][0][1] feature_dic['Important nodes time difference']=take_value_edges[1][1][0][2] ttt2=len(specific_node_charac[1]) ttt3=len(specific_node_charac[2]) if ttt1 ==0: feature_dic['This node as from']=0 feature_dic['This node as to']=0 else: feature_dic['This node as from']=ttt2/ttt1 feature_dic['This node as to']=ttt3/ttt1 if specific_node_charac[4]==0: feature_dic['This node connected with(as from)']=0 else: feature_dic['This node connected with(as from)']=ttt2/specific_node_charac[4] if specific_node_charac[3]==0: feature_dic['This node connected with(as to)']=0 else: feature_dic['This node connected with(as to)']=ttt3/specific_node_charac[3] #print(take_value_edges[1][1][0]) #print(len(take_value_edges[1][0][0])) #print(specific_node_charac[3]) if flag == True: add_feature1=nx.pagerank(G) iadd1=0 for v in add_feature1.values(): if v>0.01: iadd1=iadd1+1 # print(iadd1) add_feature2=nx.transitivity(G)#Compute graph transitivity, the fraction of all possible triangles present in G. #Possible triangles are identified by the number of “triads” (two edges with a shared vertex). #print(add_feature2) print('26/35 successful!') add_feature3=nx.degree_assortativity_coefficient(G)#Assortativity measures the similarity of connections in the graph with respect to the node degree. add_feature4=nx.is_chordal(G.to_undirected())#A graph is chordal if every cycle of length at least 4 has a chord (an edge joining two nodes not adjacent in the cycle). print('28/35 successful!') #print(add_feature4) add_feature5=nx.is_weakly_connected(G) add_feature6=nx.is_strongly_connected(G) print('30/35 successful!') add_feature7=nx.global_efficiency(G.to_undirected())#The efficiency of a pair of nodes in a graph is the multiplicative inverse of the shortest path distance between the nodes. # The average global efficiency of a graph is the average efficiency of all pairs of nodes add_feature8=nx.is_branching(G) print('32/35 successful!') add_feature9=sorted(nx.immediate_dominators(G, address).items()) add_domin=len(add_feature9) add_feature10=nx.is_simple_path(G, address)#A simple path in a graph is a nonempty sequence of nodes in which no node appears more than once in the sequence, # and each adjacent pair of nodes in the sequence is adjacent in the graph. print('34/35 successful!') add_feature11=nx.overall_reciprocity(G)# reciprocity for the whole graph print('35/35 successful!') #print(add_feature11) feature_dic['Percentage of page Rank >0.01 address'] = iadd1/addr_total_number feature_dic['Transitivity']=add_feature2 feature_dic['Assortativity']=add_feature3 feature_dic['Chordal']=add_feature4 feature_dic['Strongly connected']=add_feature5 feature_dic['Weakly connected']=add_feature6 feature_dic['Global efficiency']=add_feature7 feature_dic['Is branching'] = add_feature8 feature_dic['Immediate dominator(numbers)']=add_domin feature_dic['Is simple path']=add_feature10 feature_dic['Reciprocity']=add_feature11 Header=['Total addresses','Total Transaction','Normality check(p-value)','Significance test(p-value)','Percentage of cluster coefficient>0 addresses', 'Average cluster coefficient of the whole graph','Percentage of in centrality>0.01 addresses','Percentage of out centrality>0.01 addresses', 'Max value','Min value','Average value','Median value','One fourth value','Three fourth value','The max degree of all addresses', 'Important nodes information','Important nodes start-time','Important nodes end-time','Important nodes time difference','This node as from','This node as to', 'This node connected with(as from)','This node connected with(as to)','Percentage of page Rank >0.01 address','Transitivity','Assortativity','Chordal','Strongly connected', 'Weakly connected','Global efficiency','Is branching','Immediate dominator(numbers)','Is simple path','Reciprocity'] else: print('Finished') Header=['Total addresses','Total Transaction','Normality check(p-value)','Significance test(p-value)','Percentage of cluster coefficient>0 addresses', 'Average cluster coefficient of the whole graph','Percentage of in centrality>0.01 addresses','Percentage of out centrality>0.01 addresses', 'Max value','Min value','Average value','Median value','One fourth value','Three fourth value','The max degree of all addresses', 'Important nodes information','Important nodes start-time','Important nodes end-time','Important nodes time difference','This node as from','This node as to', 'This node connected with(as from)','This node connected with(as to)'] #print(somenodesinfo_dic) #res=stats.pearsonr(edges_info.Time,edges_info.Value) #print(res) end=time.time() print('running time =',end-start) return feature_dic,Header
print(nx.algorithms.tree.is_forest(G=circuit)) residential = nx.algorithms.cut_size(G=circuit, S={i for i in range(1, 6)}, T={j for j in range(14, 16)}) print(residential) print(nx.algorithms.is_directed_acyclic_graph(G=circuit)) branching = nx.algorithms.dag_to_branching(G=circuit) nx.draw_planar(G=branching, with_labels=True, node_color='g', node_size=800, font_size=14, width=0.8) plt.show() print('\n', nx.algorithms.maximum_branching(G=circuit)) print('\n') print(nx.is_tree(circuit), '\n') print(nx.is_forest(circuit), '\n') print(nx.is_directed_acyclic_graph(circuit), '\n') print(nx.is_arborescence(circuit), '\n') print(nx.is_branching(circuit))
def ScenarioTreeModelFromNetworkX( tree, node_name_attribute=None, edge_probability_attribute='probability', stage_names=None, scenario_name_attribute=None): """ Create a scenario tree model from a networkx tree. The height of the tree must be at least 1 (meaning at least 2 stages). Optional Arguments: - node_name_attribute: By default, node names are the same as the node hash in the networkx tree. This keyword can be set to the name of some property of nodes in the graph that will be used for their name in the PySP scenario tree. - edge_probability_attribute: Can be set to the name of some property of edges in the graph that defines the conditional probability of that branch (default: 'probability'). If this keyword is set to None, then all branches leaving a node are assigned equal conditional probabilities. - stage_names: Can define a list of stage names to use (assumed in time order). The length of this list much match the number of stages in the tree. - scenario_name_attribute: By default, scenario names are the same as the leaf-node hash in the networkx tree. This keyword can be set to the name of some property of leaf-nodes in the graph that will be used for their corresponding scenario in the PySP scenario tree. Examples: - A 2-stage scenario tree with 10 scenarios: G = networkx.DiGraph() G.add_node("Root") N = 10 for i in range(N): node_name = "Leaf"+str(i) G.add_node(node_name) G.add_edge("Root",node_name,probability=1.0/N) model = ScenarioTreeModelFromNetworkX(G) - A 4-stage scenario tree with 125 scenarios: branching_factor = 5 height = 3 G = networkx.balanced_tree( branching_factory, height, networkx.DiGraph()) model = ScenarioTreeModelFromNetworkX( G, edge_probability_attribute=None) """ if not has_networkx: raise ValueError("networkx module is not available") if not networkx.is_tree(tree): raise TypeError( "object is not a tree (see networkx.is_tree)") if not networkx.is_directed(tree): raise TypeError( "object is not directed (see networkx.is_directed)") if not networkx.is_branching(tree): raise TypeError( "object is not a branching (see networkx.is_branching") if not networkx.is_arborescence(tree): raise TypeError("Object must be a directed, rooted tree " "in which all edges point away from the " "root (see networkx.is_arborescence)") in_degree_items = tree.in_degree() # Prior to networkx ~2.0, in_degree() returned a dictionary. # Now it is a view on items, so only call .items() for the old case if hasattr(in_degree_items, 'items'): in_degree_items = in_degree_items.items() root = [u for u,d in in_degree_items if d == 0] assert len(root) == 1 root = root[0] num_stages = networkx.eccentricity(tree, v=root) + 1 if num_stages < 2: raise ValueError( "The number of stages must be at least 2") m = CreateAbstractScenarioTreeModel() if stage_names is not None: unique_stage_names = set() for cnt, stage_name in enumerate(stage_names,1): m.Stages.add(stage_name) unique_stage_names.add(stage_name) if cnt != num_stages: raise ValueError( "incorrect number of stages names (%s), should be %s" % (cnt, num_stages)) if len(unique_stage_names) != cnt: raise ValueError("all stage names were not unique") else: for i in range(num_stages): m.Stages.add('Stage'+str(i+1)) node_to_name = {} node_to_scenario = {} def _setup(u, succ): if node_name_attribute is not None: if node_name_attribute not in tree.node[u]: raise KeyError( "node '%s' missing name attribute: '%s'" % (u, node_name_attribute)) node_name = tree.node[u][node_name_attribute] else: node_name = u node_to_name[u] = node_name m.Nodes.add(node_name) if u in succ: for v in succ[u]: _setup(v, succ) else: # a leaf node if scenario_name_attribute is not None: if scenario_name_attribute not in tree.node[u]: raise KeyError( "node '%s' missing attribute: '%s'" % (u, scenario_name_attribute)) scenario_name = tree.node[u][scenario_name_attribute] else: scenario_name = u node_to_scenario[u] = scenario_name m.Scenarios.add(scenario_name) _setup(root, networkx.dfs_successors(tree, root)) m = m.create_instance() def _add_node(u, stage, succ, pred): if node_name_attribute is not None: if node_name_attribute not in tree.node[u]: raise KeyError( "node '%s' missing name attribute: '%s'" % (u, node_name_attribute)) node_name = tree.node[u][node_name_attribute] else: node_name = u m.NodeStage[node_name] = m.Stages[stage] if u == root: m.ConditionalProbability[node_name] = 1.0 else: assert u in pred # prior to networkx ~2.0, we used a .edge attribute on DiGraph, # which no longer exists. if hasattr(tree, 'edge'): edge = tree.edge[pred[u]][u] else: edge = tree.edges[pred[u],u] probability = None if edge_probability_attribute is not None: if edge_probability_attribute not in edge: raise KeyError( "edge '(%s, %s)' missing probability attribute: '%s'" % (pred[u], u, edge_probability_attribute)) probability = edge[edge_probability_attribute] else: probability = 1.0/len(succ[pred[u]]) m.ConditionalProbability[node_name] = probability if u in succ: child_names = [] for v in succ[u]: child_names.append( _add_node(v, stage+1, succ, pred)) total_probability = 0.0 for child_name in child_names: m.Children[node_name].add(child_name) total_probability += \ value(m.ConditionalProbability[child_name]) if abs(total_probability - 1.0) > 1e-5: raise ValueError( "edge probabilities leaving node '%s' " "do not sum to 1 (total=%r)" % (u, total_probability)) else: # a leaf node scenario_name = node_to_scenario[u] m.ScenarioLeafNode[scenario_name] = node_name m.Children[node_name].clear() return node_name _add_node(root, 1, networkx.dfs_successors(tree, root), networkx.dfs_predecessors(tree, root)) return m
def ScenarioTreeModelFromNetworkX( tree, node_name_attribute=None, edge_probability_attribute='weight', stage_names=None, scenario_name_attribute=None): """ Create a scenario tree model from a networkx tree. The height of the tree must be at least 1 (meaning at least 2 stages). Required node attributes: - cost (str): A string identifying a component on the model whose value indicates the cost at the time stage of the node for any scenario traveling through it. Optional node attributes: - variables (list): A list of variable identifiers that will be tracked by the node. If the node is not a leaf node, these indicate variables with non-anticipativity constraints. - derived_variables (list): A list of variable or expression identifiers that will be tracked by the node (but will never have non-anticipativity constraints enforced). - bundle: A bundle identifier for the scenario defined by a leaf-stage node. This attribute is ignored on non-terminal tree nodes. This attribute appears on at least one leaf-stage node (and is not set to :const:`None`), then it must be set on all leaf-stage nodes (to something other than :const:`None`); otherwise, an exception will be raised. Optional edge attributes: - weight (float): Indicates the conditional probability of moving from the parent node to the child node in the directed edge. If not present, it will be assumed that all edges leaving the parent node have equal probability (normalized to sum to one). Args: stage_names: Can define a list of stage names to use (assumed in time order). The length of this list much match the number of stages in the tree. The default value of :const:`None` indicates that stage names should be automatically generated in with the form ['Stage1','Stage2',...]. node_name_attribute: By default, node names are the same as the node hash in the networkx tree. This keyword can be set to the name of some property of nodes in the graph that will be used for their name in the PySP scenario tree. scenario_name_attribute: By default, scenario names are the same as the leaf-node hash in the networkx tree. This keyword can be set to the name of some property of leaf-nodes in the graph that will be used for their corresponding scenario name in the PySP scenario tree. edge_probability_attribute: Can be set to the name of some property of edges in the graph that defines the conditional probability of that branch (default: 'weight'). If this keyword is set to :const:`None`, then all branches leaving a node are assigned equal conditional probabilities. Examples: A 2-stage scenario tree with 10 scenarios grouped into 2 bundles: >>> G = networkx.DiGraph() >>> G.add_node("root", variables=["x"]) >>> N = 10 >>> for i in range(N): >>> node_name = "s"+str(i) >>> bundle_name = "b"+str(i%2) >>> G.add_node(node_name, bundle=bundle) >>> G.add_edge("root", node_name, weight=1.0/N) >>> model = ScenarioTreeModelFromNetworkX(G) A 4-stage scenario tree with 125 scenarios: >>> branching_factor = 5 >>> height = 3 >>> G = networkx.balanced_tree( branching_factor, height, networkx.DiGraph()) >>> model = ScenarioTreeModelFromNetworkX(G) """ if not has_networkx: #pragma:nocover raise ValueError( "networkx module is not available") if not networkx.is_tree(tree): raise TypeError( "Graph object is not a tree " "(see networkx.is_tree)") if not networkx.is_directed(tree): raise TypeError( "Graph object is not directed " "(see networkx.is_directed)") if not networkx.is_branching(tree): raise TypeError( "Grapn object is not a branching " "(see networkx.is_branching") in_degree_items = tree.in_degree() # Prior to networkx ~2.0, in_degree() returned a dictionary. # Now it is a view on items, so only call .items() for the old case if hasattr(in_degree_items, 'items'): in_degree_items = in_degree_items.items() root = [u for u,d in in_degree_items if d == 0] assert len(root) == 1 root = root[0] num_stages = networkx.eccentricity(tree, v=root) + 1 if num_stages < 2: raise ValueError( "The number of stages must be at least 2") m = CreateAbstractScenarioTreeModel() if stage_names is not None: unique_stage_names = set() for cnt, stage_name in enumerate(stage_names,1): m.Stages.add(stage_name) unique_stage_names.add(stage_name) if cnt != num_stages: raise ValueError( "incorrect number of stages names (%s), should be %s" % (cnt, num_stages)) if len(unique_stage_names) != cnt: raise ValueError("all stage names were not unique") else: for i in range(num_stages): m.Stages.add('Stage'+str(i+1)) node_to_name = {} node_to_scenario = {} scenario_bundle = {} def _setup(u, succ): if node_name_attribute is not None: if node_name_attribute not in tree.node[u]: raise KeyError( "node '%s' missing node name " "attribute: '%s'" % (u, node_name_attribute)) node_name = tree.node[u][node_name_attribute] else: node_name = u node_to_name[u] = node_name m.Nodes.add(node_name) if u in succ: for v in succ[u]: _setup(v, succ) else: # a leaf node if scenario_name_attribute is not None: if scenario_name_attribute not in tree.node[u]: raise KeyError( "node '%s' missing scenario name " "attribute: '%s'" % (u, scenario_name_attribute)) scenario_name = tree.node[u][scenario_name_attribute] else: scenario_name = u node_to_scenario[u] = scenario_name m.Scenarios.add(scenario_name) scenario_bundle[scenario_name] = \ tree.node[u].get('bundle', None) _setup(root, networkx.dfs_successors(tree, root)) m = m.create_instance() def _add_node(u, stage, succ, pred): node_name = node_to_name[u] m.NodeStage[node_name] = m.Stages[stage] if u == root: m.ConditionalProbability[node_name] = 1.0 else: assert u in pred # prior to networkx ~2.0, we used a .edge attribute on DiGraph, # which no longer exists. if hasattr(tree, 'edge'): edge = tree.edge[pred[u]][u] else: edge = tree.edges[pred[u],u] probability = None if edge_probability_attribute is not None: if edge_probability_attribute not in edge: raise KeyError( "edge '(%s, %s)' missing probability attribute: '%s'" % (pred[u], u, edge_probability_attribute)) probability = edge[edge_probability_attribute] else: probability = 1.0/len(succ[pred[u]]) m.ConditionalProbability[node_name] = probability # get node variables if "variables" in tree.node[u]: node_variables = tree.node[u]["variables"] assert type(node_variables) in [tuple, list] for varstring in node_variables: m.NodeVariables[node_name].add(varstring) if "derived_variables" in tree.node[u]: node_derived_variables = tree.node[u]["derived_variables"] assert type(node_derived_variables) in [tuple, list] for varstring in node_derived_variables: m.NodeDerivedVariables[node_name].add(varstring) if "cost" in tree.node[u]: assert isinstance(tree.node[u]["cost"], six.string_types) m.NodeCost[node_name].value = tree.node[u]["cost"] if u in succ: child_names = [] for v in succ[u]: child_names.append( _add_node(v, stage+1, succ, pred)) total_probability = 0.0 for child_name in child_names: m.Children[node_name].add(child_name) total_probability += \ pyomo.core.value(m.ConditionalProbability[child_name]) if abs(total_probability - 1.0) > 1e-5: raise ValueError( "edge probabilities leaving node '%s' " "do not sum to 1 (total=%r)" % (u, total_probability)) else: # a leaf node scenario_name = node_to_scenario[u] m.ScenarioLeafNode[scenario_name] = node_name m.Children[node_name].clear() return node_name _add_node(root, 1, networkx.dfs_successors(tree, root), networkx.dfs_predecessors(tree, root)) if any(_b is not None for _b in scenario_bundle.values()): if any(_b is None for _b in scenario_bundle.values()): raise ValueError("Incomplete bundle specification. " "All scenarios require a bundle " "identifier.") m.Bundling.value = True bundle_scenarios = {} for bundle_name in sorted(set(scenario_bundle.values())): m.Bundles.add(bundle_name) bundle_scenarios[bundle_name] = [] for scenario_name in m.Scenarios: bundle_scenarios[scenario_bundle[scenario_name]].\ append(scenario_name) for bundle_name in m.Bundles: for scenario_name in sorted(bundle_scenarios[bundle_name]): m.BundleScenarios[bundle_name].add(scenario_name) return m
def test_path(): G = nx.DiGraph() nx.add_path(G, range(5)) assert nx.is_branching(G) assert nx.is_arborescence(G)
def test_emptybranch(): G = nx.DiGraph() G.add_nodes_from(range(10)) assert_true(nx.is_branching(G)) assert_false(nx.is_arborescence(G))
def test_emptybranch(): G = nx.DiGraph() G.add_nodes_from(range(10)) assert nx.is_branching(G) assert not nx.is_arborescence(G)
def test_path(): G = nx.DiGraph() nx.add_path(G, range(5)) assert_true(nx.is_branching(G)) assert_true(nx.is_arborescence(G))
def test_path(): G = nx.DiGraph() G.add_path(range(5)) assert_true(nx.is_branching(G)) assert_true(nx.is_arborescence(G))
def ScenarioTreeModelFromNetworkX( tree, node_name_attribute=None, edge_probability_attribute='weight', stage_names=None, scenario_name_attribute=None): """ Create a scenario tree model from a networkx tree. The height of the tree must be at least 1 (meaning at least 2 stages). Required node attributes: - cost (str): A string identifying a component on the model whose value indicates the cost at the time stage of the node for any scenario traveling through it. Optional node attributes: - variables (list): A list of variable identifiers that will be tracked by the node. If the node is not a leaf node, these indicate variables with non-anticipativity constraints. - derived_variables (list): A list of variable or expression identifiers that will be tracked by the node (but will never have non-anticipativity constraints enforced). - bundle: A bundle identifier for the scenario defined by a leaf-stage node. This attribute is ignored on non-terminal tree nodes. This attribute appears on at least one leaf-stage node (and is not set to :const:`None`), then it must be set on all leaf-stage nodes (to something other than :const:`None`); otherwise, an exception will be raised. Optional edge attributes: - weight (float): Indicates the conditional probability of moving from the parent node to the child node in the directed edge. If not present, it will be assumed that all edges leaving the parent node have equal probability (normalized to sum to one). Args: stage_names: Can define a list of stage names to use (assumed in time order). The length of this list much match the number of stages in the tree. The default value of :const:`None` indicates that stage names should be automatically generated in with the form ['Stage1','Stage2',...]. node_name_attribute: By default, node names are the same as the node hash in the networkx tree. This keyword can be set to the name of some property of nodes in the graph that will be used for their name in the PySP scenario tree. scenario_name_attribute: By default, scenario names are the same as the leaf-node hash in the networkx tree. This keyword can be set to the name of some property of leaf-nodes in the graph that will be used for their corresponding scenario name in the PySP scenario tree. edge_probability_attribute: Can be set to the name of some property of edges in the graph that defines the conditional probability of that branch (default: 'weight'). If this keyword is set to :const:`None`, then all branches leaving a node are assigned equal conditional probabilities. Examples: A 2-stage scenario tree with 10 scenarios grouped into 2 bundles: >>> G = networkx.DiGraph() >>> G.add_node("root", variables=["x"]) >>> N = 10 >>> for i in range(N): >>> node_name = "s"+str(i) >>> bundle_name = "b"+str(i%2) >>> G.add_node(node_name, bundle=bundle) >>> G.add_edge("root", node_name, weight=1.0/N) >>> model = ScenarioTreeModelFromNetworkX(G) A 4-stage scenario tree with 125 scenarios: >>> branching_factor = 5 >>> height = 3 >>> G = networkx.balanced_tree( branching_factor, height, networkx.DiGraph()) >>> model = ScenarioTreeModelFromNetworkX(G) """ if not has_networkx: #pragma:nocover raise ValueError( "networkx>=2.0 module is not available") if not networkx.is_tree(tree): raise TypeError( "Graph object is not a tree " "(see networkx.is_tree)") if not networkx.is_directed(tree): raise TypeError( "Graph object is not directed " "(see networkx.is_directed)") if not networkx.is_branching(tree): raise TypeError( "Grapn object is not a branching " "(see networkx.is_branching") in_degree_items = tree.in_degree() # Prior to networkx ~2.0, in_degree() returned a dictionary. # Now it is a view on items, so only call .items() for the old case if hasattr(in_degree_items, 'items'): in_degree_items = in_degree_items.items() root = [u for u,d in in_degree_items if d == 0] assert len(root) == 1 root = root[0] num_stages = networkx.eccentricity(tree, v=root) + 1 if num_stages < 2: raise ValueError( "The number of stages must be at least 2") m = CreateAbstractScenarioTreeModel() if stage_names is not None: unique_stage_names = set() for cnt, stage_name in enumerate(stage_names,1): m.Stages.add(stage_name) unique_stage_names.add(stage_name) if cnt != num_stages: raise ValueError( "incorrect number of stages names (%s), should be %s" % (cnt, num_stages)) if len(unique_stage_names) != cnt: raise ValueError("all stage names were not unique") else: for i in range(num_stages): m.Stages.add('Stage'+str(i+1)) node_to_name = {} node_to_scenario = {} scenario_bundle = {} def _setup(u, succ): if node_name_attribute is not None: if node_name_attribute not in tree.nodes[u]: raise KeyError( "node '%s' missing node name " "attribute: '%s'" % (u, node_name_attribute)) node_name = tree.nodes[u][node_name_attribute] else: node_name = u node_to_name[u] = node_name m.Nodes.add(node_name) if u in succ: for v in succ[u]: _setup(v, succ) else: # a leaf node if scenario_name_attribute is not None: if scenario_name_attribute not in tree.nodes[u]: raise KeyError( "node '%s' missing scenario name " "attribute: '%s'" % (u, scenario_name_attribute)) scenario_name = tree.nodes[u][scenario_name_attribute] else: scenario_name = u node_to_scenario[u] = scenario_name m.Scenarios.add(scenario_name) scenario_bundle[scenario_name] = \ tree.nodes[u].get('bundle', None) _setup(root, networkx.dfs_successors(tree, root)) m = m.create_instance() def _add_node(u, stage, succ, pred): node_name = node_to_name[u] m.NodeStage[node_name] = m.Stages[stage] if u == root: m.ConditionalProbability[node_name] = 1.0 else: assert u in pred # prior to networkx ~2.0, we used a .edge attribute on DiGraph, # which no longer exists. if hasattr(tree, 'edge'): edge = tree.edge[pred[u]][u] else: edge = tree.edges[pred[u],u] probability = None if edge_probability_attribute is not None: if edge_probability_attribute not in edge: raise KeyError( "edge '(%s, %s)' missing probability attribute: '%s'" % (pred[u], u, edge_probability_attribute)) probability = edge[edge_probability_attribute] else: probability = 1.0/len(succ[pred[u]]) m.ConditionalProbability[node_name] = probability # get node variables if "variables" in tree.nodes[u]: node_variables = tree.nodes[u]["variables"] assert type(node_variables) in [tuple, list] for varstring in node_variables: m.NodeVariables[node_name].add(varstring) if "derived_variables" in tree.nodes[u]: node_derived_variables = tree.nodes[u]["derived_variables"] assert type(node_derived_variables) in [tuple, list] for varstring in node_derived_variables: m.NodeDerivedVariables[node_name].add(varstring) if "cost" in tree.nodes[u]: assert isinstance(tree.nodes[u]["cost"], six.string_types) m.NodeCost[node_name].value = tree.nodes[u]["cost"] if u in succ: child_names = [] for v in succ[u]: child_names.append( _add_node(v, stage+1, succ, pred)) total_probability = 0.0 for child_name in child_names: m.Children[node_name].add(child_name) total_probability += \ pyomo.core.value(m.ConditionalProbability[child_name]) if abs(total_probability - 1.0) > 1e-5: raise ValueError( "edge probabilities leaving node '%s' " "do not sum to 1 (total=%r)" % (u, total_probability)) else: # a leaf node scenario_name = node_to_scenario[u] m.ScenarioLeafNode[scenario_name] = node_name m.Children[node_name].clear() return node_name _add_node(root, 1, networkx.dfs_successors(tree, root), networkx.dfs_predecessors(tree, root)) if any(_b is not None for _b in scenario_bundle.values()): if any(_b is None for _b in scenario_bundle.values()): raise ValueError("Incomplete bundle specification. " "All scenarios require a bundle " "identifier.") m.Bundling.value = True bundle_scenarios = {} for bundle_name in sorted(set(scenario_bundle.values())): m.Bundles.add(bundle_name) bundle_scenarios[bundle_name] = [] for scenario_name in m.Scenarios: bundle_scenarios[scenario_bundle[scenario_name]].\ append(scenario_name) for bundle_name in m.Bundles: for scenario_name in sorted(bundle_scenarios[bundle_name]): m.BundleScenarios[bundle_name].add(scenario_name) return m
def ScenarioTreeModelFromNetworkX( tree, node_name_attribute=None, edge_probability_attribute='probability', stage_names=None, scenario_name_attribute=None): """ Create a scenario tree model from a networkx tree. The height of the tree must be at least 1 (meaning at least 2 stages). Optional Arguments: - node_name_attribute: By default, node names are the same as the node hash in the networkx tree. This keyword can be set to the name of some property of nodes in the graph that will be used for their name in the PySP scenario tree. - edge_probability_attribute: Can be set to the name of some property of edges in the graph that defines the conditional probability of that branch (default: 'probability'). If this keyword is set to None, then all branches leaving a node are assigned equal conditional probabilities. - stage_names: Can define a list of stage names to use (assumed in time order). The length of this list much match the number of stages in the tree. - scenario_name_attribute: By default, scenario names are the same as the leaf-node hash in the networkx tree. This keyword can be set to the name of some property of leaf-nodes in the graph that will be used for their corresponding scenario in the PySP scenario tree. Examples: - A 2-stage scenario tree with 10 scenarios: G = networkx.DiGraph() G.add_node("Root") N = 10 for i in range(N): node_name = "Leaf"+str(i) G.add_node(node_name) G.add_edge("Root",node_name,probability=1.0/N) model = ScenarioTreeModelFromNetworkX(G) - A 4-stage scenario tree with 125 scenarios: branching_factor = 5 height = 3 G = networkx.balanced_tree( branching_factory, height, networkx.DiGraph()) model = ScenarioTreeModelFromNetworkX( G, edge_probability_attribute=None) """ if not has_networkx: raise ValueError("networkx module is not available") if not networkx.is_tree(tree): raise TypeError( "object is not a tree (see networkx.is_tree)") if not networkx.is_directed(tree): raise TypeError( "object is not directed (see networkx.is_directed)") if not networkx.is_branching(tree): raise TypeError( "object is not a branching (see networkx.is_branching") if not networkx.is_arborescence(tree): raise TypeError("Object must be a directed, rooted tree " "in which all edges point away from the " "root (see networkx.is_arborescence)") root = [u for u,d in tree.in_degree().items() if d == 0] assert len(root) == 1 root = root[0] num_stages = networkx.eccentricity(tree, v=root) + 1 if num_stages < 2: raise ValueError( "The number of stages must be at least 2") m = CreateAbstractScenarioTreeModel() if stage_names is not None: unique_stage_names = set() for cnt, stage_name in enumerate(stage_names,1): m.Stages.add(stage_name) unique_stage_names.add(stage_name) if cnt != num_stages: raise ValueError( "incorrect number of stages names (%s), should be %s" % (cnt, num_stages)) if len(unique_stage_names) != cnt: raise ValueError("all stage names were not unique") else: for i in range(num_stages): m.Stages.add('Stage'+str(i+1)) node_to_name = {} node_to_scenario = {} def _setup(u, succ): if node_name_attribute is not None: if node_name_attribute not in tree.node[u]: raise KeyError( "node '%s' missing name attribute: '%s'" % (u, node_name_attribute)) node_name = tree.node[u][node_name_attribute] else: node_name = u node_to_name[u] = node_name m.Nodes.add(node_name) if u in succ: for v in succ[u]: _setup(v, succ) else: # a leaf node if scenario_name_attribute is not None: if scenario_name_attribute not in tree.node[u]: raise KeyError( "node '%s' missing attribute: '%s'" % (u, scenario_name_attribute)) scenario_name = tree.node[u][scenario_name_attribute] else: scenario_name = u node_to_scenario[u] = scenario_name m.Scenarios.add(scenario_name) _setup(root, networkx.dfs_successors(tree, root)) m = m.create_instance() def _add_node(u, stage, succ, pred): if node_name_attribute is not None: if node_name_attribute not in tree.node[u]: raise KeyError( "node '%s' missing name attribute: '%s'" % (u, node_name_attribute)) node_name = tree.node[u][node_name_attribute] else: node_name = u m.NodeStage[node_name] = m.Stages[stage] if u == root: m.ConditionalProbability[node_name] = 1.0 else: assert u in pred edge = tree.edge[pred[u]][u] probability = None if edge_probability_attribute is not None: if edge_probability_attribute not in edge: raise KeyError( "edge '(%s, %s)' missing probability attribute: '%s'" % (pred[u], u, edge_probability_attribute)) probability = edge[edge_probability_attribute] else: probability = 1.0/len(succ[pred[u]]) m.ConditionalProbability[node_name] = probability if u in succ: child_names = [] for v in succ[u]: child_names.append( _add_node(v, stage+1, succ, pred)) total_probability = 0.0 for child_name in child_names: m.Children[node_name].add(child_name) total_probability += \ value(m.ConditionalProbability[child_name]) if abs(total_probability - 1.0) > 1e-5: raise ValueError( "edge probabilities leaving node '%s' " "do not sum to 1 (total=%r)" % (u, total_probability)) else: # a leaf node scenario_name = node_to_scenario[u] m.ScenarioLeafNode[scenario_name] = node_name m.Children[node_name].clear() return node_name _add_node(root, 1, networkx.dfs_successors(tree, root), networkx.dfs_predecessors(tree, root)) return m