|
""" |
|
***** |
|
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 |
|
|
|
|
|
|
|
|
|
|
|
yield f"*vertices {G.order()}" |
|
nodes = list(G) |
|
|
|
nodenumber = dict(zip(nodes, range(1, len(nodes) + 1))) |
|
for n in nodes: |
|
|
|
|
|
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))) |
|
|
|
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 |
|
|
|
|
|
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) |
|
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 |
|
|
|
|
|
@open_file(1, mode="wb") |
|
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)) |
|
|
|
|
|
@open_file(0, mode="rb") |
|
@nx._dispatchable(graphs=None, returns_graph=True) |
|
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) |
|
|
|
|
|
@nx._dispatchable(graphs=None, returns_graph=True) |
|
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 |
|
|
|
|
|
if isinstance(lines, str): |
|
lines = iter(lines.split("\n")) |
|
lines = iter([line.rstrip("\n") for line in lines]) |
|
G = nx.MultiDiGraph() |
|
labels = [] |
|
while lines: |
|
try: |
|
l = next(lines) |
|
except: |
|
break |
|
if l.lower().startswith("*network"): |
|
try: |
|
label, name = l.split(None, 1) |
|
except ValueError: |
|
|
|
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"): |
|
|
|
G = nx.MultiGraph(G) |
|
if l.lower().startswith("*arcs"): |
|
|
|
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) |
|
|
|
edge_data = {} |
|
try: |
|
|
|
w = splitline[2:3] |
|
edge_data.update({"weight": float(w[0])}) |
|
except: |
|
pass |
|
|
|
|
|
extra_attr = zip(splitline[3::2], splitline[4::2]) |
|
edge_data.update(extra_attr) |
|
|
|
|
|
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 |
|
|