def clone_node(self, node_id, name=None): """Clone node. Create a new node, a copy of a node with `node_id`, and reconnect it with all the adjacent nodes of `node_id`. Parameters ---------- node_id : hashable, Id of a node to clone. name : hashable, optional Id for the clone, if is not specified, new id will be generated. Returns ------- new_node : hashable Id of the new node corresponding to the clone Raises ------ GraphError If node wiht `node_id` does not exists or a node with `name` (clone's name) already exists. """ if node_id not in self.nodes(): raise GraphError("Node '{}' does not exist!".format(node_id)) # generate new name for a clone if name is None: i = 1 new_node = str(node_id) + str(i) while new_node in self.nodes(): i += 1 new_node = str(node_id) + str(i) else: if name in self.nodes(): raise GraphError("Node '{}' already exists!".format(name)) else: new_node = name self.add_node(new_node, self.get_node(node_id)) # Connect all the edges self.add_edges_from( set([(n, new_node) for n, _ in self.in_edges(node_id) if (n, new_node) not in self.edges()])) self.add_edges_from( set([(new_node, n) for _, n in self.out_edges(node_id) if (new_node, n) not in self.edges()])) # Copy the attributes of the edges for s, t in self.in_edges(node_id): self.set_edge(s, new_node, safe_deepcopy_dict(self.get_edge(s, t))) for s, t in self.out_edges(node_id): self.set_edge(new_node, t, safe_deepcopy_dict(self.get_edge(s, t))) return new_node
def remove_node_attrs(self, node_id, attrs): """Remove attrs of a node specified by attrs_dict. Parameters ---------- node_id : hashable Node whose attributes to remove. attrs : dict Dictionary with attributes to remove. Raises ------ GraphError If a node with the specified id does not exist. """ if node_id not in self.nodes(): raise GraphError("Node '%s' does not exist!" % str(node_id)) elif attrs is None: warnings.warn( "You want to remove attrs from '{}' with an empty attrs_dict!". format(node_id), GraphAttrsWarning) node_attrs = safe_deepcopy_dict(self.get_node(node_id)) remove_attrs(node_attrs, attrs, normalize=True) self.update_node_attrs(node_id, node_attrs)
def set_edge_attrs(self, s, t, attrs, normalize=True, update=True): """Set edge attrs. Parameters ---------- attrs : dict Dictionary with new attributes to set normalize : bool, optional Flag, when set to True attributes are normalized to be set-valued. True by default update : bool, optional Flag, when set to True attributes whose keys are not present in attrs are removed, True by default Raises ------ GraphError If an edge between `s` and `t` does not exist. """ if not self.exists_edge(s, t): raise GraphError("Edge {}->{} does not exist".format(s, t)) edge_attrs = safe_deepcopy_dict(self.get_edge(s, t)) set_attrs(edge_attrs, attrs, normalize, update) self.update_edge_attrs(s, t, edge_attrs, normalize=normalize)
def set_node_attrs(self, node_id, attrs, normalize=True, update=True): """Set node attrs. Parameters ---------- node_id : hashable Id of the node to update attrs : dict Dictionary with new attributes to set normalize : bool, optional Flag, when set to True attributes are normalized to be set-valued. True by default update : bool, optional Flag, when set to True attributes whose keys are not present in attrs are removed, True by default Raises ------ GraphError If a node `node_id` does not exist. """ if node_id not in self.nodes(): raise GraphError("Node '{}' does not exist!".format(node_id)) node_attrs = safe_deepcopy_dict(self.get_node(node_id)) set_attrs(node_attrs, attrs, normalize, update) self.update_node_attrs(node_id, node_attrs, normalize)
def update_node_attrs(self, node_id, attrs, normalize=True): """Update attributes of a node. Parameters ---------- node_id : hashable, node to update. attrs : dict New attributes to assign to the node """ new_attrs = safe_deepcopy_dict(attrs) if node_id not in self.nodes(): raise GraphError("Node '{}' does not exist!".format(node_id)) elif new_attrs is None: warnings.warn( "You want to update '{}' attrs with an empty attrs_dict!". format(node_id), GraphAttrsWarning) else: if normalize is True: normalize_attrs(new_attrs) attrs_to_remove = set() for k in self._graph.nodes[node_id].keys(): if k not in new_attrs.keys(): attrs_to_remove.add(k) self._graph.add_node(node_id, **new_attrs) for k in attrs_to_remove: del self._graph.nodes[node_id][k]
def add_edge(self, s, t, attrs=None, **attr): """Add an edge to a graph. Parameters ---------- graph : networkx.(Di)Graph s : hashable, source node id. t : hashable, target node id. attrs : dict Edge attributes. """ if attrs is None: attrs = attr else: try: attrs.update(attr) except AttributeError: raise ReGraphError( "The attr_dict argument must be a dictionary.") new_attrs = safe_deepcopy_dict(attrs) if s not in self.nodes(): raise GraphError("Node '{}' does not exist!".format(s)) if t not in self.nodes(): raise GraphError("Node '{}' does not exist!".format(t)) normalize_attrs(new_attrs) if (s, t) in self.edges(): raise GraphError("Edge '{}'->'{}' already exists!".format(s, t)) self._graph.add_edge(s, t, **new_attrs)
def add_node(self, node_id, attrs=None): """Abstract method for adding a node. Parameters ---------- node_id : hashable Prefix that is prepended to the new unique name. attrs : dict, optional Node attributes. """ if attrs is None: new_attrs = dict() else: new_attrs = safe_deepcopy_dict(attrs) normalize_attrs(new_attrs) if node_id not in self.nodes(): self._graph.add_node(node_id, **new_attrs) return node_id else: raise GraphError("Node '{}' already exists!".format(node_id))
def add_node_attrs(self, node, attrs): """Add new attributes to a node. Parameters ---------- node : hashable Id of a node to add attributes to. attrs : dict Attributes to add. Raises ------ GraphError If a node `node_id` does not exist. """ if node not in self.nodes(): raise GraphError("Node '{}' does not exist!".format(node)) node_attrs = safe_deepcopy_dict(self.get_node(node)) add_attrs(node_attrs, attrs, normalize=True) self.update_node_attrs(node, node_attrs)
def remove_edge_attrs(self, s, t, attrs): """Remove attrs of an edge specified by attrs. Parameters ---------- s : hashable Source node id. t : hashable Target node id. attrs : dict Dictionary with attributes to remove. Raises ------ GraphError If an edge between `s` and `t` does not exist. """ if not self.exists_edge(s, t): raise GraphError("Edge {}->{} does not exist".format(s, t)) edge_attrs = safe_deepcopy_dict(self.get_edge(s, t)) remove_attrs(edge_attrs, attrs, normalize=True) self.update_edge_attrs(s, t, edge_attrs)
def merge_nodes(self, nodes, node_id=None, method="union", edge_method="union"): """Merge a list of nodes. Parameters ---------- nodes : iterable Collection of node id's to merge. node_id : hashable, optional Id of a new node corresponding to the result of merge. method : optional Method of node attributes merge: if `"union"` the resulting node will contain the union of all attributes of the merged nodes, if `"intersection"`, the resulting node will contain their intersection. Default value is `"union"`. edge_method : optional Method of edge attributes merge: if `"union"` the edges that were merged will contain the union of all attributes, if `"intersection"` -- their ntersection. Default value is `"union"`. """ if len(nodes) > 1: if method is None: method = "union" if edge_method is None: method = "union" # Generate name for new node if node_id is None: node_id = "_".join(sorted([str(n) for n in nodes])) if node_id in self.nodes(): node_id = self.generate_new_node_id(node_id) elif node_id in self.nodes() and (node_id not in nodes): raise GraphError("New name for merged node is not valid: " "node with name '%s' already exists!" % node_id) # Merge data attached to node according to the method specified # restore proper connectivity if method == "union": attr_accumulator = {} elif method == "intersection": attr_accumulator = safe_deepcopy_dict(self.get_node(nodes[0])) else: raise ReGraphError( "Merging method '{}' is not defined!".format(method)) self_loop = False self_loop_attrs = {} source_nodes = set() target_nodes = set() source_dict = {} target_dict = {} for node in nodes: attr_accumulator = merge_attributes(attr_accumulator, self.get_node(node), method) in_edges = self.in_edges(node) out_edges = self.out_edges(node) # manage self loops for s, t in in_edges: if s in nodes: self_loop = True if len(self_loop_attrs) == 0: self_loop_attrs = self.get_edge(s, t) else: self_loop_attrs = merge_attributes( self_loop_attrs, self.get_edge(s, t), edge_method) for s, t in out_edges: if t in nodes: self_loop = True if len(self_loop_attrs) == 0: self_loop_attrs = self.get_edge(s, t) else: self_loop_attrs = merge_attributes( self_loop_attrs, self.get_edge(s, t), edge_method) source_nodes.update( [n if n not in nodes else node_id for n, _ in in_edges]) target_nodes.update( [n if n not in nodes else node_id for _, n in out_edges]) for edge in in_edges: if not edge[0] in source_dict.keys(): attrs = self.get_edge(edge[0], edge[1]) source_dict.update({edge[0]: attrs}) else: attrs = merge_attributes( source_dict[edge[0]], self.get_edge(edge[0], edge[1]), edge_method) source_dict.update({edge[0]: attrs}) for edge in out_edges: if not edge[1] in target_dict.keys(): attrs = self.get_edge(edge[0], edge[1]) target_dict.update({edge[1]: attrs}) else: attrs = merge_attributes( target_dict[edge[1]], self.get_edge(edge[0], edge[1]), edge_method) target_dict.update({edge[1]: attrs}) self.remove_node(node) self.add_node(node_id, attr_accumulator) if self_loop: self.add_edges_from([(node_id, node_id)]) self.set_edge(node_id, node_id, self_loop_attrs) for n in source_nodes: if not self.exists_edge(n, node_id): self.add_edge(n, node_id) for n in target_nodes: if not self.exists_edge(node_id, n): self.add_edge(node_id, n) # Attach accumulated attributes to edges for node, attrs in source_dict.items(): if node not in nodes: self.set_edge(node, node_id, attrs) for node, attrs in target_dict.items(): if node not in nodes: self.set_edge(node_id, node, attrs) return node_id else: raise ReGraphError( "More than two nodes should be specified for merging!")