def _(self, *edge: Union[str, Node], **kwargs: Any) -> None: # get additional parameters uid: Optional[str] = kwargs.pop('uid', None) nodes: bool = kwargs.pop('nodes', True) # check if all objects are node or str if not all(isinstance(arg, (Node, str)) for arg in edge): LOG.error('All objects have to be Node objects or str uids!') raise TypeError # if nodes is true add nodes if nodes: _nodes: tuple = () for node in edge: if node not in self.nodes: self.nodes.add(node) _nodes += (self.nodes[node], ) # create new edge object and add it to the network if _nodes not in self or self.multiedges: self.add(self._edge_class(*_nodes, uid=uid, **kwargs)) # raise error if edge already exists else: self._if_exist(_nodes, **kwargs) # add edge with unknown nodes else: self.add(self._edge_class(Node(), Node(), uid=edge[0], **kwargs))
def __init__(self, *args: Union[Node, Edge], uid: Optional[str] = None, **kwargs: Any) -> None: # initializing the parent classes Node.__init__(self, uid, **kwargs) Path.__init__(self, *args, uid=uid, **kwargs)
def _add_edge_from_str(self, edge: str, **kwargs: Any) -> None: """Helper function to add an edge from nodes.""" # check if edge with given uid str exists already if edge not in self: # if not add new node with provided uid str self._add_edge(Edge(Node(), Node(), uid=edge, **kwargs)) else: # raise error if node already exists self._if_edge_exists(edge, **kwargs)
def __init__(self, *args: Union[Node, Edge], uid: Optional[str] = None, **kwargs: Any) -> None: # initializing the parent classes Node.__init__(self, uid, **kwargs) Path.__init__(self, *args, uid=uid, **kwargs) self['label'] = '-'.join([n.uid for n in self.nodes])
def __init__(self, *node: Union[str, PathPyObject], uid: Optional[str] = None, **kwargs: Any) -> None: """Initialize the node object.""" # initializing the parent classes kwargs.pop('directed', None) kwargs.pop('ordered', None) Node.__init__(self, *node, uid=uid, **kwargs) TemporalPathPyObject.__init__(self, uid=self.uid, **kwargs)
def to_network(frame: pd.DataFrame, loops: bool = True, directed: bool = True, multiedges: bool = False, **kwargs: Any) -> Network: """Read network from a pandas data frame.""" # if no v/w columns are included, pick first synonym frame = _check_column_name(frame, 'v', config['edge']['v_synonyms']) frame = _check_column_name(frame, 'w', config['edge']['w_synonyms']) LOG.debug('Creating %s network', directed) node_set = set(frame['v']).union(set(frame['w'])) if None in node_set: LOG.error('DataFrame minimally needs columns \'v\' and \'w\'') raise IOError nodes = {n: Node(n) for n in node_set} edges: list = [] edge_set: set = set() # TODO: Make this for loop faster! for row in frame.to_dict(orient='records'): v = row.pop('v') w = row.pop('w') uid = row.pop('uid', None) if (v, w) in edge_set and not multiedges: LOG.warning( 'The edge (%s,%s) exist already ' 'and will not be considered. ' 'To capture this edge, please ' 'enalbe multiedges and/or directed!', v, w) elif loops or v != w: edges.append(Edge(nodes[v], nodes[w], uid=uid, **row)) edge_set.add((v, w)) if not directed: edge_set.add((w, v)) else: continue net = Network(directed=directed, multiedges=multiedges, **kwargs) for node in nodes.values(): net.nodes.add(node) for edge in edges: net.edges._add(edge) net._add_edge_properties() return net
def lattice_network(start: int = 0, stop: int = 10, dims: int = 2): """ Generates a n-dimensional lattice network with coordinates in each dimension ranging from start (inclusive) to stop (exclusive) """ network = Network(directed=False) for pos in _multi_dim_range(start, stop, dims): network.add_node( Node("".join(str(i) + '-' for i in pos).strip('-'), pos=np.array(pos))) for v in network.nodes: for w in network.nodes: if np.sum(np.abs(v['pos'] - w['pos'])) == 1 and ( v.uid, w.uid) not in network.edges: network.add_edge(v, w) return network
def _add_path_from_edges(self, *edges: Union[str, Edge], uid: Optional[str] = None, **kwargs: Any) -> None: """Helper function to add a path from edges.""" _edges: list = [] for edge in edges: if edge not in self.edges or self.multiedges: if isinstance(edge, str) and len(_edges) > 0: self.edges.add(Edge(_edges[-1].w, Node(), uid=edge)) else: self.edges.add(edge, nodes=False) _edges.append(self.edges[edge]) _path = _edges if _path not in self or self.multipaths: self._add_path(self._path_class(*_path, uid=uid, **kwargs)) else: # raise error if node already exists self._if_exist(_path, **kwargs)
def read_graphml(filename: str): """Reads a pathyp.Network from a graphml file. This function supports typed Node and Edge attributes including default values. Warnings are issued if the type of Node or Edge attributes are undeclared, in which case the attribute type will fall back to string. Parameters ---------- filename: str The graphml file to read the graph from """ root = ET.parse(filename).getroot() graph = root.find('{http://graphml.graphdrawing.org/xmlns}graph') directed = graph.attrib['edgedefault'] != 'undirected' uid = graph.attrib['id'] n = Network(directed=directed, uid=uid) node_attributes = {} edge_attributes = {} # read attribute types and default values for a in root.findall('{http://graphml.graphdrawing.org/xmlns}key'): a_id = a.attrib['id'] a_name = a.attrib['attr.name'] a_type = a.attrib['attr.type'] a_for = a.attrib['for'] # store attribute info and assign data types a_data = {'name': a_name} if a_type == 'string': a_data['type'] = str elif a_type == 'float': a_data['type'] = float elif a_type == 'double': a_data['type'] = float elif a_type == 'int': a_data['type'] = int elif a_type == 'long': a_data['type'] = int elif a_type == 'boolean': a_data['type'] = bool else: a_data['type'] = str d = a.find('{http://graphml.graphdrawing.org/xmlns}default') if d is not None: a_data['default'] = a_data['type'](d.text) if a_for == 'node': node_attributes[a_name] = a_data if a_for == 'edge': edge_attributes[a_name] = a_data # add nodes with uids and attributes for node in graph.findall('{http://graphml.graphdrawing.org/xmlns}node'): # create node uid = node.attrib['id'] v = Node(uid=uid) # set attribute values for a in node.findall('{http://graphml.graphdrawing.org/xmlns}data'): key = a.attrib['key'] val = a.text if key not in node_attributes: LOG.warning( 'Undeclared Node attribute "{}". Defaulting to string type.' .format(key)) v.attributes[key] = val else: v.attributes[key] = node_attributes[key]['type'](val) # set default values for a_name in node_attributes: if 'default' in node_attributes[ a_name] and v.attributes[a_name] is None: v.attributes[a_name] = node_attributes[a_name]['default'] n.add_node(v) # add edges with uids and attributes for edge in graph.findall('{http://graphml.graphdrawing.org/xmlns}edge'): # create edge source = edge.attrib['source'] target = edge.attrib['target'] uid = edge.attrib['id'] e = Edge(n.nodes[source], n.nodes[target], uid=uid) # set attribute values for a in edge.findall('{http://graphml.graphdrawing.org/xmlns}data'): key = a.attrib['key'] val = a.text if key not in edge_attributes: LOG.warning( 'Warning: Undeclared Edge attribute "{}". Defaulting to string type.' .format(key)) e.attributes[key] = val else: e.attributes[key] = edge_attributes[key]['type'](val) # set default values for a_name in edge_attributes: if 'default' in edge_attributes[ a_name] and e.attributes[a_name] is None: e.attributes[a_name] = edge_attributes[a_name]['default'] n.add_edge(e) return n
def Watts_Strogatz(n: int, s: int, p: float = 0.0, loops: bool = False, node_uids: Optional[list] = None) -> Network: """Undirected Watts-Strogatz lattice network Generates an undirected Watts-Strogatz lattice network with lattice dimensionality one. Parameters ---------- n : int The number of nodes in the generated network s : float The number of nearest neighbors that will be connected in the ring lattice p : float The rewiring probability Examples -------- Generate a Watts-Strogatz network with 100 nodes >>> import pathpy as pp >>> small_world = pp.algorithms.random_graphs.Watts_Strogatz(n=100, s=2, p=0.1) >>> print(small_world.summary()) ... """ network = Network(directed=False) if node_uids is None or len(node_uids) != n: LOG.info('No valid node uids given, generating numeric node uids') node_uids = [] for i in range(n): network.add_node(Node(str(i))) node_uids.append(str(i)) else: for i in range(n): network.add_node(node_uids[i]) # construct a ring lattice (dimension 1) for i in range(n): if loops: x = 0 y = s else: x = 1 y = s + 1 for j in range(x, y): v = network.nodes[node_uids[i]] w = network.nodes[node_uids[(i + j) % n]] if (v.uid, w.uid) not in network.edges: network.add_edge(v, w) if p == 0: # nothing to do here return network # Rewire each link with probability p for edge in tqdm(list(network.edges.values()), 'generating WS network'): if np.random.rand() < p: # Delete original link and remember source node v = edge.v.uid network.remove_edge(edge) # Find new random tgt, which is not yet connected to src new_target = None # This loop repeatedly chooses a random target until we find # a target not yet connected to src. Note that this could potentially # result in an infinite loop depending on parameters. while new_target is None: x = str(np.random.randint(n)) if (x != v or loops) and (v, x) not in network.edges: new_target = x network.add_edge(v, new_target) return network
def read_pathcollection(filename: str, separator: str = ',', frequency: bool = False, directed: bool = True, maxlines: int = None) -> PathCollection: """Read path in edgelist format Reads data from a file containing multiple lines of *edges* of the form "v,w,frequency,X" (where frequency is optional and X are arbitrary additional columns). The default separating character ',' can be changed. Parameters ---------- filename : str path to edgelist file separator : str character separating the nodes frequency : bool is a frequency given? if ``True`` it is the last element in the edge (i.e. ``a,b,2``) directed : bool are the edges directed or undirected maxlines : int number of lines to read (useful to test large files). None means the entire file is read """ from pathpy.core.path import Path, PathCollection nodes: dict = {} edges: dict = {} paths: dict = {} with open(filename, 'r') as csv: for n, line in enumerate(csv): fields = line.rstrip().split(separator) assert len(fields) >= 1, 'Error: empty line: {0}'.format(line) if frequency: path = tuple(fields[:-1]) freq = float(fields[-1]) else: path = tuple(fields) freq = 1.0 for node in path: if node not in nodes: nodes[node] = Node(node) if len(path) == 1 and path not in paths: paths[path] = Path(nodes[path[0]], frequency=freq) else: edge_list = [] for u, v in zip(path[:-1], path[1:]): if (u, v) not in edges: edges[(u, v)] = Edge(nodes[u], nodes[v]) edge_list.append(edges[(u, v)]) if path not in paths: paths[path] = Path(*edge_list, frequency=freq) if maxlines is not None and n >= maxlines: break ncoll = NodeCollection() for node in nodes.values(): ncoll.add(node) ecoll = EdgeCollection(nodes=ncoll) for edge in edges.values(): ecoll._add(edge) _paths = PathCollection(directed=directed, nodes=ncoll, edges=ecoll) for _path in paths.values(): _paths._add(_path) return _paths
def to_temporal_network(frame: pd.DataFrame, loops: bool = True, directed: bool = True, multiedges: bool = False, **kwargs: Any) -> TemporalNetwork: """Read temporal network from a pandas data frame.""" from pathpy.models.temporal_network import TemporalNetwork # if no v/w columns are included, pick first synonym frame = _check_column_name(frame, 'v', config['edge']['v_synonyms']) frame = _check_column_name(frame, 'w', config['edge']['w_synonyms']) _begin = config['temporal']['begin'] _end = config['temporal']['end'] _timestamp = config['temporal']['timestamp'] _duration = config['temporal']['duration'] _key_words = { 'begin': _begin, 'end': _end, 'timestamp': _timestamp, 'duration': _duration } for key, name in _key_words.items(): frame = _check_column_name(frame, name, config['temporal'][key + '_synonyms']) if _timestamp in frame.columns: frame[_begin] = frame[_timestamp] if _duration in frame.columns: frame[_end] = frame[_timestamp] + frame[_duration] else: frame[_end] = frame[_timestamp] + \ config['temporal']['duration_value'] if _begin and _end not in frame.columns: LOG.error( 'A TemporalNetwork needs "%s" and "%s" (or "%s" and "%s") ' 'attributes!', _begin, _end, _timestamp, _duration) raise IOError LOG.debug('Creating %s network', directed) node_set = set(frame['v']).union(set(frame['w'])) if None in node_set: LOG.error('DataFrame minimally needs columns \'v\' and \'w\'') raise IOError nodes = {n: Node(n) for n in node_set} net = TemporalNetwork(directed=directed, multiedges=multiedges, **kwargs) for node in nodes.values(): net.nodes.add(node) # TODO: Make this for loop faster! #rows = [] #edges = {} for row in frame.to_dict(orient='records'): v = row.pop('v') w = row.pop('w') uid = row.pop('uid', None) # if (v, w) not in edges: # edge = Edge(nodes[v], nodes[w], uid=uid, **row) # net.edges._add(edge) # edges[(v, w)] = edge # else: # begin = row.pop(_begin) # end = row.pop(_end) # net.edges._intervals.addi(begin, end, edges[(v, w)]) # net.edges._interval_map[edges[(v, w)]].add((begin, end)) net.add_edge(nodes[v], nodes[w], uid=uid, **row) # net.add_edge(nodes[v], nodes[w], uid=uid, **row) # net._add_edge_properties() return net
def read_file(cls, filename: str, separator: str = ',', frequency: bool = False, directed: bool = True, maxlines: int = None) -> None: """ Read path in edgelist format Reads data from a file containing multiple lines of *edges* of the form "v,w,frequency,X" (where frequency is optional and X are arbitrary additional columns). The default separating character ',' can be changed. Parameters ---------- filename : str path to edgelist file separator : str character separating the nodes frequency : bool is a frequency given? if ``True`` it is the last element in the edge (i.e. ``a,b,2``) directed : bool are the edges directed or undirected maxlines : int number of lines to read (useful to test large files). None means the entire file is read """ nodes = {} edges = {} paths = {} with open(filename, 'r') as f: for n, line in enumerate(f): fields = line.rstrip().split(separator) assert len(fields) >= 2, 'Error: malformed line: {0}'.format( line) if frequency: path = tuple(fields[:-1]) f = int(fields[-1]) else: path = tuple(fields) f = 1 for node in path: if node not in nodes: nodes[node] = Node(node) edge_list = [] for u, v in zip(path[:-1], path[1:]): if (u, v) not in edges: edges[(u, v)] = Edge(nodes[u], nodes[v], uid=u + '-' + v) edge_list.append(edges[(u, v)]) if path not in paths: paths[path] = Path(*edge_list, frequency=f) if maxlines is not None and n >= maxlines: break nc = NodeCollection() nc.add(*nodes.values()) ec = EdgeCollection(nodes=nc) for edge in edges.values(): ec._add(edge) p = PathCollection(nodes=nc, edges=ec) for path in paths.values(): p._add(path) return p