Spaces:
Running
Running
""" | |
***** | |
Pajek | |
***** | |
Read graphs in Pajek format. | |
This implementation handles directed and undirected graphs including | |
those with self loops and parallel edges. | |
Format | |
------ | |
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm | |
for format information. | |
""" | |
import warnings | |
import networkx as nx | |
from networkx.utils import open_file | |
__all__ = ["read_pajek", "parse_pajek", "generate_pajek", "write_pajek"] | |
def generate_pajek(G): | |
"""Generate lines in Pajek graph format. | |
Parameters | |
---------- | |
G : graph | |
A Networkx graph | |
References | |
---------- | |
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm | |
for format information. | |
""" | |
if G.name == "": | |
name = "NetworkX" | |
else: | |
name = G.name | |
# Apparently many Pajek format readers can't process this line | |
# So we'll leave it out for now. | |
# yield '*network %s'%name | |
# write nodes with attributes | |
yield f"*vertices {G.order()}" | |
nodes = list(G) | |
# make dictionary mapping nodes to integers | |
nodenumber = dict(zip(nodes, range(1, len(nodes) + 1))) | |
for n in nodes: | |
# copy node attributes and pop mandatory attributes | |
# to avoid duplication. | |
na = G.nodes.get(n, {}).copy() | |
x = na.pop("x", 0.0) | |
y = na.pop("y", 0.0) | |
try: | |
id = int(na.pop("id", nodenumber[n])) | |
except ValueError as err: | |
err.args += ( | |
( | |
"Pajek format requires 'id' to be an int()." | |
" Refer to the 'Relabeling nodes' section." | |
), | |
) | |
raise | |
nodenumber[n] = id | |
shape = na.pop("shape", "ellipse") | |
s = " ".join(map(make_qstr, (id, n, x, y, shape))) | |
# only optional attributes are left in na. | |
for k, v in na.items(): | |
if isinstance(v, str) and v.strip() != "": | |
s += f" {make_qstr(k)} {make_qstr(v)}" | |
else: | |
warnings.warn( | |
f"Node attribute {k} is not processed. {('Empty attribute' if isinstance(v, str) else 'Non-string attribute')}." | |
) | |
yield s | |
# write edges with attributes | |
if G.is_directed(): | |
yield "*arcs" | |
else: | |
yield "*edges" | |
for u, v, edgedata in G.edges(data=True): | |
d = edgedata.copy() | |
value = d.pop("weight", 1.0) # use 1 as default edge value | |
s = " ".join(map(make_qstr, (nodenumber[u], nodenumber[v], value))) | |
for k, v in d.items(): | |
if isinstance(v, str) and v.strip() != "": | |
s += f" {make_qstr(k)} {make_qstr(v)}" | |
else: | |
warnings.warn( | |
f"Edge attribute {k} is not processed. {('Empty attribute' if isinstance(v, str) else 'Non-string attribute')}." | |
) | |
yield s | |
def write_pajek(G, path, encoding="UTF-8"): | |
"""Write graph in Pajek format to path. | |
Parameters | |
---------- | |
G : graph | |
A Networkx graph | |
path : file or string | |
File or filename to write. | |
Filenames ending in .gz or .bz2 will be compressed. | |
Examples | |
-------- | |
>>> G = nx.path_graph(4) | |
>>> nx.write_pajek(G, "test.net") | |
Warnings | |
-------- | |
Optional node attributes and edge attributes must be non-empty strings. | |
Otherwise it will not be written into the file. You will need to | |
convert those attributes to strings if you want to keep them. | |
References | |
---------- | |
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm | |
for format information. | |
""" | |
for line in generate_pajek(G): | |
line += "\n" | |
path.write(line.encode(encoding)) | |
def read_pajek(path, encoding="UTF-8"): | |
"""Read graph in Pajek format from path. | |
Parameters | |
---------- | |
path : file or string | |
File or filename to write. | |
Filenames ending in .gz or .bz2 will be uncompressed. | |
Returns | |
------- | |
G : NetworkX MultiGraph or MultiDiGraph. | |
Examples | |
-------- | |
>>> G = nx.path_graph(4) | |
>>> nx.write_pajek(G, "test.net") | |
>>> G = nx.read_pajek("test.net") | |
To create a Graph instead of a MultiGraph use | |
>>> G1 = nx.Graph(G) | |
References | |
---------- | |
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm | |
for format information. | |
""" | |
lines = (line.decode(encoding) for line in path) | |
return parse_pajek(lines) | |
def parse_pajek(lines): | |
"""Parse Pajek format graph from string or iterable. | |
Parameters | |
---------- | |
lines : string or iterable | |
Data in Pajek format. | |
Returns | |
------- | |
G : NetworkX graph | |
See Also | |
-------- | |
read_pajek | |
""" | |
import shlex | |
# multigraph=False | |
if isinstance(lines, str): | |
lines = iter(lines.split("\n")) | |
lines = iter([line.rstrip("\n") for line in lines]) | |
G = nx.MultiDiGraph() # are multiedges allowed in Pajek? assume yes | |
labels = [] # in the order of the file, needed for matrix | |
while lines: | |
try: | |
l = next(lines) | |
except: # EOF | |
break | |
if l.lower().startswith("*network"): | |
try: | |
label, name = l.split(None, 1) | |
except ValueError: | |
# Line was not of the form: *network NAME | |
pass | |
else: | |
G.graph["name"] = name | |
elif l.lower().startswith("*vertices"): | |
nodelabels = {} | |
l, nnodes = l.split() | |
for i in range(int(nnodes)): | |
l = next(lines) | |
try: | |
splitline = [ | |
x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8")) | |
] | |
except AttributeError: | |
splitline = shlex.split(str(l)) | |
id, label = splitline[0:2] | |
labels.append(label) | |
G.add_node(label) | |
nodelabels[id] = label | |
G.nodes[label]["id"] = id | |
try: | |
x, y, shape = splitline[2:5] | |
G.nodes[label].update( | |
{"x": float(x), "y": float(y), "shape": shape} | |
) | |
except: | |
pass | |
extra_attr = zip(splitline[5::2], splitline[6::2]) | |
G.nodes[label].update(extra_attr) | |
elif l.lower().startswith("*edges") or l.lower().startswith("*arcs"): | |
if l.lower().startswith("*edge"): | |
# switch from multidigraph to multigraph | |
G = nx.MultiGraph(G) | |
if l.lower().startswith("*arcs"): | |
# switch to directed with multiple arcs for each existing edge | |
G = G.to_directed() | |
for l in lines: | |
try: | |
splitline = [ | |
x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8")) | |
] | |
except AttributeError: | |
splitline = shlex.split(str(l)) | |
if len(splitline) < 2: | |
continue | |
ui, vi = splitline[0:2] | |
u = nodelabels.get(ui, ui) | |
v = nodelabels.get(vi, vi) | |
# parse the data attached to this edge and put in a dictionary | |
edge_data = {} | |
try: | |
# there should always be a single value on the edge? | |
w = splitline[2:3] | |
edge_data.update({"weight": float(w[0])}) | |
except: | |
pass | |
# if there isn't, just assign a 1 | |
# edge_data.update({'value':1}) | |
extra_attr = zip(splitline[3::2], splitline[4::2]) | |
edge_data.update(extra_attr) | |
# if G.has_edge(u,v): | |
# multigraph=True | |
G.add_edge(u, v, **edge_data) | |
elif l.lower().startswith("*matrix"): | |
G = nx.DiGraph(G) | |
adj_list = ( | |
(labels[row], labels[col], {"weight": int(data)}) | |
for (row, line) in enumerate(lines) | |
for (col, data) in enumerate(line.split()) | |
if int(data) != 0 | |
) | |
G.add_edges_from(adj_list) | |
return G | |
def make_qstr(t): | |
"""Returns the string representation of t. | |
Add outer double-quotes if the string has a space. | |
""" | |
if not isinstance(t, str): | |
t = str(t) | |
if " " in t: | |
t = f'"{t}"' | |
return t | |