Adding all project files
This commit is contained in:
parent
6c9e127bdc
commit
cd4316ad0f
42289 changed files with 8009643 additions and 0 deletions
18
venv/Lib/site-packages/networkx/readwrite/__init__.py
Normal file
18
venv/Lib/site-packages/networkx/readwrite/__init__.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
A package for reading and writing graphs in various formats.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from networkx.readwrite.adjlist import *
|
||||
from networkx.readwrite.multiline_adjlist import *
|
||||
from networkx.readwrite.edgelist import *
|
||||
from networkx.readwrite.pajek import *
|
||||
from networkx.readwrite.leda import *
|
||||
from networkx.readwrite.sparse6 import *
|
||||
from networkx.readwrite.graph6 import *
|
||||
from networkx.readwrite.gml import *
|
||||
from networkx.readwrite.graphml import *
|
||||
from networkx.readwrite.gexf import *
|
||||
from networkx.readwrite.json_graph import *
|
||||
from networkx.readwrite.text import *
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
310
venv/Lib/site-packages/networkx/readwrite/adjlist.py
Normal file
310
venv/Lib/site-packages/networkx/readwrite/adjlist.py
Normal file
|
@ -0,0 +1,310 @@
|
|||
"""
|
||||
**************
|
||||
Adjacency List
|
||||
**************
|
||||
Read and write NetworkX graphs as adjacency lists.
|
||||
|
||||
Adjacency list format is useful for graphs without data associated
|
||||
with nodes or edges and for nodes that can be meaningfully represented
|
||||
as strings.
|
||||
|
||||
Format
|
||||
------
|
||||
The adjacency list format consists of lines with node labels. The
|
||||
first label in a line is the source node. Further labels in the line
|
||||
are considered target nodes and are added to the graph along with an edge
|
||||
between the source node and target node.
|
||||
|
||||
The graph with edges a-b, a-c, d-e can be represented as the following
|
||||
adjacency list (anything following the # in a line is a comment)::
|
||||
|
||||
a b c # source target target
|
||||
d e
|
||||
"""
|
||||
|
||||
__all__ = ["generate_adjlist", "write_adjlist", "parse_adjlist", "read_adjlist"]
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import open_file
|
||||
|
||||
|
||||
def generate_adjlist(G, delimiter=" "):
|
||||
"""Generate a single line of the graph G in adjacency list format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
delimiter : string, optional
|
||||
Separator for node labels
|
||||
|
||||
Returns
|
||||
-------
|
||||
lines : string
|
||||
Lines of data in adjlist format.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.lollipop_graph(4, 3)
|
||||
>>> for line in nx.generate_adjlist(G):
|
||||
... print(line)
|
||||
0 1 2 3
|
||||
1 2 3
|
||||
2 3
|
||||
3 4
|
||||
4 5
|
||||
5 6
|
||||
6
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_adjlist, read_adjlist
|
||||
|
||||
Notes
|
||||
-----
|
||||
The default `delimiter=" "` will result in unexpected results if node names contain
|
||||
whitespace characters. To avoid this problem, specify an alternate delimiter when spaces are
|
||||
valid in node names.
|
||||
|
||||
NB: This option is not available for data that isn't user-generated.
|
||||
|
||||
"""
|
||||
directed = G.is_directed()
|
||||
seen = set()
|
||||
for s, nbrs in G.adjacency():
|
||||
line = str(s) + delimiter
|
||||
for t, data in nbrs.items():
|
||||
if not directed and t in seen:
|
||||
continue
|
||||
if G.is_multigraph():
|
||||
for d in data.values():
|
||||
line += str(t) + delimiter
|
||||
else:
|
||||
line += str(t) + delimiter
|
||||
if not directed:
|
||||
seen.add(s)
|
||||
yield line[: -len(delimiter)]
|
||||
|
||||
|
||||
@open_file(1, mode="wb")
|
||||
def write_adjlist(G, path, comments="#", delimiter=" ", encoding="utf-8"):
|
||||
"""Write graph G in single-line adjacency-list format to path.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
path : string or file
|
||||
Filename or file handle for data output.
|
||||
Filenames ending in .gz or .bz2 will be compressed.
|
||||
|
||||
comments : string, optional
|
||||
Marker for comment lines
|
||||
|
||||
delimiter : string, optional
|
||||
Separator for node labels
|
||||
|
||||
encoding : string, optional
|
||||
Text encoding.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.write_adjlist(G, "test.adjlist")
|
||||
|
||||
The path can be a filehandle or a string with the name of the file. If a
|
||||
filehandle is provided, it has to be opened in 'wb' mode.
|
||||
|
||||
>>> fh = open("test.adjlist", "wb")
|
||||
>>> nx.write_adjlist(G, fh)
|
||||
|
||||
Notes
|
||||
-----
|
||||
The default `delimiter=" "` will result in unexpected results if node names contain
|
||||
whitespace characters. To avoid this problem, specify an alternate delimiter when spaces are
|
||||
valid in node names.
|
||||
NB: This option is not available for data that isn't user-generated.
|
||||
|
||||
This format does not store graph, node, or edge data.
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_adjlist, generate_adjlist
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
|
||||
pargs = comments + " ".join(sys.argv) + "\n"
|
||||
header = (
|
||||
pargs
|
||||
+ comments
|
||||
+ f" GMT {time.asctime(time.gmtime())}\n"
|
||||
+ comments
|
||||
+ f" {G.name}\n"
|
||||
)
|
||||
path.write(header.encode(encoding))
|
||||
|
||||
for line in generate_adjlist(G, delimiter):
|
||||
line += "\n"
|
||||
path.write(line.encode(encoding))
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def parse_adjlist(
|
||||
lines, comments="#", delimiter=None, create_using=None, nodetype=None
|
||||
):
|
||||
"""Parse lines of a graph adjacency list representation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lines : list or iterator of strings
|
||||
Input data in adjlist format
|
||||
|
||||
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
||||
Graph type to create. If graph instance, then cleared before populated.
|
||||
|
||||
nodetype : Python type, optional
|
||||
Convert nodes to this type.
|
||||
|
||||
comments : string, optional
|
||||
Marker for comment lines
|
||||
|
||||
delimiter : string, optional
|
||||
Separator for node labels. The default is whitespace.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G: NetworkX graph
|
||||
The graph corresponding to the lines in adjacency list format.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> lines = ["1 2 5", "2 3 4", "3 5", "4", "5"]
|
||||
>>> G = nx.parse_adjlist(lines, nodetype=int)
|
||||
>>> nodes = [1, 2, 3, 4, 5]
|
||||
>>> all(node in G for node in nodes)
|
||||
True
|
||||
>>> edges = [(1, 2), (1, 5), (2, 3), (2, 4), (3, 5)]
|
||||
>>> all((u, v) in G.edges() or (v, u) in G.edges() for (u, v) in edges)
|
||||
True
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_adjlist
|
||||
|
||||
"""
|
||||
G = nx.empty_graph(0, create_using)
|
||||
for line in lines:
|
||||
p = line.find(comments)
|
||||
if p >= 0:
|
||||
line = line[:p]
|
||||
if not len(line):
|
||||
continue
|
||||
vlist = line.strip().split(delimiter)
|
||||
u = vlist.pop(0)
|
||||
# convert types
|
||||
if nodetype is not None:
|
||||
try:
|
||||
u = nodetype(u)
|
||||
except BaseException as err:
|
||||
raise TypeError(
|
||||
f"Failed to convert node ({u}) to type " f"{nodetype}"
|
||||
) from err
|
||||
G.add_node(u)
|
||||
if nodetype is not None:
|
||||
try:
|
||||
vlist = list(map(nodetype, vlist))
|
||||
except BaseException as err:
|
||||
raise TypeError(
|
||||
f"Failed to convert nodes ({','.join(vlist)}) to type {nodetype}"
|
||||
) from err
|
||||
G.add_edges_from([(u, v) for v in vlist])
|
||||
return G
|
||||
|
||||
|
||||
@open_file(0, mode="rb")
|
||||
@nx._dispatch(graphs=None)
|
||||
def read_adjlist(
|
||||
path,
|
||||
comments="#",
|
||||
delimiter=None,
|
||||
create_using=None,
|
||||
nodetype=None,
|
||||
encoding="utf-8",
|
||||
):
|
||||
"""Read graph in adjacency list format from path.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : string or file
|
||||
Filename or file handle to read.
|
||||
Filenames ending in .gz or .bz2 will be uncompressed.
|
||||
|
||||
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
||||
Graph type to create. If graph instance, then cleared before populated.
|
||||
|
||||
nodetype : Python type, optional
|
||||
Convert nodes to this type.
|
||||
|
||||
comments : string, optional
|
||||
Marker for comment lines
|
||||
|
||||
delimiter : string, optional
|
||||
Separator for node labels. The default is whitespace.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G: NetworkX graph
|
||||
The graph corresponding to the lines in adjacency list format.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.write_adjlist(G, "test.adjlist")
|
||||
>>> G = nx.read_adjlist("test.adjlist")
|
||||
|
||||
The path can be a filehandle or a string with the name of the file. If a
|
||||
filehandle is provided, it has to be opened in 'rb' mode.
|
||||
|
||||
>>> fh = open("test.adjlist", "rb")
|
||||
>>> G = nx.read_adjlist(fh)
|
||||
|
||||
Filenames ending in .gz or .bz2 will be compressed.
|
||||
|
||||
>>> nx.write_adjlist(G, "test.adjlist.gz")
|
||||
>>> G = nx.read_adjlist("test.adjlist.gz")
|
||||
|
||||
The optional nodetype is a function to convert node strings to nodetype.
|
||||
|
||||
For example
|
||||
|
||||
>>> G = nx.read_adjlist("test.adjlist", nodetype=int)
|
||||
|
||||
will attempt to convert all nodes to integer type.
|
||||
|
||||
Since nodes must be hashable, the function nodetype must return hashable
|
||||
types (e.g. int, float, str, frozenset - or tuples of those, etc.)
|
||||
|
||||
The optional create_using parameter indicates the type of NetworkX graph
|
||||
created. The default is `nx.Graph`, an undirected graph.
|
||||
To read the data as a directed graph use
|
||||
|
||||
>>> G = nx.read_adjlist("test.adjlist", create_using=nx.DiGraph)
|
||||
|
||||
Notes
|
||||
-----
|
||||
This format does not store graph or node data.
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_adjlist
|
||||
"""
|
||||
lines = (line.decode(encoding) for line in path)
|
||||
return parse_adjlist(
|
||||
lines,
|
||||
comments=comments,
|
||||
delimiter=delimiter,
|
||||
create_using=create_using,
|
||||
nodetype=nodetype,
|
||||
)
|
489
venv/Lib/site-packages/networkx/readwrite/edgelist.py
Normal file
489
venv/Lib/site-packages/networkx/readwrite/edgelist.py
Normal file
|
@ -0,0 +1,489 @@
|
|||
"""
|
||||
**********
|
||||
Edge Lists
|
||||
**********
|
||||
Read and write NetworkX graphs as edge lists.
|
||||
|
||||
The multi-line adjacency list format is useful for graphs with nodes
|
||||
that can be meaningfully represented as strings. With the edgelist
|
||||
format simple edge data can be stored but node or graph data is not.
|
||||
There is no way of representing isolated nodes unless the node has a
|
||||
self-loop edge.
|
||||
|
||||
Format
|
||||
------
|
||||
You can read or write three formats of edge lists with these functions.
|
||||
|
||||
Node pairs with no data::
|
||||
|
||||
1 2
|
||||
|
||||
Python dictionary as data::
|
||||
|
||||
1 2 {'weight':7, 'color':'green'}
|
||||
|
||||
Arbitrary data::
|
||||
|
||||
1 2 7 green
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"generate_edgelist",
|
||||
"write_edgelist",
|
||||
"parse_edgelist",
|
||||
"read_edgelist",
|
||||
"read_weighted_edgelist",
|
||||
"write_weighted_edgelist",
|
||||
]
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import open_file
|
||||
|
||||
|
||||
def generate_edgelist(G, delimiter=" ", data=True):
|
||||
"""Generate a single line of the graph G in edge list format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
delimiter : string, optional
|
||||
Separator for node labels
|
||||
|
||||
data : bool or list of keys
|
||||
If False generate no edge data. If True use a dictionary
|
||||
representation of edge data. If a list of keys use a list of data
|
||||
values corresponding to the keys.
|
||||
|
||||
Returns
|
||||
-------
|
||||
lines : string
|
||||
Lines of data in adjlist format.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.lollipop_graph(4, 3)
|
||||
>>> G[1][2]["weight"] = 3
|
||||
>>> G[3][4]["capacity"] = 12
|
||||
>>> for line in nx.generate_edgelist(G, data=False):
|
||||
... print(line)
|
||||
0 1
|
||||
0 2
|
||||
0 3
|
||||
1 2
|
||||
1 3
|
||||
2 3
|
||||
3 4
|
||||
4 5
|
||||
5 6
|
||||
|
||||
>>> for line in nx.generate_edgelist(G):
|
||||
... print(line)
|
||||
0 1 {}
|
||||
0 2 {}
|
||||
0 3 {}
|
||||
1 2 {'weight': 3}
|
||||
1 3 {}
|
||||
2 3 {}
|
||||
3 4 {'capacity': 12}
|
||||
4 5 {}
|
||||
5 6 {}
|
||||
|
||||
>>> for line in nx.generate_edgelist(G, data=["weight"]):
|
||||
... print(line)
|
||||
0 1
|
||||
0 2
|
||||
0 3
|
||||
1 2 3
|
||||
1 3
|
||||
2 3
|
||||
3 4
|
||||
4 5
|
||||
5 6
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_adjlist, read_adjlist
|
||||
"""
|
||||
if data is True:
|
||||
for u, v, d in G.edges(data=True):
|
||||
e = u, v, dict(d)
|
||||
yield delimiter.join(map(str, e))
|
||||
elif data is False:
|
||||
for u, v in G.edges(data=False):
|
||||
e = u, v
|
||||
yield delimiter.join(map(str, e))
|
||||
else:
|
||||
for u, v, d in G.edges(data=True):
|
||||
e = [u, v]
|
||||
try:
|
||||
e.extend(d[k] for k in data)
|
||||
except KeyError:
|
||||
pass # missing data for this edge, should warn?
|
||||
yield delimiter.join(map(str, e))
|
||||
|
||||
|
||||
@open_file(1, mode="wb")
|
||||
def write_edgelist(G, path, comments="#", delimiter=" ", data=True, encoding="utf-8"):
|
||||
"""Write graph as a list of edges.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
path : file or string
|
||||
File or filename to write. If a file is provided, it must be
|
||||
opened in 'wb' mode. Filenames ending in .gz or .bz2 will be compressed.
|
||||
comments : string, optional
|
||||
The character used to indicate the start of a comment
|
||||
delimiter : string, optional
|
||||
The string used to separate values. The default is whitespace.
|
||||
data : bool or list, optional
|
||||
If False write no edge data.
|
||||
If True write a string representation of the edge data dictionary..
|
||||
If a list (or other iterable) is provided, write the keys specified
|
||||
in the list.
|
||||
encoding: string, optional
|
||||
Specify which encoding to use when writing file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.write_edgelist(G, "test.edgelist")
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> fh = open("test.edgelist", "wb")
|
||||
>>> nx.write_edgelist(G, fh)
|
||||
>>> nx.write_edgelist(G, "test.edgelist.gz")
|
||||
>>> nx.write_edgelist(G, "test.edgelist.gz", data=False)
|
||||
|
||||
>>> G = nx.Graph()
|
||||
>>> G.add_edge(1, 2, weight=7, color="red")
|
||||
>>> nx.write_edgelist(G, "test.edgelist", data=False)
|
||||
>>> nx.write_edgelist(G, "test.edgelist", data=["color"])
|
||||
>>> nx.write_edgelist(G, "test.edgelist", data=["color", "weight"])
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_edgelist
|
||||
write_weighted_edgelist
|
||||
"""
|
||||
|
||||
for line in generate_edgelist(G, delimiter, data):
|
||||
line += "\n"
|
||||
path.write(line.encode(encoding))
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def parse_edgelist(
|
||||
lines, comments="#", delimiter=None, create_using=None, nodetype=None, data=True
|
||||
):
|
||||
"""Parse lines of an edge list representation of a graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lines : list or iterator of strings
|
||||
Input data in edgelist format
|
||||
comments : string, optional
|
||||
Marker for comment lines. Default is `'#'`. To specify that no character
|
||||
should be treated as a comment, use ``comments=None``.
|
||||
delimiter : string, optional
|
||||
Separator for node labels. Default is `None`, meaning any whitespace.
|
||||
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
||||
Graph type to create. If graph instance, then cleared before populated.
|
||||
nodetype : Python type, optional
|
||||
Convert nodes to this type. Default is `None`, meaning no conversion is
|
||||
performed.
|
||||
data : bool or list of (label,type) tuples
|
||||
If `False` generate no edge data or if `True` use a dictionary
|
||||
representation of edge data or a list tuples specifying dictionary
|
||||
key names and types for edge data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G: NetworkX Graph
|
||||
The graph corresponding to lines
|
||||
|
||||
Examples
|
||||
--------
|
||||
Edgelist with no data:
|
||||
|
||||
>>> lines = ["1 2", "2 3", "3 4"]
|
||||
>>> G = nx.parse_edgelist(lines, nodetype=int)
|
||||
>>> list(G)
|
||||
[1, 2, 3, 4]
|
||||
>>> list(G.edges())
|
||||
[(1, 2), (2, 3), (3, 4)]
|
||||
|
||||
Edgelist with data in Python dictionary representation:
|
||||
|
||||
>>> lines = ["1 2 {'weight': 3}", "2 3 {'weight': 27}", "3 4 {'weight': 3.0}"]
|
||||
>>> G = nx.parse_edgelist(lines, nodetype=int)
|
||||
>>> list(G)
|
||||
[1, 2, 3, 4]
|
||||
>>> list(G.edges(data=True))
|
||||
[(1, 2, {'weight': 3}), (2, 3, {'weight': 27}), (3, 4, {'weight': 3.0})]
|
||||
|
||||
Edgelist with data in a list:
|
||||
|
||||
>>> lines = ["1 2 3", "2 3 27", "3 4 3.0"]
|
||||
>>> G = nx.parse_edgelist(lines, nodetype=int, data=(("weight", float),))
|
||||
>>> list(G)
|
||||
[1, 2, 3, 4]
|
||||
>>> list(G.edges(data=True))
|
||||
[(1, 2, {'weight': 3.0}), (2, 3, {'weight': 27.0}), (3, 4, {'weight': 3.0})]
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_weighted_edgelist
|
||||
"""
|
||||
from ast import literal_eval
|
||||
|
||||
G = nx.empty_graph(0, create_using)
|
||||
for line in lines:
|
||||
if comments is not None:
|
||||
p = line.find(comments)
|
||||
if p >= 0:
|
||||
line = line[:p]
|
||||
if not line:
|
||||
continue
|
||||
# split line, should have 2 or more
|
||||
s = line.strip().split(delimiter)
|
||||
if len(s) < 2:
|
||||
continue
|
||||
u = s.pop(0)
|
||||
v = s.pop(0)
|
||||
d = s
|
||||
if nodetype is not None:
|
||||
try:
|
||||
u = nodetype(u)
|
||||
v = nodetype(v)
|
||||
except Exception as err:
|
||||
raise TypeError(
|
||||
f"Failed to convert nodes {u},{v} to type {nodetype}."
|
||||
) from err
|
||||
|
||||
if len(d) == 0 or data is False:
|
||||
# no data or data type specified
|
||||
edgedata = {}
|
||||
elif data is True:
|
||||
# no edge types specified
|
||||
try: # try to evaluate as dictionary
|
||||
if delimiter == ",":
|
||||
edgedata_str = ",".join(d)
|
||||
else:
|
||||
edgedata_str = " ".join(d)
|
||||
edgedata = dict(literal_eval(edgedata_str.strip()))
|
||||
except Exception as err:
|
||||
raise TypeError(
|
||||
f"Failed to convert edge data ({d}) to dictionary."
|
||||
) from err
|
||||
else:
|
||||
# convert edge data to dictionary with specified keys and type
|
||||
if len(d) != len(data):
|
||||
raise IndexError(
|
||||
f"Edge data {d} and data_keys {data} are not the same length"
|
||||
)
|
||||
edgedata = {}
|
||||
for (edge_key, edge_type), edge_value in zip(data, d):
|
||||
try:
|
||||
edge_value = edge_type(edge_value)
|
||||
except Exception as err:
|
||||
raise TypeError(
|
||||
f"Failed to convert {edge_key} data {edge_value} "
|
||||
f"to type {edge_type}."
|
||||
) from err
|
||||
edgedata.update({edge_key: edge_value})
|
||||
G.add_edge(u, v, **edgedata)
|
||||
return G
|
||||
|
||||
|
||||
@open_file(0, mode="rb")
|
||||
@nx._dispatch(graphs=None)
|
||||
def read_edgelist(
|
||||
path,
|
||||
comments="#",
|
||||
delimiter=None,
|
||||
create_using=None,
|
||||
nodetype=None,
|
||||
data=True,
|
||||
edgetype=None,
|
||||
encoding="utf-8",
|
||||
):
|
||||
"""Read a graph from a list of edges.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : file or string
|
||||
File or filename to read. If a file is provided, it must be
|
||||
opened in 'rb' mode.
|
||||
Filenames ending in .gz or .bz2 will be uncompressed.
|
||||
comments : string, optional
|
||||
The character used to indicate the start of a comment. To specify that
|
||||
no character should be treated as a comment, use ``comments=None``.
|
||||
delimiter : string, optional
|
||||
The string used to separate values. The default is whitespace.
|
||||
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
||||
Graph type to create. If graph instance, then cleared before populated.
|
||||
nodetype : int, float, str, Python type, optional
|
||||
Convert node data from strings to specified type
|
||||
data : bool or list of (label,type) tuples
|
||||
Tuples specifying dictionary key names and types for edge data
|
||||
edgetype : int, float, str, Python type, optional OBSOLETE
|
||||
Convert edge data from strings to specified type and use as 'weight'
|
||||
encoding: string, optional
|
||||
Specify which encoding to use when reading file.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : graph
|
||||
A networkx Graph or other type specified with create_using
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> nx.write_edgelist(nx.path_graph(4), "test.edgelist")
|
||||
>>> G = nx.read_edgelist("test.edgelist")
|
||||
|
||||
>>> fh = open("test.edgelist", "rb")
|
||||
>>> G = nx.read_edgelist(fh)
|
||||
>>> fh.close()
|
||||
|
||||
>>> G = nx.read_edgelist("test.edgelist", nodetype=int)
|
||||
>>> G = nx.read_edgelist("test.edgelist", create_using=nx.DiGraph)
|
||||
|
||||
Edgelist with data in a list:
|
||||
|
||||
>>> textline = "1 2 3"
|
||||
>>> fh = open("test.edgelist", "w")
|
||||
>>> d = fh.write(textline)
|
||||
>>> fh.close()
|
||||
>>> G = nx.read_edgelist("test.edgelist", nodetype=int, data=(("weight", float),))
|
||||
>>> list(G)
|
||||
[1, 2]
|
||||
>>> list(G.edges(data=True))
|
||||
[(1, 2, {'weight': 3.0})]
|
||||
|
||||
See parse_edgelist() for more examples of formatting.
|
||||
|
||||
See Also
|
||||
--------
|
||||
parse_edgelist
|
||||
write_edgelist
|
||||
|
||||
Notes
|
||||
-----
|
||||
Since nodes must be hashable, the function nodetype must return hashable
|
||||
types (e.g. int, float, str, frozenset - or tuples of those, etc.)
|
||||
"""
|
||||
lines = (line if isinstance(line, str) else line.decode(encoding) for line in path)
|
||||
return parse_edgelist(
|
||||
lines,
|
||||
comments=comments,
|
||||
delimiter=delimiter,
|
||||
create_using=create_using,
|
||||
nodetype=nodetype,
|
||||
data=data,
|
||||
)
|
||||
|
||||
|
||||
def write_weighted_edgelist(G, path, comments="#", delimiter=" ", encoding="utf-8"):
|
||||
"""Write graph G as a list of edges with numeric weights.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A NetworkX graph
|
||||
path : file or string
|
||||
File or filename to write. If a file is provided, it must be
|
||||
opened in 'wb' mode.
|
||||
Filenames ending in .gz or .bz2 will be compressed.
|
||||
comments : string, optional
|
||||
The character used to indicate the start of a comment
|
||||
delimiter : string, optional
|
||||
The string used to separate values. The default is whitespace.
|
||||
encoding: string, optional
|
||||
Specify which encoding to use when writing file.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.Graph()
|
||||
>>> G.add_edge(1, 2, weight=7)
|
||||
>>> nx.write_weighted_edgelist(G, "test.weighted.edgelist")
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_edgelist
|
||||
write_edgelist
|
||||
read_weighted_edgelist
|
||||
"""
|
||||
write_edgelist(
|
||||
G,
|
||||
path,
|
||||
comments=comments,
|
||||
delimiter=delimiter,
|
||||
data=("weight",),
|
||||
encoding=encoding,
|
||||
)
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def read_weighted_edgelist(
|
||||
path,
|
||||
comments="#",
|
||||
delimiter=None,
|
||||
create_using=None,
|
||||
nodetype=None,
|
||||
encoding="utf-8",
|
||||
):
|
||||
"""Read a graph as list of edges with numeric weights.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : file or string
|
||||
File or filename to read. If a file is provided, it must be
|
||||
opened in 'rb' mode.
|
||||
Filenames ending in .gz or .bz2 will be uncompressed.
|
||||
comments : string, optional
|
||||
The character used to indicate the start of a comment.
|
||||
delimiter : string, optional
|
||||
The string used to separate values. The default is whitespace.
|
||||
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
||||
Graph type to create. If graph instance, then cleared before populated.
|
||||
nodetype : int, float, str, Python type, optional
|
||||
Convert node data from strings to specified type
|
||||
encoding: string, optional
|
||||
Specify which encoding to use when reading file.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : graph
|
||||
A networkx Graph or other type specified with create_using
|
||||
|
||||
Notes
|
||||
-----
|
||||
Since nodes must be hashable, the function nodetype must return hashable
|
||||
types (e.g. int, float, str, frozenset - or tuples of those, etc.)
|
||||
|
||||
Example edgelist file format.
|
||||
|
||||
With numeric edge data::
|
||||
|
||||
# read with
|
||||
# >>> G=nx.read_weighted_edgelist(fh)
|
||||
# source target data
|
||||
a b 1
|
||||
a c 3.14159
|
||||
d e 42
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_weighted_edgelist
|
||||
"""
|
||||
return read_edgelist(
|
||||
path,
|
||||
comments=comments,
|
||||
delimiter=delimiter,
|
||||
create_using=create_using,
|
||||
nodetype=nodetype,
|
||||
data=(("weight", float),),
|
||||
encoding=encoding,
|
||||
)
|
1065
venv/Lib/site-packages/networkx/readwrite/gexf.py
Normal file
1065
venv/Lib/site-packages/networkx/readwrite/gexf.py
Normal file
File diff suppressed because it is too large
Load diff
878
venv/Lib/site-packages/networkx/readwrite/gml.py
Normal file
878
venv/Lib/site-packages/networkx/readwrite/gml.py
Normal file
|
@ -0,0 +1,878 @@
|
|||
"""
|
||||
Read graphs in GML format.
|
||||
|
||||
"GML, the Graph Modelling Language, is our proposal for a portable
|
||||
file format for graphs. GML's key features are portability, simple
|
||||
syntax, extensibility and flexibility. A GML file consists of a
|
||||
hierarchical key-value lists. Graphs can be annotated with arbitrary
|
||||
data structures. The idea for a common file format was born at the
|
||||
GD'95; this proposal is the outcome of many discussions. GML is the
|
||||
standard file format in the Graphlet graph editor system. It has been
|
||||
overtaken and adapted by several other systems for drawing graphs."
|
||||
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
You will need to give some thought into how the exported data should
|
||||
interact with different languages and even different Python versions.
|
||||
Re-importing from gml is also a concern.
|
||||
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For writing other data types, and for reading data other
|
||||
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
|
||||
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML website <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
|
||||
|
||||
Several example graphs in GML format may be found on Mark Newman's
|
||||
`Network data page <http://www-personal.umich.edu/~mejn/netdata/>`_.
|
||||
"""
|
||||
import html.entities as htmlentitydefs
|
||||
import re
|
||||
import warnings
|
||||
from ast import literal_eval
|
||||
from collections import defaultdict
|
||||
from enum import Enum
|
||||
from io import StringIO
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
import networkx as nx
|
||||
from networkx.exception import NetworkXError
|
||||
from networkx.utils import open_file
|
||||
|
||||
__all__ = ["read_gml", "parse_gml", "generate_gml", "write_gml"]
|
||||
|
||||
|
||||
def escape(text):
|
||||
"""Use XML character references to escape characters.
|
||||
|
||||
Use XML character references for unprintable or non-ASCII
|
||||
characters, double quotes and ampersands in a string
|
||||
"""
|
||||
|
||||
def fixup(m):
|
||||
ch = m.group(0)
|
||||
return "&#" + str(ord(ch)) + ";"
|
||||
|
||||
text = re.sub('[^ -~]|[&"]', fixup, text)
|
||||
return text if isinstance(text, str) else str(text)
|
||||
|
||||
|
||||
def unescape(text):
|
||||
"""Replace XML character references with the referenced characters"""
|
||||
|
||||
def fixup(m):
|
||||
text = m.group(0)
|
||||
if text[1] == "#":
|
||||
# Character reference
|
||||
if text[2] == "x":
|
||||
code = int(text[3:-1], 16)
|
||||
else:
|
||||
code = int(text[2:-1])
|
||||
else:
|
||||
# Named entity
|
||||
try:
|
||||
code = htmlentitydefs.name2codepoint[text[1:-1]]
|
||||
except KeyError:
|
||||
return text # leave unchanged
|
||||
try:
|
||||
return chr(code)
|
||||
except (ValueError, OverflowError):
|
||||
return text # leave unchanged
|
||||
|
||||
return re.sub("&(?:[0-9A-Za-z]+|#(?:[0-9]+|x[0-9A-Fa-f]+));", fixup, text)
|
||||
|
||||
|
||||
def literal_destringizer(rep):
|
||||
"""Convert a Python literal to the value it represents.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rep : string
|
||||
A Python literal.
|
||||
|
||||
Returns
|
||||
-------
|
||||
value : object
|
||||
The value of the Python literal.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If `rep` is not a Python literal.
|
||||
"""
|
||||
if isinstance(rep, str):
|
||||
orig_rep = rep
|
||||
try:
|
||||
return literal_eval(rep)
|
||||
except SyntaxError as err:
|
||||
raise ValueError(f"{orig_rep!r} is not a valid Python literal") from err
|
||||
else:
|
||||
raise ValueError(f"{rep!r} is not a string")
|
||||
|
||||
|
||||
@open_file(0, mode="rb")
|
||||
@nx._dispatch(graphs=None)
|
||||
def read_gml(path, label="label", destringizer=None):
|
||||
"""Read graph in GML format from `path`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : filename or filehandle
|
||||
The filename or filehandle to read from.
|
||||
|
||||
label : string, optional
|
||||
If not None, the parsed nodes will be renamed according to node
|
||||
attributes indicated by `label`. Default value: 'label'.
|
||||
|
||||
destringizer : callable, optional
|
||||
A `destringizer` that recovers values stored as strings in GML. If it
|
||||
cannot convert a string to a value, a `ValueError` is raised. Default
|
||||
value : None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : NetworkX graph
|
||||
The parsed graph.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the input cannot be parsed.
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_gml, parse_gml
|
||||
literal_destringizer
|
||||
|
||||
Notes
|
||||
-----
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For writing other data types, and for reading data other
|
||||
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
|
||||
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
|
||||
|
||||
See the module docstring :mod:`networkx.readwrite.gml` for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.write_gml(G, "test.gml")
|
||||
|
||||
GML values are interpreted as strings by default:
|
||||
|
||||
>>> H = nx.read_gml("test.gml")
|
||||
>>> H.nodes
|
||||
NodeView(('0', '1', '2', '3'))
|
||||
|
||||
When a `destringizer` is provided, GML values are converted to the provided type.
|
||||
For example, integer nodes can be recovered as shown below:
|
||||
|
||||
>>> J = nx.read_gml("test.gml", destringizer=int)
|
||||
>>> J.nodes
|
||||
NodeView((0, 1, 2, 3))
|
||||
|
||||
"""
|
||||
|
||||
def filter_lines(lines):
|
||||
for line in lines:
|
||||
try:
|
||||
line = line.decode("ascii")
|
||||
except UnicodeDecodeError as err:
|
||||
raise NetworkXError("input is not ASCII-encoded") from err
|
||||
if not isinstance(line, str):
|
||||
lines = str(lines)
|
||||
if line and line[-1] == "\n":
|
||||
line = line[:-1]
|
||||
yield line
|
||||
|
||||
G = parse_gml_lines(filter_lines(path), label, destringizer)
|
||||
return G
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def parse_gml(lines, label="label", destringizer=None):
|
||||
"""Parse GML graph from a string or iterable.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lines : string or iterable of strings
|
||||
Data in GML format.
|
||||
|
||||
label : string, optional
|
||||
If not None, the parsed nodes will be renamed according to node
|
||||
attributes indicated by `label`. Default value: 'label'.
|
||||
|
||||
destringizer : callable, optional
|
||||
A `destringizer` that recovers values stored as strings in GML. If it
|
||||
cannot convert a string to a value, a `ValueError` is raised. Default
|
||||
value : None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : NetworkX graph
|
||||
The parsed graph.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the input cannot be parsed.
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_gml, read_gml
|
||||
|
||||
Notes
|
||||
-----
|
||||
This stores nested GML attributes as dictionaries in the NetworkX graph,
|
||||
node, and edge attribute structures.
|
||||
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For writing other data types, and for reading data other
|
||||
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
|
||||
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
|
||||
|
||||
See the module docstring :mod:`networkx.readwrite.gml` for more details.
|
||||
"""
|
||||
|
||||
def decode_line(line):
|
||||
if isinstance(line, bytes):
|
||||
try:
|
||||
line.decode("ascii")
|
||||
except UnicodeDecodeError as err:
|
||||
raise NetworkXError("input is not ASCII-encoded") from err
|
||||
if not isinstance(line, str):
|
||||
line = str(line)
|
||||
return line
|
||||
|
||||
def filter_lines(lines):
|
||||
if isinstance(lines, str):
|
||||
lines = decode_line(lines)
|
||||
lines = lines.splitlines()
|
||||
yield from lines
|
||||
else:
|
||||
for line in lines:
|
||||
line = decode_line(line)
|
||||
if line and line[-1] == "\n":
|
||||
line = line[:-1]
|
||||
if line.find("\n") != -1:
|
||||
raise NetworkXError("input line contains newline")
|
||||
yield line
|
||||
|
||||
G = parse_gml_lines(filter_lines(lines), label, destringizer)
|
||||
return G
|
||||
|
||||
|
||||
class Pattern(Enum):
|
||||
"""encodes the index of each token-matching pattern in `tokenize`."""
|
||||
|
||||
KEYS = 0
|
||||
REALS = 1
|
||||
INTS = 2
|
||||
STRINGS = 3
|
||||
DICT_START = 4
|
||||
DICT_END = 5
|
||||
COMMENT_WHITESPACE = 6
|
||||
|
||||
|
||||
class Token(NamedTuple):
|
||||
category: Pattern
|
||||
value: Any
|
||||
line: int
|
||||
position: int
|
||||
|
||||
|
||||
LIST_START_VALUE = "_networkx_list_start"
|
||||
|
||||
|
||||
def parse_gml_lines(lines, label, destringizer):
|
||||
"""Parse GML `lines` into a graph."""
|
||||
|
||||
def tokenize():
|
||||
patterns = [
|
||||
r"[A-Za-z][0-9A-Za-z_]*\b", # keys
|
||||
# reals
|
||||
r"[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.[0-9]*|INF)(?:[Ee][+-]?[0-9]+)?",
|
||||
r"[+-]?[0-9]+", # ints
|
||||
r'".*?"', # strings
|
||||
r"\[", # dict start
|
||||
r"\]", # dict end
|
||||
r"#.*$|\s+", # comments and whitespaces
|
||||
]
|
||||
tokens = re.compile("|".join(f"({pattern})" for pattern in patterns))
|
||||
lineno = 0
|
||||
multilines = [] # entries spread across multiple lines
|
||||
for line in lines:
|
||||
pos = 0
|
||||
|
||||
# deal with entries spread across multiple lines
|
||||
#
|
||||
# should we actually have to deal with escaped "s then do it here
|
||||
if multilines:
|
||||
multilines.append(line.strip())
|
||||
if line[-1] == '"': # closing multiline entry
|
||||
# multiline entries will be joined by space. cannot
|
||||
# reintroduce newlines as this will break the tokenizer
|
||||
line = " ".join(multilines)
|
||||
multilines = []
|
||||
else: # continued multiline entry
|
||||
lineno += 1
|
||||
continue
|
||||
else:
|
||||
if line.count('"') == 1: # opening multiline entry
|
||||
if line.strip()[0] != '"' and line.strip()[-1] != '"':
|
||||
# since we expect something like key "value", the " should not be found at ends
|
||||
# otherwise tokenizer will pick up the formatting mistake.
|
||||
multilines = [line.rstrip()]
|
||||
lineno += 1
|
||||
continue
|
||||
|
||||
length = len(line)
|
||||
|
||||
while pos < length:
|
||||
match = tokens.match(line, pos)
|
||||
if match is None:
|
||||
m = f"cannot tokenize {line[pos:]} at ({lineno + 1}, {pos + 1})"
|
||||
raise NetworkXError(m)
|
||||
for i in range(len(patterns)):
|
||||
group = match.group(i + 1)
|
||||
if group is not None:
|
||||
if i == 0: # keys
|
||||
value = group.rstrip()
|
||||
elif i == 1: # reals
|
||||
value = float(group)
|
||||
elif i == 2: # ints
|
||||
value = int(group)
|
||||
else:
|
||||
value = group
|
||||
if i != 6: # comments and whitespaces
|
||||
yield Token(Pattern(i), value, lineno + 1, pos + 1)
|
||||
pos += len(group)
|
||||
break
|
||||
lineno += 1
|
||||
yield Token(None, None, lineno + 1, 1) # EOF
|
||||
|
||||
def unexpected(curr_token, expected):
|
||||
category, value, lineno, pos = curr_token
|
||||
value = repr(value) if value is not None else "EOF"
|
||||
raise NetworkXError(f"expected {expected}, found {value} at ({lineno}, {pos})")
|
||||
|
||||
def consume(curr_token, category, expected):
|
||||
if curr_token.category == category:
|
||||
return next(tokens)
|
||||
unexpected(curr_token, expected)
|
||||
|
||||
def parse_kv(curr_token):
|
||||
dct = defaultdict(list)
|
||||
while curr_token.category == Pattern.KEYS:
|
||||
key = curr_token.value
|
||||
curr_token = next(tokens)
|
||||
category = curr_token.category
|
||||
if category == Pattern.REALS or category == Pattern.INTS:
|
||||
value = curr_token.value
|
||||
curr_token = next(tokens)
|
||||
elif category == Pattern.STRINGS:
|
||||
value = unescape(curr_token.value[1:-1])
|
||||
if destringizer:
|
||||
try:
|
||||
value = destringizer(value)
|
||||
except ValueError:
|
||||
pass
|
||||
# Special handling for empty lists and tuples
|
||||
if value == "()":
|
||||
value = ()
|
||||
if value == "[]":
|
||||
value = []
|
||||
curr_token = next(tokens)
|
||||
elif category == Pattern.DICT_START:
|
||||
curr_token, value = parse_dict(curr_token)
|
||||
else:
|
||||
# Allow for string convertible id and label values
|
||||
if key in ("id", "label", "source", "target"):
|
||||
try:
|
||||
# String convert the token value
|
||||
value = unescape(str(curr_token.value))
|
||||
if destringizer:
|
||||
try:
|
||||
value = destringizer(value)
|
||||
except ValueError:
|
||||
pass
|
||||
curr_token = next(tokens)
|
||||
except Exception:
|
||||
msg = (
|
||||
"an int, float, string, '[' or string"
|
||||
+ " convertible ASCII value for node id or label"
|
||||
)
|
||||
unexpected(curr_token, msg)
|
||||
# Special handling for nan and infinity. Since the gml language
|
||||
# defines unquoted strings as keys, the numeric and string branches
|
||||
# are skipped and we end up in this special branch, so we need to
|
||||
# convert the current token value to a float for NAN and plain INF.
|
||||
# +/-INF are handled in the pattern for 'reals' in tokenize(). This
|
||||
# allows labels and values to be nan or infinity, but not keys.
|
||||
elif curr_token.value in {"NAN", "INF"}:
|
||||
value = float(curr_token.value)
|
||||
curr_token = next(tokens)
|
||||
else: # Otherwise error out
|
||||
unexpected(curr_token, "an int, float, string or '['")
|
||||
dct[key].append(value)
|
||||
|
||||
def clean_dict_value(value):
|
||||
if not isinstance(value, list):
|
||||
return value
|
||||
if len(value) == 1:
|
||||
return value[0]
|
||||
if value[0] == LIST_START_VALUE:
|
||||
return value[1:]
|
||||
return value
|
||||
|
||||
dct = {key: clean_dict_value(value) for key, value in dct.items()}
|
||||
return curr_token, dct
|
||||
|
||||
def parse_dict(curr_token):
|
||||
# dict start
|
||||
curr_token = consume(curr_token, Pattern.DICT_START, "'['")
|
||||
# dict contents
|
||||
curr_token, dct = parse_kv(curr_token)
|
||||
# dict end
|
||||
curr_token = consume(curr_token, Pattern.DICT_END, "']'")
|
||||
return curr_token, dct
|
||||
|
||||
def parse_graph():
|
||||
curr_token, dct = parse_kv(next(tokens))
|
||||
if curr_token.category is not None: # EOF
|
||||
unexpected(curr_token, "EOF")
|
||||
if "graph" not in dct:
|
||||
raise NetworkXError("input contains no graph")
|
||||
graph = dct["graph"]
|
||||
if isinstance(graph, list):
|
||||
raise NetworkXError("input contains more than one graph")
|
||||
return graph
|
||||
|
||||
tokens = tokenize()
|
||||
graph = parse_graph()
|
||||
|
||||
directed = graph.pop("directed", False)
|
||||
multigraph = graph.pop("multigraph", False)
|
||||
if not multigraph:
|
||||
G = nx.DiGraph() if directed else nx.Graph()
|
||||
else:
|
||||
G = nx.MultiDiGraph() if directed else nx.MultiGraph()
|
||||
graph_attr = {k: v for k, v in graph.items() if k not in ("node", "edge")}
|
||||
G.graph.update(graph_attr)
|
||||
|
||||
def pop_attr(dct, category, attr, i):
|
||||
try:
|
||||
return dct.pop(attr)
|
||||
except KeyError as err:
|
||||
raise NetworkXError(f"{category} #{i} has no {attr!r} attribute") from err
|
||||
|
||||
nodes = graph.get("node", [])
|
||||
mapping = {}
|
||||
node_labels = set()
|
||||
for i, node in enumerate(nodes if isinstance(nodes, list) else [nodes]):
|
||||
id = pop_attr(node, "node", "id", i)
|
||||
if id in G:
|
||||
raise NetworkXError(f"node id {id!r} is duplicated")
|
||||
if label is not None and label != "id":
|
||||
node_label = pop_attr(node, "node", label, i)
|
||||
if node_label in node_labels:
|
||||
raise NetworkXError(f"node label {node_label!r} is duplicated")
|
||||
node_labels.add(node_label)
|
||||
mapping[id] = node_label
|
||||
G.add_node(id, **node)
|
||||
|
||||
edges = graph.get("edge", [])
|
||||
for i, edge in enumerate(edges if isinstance(edges, list) else [edges]):
|
||||
source = pop_attr(edge, "edge", "source", i)
|
||||
target = pop_attr(edge, "edge", "target", i)
|
||||
if source not in G:
|
||||
raise NetworkXError(f"edge #{i} has undefined source {source!r}")
|
||||
if target not in G:
|
||||
raise NetworkXError(f"edge #{i} has undefined target {target!r}")
|
||||
if not multigraph:
|
||||
if not G.has_edge(source, target):
|
||||
G.add_edge(source, target, **edge)
|
||||
else:
|
||||
arrow = "->" if directed else "--"
|
||||
msg = f"edge #{i} ({source!r}{arrow}{target!r}) is duplicated"
|
||||
raise nx.NetworkXError(msg)
|
||||
else:
|
||||
key = edge.pop("key", None)
|
||||
if key is not None and G.has_edge(source, target, key):
|
||||
arrow = "->" if directed else "--"
|
||||
msg = f"edge #{i} ({source!r}{arrow}{target!r}, {key!r})"
|
||||
msg2 = 'Hint: If multigraph add "multigraph 1" to file header.'
|
||||
raise nx.NetworkXError(msg + " is duplicated\n" + msg2)
|
||||
G.add_edge(source, target, key, **edge)
|
||||
|
||||
if label is not None and label != "id":
|
||||
G = nx.relabel_nodes(G, mapping)
|
||||
return G
|
||||
|
||||
|
||||
def literal_stringizer(value):
|
||||
"""Convert a `value` to a Python literal in GML representation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : object
|
||||
The `value` to be converted to GML representation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rep : string
|
||||
A double-quoted Python literal representing value. Unprintable
|
||||
characters are replaced by XML character references.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If `value` cannot be converted to GML.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The original value can be recovered using the
|
||||
:func:`networkx.readwrite.gml.literal_destringizer` function.
|
||||
"""
|
||||
|
||||
def stringize(value):
|
||||
if isinstance(value, (int, bool)) or value is None:
|
||||
if value is True: # GML uses 1/0 for boolean values.
|
||||
buf.write(str(1))
|
||||
elif value is False:
|
||||
buf.write(str(0))
|
||||
else:
|
||||
buf.write(str(value))
|
||||
elif isinstance(value, str):
|
||||
text = repr(value)
|
||||
if text[0] != "u":
|
||||
try:
|
||||
value.encode("latin1")
|
||||
except UnicodeEncodeError:
|
||||
text = "u" + text
|
||||
buf.write(text)
|
||||
elif isinstance(value, (float, complex, str, bytes)):
|
||||
buf.write(repr(value))
|
||||
elif isinstance(value, list):
|
||||
buf.write("[")
|
||||
first = True
|
||||
for item in value:
|
||||
if not first:
|
||||
buf.write(",")
|
||||
else:
|
||||
first = False
|
||||
stringize(item)
|
||||
buf.write("]")
|
||||
elif isinstance(value, tuple):
|
||||
if len(value) > 1:
|
||||
buf.write("(")
|
||||
first = True
|
||||
for item in value:
|
||||
if not first:
|
||||
buf.write(",")
|
||||
else:
|
||||
first = False
|
||||
stringize(item)
|
||||
buf.write(")")
|
||||
elif value:
|
||||
buf.write("(")
|
||||
stringize(value[0])
|
||||
buf.write(",)")
|
||||
else:
|
||||
buf.write("()")
|
||||
elif isinstance(value, dict):
|
||||
buf.write("{")
|
||||
first = True
|
||||
for key, value in value.items():
|
||||
if not first:
|
||||
buf.write(",")
|
||||
else:
|
||||
first = False
|
||||
stringize(key)
|
||||
buf.write(":")
|
||||
stringize(value)
|
||||
buf.write("}")
|
||||
elif isinstance(value, set):
|
||||
buf.write("{")
|
||||
first = True
|
||||
for item in value:
|
||||
if not first:
|
||||
buf.write(",")
|
||||
else:
|
||||
first = False
|
||||
stringize(item)
|
||||
buf.write("}")
|
||||
else:
|
||||
msg = f"{value!r} cannot be converted into a Python literal"
|
||||
raise ValueError(msg)
|
||||
|
||||
buf = StringIO()
|
||||
stringize(value)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def generate_gml(G, stringizer=None):
|
||||
r"""Generate a single entry of the graph `G` in GML format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
The graph to be converted to GML.
|
||||
|
||||
stringizer : callable, optional
|
||||
A `stringizer` which converts non-int/non-float/non-dict values into
|
||||
strings. If it cannot convert a value into a string, it should raise a
|
||||
`ValueError` to indicate that. Default value: None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
lines: generator of strings
|
||||
Lines of GML data. Newlines are not appended.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If `stringizer` cannot convert a value into a string, or the value to
|
||||
convert is not a string while `stringizer` is None.
|
||||
|
||||
See Also
|
||||
--------
|
||||
literal_stringizer
|
||||
|
||||
Notes
|
||||
-----
|
||||
Graph attributes named 'directed', 'multigraph', 'node' or
|
||||
'edge', node attributes named 'id' or 'label', edge attributes
|
||||
named 'source' or 'target' (or 'key' if `G` is a multigraph)
|
||||
are ignored because these attribute names are used to encode the graph
|
||||
structure.
|
||||
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For writing other data types, and for reading data other
|
||||
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
|
||||
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
|
||||
|
||||
See the module docstring :mod:`networkx.readwrite.gml` for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.Graph()
|
||||
>>> G.add_node("1")
|
||||
>>> print("\n".join(nx.generate_gml(G)))
|
||||
graph [
|
||||
node [
|
||||
id 0
|
||||
label "1"
|
||||
]
|
||||
]
|
||||
>>> G = nx.MultiGraph([("a", "b"), ("a", "b")])
|
||||
>>> print("\n".join(nx.generate_gml(G)))
|
||||
graph [
|
||||
multigraph 1
|
||||
node [
|
||||
id 0
|
||||
label "a"
|
||||
]
|
||||
node [
|
||||
id 1
|
||||
label "b"
|
||||
]
|
||||
edge [
|
||||
source 0
|
||||
target 1
|
||||
key 0
|
||||
]
|
||||
edge [
|
||||
source 0
|
||||
target 1
|
||||
key 1
|
||||
]
|
||||
]
|
||||
"""
|
||||
valid_keys = re.compile("^[A-Za-z][0-9A-Za-z_]*$")
|
||||
|
||||
def stringize(key, value, ignored_keys, indent, in_list=False):
|
||||
if not isinstance(key, str):
|
||||
raise NetworkXError(f"{key!r} is not a string")
|
||||
if not valid_keys.match(key):
|
||||
raise NetworkXError(f"{key!r} is not a valid key")
|
||||
if not isinstance(key, str):
|
||||
key = str(key)
|
||||
if key not in ignored_keys:
|
||||
if isinstance(value, (int, bool)):
|
||||
if key == "label":
|
||||
yield indent + key + ' "' + str(value) + '"'
|
||||
elif value is True:
|
||||
# python bool is an instance of int
|
||||
yield indent + key + " 1"
|
||||
elif value is False:
|
||||
yield indent + key + " 0"
|
||||
# GML only supports signed 32-bit integers
|
||||
elif value < -(2**31) or value >= 2**31:
|
||||
yield indent + key + ' "' + str(value) + '"'
|
||||
else:
|
||||
yield indent + key + " " + str(value)
|
||||
elif isinstance(value, float):
|
||||
text = repr(value).upper()
|
||||
# GML matches INF to keys, so prepend + to INF. Use repr(float(*))
|
||||
# instead of string literal to future proof against changes to repr.
|
||||
if text == repr(float("inf")).upper():
|
||||
text = "+" + text
|
||||
else:
|
||||
# GML requires that a real literal contain a decimal point, but
|
||||
# repr may not output a decimal point when the mantissa is
|
||||
# integral and hence needs fixing.
|
||||
epos = text.rfind("E")
|
||||
if epos != -1 and text.find(".", 0, epos) == -1:
|
||||
text = text[:epos] + "." + text[epos:]
|
||||
if key == "label":
|
||||
yield indent + key + ' "' + text + '"'
|
||||
else:
|
||||
yield indent + key + " " + text
|
||||
elif isinstance(value, dict):
|
||||
yield indent + key + " ["
|
||||
next_indent = indent + " "
|
||||
for key, value in value.items():
|
||||
yield from stringize(key, value, (), next_indent)
|
||||
yield indent + "]"
|
||||
elif isinstance(value, tuple) and key == "label":
|
||||
yield indent + key + f" \"({','.join(repr(v) for v in value)})\""
|
||||
elif isinstance(value, (list, tuple)) and key != "label" and not in_list:
|
||||
if len(value) == 0:
|
||||
yield indent + key + " " + f'"{value!r}"'
|
||||
if len(value) == 1:
|
||||
yield indent + key + " " + f'"{LIST_START_VALUE}"'
|
||||
for val in value:
|
||||
yield from stringize(key, val, (), indent, True)
|
||||
else:
|
||||
if stringizer:
|
||||
try:
|
||||
value = stringizer(value)
|
||||
except ValueError as err:
|
||||
raise NetworkXError(
|
||||
f"{value!r} cannot be converted into a string"
|
||||
) from err
|
||||
if not isinstance(value, str):
|
||||
raise NetworkXError(f"{value!r} is not a string")
|
||||
yield indent + key + ' "' + escape(value) + '"'
|
||||
|
||||
multigraph = G.is_multigraph()
|
||||
yield "graph ["
|
||||
|
||||
# Output graph attributes
|
||||
if G.is_directed():
|
||||
yield " directed 1"
|
||||
if multigraph:
|
||||
yield " multigraph 1"
|
||||
ignored_keys = {"directed", "multigraph", "node", "edge"}
|
||||
for attr, value in G.graph.items():
|
||||
yield from stringize(attr, value, ignored_keys, " ")
|
||||
|
||||
# Output node data
|
||||
node_id = dict(zip(G, range(len(G))))
|
||||
ignored_keys = {"id", "label"}
|
||||
for node, attrs in G.nodes.items():
|
||||
yield " node ["
|
||||
yield " id " + str(node_id[node])
|
||||
yield from stringize("label", node, (), " ")
|
||||
for attr, value in attrs.items():
|
||||
yield from stringize(attr, value, ignored_keys, " ")
|
||||
yield " ]"
|
||||
|
||||
# Output edge data
|
||||
ignored_keys = {"source", "target"}
|
||||
kwargs = {"data": True}
|
||||
if multigraph:
|
||||
ignored_keys.add("key")
|
||||
kwargs["keys"] = True
|
||||
for e in G.edges(**kwargs):
|
||||
yield " edge ["
|
||||
yield " source " + str(node_id[e[0]])
|
||||
yield " target " + str(node_id[e[1]])
|
||||
if multigraph:
|
||||
yield from stringize("key", e[2], (), " ")
|
||||
for attr, value in e[-1].items():
|
||||
yield from stringize(attr, value, ignored_keys, " ")
|
||||
yield " ]"
|
||||
yield "]"
|
||||
|
||||
|
||||
@open_file(1, mode="wb")
|
||||
def write_gml(G, path, stringizer=None):
|
||||
"""Write a graph `G` in GML format to the file or file handle `path`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
The graph to be converted to GML.
|
||||
|
||||
path : filename or filehandle
|
||||
The filename or filehandle to write. Files whose names end with .gz or
|
||||
.bz2 will be compressed.
|
||||
|
||||
stringizer : callable, optional
|
||||
A `stringizer` which converts non-int/non-float/non-dict values into
|
||||
strings. If it cannot convert a value into a string, it should raise a
|
||||
`ValueError` to indicate that. Default value: None.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If `stringizer` cannot convert a value into a string, or the value to
|
||||
convert is not a string while `stringizer` is None.
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_gml, generate_gml
|
||||
literal_stringizer
|
||||
|
||||
Notes
|
||||
-----
|
||||
Graph attributes named 'directed', 'multigraph', 'node' or
|
||||
'edge', node attributes named 'id' or 'label', edge attributes
|
||||
named 'source' or 'target' (or 'key' if `G` is a multigraph)
|
||||
are ignored because these attribute names are used to encode the graph
|
||||
structure.
|
||||
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For writing other data types, and for reading data other
|
||||
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
|
||||
|
||||
Note that while we allow non-standard GML to be read from a file, we make
|
||||
sure to write GML format. In particular, underscores are not allowed in
|
||||
attribute names.
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
|
||||
|
||||
See the module docstring :mod:`networkx.readwrite.gml` for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.write_gml(G, "test.gml")
|
||||
|
||||
Filenames ending in .gz or .bz2 will be compressed.
|
||||
|
||||
>>> nx.write_gml(G, "test.gml.gz")
|
||||
"""
|
||||
for line in generate_gml(G, stringizer):
|
||||
path.write((line + "\n").encode("ascii"))
|
416
venv/Lib/site-packages/networkx/readwrite/graph6.py
Normal file
416
venv/Lib/site-packages/networkx/readwrite/graph6.py
Normal file
|
@ -0,0 +1,416 @@
|
|||
# Original author: D. Eppstein, UC Irvine, August 12, 2003.
|
||||
# The original code at http://www.ics.uci.edu/~eppstein/PADS/ is public domain.
|
||||
"""Functions for reading and writing graphs in the *graph6* format.
|
||||
|
||||
The *graph6* file format is suitable for small graphs or large dense
|
||||
graphs. For large sparse graphs, use the *sparse6* format.
|
||||
|
||||
For more information, see the `graph6`_ homepage.
|
||||
|
||||
.. _graph6: http://users.cecs.anu.edu.au/~bdm/data/formats.html
|
||||
|
||||
"""
|
||||
from itertools import islice
|
||||
|
||||
import networkx as nx
|
||||
from networkx.exception import NetworkXError
|
||||
from networkx.utils import not_implemented_for, open_file
|
||||
|
||||
__all__ = ["from_graph6_bytes", "read_graph6", "to_graph6_bytes", "write_graph6"]
|
||||
|
||||
|
||||
def _generate_graph6_bytes(G, nodes, header):
|
||||
"""Yield bytes in the graph6 encoding of a graph.
|
||||
|
||||
`G` is an undirected simple graph. `nodes` is the list of nodes for
|
||||
which the node-induced subgraph will be encoded; if `nodes` is the
|
||||
list of all nodes in the graph, the entire graph will be
|
||||
encoded. `header` is a Boolean that specifies whether to generate
|
||||
the header ``b'>>graph6<<'`` before the remaining data.
|
||||
|
||||
This function generates `bytes` objects in the following order:
|
||||
|
||||
1. the header (if requested),
|
||||
2. the encoding of the number of nodes,
|
||||
3. each character, one-at-a-time, in the encoding of the requested
|
||||
node-induced subgraph,
|
||||
4. a newline character.
|
||||
|
||||
This function raises :exc:`ValueError` if the graph is too large for
|
||||
the graph6 format (that is, greater than ``2 ** 36`` nodes).
|
||||
|
||||
"""
|
||||
n = len(G)
|
||||
if n >= 2**36:
|
||||
raise ValueError(
|
||||
"graph6 is only defined if number of nodes is less " "than 2 ** 36"
|
||||
)
|
||||
if header:
|
||||
yield b">>graph6<<"
|
||||
for d in n_to_data(n):
|
||||
yield str.encode(chr(d + 63))
|
||||
# This generates the same as `(v in G[u] for u, v in combinations(G, 2))`,
|
||||
# but in "column-major" order instead of "row-major" order.
|
||||
bits = (nodes[j] in G[nodes[i]] for j in range(1, n) for i in range(j))
|
||||
chunk = list(islice(bits, 6))
|
||||
while chunk:
|
||||
d = sum(b << 5 - i for i, b in enumerate(chunk))
|
||||
yield str.encode(chr(d + 63))
|
||||
chunk = list(islice(bits, 6))
|
||||
yield b"\n"
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def from_graph6_bytes(bytes_in):
|
||||
"""Read a simple undirected graph in graph6 format from bytes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bytes_in : bytes
|
||||
Data in graph6 format, without a trailing newline.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : Graph
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If bytes_in is unable to be parsed in graph6 format
|
||||
|
||||
ValueError
|
||||
If any character ``c`` in bytes_in does not satisfy
|
||||
``63 <= ord(c) < 127``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.from_graph6_bytes(b"A_")
|
||||
>>> sorted(G.edges())
|
||||
[(0, 1)]
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_graph6, write_graph6
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Graph6 specification
|
||||
<http://users.cecs.anu.edu.au/~bdm/data/formats.html>
|
||||
|
||||
"""
|
||||
|
||||
def bits():
|
||||
"""Returns sequence of individual bits from 6-bit-per-value
|
||||
list of data values."""
|
||||
for d in data:
|
||||
for i in [5, 4, 3, 2, 1, 0]:
|
||||
yield (d >> i) & 1
|
||||
|
||||
if bytes_in.startswith(b">>graph6<<"):
|
||||
bytes_in = bytes_in[10:]
|
||||
|
||||
data = [c - 63 for c in bytes_in]
|
||||
if any(c > 63 for c in data):
|
||||
raise ValueError("each input character must be in range(63, 127)")
|
||||
|
||||
n, data = data_to_n(data)
|
||||
nd = (n * (n - 1) // 2 + 5) // 6
|
||||
if len(data) != nd:
|
||||
raise NetworkXError(
|
||||
f"Expected {n * (n - 1) // 2} bits but got {len(data) * 6} in graph6"
|
||||
)
|
||||
|
||||
G = nx.Graph()
|
||||
G.add_nodes_from(range(n))
|
||||
for (i, j), b in zip(((i, j) for j in range(1, n) for i in range(j)), bits()):
|
||||
if b:
|
||||
G.add_edge(i, j)
|
||||
|
||||
return G
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def to_graph6_bytes(G, nodes=None, header=True):
|
||||
"""Convert a simple undirected graph to bytes in graph6 format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : Graph (undirected)
|
||||
|
||||
nodes: list or iterable
|
||||
Nodes are labeled 0...n-1 in the order provided. If None the ordering
|
||||
given by ``G.nodes()`` is used.
|
||||
|
||||
header: bool
|
||||
If True add '>>graph6<<' bytes to head of data.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If the graph is directed or is a multigraph.
|
||||
|
||||
ValueError
|
||||
If the graph has at least ``2 ** 36`` nodes; the graph6 format
|
||||
is only defined for graphs of order less than ``2 ** 36``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> nx.to_graph6_bytes(nx.path_graph(2))
|
||||
b'>>graph6<<A_\\n'
|
||||
|
||||
See Also
|
||||
--------
|
||||
from_graph6_bytes, read_graph6, write_graph6_bytes
|
||||
|
||||
Notes
|
||||
-----
|
||||
The returned bytes end with a newline character.
|
||||
|
||||
The format does not support edge or node labels, parallel edges or
|
||||
self loops. If self loops are present they are silently ignored.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Graph6 specification
|
||||
<http://users.cecs.anu.edu.au/~bdm/data/formats.html>
|
||||
|
||||
"""
|
||||
if nodes is not None:
|
||||
G = G.subgraph(nodes)
|
||||
H = nx.convert_node_labels_to_integers(G)
|
||||
nodes = sorted(H.nodes())
|
||||
return b"".join(_generate_graph6_bytes(H, nodes, header))
|
||||
|
||||
|
||||
@open_file(0, mode="rb")
|
||||
@nx._dispatch(graphs=None)
|
||||
def read_graph6(path):
|
||||
"""Read simple undirected graphs in graph6 format from path.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : file or string
|
||||
File or filename to write.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : Graph or list of Graphs
|
||||
If the file contains multiple lines then a list of graphs is returned
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the string is unable to be parsed in graph6 format
|
||||
|
||||
Examples
|
||||
--------
|
||||
You can read a graph6 file by giving the path to the file::
|
||||
|
||||
>>> import tempfile
|
||||
>>> with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
... _ = f.write(b">>graph6<<A_\\n")
|
||||
... _ = f.seek(0)
|
||||
... G = nx.read_graph6(f.name)
|
||||
>>> list(G.edges())
|
||||
[(0, 1)]
|
||||
|
||||
You can also read a graph6 file by giving an open file-like object::
|
||||
|
||||
>>> import tempfile
|
||||
>>> with tempfile.NamedTemporaryFile() as f:
|
||||
... _ = f.write(b">>graph6<<A_\\n")
|
||||
... _ = f.seek(0)
|
||||
... G = nx.read_graph6(f)
|
||||
>>> list(G.edges())
|
||||
[(0, 1)]
|
||||
|
||||
See Also
|
||||
--------
|
||||
from_graph6_bytes, write_graph6
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Graph6 specification
|
||||
<http://users.cecs.anu.edu.au/~bdm/data/formats.html>
|
||||
|
||||
"""
|
||||
glist = []
|
||||
for line in path:
|
||||
line = line.strip()
|
||||
if not len(line):
|
||||
continue
|
||||
glist.append(from_graph6_bytes(line))
|
||||
if len(glist) == 1:
|
||||
return glist[0]
|
||||
else:
|
||||
return glist
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
@open_file(1, mode="wb")
|
||||
def write_graph6(G, path, nodes=None, header=True):
|
||||
"""Write a simple undirected graph to a path in graph6 format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : Graph (undirected)
|
||||
|
||||
path : str
|
||||
The path naming the file to which to write the graph.
|
||||
|
||||
nodes: list or iterable
|
||||
Nodes are labeled 0...n-1 in the order provided. If None the ordering
|
||||
given by ``G.nodes()`` is used.
|
||||
|
||||
header: bool
|
||||
If True add '>>graph6<<' string to head of data
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If the graph is directed or is a multigraph.
|
||||
|
||||
ValueError
|
||||
If the graph has at least ``2 ** 36`` nodes; the graph6 format
|
||||
is only defined for graphs of order less than ``2 ** 36``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
You can write a graph6 file by giving the path to a file::
|
||||
|
||||
>>> import tempfile
|
||||
>>> with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
... nx.write_graph6(nx.path_graph(2), f.name)
|
||||
... _ = f.seek(0)
|
||||
... print(f.read())
|
||||
b'>>graph6<<A_\\n'
|
||||
|
||||
See Also
|
||||
--------
|
||||
from_graph6_bytes, read_graph6
|
||||
|
||||
Notes
|
||||
-----
|
||||
The function writes a newline character after writing the encoding
|
||||
of the graph.
|
||||
|
||||
The format does not support edge or node labels, parallel edges or
|
||||
self loops. If self loops are present they are silently ignored.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Graph6 specification
|
||||
<http://users.cecs.anu.edu.au/~bdm/data/formats.html>
|
||||
|
||||
"""
|
||||
return write_graph6_file(G, path, nodes=nodes, header=header)
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def write_graph6_file(G, f, nodes=None, header=True):
|
||||
"""Write a simple undirected graph to a file-like object in graph6 format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : Graph (undirected)
|
||||
|
||||
f : file-like object
|
||||
The file to write.
|
||||
|
||||
nodes: list or iterable
|
||||
Nodes are labeled 0...n-1 in the order provided. If None the ordering
|
||||
given by ``G.nodes()`` is used.
|
||||
|
||||
header: bool
|
||||
If True add '>>graph6<<' string to head of data
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If the graph is directed or is a multigraph.
|
||||
|
||||
ValueError
|
||||
If the graph has at least ``2 ** 36`` nodes; the graph6 format
|
||||
is only defined for graphs of order less than ``2 ** 36``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
You can write a graph6 file by giving an open file-like object::
|
||||
|
||||
>>> import tempfile
|
||||
>>> with tempfile.NamedTemporaryFile() as f:
|
||||
... nx.write_graph6(nx.path_graph(2), f)
|
||||
... _ = f.seek(0)
|
||||
... print(f.read())
|
||||
b'>>graph6<<A_\\n'
|
||||
|
||||
See Also
|
||||
--------
|
||||
from_graph6_bytes, read_graph6
|
||||
|
||||
Notes
|
||||
-----
|
||||
The function writes a newline character after writing the encoding
|
||||
of the graph.
|
||||
|
||||
The format does not support edge or node labels, parallel edges or
|
||||
self loops. If self loops are present they are silently ignored.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Graph6 specification
|
||||
<http://users.cecs.anu.edu.au/~bdm/data/formats.html>
|
||||
|
||||
"""
|
||||
if nodes is not None:
|
||||
G = G.subgraph(nodes)
|
||||
H = nx.convert_node_labels_to_integers(G)
|
||||
nodes = sorted(H.nodes())
|
||||
for b in _generate_graph6_bytes(H, nodes, header):
|
||||
f.write(b)
|
||||
|
||||
|
||||
def data_to_n(data):
|
||||
"""Read initial one-, four- or eight-unit value from graph6
|
||||
integer sequence.
|
||||
|
||||
Return (value, rest of seq.)"""
|
||||
if data[0] <= 62:
|
||||
return data[0], data[1:]
|
||||
if data[1] <= 62:
|
||||
return (data[1] << 12) + (data[2] << 6) + data[3], data[4:]
|
||||
return (
|
||||
(data[2] << 30)
|
||||
+ (data[3] << 24)
|
||||
+ (data[4] << 18)
|
||||
+ (data[5] << 12)
|
||||
+ (data[6] << 6)
|
||||
+ data[7],
|
||||
data[8:],
|
||||
)
|
||||
|
||||
|
||||
def n_to_data(n):
|
||||
"""Convert an integer to one-, four- or eight-unit graph6 sequence.
|
||||
|
||||
This function is undefined if `n` is not in ``range(2 ** 36)``.
|
||||
|
||||
"""
|
||||
if n <= 62:
|
||||
return [n]
|
||||
elif n <= 258047:
|
||||
return [63, (n >> 12) & 0x3F, (n >> 6) & 0x3F, n & 0x3F]
|
||||
else: # if n <= 68719476735:
|
||||
return [
|
||||
63,
|
||||
63,
|
||||
(n >> 30) & 0x3F,
|
||||
(n >> 24) & 0x3F,
|
||||
(n >> 18) & 0x3F,
|
||||
(n >> 12) & 0x3F,
|
||||
(n >> 6) & 0x3F,
|
||||
n & 0x3F,
|
||||
]
|
1051
venv/Lib/site-packages/networkx/readwrite/graphml.py
Normal file
1051
venv/Lib/site-packages/networkx/readwrite/graphml.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,18 @@
|
|||
"""
|
||||
*********
|
||||
JSON data
|
||||
*********
|
||||
Generate and parse JSON serializable data for NetworkX graphs.
|
||||
|
||||
These formats are suitable for use with the d3.js examples https://d3js.org/
|
||||
|
||||
The three formats that you can generate with NetworkX are:
|
||||
|
||||
- node-link like in the d3.js example https://bl.ocks.org/mbostock/4062045
|
||||
- tree like in the d3.js example https://bl.ocks.org/mbostock/4063550
|
||||
- adjacency like in the d3.js example https://bost.ocks.org/mike/miserables/
|
||||
"""
|
||||
from networkx.readwrite.json_graph.node_link import *
|
||||
from networkx.readwrite.json_graph.adjacency import *
|
||||
from networkx.readwrite.json_graph.tree import *
|
||||
from networkx.readwrite.json_graph.cytoscape import *
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,156 @@
|
|||
import networkx as nx
|
||||
|
||||
__all__ = ["adjacency_data", "adjacency_graph"]
|
||||
|
||||
_attrs = {"id": "id", "key": "key"}
|
||||
|
||||
|
||||
def adjacency_data(G, attrs=_attrs):
|
||||
"""Returns data in adjacency format that is suitable for JSON serialization
|
||||
and use in JavaScript documents.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
attrs : dict
|
||||
A dictionary that contains two keys 'id' and 'key'. The corresponding
|
||||
values provide the attribute names for storing NetworkX-internal graph
|
||||
data. The values should be unique. Default value:
|
||||
:samp:`dict(id='id', key='key')`.
|
||||
|
||||
If some user-defined graph data use these attribute names as data keys,
|
||||
they may be silently dropped.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data : dict
|
||||
A dictionary with adjacency formatted data.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If values in attrs are not unique.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from networkx.readwrite import json_graph
|
||||
>>> G = nx.Graph([(1, 2)])
|
||||
>>> data = json_graph.adjacency_data(G)
|
||||
|
||||
To serialize with json
|
||||
|
||||
>>> import json
|
||||
>>> s = json.dumps(data)
|
||||
|
||||
Notes
|
||||
-----
|
||||
Graph, node, and link attributes will be written when using this format
|
||||
but attribute keys must be strings if you want to serialize the resulting
|
||||
data with JSON.
|
||||
|
||||
The default value of attrs will be changed in a future release of NetworkX.
|
||||
|
||||
See Also
|
||||
--------
|
||||
adjacency_graph, node_link_data, tree_data
|
||||
"""
|
||||
multigraph = G.is_multigraph()
|
||||
id_ = attrs["id"]
|
||||
# Allow 'key' to be omitted from attrs if the graph is not a multigraph.
|
||||
key = None if not multigraph else attrs["key"]
|
||||
if id_ == key:
|
||||
raise nx.NetworkXError("Attribute names are not unique.")
|
||||
data = {}
|
||||
data["directed"] = G.is_directed()
|
||||
data["multigraph"] = multigraph
|
||||
data["graph"] = list(G.graph.items())
|
||||
data["nodes"] = []
|
||||
data["adjacency"] = []
|
||||
for n, nbrdict in G.adjacency():
|
||||
data["nodes"].append({**G.nodes[n], id_: n})
|
||||
adj = []
|
||||
if multigraph:
|
||||
for nbr, keys in nbrdict.items():
|
||||
for k, d in keys.items():
|
||||
adj.append({**d, id_: nbr, key: k})
|
||||
else:
|
||||
for nbr, d in nbrdict.items():
|
||||
adj.append({**d, id_: nbr})
|
||||
data["adjacency"].append(adj)
|
||||
return data
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def adjacency_graph(data, directed=False, multigraph=True, attrs=_attrs):
|
||||
"""Returns graph from adjacency data format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : dict
|
||||
Adjacency list formatted graph data
|
||||
|
||||
directed : bool
|
||||
If True, and direction not specified in data, return a directed graph.
|
||||
|
||||
multigraph : bool
|
||||
If True, and multigraph not specified in data, return a multigraph.
|
||||
|
||||
attrs : dict
|
||||
A dictionary that contains two keys 'id' and 'key'. The corresponding
|
||||
values provide the attribute names for storing NetworkX-internal graph
|
||||
data. The values should be unique. Default value:
|
||||
:samp:`dict(id='id', key='key')`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : NetworkX graph
|
||||
A NetworkX graph object
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from networkx.readwrite import json_graph
|
||||
>>> G = nx.Graph([(1, 2)])
|
||||
>>> data = json_graph.adjacency_data(G)
|
||||
>>> H = json_graph.adjacency_graph(data)
|
||||
|
||||
Notes
|
||||
-----
|
||||
The default value of attrs will be changed in a future release of NetworkX.
|
||||
|
||||
See Also
|
||||
--------
|
||||
adjacency_graph, node_link_data, tree_data
|
||||
"""
|
||||
multigraph = data.get("multigraph", multigraph)
|
||||
directed = data.get("directed", directed)
|
||||
if multigraph:
|
||||
graph = nx.MultiGraph()
|
||||
else:
|
||||
graph = nx.Graph()
|
||||
if directed:
|
||||
graph = graph.to_directed()
|
||||
id_ = attrs["id"]
|
||||
# Allow 'key' to be omitted from attrs if the graph is not a multigraph.
|
||||
key = None if not multigraph else attrs["key"]
|
||||
graph.graph = dict(data.get("graph", []))
|
||||
mapping = []
|
||||
for d in data["nodes"]:
|
||||
node_data = d.copy()
|
||||
node = node_data.pop(id_)
|
||||
mapping.append(node)
|
||||
graph.add_node(node)
|
||||
graph.nodes[node].update(node_data)
|
||||
for i, d in enumerate(data["adjacency"]):
|
||||
source = mapping[i]
|
||||
for tdata in d:
|
||||
target_data = tdata.copy()
|
||||
target = target_data.pop(id_)
|
||||
if not multigraph:
|
||||
graph.add_edge(source, target)
|
||||
graph[source][target].update(target_data)
|
||||
else:
|
||||
ky = target_data.pop(key, None)
|
||||
graph.add_edge(source, target, key=ky)
|
||||
graph[source][target][ky].update(target_data)
|
||||
return graph
|
|
@ -0,0 +1,174 @@
|
|||
import networkx as nx
|
||||
|
||||
__all__ = ["cytoscape_data", "cytoscape_graph"]
|
||||
|
||||
|
||||
def cytoscape_data(G, name="name", ident="id"):
|
||||
"""Returns data in Cytoscape JSON format (cyjs).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX Graph
|
||||
The graph to convert to cytoscape format
|
||||
name : string
|
||||
A string which is mapped to the 'name' node element in cyjs format.
|
||||
Must not have the same value as `ident`.
|
||||
ident : string
|
||||
A string which is mapped to the 'id' node element in cyjs format.
|
||||
Must not have the same value as `name`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data: dict
|
||||
A dictionary with cyjs formatted data.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the values for `name` and `ident` are identical.
|
||||
|
||||
See Also
|
||||
--------
|
||||
cytoscape_graph: convert a dictionary in cyjs format to a graph
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Cytoscape user's manual:
|
||||
http://manual.cytoscape.org/en/stable/index.html
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(2)
|
||||
>>> nx.cytoscape_data(G) # doctest: +SKIP
|
||||
{'data': [],
|
||||
'directed': False,
|
||||
'multigraph': False,
|
||||
'elements': {'nodes': [{'data': {'id': '0', 'value': 0, 'name': '0'}},
|
||||
{'data': {'id': '1', 'value': 1, 'name': '1'}}],
|
||||
'edges': [{'data': {'source': 0, 'target': 1}}]}}
|
||||
"""
|
||||
if name == ident:
|
||||
raise nx.NetworkXError("name and ident must be different.")
|
||||
|
||||
jsondata = {"data": list(G.graph.items())}
|
||||
jsondata["directed"] = G.is_directed()
|
||||
jsondata["multigraph"] = G.is_multigraph()
|
||||
jsondata["elements"] = {"nodes": [], "edges": []}
|
||||
nodes = jsondata["elements"]["nodes"]
|
||||
edges = jsondata["elements"]["edges"]
|
||||
|
||||
for i, j in G.nodes.items():
|
||||
n = {"data": j.copy()}
|
||||
n["data"]["id"] = j.get(ident) or str(i)
|
||||
n["data"]["value"] = i
|
||||
n["data"]["name"] = j.get(name) or str(i)
|
||||
nodes.append(n)
|
||||
|
||||
if G.is_multigraph():
|
||||
for e in G.edges(keys=True):
|
||||
n = {"data": G.adj[e[0]][e[1]][e[2]].copy()}
|
||||
n["data"]["source"] = e[0]
|
||||
n["data"]["target"] = e[1]
|
||||
n["data"]["key"] = e[2]
|
||||
edges.append(n)
|
||||
else:
|
||||
for e in G.edges():
|
||||
n = {"data": G.adj[e[0]][e[1]].copy()}
|
||||
n["data"]["source"] = e[0]
|
||||
n["data"]["target"] = e[1]
|
||||
edges.append(n)
|
||||
return jsondata
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def cytoscape_graph(data, name="name", ident="id"):
|
||||
"""
|
||||
Create a NetworkX graph from a dictionary in cytoscape JSON format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : dict
|
||||
A dictionary of data conforming to cytoscape JSON format.
|
||||
name : string
|
||||
A string which is mapped to the 'name' node element in cyjs format.
|
||||
Must not have the same value as `ident`.
|
||||
ident : string
|
||||
A string which is mapped to the 'id' node element in cyjs format.
|
||||
Must not have the same value as `name`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
graph : a NetworkX graph instance
|
||||
The `graph` can be an instance of `Graph`, `DiGraph`, `MultiGraph`, or
|
||||
`MultiDiGraph` depending on the input data.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the `name` and `ident` attributes are identical.
|
||||
|
||||
See Also
|
||||
--------
|
||||
cytoscape_data: convert a NetworkX graph to a dict in cyjs format
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Cytoscape user's manual:
|
||||
http://manual.cytoscape.org/en/stable/index.html
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> data_dict = {
|
||||
... 'data': [],
|
||||
... 'directed': False,
|
||||
... 'multigraph': False,
|
||||
... 'elements': {'nodes': [{'data': {'id': '0', 'value': 0, 'name': '0'}},
|
||||
... {'data': {'id': '1', 'value': 1, 'name': '1'}}],
|
||||
... 'edges': [{'data': {'source': 0, 'target': 1}}]}
|
||||
... }
|
||||
>>> G = nx.cytoscape_graph(data_dict)
|
||||
>>> G.name
|
||||
''
|
||||
>>> G.nodes()
|
||||
NodeView((0, 1))
|
||||
>>> G.nodes(data=True)[0]
|
||||
{'id': '0', 'value': 0, 'name': '0'}
|
||||
>>> G.edges(data=True)
|
||||
EdgeDataView([(0, 1, {'source': 0, 'target': 1})])
|
||||
"""
|
||||
if name == ident:
|
||||
raise nx.NetworkXError("name and ident must be different.")
|
||||
|
||||
multigraph = data.get("multigraph")
|
||||
directed = data.get("directed")
|
||||
if multigraph:
|
||||
graph = nx.MultiGraph()
|
||||
else:
|
||||
graph = nx.Graph()
|
||||
if directed:
|
||||
graph = graph.to_directed()
|
||||
graph.graph = dict(data.get("data"))
|
||||
for d in data["elements"]["nodes"]:
|
||||
node_data = d["data"].copy()
|
||||
node = d["data"]["value"]
|
||||
|
||||
if d["data"].get(name):
|
||||
node_data[name] = d["data"].get(name)
|
||||
if d["data"].get(ident):
|
||||
node_data[ident] = d["data"].get(ident)
|
||||
|
||||
graph.add_node(node)
|
||||
graph.nodes[node].update(node_data)
|
||||
|
||||
for d in data["elements"]["edges"]:
|
||||
edge_data = d["data"].copy()
|
||||
sour = d["data"]["source"]
|
||||
targ = d["data"]["target"]
|
||||
if multigraph:
|
||||
key = d["data"].get("key", 0)
|
||||
graph.add_edge(sour, targ, key=key)
|
||||
graph.edges[sour, targ, key].update(edge_data)
|
||||
else:
|
||||
graph.add_edge(sour, targ)
|
||||
graph.edges[sour, targ].update(edge_data)
|
||||
return graph
|
|
@ -0,0 +1,244 @@
|
|||
from itertools import chain, count
|
||||
|
||||
import networkx as nx
|
||||
|
||||
__all__ = ["node_link_data", "node_link_graph"]
|
||||
|
||||
|
||||
_attrs = {
|
||||
"source": "source",
|
||||
"target": "target",
|
||||
"name": "id",
|
||||
"key": "key",
|
||||
"link": "links",
|
||||
}
|
||||
|
||||
|
||||
def _to_tuple(x):
|
||||
"""Converts lists to tuples, including nested lists.
|
||||
|
||||
All other non-list inputs are passed through unmodified. This function is
|
||||
intended to be used to convert potentially nested lists from json files
|
||||
into valid nodes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> _to_tuple([1, 2, [3, 4]])
|
||||
(1, 2, (3, 4))
|
||||
"""
|
||||
if not isinstance(x, (tuple, list)):
|
||||
return x
|
||||
return tuple(map(_to_tuple, x))
|
||||
|
||||
|
||||
def node_link_data(
|
||||
G,
|
||||
*,
|
||||
source="source",
|
||||
target="target",
|
||||
name="id",
|
||||
key="key",
|
||||
link="links",
|
||||
):
|
||||
"""Returns data in node-link format that is suitable for JSON serialization
|
||||
and use in JavaScript documents.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
source : string
|
||||
A string that provides the 'source' attribute name for storing NetworkX-internal graph data.
|
||||
target : string
|
||||
A string that provides the 'target' attribute name for storing NetworkX-internal graph data.
|
||||
name : string
|
||||
A string that provides the 'name' attribute name for storing NetworkX-internal graph data.
|
||||
key : string
|
||||
A string that provides the 'key' attribute name for storing NetworkX-internal graph data.
|
||||
link : string
|
||||
A string that provides the 'link' attribute name for storing NetworkX-internal graph data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data : dict
|
||||
A dictionary with node-link formatted data.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the values of 'source', 'target' and 'key' are not unique.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.Graph([("A", "B")])
|
||||
>>> data1 = nx.node_link_data(G)
|
||||
>>> data1
|
||||
{'directed': False, 'multigraph': False, 'graph': {}, 'nodes': [{'id': 'A'}, {'id': 'B'}], 'links': [{'source': 'A', 'target': 'B'}]}
|
||||
|
||||
To serialize with JSON
|
||||
|
||||
>>> import json
|
||||
>>> s1 = json.dumps(data1)
|
||||
>>> s1
|
||||
'{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": "A"}, {"id": "B"}], "links": [{"source": "A", "target": "B"}]}'
|
||||
|
||||
A graph can also be serialized by passing `node_link_data` as an encoder function. The two methods are equivalent.
|
||||
|
||||
>>> s1 = json.dumps(G, default=nx.node_link_data)
|
||||
>>> s1
|
||||
'{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": "A"}, {"id": "B"}], "links": [{"source": "A", "target": "B"}]}'
|
||||
|
||||
The attribute names for storing NetworkX-internal graph data can
|
||||
be specified as keyword options.
|
||||
|
||||
>>> H = nx.gn_graph(2)
|
||||
>>> data2 = nx.node_link_data(H, link="edges", source="from", target="to")
|
||||
>>> data2
|
||||
{'directed': True, 'multigraph': False, 'graph': {}, 'nodes': [{'id': 0}, {'id': 1}], 'edges': [{'from': 1, 'to': 0}]}
|
||||
|
||||
Notes
|
||||
-----
|
||||
Graph, node, and link attributes are stored in this format. Note that
|
||||
attribute keys will be converted to strings in order to comply with JSON.
|
||||
|
||||
Attribute 'key' is only used for multigraphs.
|
||||
|
||||
To use `node_link_data` in conjunction with `node_link_graph`,
|
||||
the keyword names for the attributes must match.
|
||||
|
||||
|
||||
See Also
|
||||
--------
|
||||
node_link_graph, adjacency_data, tree_data
|
||||
"""
|
||||
multigraph = G.is_multigraph()
|
||||
|
||||
# Allow 'key' to be omitted from attrs if the graph is not a multigraph.
|
||||
key = None if not multigraph else key
|
||||
if len({source, target, key}) < 3:
|
||||
raise nx.NetworkXError("Attribute names are not unique.")
|
||||
data = {
|
||||
"directed": G.is_directed(),
|
||||
"multigraph": multigraph,
|
||||
"graph": G.graph,
|
||||
"nodes": [{**G.nodes[n], name: n} for n in G],
|
||||
}
|
||||
if multigraph:
|
||||
data[link] = [
|
||||
{**d, source: u, target: v, key: k}
|
||||
for u, v, k, d in G.edges(keys=True, data=True)
|
||||
]
|
||||
else:
|
||||
data[link] = [{**d, source: u, target: v} for u, v, d in G.edges(data=True)]
|
||||
return data
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def node_link_graph(
|
||||
data,
|
||||
directed=False,
|
||||
multigraph=True,
|
||||
*,
|
||||
source="source",
|
||||
target="target",
|
||||
name="id",
|
||||
key="key",
|
||||
link="links",
|
||||
):
|
||||
"""Returns graph from node-link data format.
|
||||
Useful for de-serialization from JSON.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : dict
|
||||
node-link formatted graph data
|
||||
|
||||
directed : bool
|
||||
If True, and direction not specified in data, return a directed graph.
|
||||
|
||||
multigraph : bool
|
||||
If True, and multigraph not specified in data, return a multigraph.
|
||||
|
||||
source : string
|
||||
A string that provides the 'source' attribute name for storing NetworkX-internal graph data.
|
||||
target : string
|
||||
A string that provides the 'target' attribute name for storing NetworkX-internal graph data.
|
||||
name : string
|
||||
A string that provides the 'name' attribute name for storing NetworkX-internal graph data.
|
||||
key : string
|
||||
A string that provides the 'key' attribute name for storing NetworkX-internal graph data.
|
||||
link : string
|
||||
A string that provides the 'link' attribute name for storing NetworkX-internal graph data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : NetworkX graph
|
||||
A NetworkX graph object
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Create data in node-link format by converting a graph.
|
||||
|
||||
>>> G = nx.Graph([('A', 'B')])
|
||||
>>> data = nx.node_link_data(G)
|
||||
>>> data
|
||||
{'directed': False, 'multigraph': False, 'graph': {}, 'nodes': [{'id': 'A'}, {'id': 'B'}], 'links': [{'source': 'A', 'target': 'B'}]}
|
||||
|
||||
Revert data in node-link format to a graph.
|
||||
|
||||
>>> H = nx.node_link_graph(data)
|
||||
>>> print(H.edges)
|
||||
[('A', 'B')]
|
||||
|
||||
To serialize and deserialize a graph with JSON,
|
||||
|
||||
>>> import json
|
||||
>>> d = json.dumps(node_link_data(G))
|
||||
>>> H = node_link_graph(json.loads(d))
|
||||
>>> print(G.edges, H.edges)
|
||||
[('A', 'B')] [('A', 'B')]
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
Attribute 'key' is only used for multigraphs.
|
||||
|
||||
To use `node_link_data` in conjunction with `node_link_graph`,
|
||||
the keyword names for the attributes must match.
|
||||
|
||||
See Also
|
||||
--------
|
||||
node_link_data, adjacency_data, tree_data
|
||||
"""
|
||||
multigraph = data.get("multigraph", multigraph)
|
||||
directed = data.get("directed", directed)
|
||||
if multigraph:
|
||||
graph = nx.MultiGraph()
|
||||
else:
|
||||
graph = nx.Graph()
|
||||
if directed:
|
||||
graph = graph.to_directed()
|
||||
|
||||
# Allow 'key' to be omitted from attrs if the graph is not a multigraph.
|
||||
key = None if not multigraph else key
|
||||
graph.graph = data.get("graph", {})
|
||||
c = count()
|
||||
for d in data["nodes"]:
|
||||
node = _to_tuple(d.get(name, next(c)))
|
||||
nodedata = {str(k): v for k, v in d.items() if k != name}
|
||||
graph.add_node(node, **nodedata)
|
||||
for d in data[link]:
|
||||
src = tuple(d[source]) if isinstance(d[source], list) else d[source]
|
||||
tgt = tuple(d[target]) if isinstance(d[target], list) else d[target]
|
||||
if not multigraph:
|
||||
edgedata = {str(k): v for k, v in d.items() if k != source and k != target}
|
||||
graph.add_edge(src, tgt, **edgedata)
|
||||
else:
|
||||
ky = d.get(key, None)
|
||||
edgedata = {
|
||||
str(k): v
|
||||
for k, v in d.items()
|
||||
if k != source and k != target and k != key
|
||||
}
|
||||
graph.add_edge(src, tgt, ky, **edgedata)
|
||||
return graph
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,78 @@
|
|||
import copy
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.readwrite.json_graph import adjacency_data, adjacency_graph
|
||||
from networkx.utils import graphs_equal
|
||||
|
||||
|
||||
class TestAdjacency:
|
||||
def test_graph(self):
|
||||
G = nx.path_graph(4)
|
||||
H = adjacency_graph(adjacency_data(G))
|
||||
assert graphs_equal(G, H)
|
||||
|
||||
def test_graph_attributes(self):
|
||||
G = nx.path_graph(4)
|
||||
G.add_node(1, color="red")
|
||||
G.add_edge(1, 2, width=7)
|
||||
G.graph["foo"] = "bar"
|
||||
G.graph[1] = "one"
|
||||
|
||||
H = adjacency_graph(adjacency_data(G))
|
||||
assert graphs_equal(G, H)
|
||||
assert H.graph["foo"] == "bar"
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
assert H[1][2]["width"] == 7
|
||||
|
||||
d = json.dumps(adjacency_data(G))
|
||||
H = adjacency_graph(json.loads(d))
|
||||
assert graphs_equal(G, H)
|
||||
assert H.graph["foo"] == "bar"
|
||||
assert H.graph[1] == "one"
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
assert H[1][2]["width"] == 7
|
||||
|
||||
def test_digraph(self):
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, [1, 2, 3])
|
||||
H = adjacency_graph(adjacency_data(G))
|
||||
assert H.is_directed()
|
||||
assert graphs_equal(G, H)
|
||||
|
||||
def test_multidigraph(self):
|
||||
G = nx.MultiDiGraph()
|
||||
nx.add_path(G, [1, 2, 3])
|
||||
H = adjacency_graph(adjacency_data(G))
|
||||
assert H.is_directed()
|
||||
assert H.is_multigraph()
|
||||
assert graphs_equal(G, H)
|
||||
|
||||
def test_multigraph(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(1, 2, key="first")
|
||||
G.add_edge(1, 2, key="second", color="blue")
|
||||
H = adjacency_graph(adjacency_data(G))
|
||||
assert graphs_equal(G, H)
|
||||
assert H[1][2]["second"]["color"] == "blue"
|
||||
|
||||
def test_input_data_is_not_modified_when_building_graph(self):
|
||||
G = nx.path_graph(4)
|
||||
input_data = adjacency_data(G)
|
||||
orig_data = copy.deepcopy(input_data)
|
||||
# Ensure input is unmodified by deserialisation
|
||||
assert graphs_equal(G, adjacency_graph(input_data))
|
||||
assert input_data == orig_data
|
||||
|
||||
def test_adjacency_form_json_serialisable(self):
|
||||
G = nx.path_graph(4)
|
||||
H = adjacency_graph(json.loads(json.dumps(adjacency_data(G))))
|
||||
assert graphs_equal(G, H)
|
||||
|
||||
def test_exception(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = nx.MultiDiGraph()
|
||||
attrs = {"id": "node", "key": "node"}
|
||||
adjacency_data(G, attrs)
|
|
@ -0,0 +1,78 @@
|
|||
import copy
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.readwrite.json_graph import cytoscape_data, cytoscape_graph
|
||||
|
||||
|
||||
def test_graph():
|
||||
G = nx.path_graph(4)
|
||||
H = cytoscape_graph(cytoscape_data(G))
|
||||
assert nx.is_isomorphic(G, H)
|
||||
|
||||
|
||||
def test_input_data_is_not_modified_when_building_graph():
|
||||
G = nx.path_graph(4)
|
||||
input_data = cytoscape_data(G)
|
||||
orig_data = copy.deepcopy(input_data)
|
||||
# Ensure input is unmodified by cytoscape_graph (gh-4173)
|
||||
cytoscape_graph(input_data)
|
||||
assert input_data == orig_data
|
||||
|
||||
|
||||
def test_graph_attributes():
|
||||
G = nx.path_graph(4)
|
||||
G.add_node(1, color="red")
|
||||
G.add_edge(1, 2, width=7)
|
||||
G.graph["foo"] = "bar"
|
||||
G.graph[1] = "one"
|
||||
G.add_node(3, name="node", id="123")
|
||||
|
||||
H = cytoscape_graph(cytoscape_data(G))
|
||||
assert H.graph["foo"] == "bar"
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
assert H[1][2]["width"] == 7
|
||||
assert H.nodes[3]["name"] == "node"
|
||||
assert H.nodes[3]["id"] == "123"
|
||||
|
||||
d = json.dumps(cytoscape_data(G))
|
||||
H = cytoscape_graph(json.loads(d))
|
||||
assert H.graph["foo"] == "bar"
|
||||
assert H.graph[1] == "one"
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
assert H[1][2]["width"] == 7
|
||||
assert H.nodes[3]["name"] == "node"
|
||||
assert H.nodes[3]["id"] == "123"
|
||||
|
||||
|
||||
def test_digraph():
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, [1, 2, 3])
|
||||
H = cytoscape_graph(cytoscape_data(G))
|
||||
assert H.is_directed()
|
||||
assert nx.is_isomorphic(G, H)
|
||||
|
||||
|
||||
def test_multidigraph():
|
||||
G = nx.MultiDiGraph()
|
||||
nx.add_path(G, [1, 2, 3])
|
||||
H = cytoscape_graph(cytoscape_data(G))
|
||||
assert H.is_directed()
|
||||
assert H.is_multigraph()
|
||||
|
||||
|
||||
def test_multigraph():
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(1, 2, key="first")
|
||||
G.add_edge(1, 2, key="second", color="blue")
|
||||
H = cytoscape_graph(cytoscape_data(G))
|
||||
assert nx.is_isomorphic(G, H)
|
||||
assert H[1][2]["second"]["color"] == "blue"
|
||||
|
||||
|
||||
def test_exception():
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = nx.MultiDiGraph()
|
||||
cytoscape_data(G, name="foo", ident="foo")
|
|
@ -0,0 +1,144 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.readwrite.json_graph import node_link_data, node_link_graph
|
||||
|
||||
|
||||
class TestNodeLink:
|
||||
# TODO: To be removed when signature change complete
|
||||
def test_custom_attrs_dep(self):
|
||||
G = nx.path_graph(4)
|
||||
G.add_node(1, color="red")
|
||||
G.add_edge(1, 2, width=7)
|
||||
G.graph[1] = "one"
|
||||
G.graph["foo"] = "bar"
|
||||
|
||||
attrs = {
|
||||
"source": "c_source",
|
||||
"target": "c_target",
|
||||
"name": "c_id",
|
||||
"key": "c_key",
|
||||
"link": "c_links",
|
||||
}
|
||||
|
||||
H = node_link_graph(node_link_data(G, **attrs), multigraph=False, **attrs)
|
||||
assert nx.is_isomorphic(G, H)
|
||||
assert H.graph["foo"] == "bar"
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
assert H[1][2]["width"] == 7
|
||||
|
||||
# provide only a partial dictionary of keywords.
|
||||
# This is similar to an example in the doc string
|
||||
attrs = {
|
||||
"link": "c_links",
|
||||
"source": "c_source",
|
||||
"target": "c_target",
|
||||
}
|
||||
H = node_link_graph(node_link_data(G, **attrs), multigraph=False, **attrs)
|
||||
assert nx.is_isomorphic(G, H)
|
||||
assert H.graph["foo"] == "bar"
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
assert H[1][2]["width"] == 7
|
||||
|
||||
def test_exception_dep(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = nx.MultiDiGraph()
|
||||
node_link_data(G, name="node", source="node", target="node", key="node")
|
||||
|
||||
def test_graph(self):
|
||||
G = nx.path_graph(4)
|
||||
H = node_link_graph(node_link_data(G))
|
||||
assert nx.is_isomorphic(G, H)
|
||||
|
||||
def test_graph_attributes(self):
|
||||
G = nx.path_graph(4)
|
||||
G.add_node(1, color="red")
|
||||
G.add_edge(1, 2, width=7)
|
||||
G.graph[1] = "one"
|
||||
G.graph["foo"] = "bar"
|
||||
|
||||
H = node_link_graph(node_link_data(G))
|
||||
assert H.graph["foo"] == "bar"
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
assert H[1][2]["width"] == 7
|
||||
|
||||
d = json.dumps(node_link_data(G))
|
||||
H = node_link_graph(json.loads(d))
|
||||
assert H.graph["foo"] == "bar"
|
||||
assert H.graph["1"] == "one"
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
assert H[1][2]["width"] == 7
|
||||
|
||||
def test_digraph(self):
|
||||
G = nx.DiGraph()
|
||||
H = node_link_graph(node_link_data(G))
|
||||
assert H.is_directed()
|
||||
|
||||
def test_multigraph(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(1, 2, key="first")
|
||||
G.add_edge(1, 2, key="second", color="blue")
|
||||
H = node_link_graph(node_link_data(G))
|
||||
assert nx.is_isomorphic(G, H)
|
||||
assert H[1][2]["second"]["color"] == "blue"
|
||||
|
||||
def test_graph_with_tuple_nodes(self):
|
||||
G = nx.Graph()
|
||||
G.add_edge((0, 0), (1, 0), color=[255, 255, 0])
|
||||
d = node_link_data(G)
|
||||
dumped_d = json.dumps(d)
|
||||
dd = json.loads(dumped_d)
|
||||
H = node_link_graph(dd)
|
||||
assert H.nodes[(0, 0)] == G.nodes[(0, 0)]
|
||||
assert H[(0, 0)][(1, 0)]["color"] == [255, 255, 0]
|
||||
|
||||
def test_unicode_keys(self):
|
||||
q = "qualité"
|
||||
G = nx.Graph()
|
||||
G.add_node(1, **{q: q})
|
||||
s = node_link_data(G)
|
||||
output = json.dumps(s, ensure_ascii=False)
|
||||
data = json.loads(output)
|
||||
H = node_link_graph(data)
|
||||
assert H.nodes[1][q] == q
|
||||
|
||||
def test_exception(self):
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
G = nx.MultiDiGraph()
|
||||
attrs = {"name": "node", "source": "node", "target": "node", "key": "node"}
|
||||
node_link_data(G, **attrs)
|
||||
|
||||
def test_string_ids(self):
|
||||
q = "qualité"
|
||||
G = nx.DiGraph()
|
||||
G.add_node("A")
|
||||
G.add_node(q)
|
||||
G.add_edge("A", q)
|
||||
data = node_link_data(G)
|
||||
assert data["links"][0]["source"] == "A"
|
||||
assert data["links"][0]["target"] == q
|
||||
H = node_link_graph(data)
|
||||
assert nx.is_isomorphic(G, H)
|
||||
|
||||
def test_custom_attrs(self):
|
||||
G = nx.path_graph(4)
|
||||
G.add_node(1, color="red")
|
||||
G.add_edge(1, 2, width=7)
|
||||
G.graph[1] = "one"
|
||||
G.graph["foo"] = "bar"
|
||||
|
||||
attrs = {
|
||||
"source": "c_source",
|
||||
"target": "c_target",
|
||||
"name": "c_id",
|
||||
"key": "c_key",
|
||||
"link": "c_links",
|
||||
}
|
||||
|
||||
H = node_link_graph(node_link_data(G, **attrs), multigraph=False, **attrs)
|
||||
assert nx.is_isomorphic(G, H)
|
||||
assert H.graph["foo"] == "bar"
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
assert H[1][2]["width"] == 7
|
|
@ -0,0 +1,48 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.readwrite.json_graph import tree_data, tree_graph
|
||||
|
||||
|
||||
def test_graph():
|
||||
G = nx.DiGraph()
|
||||
G.add_nodes_from([1, 2, 3], color="red")
|
||||
G.add_edge(1, 2, foo=7)
|
||||
G.add_edge(1, 3, foo=10)
|
||||
G.add_edge(3, 4, foo=10)
|
||||
H = tree_graph(tree_data(G, 1))
|
||||
assert nx.is_isomorphic(G, H)
|
||||
|
||||
|
||||
def test_graph_attributes():
|
||||
G = nx.DiGraph()
|
||||
G.add_nodes_from([1, 2, 3], color="red")
|
||||
G.add_edge(1, 2, foo=7)
|
||||
G.add_edge(1, 3, foo=10)
|
||||
G.add_edge(3, 4, foo=10)
|
||||
H = tree_graph(tree_data(G, 1))
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
|
||||
d = json.dumps(tree_data(G, 1))
|
||||
H = tree_graph(json.loads(d))
|
||||
assert H.nodes[1]["color"] == "red"
|
||||
|
||||
|
||||
def test_exceptions():
|
||||
with pytest.raises(TypeError, match="is not a tree."):
|
||||
G = nx.complete_graph(3)
|
||||
tree_data(G, 0)
|
||||
with pytest.raises(TypeError, match="is not directed."):
|
||||
G = nx.path_graph(3)
|
||||
tree_data(G, 0)
|
||||
with pytest.raises(TypeError, match="is not weakly connected."):
|
||||
G = nx.path_graph(3, create_using=nx.DiGraph)
|
||||
G.add_edge(2, 0)
|
||||
G.add_node(3)
|
||||
tree_data(G, 0)
|
||||
with pytest.raises(nx.NetworkXError, match="must be different."):
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_node(0)
|
||||
tree_data(G, 0, ident="node", children="node")
|
137
venv/Lib/site-packages/networkx/readwrite/json_graph/tree.py
Normal file
137
venv/Lib/site-packages/networkx/readwrite/json_graph/tree.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
from itertools import chain
|
||||
|
||||
import networkx as nx
|
||||
|
||||
__all__ = ["tree_data", "tree_graph"]
|
||||
|
||||
|
||||
def tree_data(G, root, ident="id", children="children"):
|
||||
"""Returns data in tree format that is suitable for JSON serialization
|
||||
and use in JavaScript documents.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
G must be an oriented tree
|
||||
|
||||
root : node
|
||||
The root of the tree
|
||||
|
||||
ident : string
|
||||
Attribute name for storing NetworkX-internal graph data. `ident` must
|
||||
have a different value than `children`. The default is 'id'.
|
||||
|
||||
children : string
|
||||
Attribute name for storing NetworkX-internal graph data. `children`
|
||||
must have a different value than `ident`. The default is 'children'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data : dict
|
||||
A dictionary with node-link formatted data.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If `children` and `ident` attributes are identical.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from networkx.readwrite import json_graph
|
||||
>>> G = nx.DiGraph([(1, 2)])
|
||||
>>> data = json_graph.tree_data(G, root=1)
|
||||
|
||||
To serialize with json
|
||||
|
||||
>>> import json
|
||||
>>> s = json.dumps(data)
|
||||
|
||||
Notes
|
||||
-----
|
||||
Node attributes are stored in this format but keys
|
||||
for attributes must be strings if you want to serialize with JSON.
|
||||
|
||||
Graph and edge attributes are not stored.
|
||||
|
||||
See Also
|
||||
--------
|
||||
tree_graph, node_link_data, adjacency_data
|
||||
"""
|
||||
if G.number_of_nodes() != G.number_of_edges() + 1:
|
||||
raise TypeError("G is not a tree.")
|
||||
if not G.is_directed():
|
||||
raise TypeError("G is not directed.")
|
||||
if not nx.is_weakly_connected(G):
|
||||
raise TypeError("G is not weakly connected.")
|
||||
|
||||
if ident == children:
|
||||
raise nx.NetworkXError("The values for `id` and `children` must be different.")
|
||||
|
||||
def add_children(n, G):
|
||||
nbrs = G[n]
|
||||
if len(nbrs) == 0:
|
||||
return []
|
||||
children_ = []
|
||||
for child in nbrs:
|
||||
d = {**G.nodes[child], ident: child}
|
||||
c = add_children(child, G)
|
||||
if c:
|
||||
d[children] = c
|
||||
children_.append(d)
|
||||
return children_
|
||||
|
||||
return {**G.nodes[root], ident: root, children: add_children(root, G)}
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def tree_graph(data, ident="id", children="children"):
|
||||
"""Returns graph from tree data format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : dict
|
||||
Tree formatted graph data
|
||||
|
||||
ident : string
|
||||
Attribute name for storing NetworkX-internal graph data. `ident` must
|
||||
have a different value than `children`. The default is 'id'.
|
||||
|
||||
children : string
|
||||
Attribute name for storing NetworkX-internal graph data. `children`
|
||||
must have a different value than `ident`. The default is 'children'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : NetworkX DiGraph
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from networkx.readwrite import json_graph
|
||||
>>> G = nx.DiGraph([(1, 2)])
|
||||
>>> data = json_graph.tree_data(G, root=1)
|
||||
>>> H = json_graph.tree_graph(data)
|
||||
|
||||
See Also
|
||||
--------
|
||||
tree_data, node_link_data, adjacency_data
|
||||
"""
|
||||
graph = nx.DiGraph()
|
||||
|
||||
def add_children(parent, children_):
|
||||
for data in children_:
|
||||
child = data[ident]
|
||||
graph.add_edge(parent, child)
|
||||
grandchildren = data.get(children, [])
|
||||
if grandchildren:
|
||||
add_children(child, grandchildren)
|
||||
nodedata = {
|
||||
str(k): v for k, v in data.items() if k != ident and k != children
|
||||
}
|
||||
graph.add_node(child, **nodedata)
|
||||
|
||||
root = data[ident]
|
||||
children_ = data.get(children, [])
|
||||
nodedata = {str(k): v for k, v in data.items() if k != ident and k != children}
|
||||
graph.add_node(root, **nodedata)
|
||||
add_children(root, children_)
|
||||
return graph
|
108
venv/Lib/site-packages/networkx/readwrite/leda.py
Normal file
108
venv/Lib/site-packages/networkx/readwrite/leda.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
"""
|
||||
Read graphs in LEDA format.
|
||||
|
||||
LEDA is a C++ class library for efficient data types and algorithms.
|
||||
|
||||
Format
|
||||
------
|
||||
See http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html
|
||||
|
||||
"""
|
||||
# Original author: D. Eppstein, UC Irvine, August 12, 2003.
|
||||
# The original code at http://www.ics.uci.edu/~eppstein/PADS/ is public domain.
|
||||
|
||||
__all__ = ["read_leda", "parse_leda"]
|
||||
|
||||
import networkx as nx
|
||||
from networkx.exception import NetworkXError
|
||||
from networkx.utils import open_file
|
||||
|
||||
|
||||
@open_file(0, mode="rb")
|
||||
@nx._dispatch(graphs=None)
|
||||
def read_leda(path, encoding="UTF-8"):
|
||||
"""Read graph in LEDA format from path.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : file or string
|
||||
File or filename to read. Filenames ending in .gz or .bz2 will be
|
||||
uncompressed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : NetworkX graph
|
||||
|
||||
Examples
|
||||
--------
|
||||
G=nx.read_leda('file.leda')
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html
|
||||
"""
|
||||
lines = (line.decode(encoding) for line in path)
|
||||
G = parse_leda(lines)
|
||||
return G
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def parse_leda(lines):
|
||||
"""Read graph in LEDA format from string or iterable.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lines : string or iterable
|
||||
Data in LEDA format.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : NetworkX graph
|
||||
|
||||
Examples
|
||||
--------
|
||||
G=nx.parse_leda(string)
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html
|
||||
"""
|
||||
if isinstance(lines, str):
|
||||
lines = iter(lines.split("\n"))
|
||||
lines = iter(
|
||||
[
|
||||
line.rstrip("\n")
|
||||
for line in lines
|
||||
if not (line.startswith(("#", "\n")) or line == "")
|
||||
]
|
||||
)
|
||||
for i in range(3):
|
||||
next(lines)
|
||||
# Graph
|
||||
du = int(next(lines)) # -1=directed, -2=undirected
|
||||
if du == -1:
|
||||
G = nx.DiGraph()
|
||||
else:
|
||||
G = nx.Graph()
|
||||
|
||||
# Nodes
|
||||
n = int(next(lines)) # number of nodes
|
||||
node = {}
|
||||
for i in range(1, n + 1): # LEDA counts from 1 to n
|
||||
symbol = next(lines).rstrip().strip("|{}| ")
|
||||
if symbol == "":
|
||||
symbol = str(i) # use int if no label - could be trouble
|
||||
node[i] = symbol
|
||||
|
||||
G.add_nodes_from([s for i, s in node.items()])
|
||||
|
||||
# Edges
|
||||
m = int(next(lines)) # number of edges
|
||||
for i in range(m):
|
||||
try:
|
||||
s, t, reversal, label = next(lines).split()
|
||||
except BaseException as err:
|
||||
raise NetworkXError(f"Too few fields in LEDA.GRAPH edge {i+1}") from err
|
||||
# BEWARE: no handling of reversal edges
|
||||
G.add_edge(node[int(s)], node[int(t)], label=label[2:-2])
|
||||
return G
|
393
venv/Lib/site-packages/networkx/readwrite/multiline_adjlist.py
Normal file
393
venv/Lib/site-packages/networkx/readwrite/multiline_adjlist.py
Normal file
|
@ -0,0 +1,393 @@
|
|||
"""
|
||||
*************************
|
||||
Multi-line Adjacency List
|
||||
*************************
|
||||
Read and write NetworkX graphs as multi-line adjacency lists.
|
||||
|
||||
The multi-line adjacency list format is useful for graphs with
|
||||
nodes that can be meaningfully represented as strings. With this format
|
||||
simple edge data can be stored but node or graph data is not.
|
||||
|
||||
Format
|
||||
------
|
||||
The first label in a line is the source node label followed by the node degree
|
||||
d. The next d lines are target node labels and optional edge data.
|
||||
That pattern repeats for all nodes in the graph.
|
||||
|
||||
The graph with edges a-b, a-c, d-e can be represented as the following
|
||||
adjacency list (anything following the # in a line is a comment)::
|
||||
|
||||
# example.multiline-adjlist
|
||||
a 2
|
||||
b
|
||||
c
|
||||
d 1
|
||||
e
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"generate_multiline_adjlist",
|
||||
"write_multiline_adjlist",
|
||||
"parse_multiline_adjlist",
|
||||
"read_multiline_adjlist",
|
||||
]
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import open_file
|
||||
|
||||
|
||||
def generate_multiline_adjlist(G, delimiter=" "):
|
||||
"""Generate a single line of the graph G in multiline adjacency list format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
delimiter : string, optional
|
||||
Separator for node labels
|
||||
|
||||
Returns
|
||||
-------
|
||||
lines : string
|
||||
Lines of data in multiline adjlist format.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.lollipop_graph(4, 3)
|
||||
>>> for line in nx.generate_multiline_adjlist(G):
|
||||
... print(line)
|
||||
0 3
|
||||
1 {}
|
||||
2 {}
|
||||
3 {}
|
||||
1 2
|
||||
2 {}
|
||||
3 {}
|
||||
2 1
|
||||
3 {}
|
||||
3 1
|
||||
4 {}
|
||||
4 1
|
||||
5 {}
|
||||
5 1
|
||||
6 {}
|
||||
6 0
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_multiline_adjlist, read_multiline_adjlist
|
||||
"""
|
||||
if G.is_directed():
|
||||
if G.is_multigraph():
|
||||
for s, nbrs in G.adjacency():
|
||||
nbr_edges = [
|
||||
(u, data)
|
||||
for u, datadict in nbrs.items()
|
||||
for key, data in datadict.items()
|
||||
]
|
||||
deg = len(nbr_edges)
|
||||
yield str(s) + delimiter + str(deg)
|
||||
for u, d in nbr_edges:
|
||||
if d is None:
|
||||
yield str(u)
|
||||
else:
|
||||
yield str(u) + delimiter + str(d)
|
||||
else: # directed single edges
|
||||
for s, nbrs in G.adjacency():
|
||||
deg = len(nbrs)
|
||||
yield str(s) + delimiter + str(deg)
|
||||
for u, d in nbrs.items():
|
||||
if d is None:
|
||||
yield str(u)
|
||||
else:
|
||||
yield str(u) + delimiter + str(d)
|
||||
else: # undirected
|
||||
if G.is_multigraph():
|
||||
seen = set() # helper dict used to avoid duplicate edges
|
||||
for s, nbrs in G.adjacency():
|
||||
nbr_edges = [
|
||||
(u, data)
|
||||
for u, datadict in nbrs.items()
|
||||
if u not in seen
|
||||
for key, data in datadict.items()
|
||||
]
|
||||
deg = len(nbr_edges)
|
||||
yield str(s) + delimiter + str(deg)
|
||||
for u, d in nbr_edges:
|
||||
if d is None:
|
||||
yield str(u)
|
||||
else:
|
||||
yield str(u) + delimiter + str(d)
|
||||
seen.add(s)
|
||||
else: # undirected single edges
|
||||
seen = set() # helper dict used to avoid duplicate edges
|
||||
for s, nbrs in G.adjacency():
|
||||
nbr_edges = [(u, d) for u, d in nbrs.items() if u not in seen]
|
||||
deg = len(nbr_edges)
|
||||
yield str(s) + delimiter + str(deg)
|
||||
for u, d in nbr_edges:
|
||||
if d is None:
|
||||
yield str(u)
|
||||
else:
|
||||
yield str(u) + delimiter + str(d)
|
||||
seen.add(s)
|
||||
|
||||
|
||||
@open_file(1, mode="wb")
|
||||
def write_multiline_adjlist(G, path, delimiter=" ", comments="#", encoding="utf-8"):
|
||||
"""Write the graph G in multiline adjacency list format to path
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
path : string or file
|
||||
Filename or file handle to write to.
|
||||
Filenames ending in .gz or .bz2 will be compressed.
|
||||
|
||||
comments : string, optional
|
||||
Marker for comment lines
|
||||
|
||||
delimiter : string, optional
|
||||
Separator for node labels
|
||||
|
||||
encoding : string, optional
|
||||
Text encoding.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.write_multiline_adjlist(G, "test.adjlist")
|
||||
|
||||
The path can be a file handle or a string with the name of the file. If a
|
||||
file handle is provided, it has to be opened in 'wb' mode.
|
||||
|
||||
>>> fh = open("test.adjlist", "wb")
|
||||
>>> nx.write_multiline_adjlist(G, fh)
|
||||
|
||||
Filenames ending in .gz or .bz2 will be compressed.
|
||||
|
||||
>>> nx.write_multiline_adjlist(G, "test.adjlist.gz")
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_multiline_adjlist
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
|
||||
pargs = comments + " ".join(sys.argv)
|
||||
header = (
|
||||
f"{pargs}\n"
|
||||
+ comments
|
||||
+ f" GMT {time.asctime(time.gmtime())}\n"
|
||||
+ comments
|
||||
+ f" {G.name}\n"
|
||||
)
|
||||
path.write(header.encode(encoding))
|
||||
|
||||
for multiline in generate_multiline_adjlist(G, delimiter):
|
||||
multiline += "\n"
|
||||
path.write(multiline.encode(encoding))
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def parse_multiline_adjlist(
|
||||
lines, comments="#", delimiter=None, create_using=None, nodetype=None, edgetype=None
|
||||
):
|
||||
"""Parse lines of a multiline adjacency list representation of a graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lines : list or iterator of strings
|
||||
Input data in multiline adjlist format
|
||||
|
||||
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
||||
Graph type to create. If graph instance, then cleared before populated.
|
||||
|
||||
nodetype : Python type, optional
|
||||
Convert nodes to this type.
|
||||
|
||||
edgetype : Python type, optional
|
||||
Convert edges to this type.
|
||||
|
||||
comments : string, optional
|
||||
Marker for comment lines
|
||||
|
||||
delimiter : string, optional
|
||||
Separator for node labels. The default is whitespace.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G: NetworkX graph
|
||||
The graph corresponding to the lines in multiline adjacency list format.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> lines = [
|
||||
... "1 2",
|
||||
... "2 {'weight':3, 'name': 'Frodo'}",
|
||||
... "3 {}",
|
||||
... "2 1",
|
||||
... "5 {'weight':6, 'name': 'Saruman'}",
|
||||
... ]
|
||||
>>> G = nx.parse_multiline_adjlist(iter(lines), nodetype=int)
|
||||
>>> list(G)
|
||||
[1, 2, 3, 5]
|
||||
|
||||
"""
|
||||
from ast import literal_eval
|
||||
|
||||
G = nx.empty_graph(0, create_using)
|
||||
for line in lines:
|
||||
p = line.find(comments)
|
||||
if p >= 0:
|
||||
line = line[:p]
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
(u, deg) = line.strip().split(delimiter)
|
||||
deg = int(deg)
|
||||
except BaseException as err:
|
||||
raise TypeError(f"Failed to read node and degree on line ({line})") from err
|
||||
if nodetype is not None:
|
||||
try:
|
||||
u = nodetype(u)
|
||||
except BaseException as err:
|
||||
raise TypeError(
|
||||
f"Failed to convert node ({u}) to " f"type {nodetype}"
|
||||
) from err
|
||||
G.add_node(u)
|
||||
for i in range(deg):
|
||||
while True:
|
||||
try:
|
||||
line = next(lines)
|
||||
except StopIteration as err:
|
||||
msg = f"Failed to find neighbor for node ({u})"
|
||||
raise TypeError(msg) from err
|
||||
p = line.find(comments)
|
||||
if p >= 0:
|
||||
line = line[:p]
|
||||
if line:
|
||||
break
|
||||
vlist = line.strip().split(delimiter)
|
||||
numb = len(vlist)
|
||||
if numb < 1:
|
||||
continue # isolated node
|
||||
v = vlist.pop(0)
|
||||
data = "".join(vlist)
|
||||
if nodetype is not None:
|
||||
try:
|
||||
v = nodetype(v)
|
||||
except BaseException as err:
|
||||
raise TypeError(
|
||||
f"Failed to convert node ({v}) " f"to type {nodetype}"
|
||||
) from err
|
||||
if edgetype is not None:
|
||||
try:
|
||||
edgedata = {"weight": edgetype(data)}
|
||||
except BaseException as err:
|
||||
raise TypeError(
|
||||
f"Failed to convert edge data ({data}) " f"to type {edgetype}"
|
||||
) from err
|
||||
else:
|
||||
try: # try to evaluate
|
||||
edgedata = literal_eval(data)
|
||||
except:
|
||||
edgedata = {}
|
||||
G.add_edge(u, v, **edgedata)
|
||||
|
||||
return G
|
||||
|
||||
|
||||
@open_file(0, mode="rb")
|
||||
@nx._dispatch(graphs=None)
|
||||
def read_multiline_adjlist(
|
||||
path,
|
||||
comments="#",
|
||||
delimiter=None,
|
||||
create_using=None,
|
||||
nodetype=None,
|
||||
edgetype=None,
|
||||
encoding="utf-8",
|
||||
):
|
||||
"""Read graph in multi-line adjacency list format from path.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : string or file
|
||||
Filename or file handle to read.
|
||||
Filenames ending in .gz or .bz2 will be uncompressed.
|
||||
|
||||
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
||||
Graph type to create. If graph instance, then cleared before populated.
|
||||
|
||||
nodetype : Python type, optional
|
||||
Convert nodes to this type.
|
||||
|
||||
edgetype : Python type, optional
|
||||
Convert edge data to this type.
|
||||
|
||||
comments : string, optional
|
||||
Marker for comment lines
|
||||
|
||||
delimiter : string, optional
|
||||
Separator for node labels. The default is whitespace.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G: NetworkX graph
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.write_multiline_adjlist(G, "test.adjlist")
|
||||
>>> G = nx.read_multiline_adjlist("test.adjlist")
|
||||
|
||||
The path can be a file or a string with the name of the file. If a
|
||||
file s provided, it has to be opened in 'rb' mode.
|
||||
|
||||
>>> fh = open("test.adjlist", "rb")
|
||||
>>> G = nx.read_multiline_adjlist(fh)
|
||||
|
||||
Filenames ending in .gz or .bz2 will be compressed.
|
||||
|
||||
>>> nx.write_multiline_adjlist(G, "test.adjlist.gz")
|
||||
>>> G = nx.read_multiline_adjlist("test.adjlist.gz")
|
||||
|
||||
The optional nodetype is a function to convert node strings to nodetype.
|
||||
|
||||
For example
|
||||
|
||||
>>> G = nx.read_multiline_adjlist("test.adjlist", nodetype=int)
|
||||
|
||||
will attempt to convert all nodes to integer type.
|
||||
|
||||
The optional edgetype is a function to convert edge data strings to
|
||||
edgetype.
|
||||
|
||||
>>> G = nx.read_multiline_adjlist("test.adjlist")
|
||||
|
||||
The optional create_using parameter is a NetworkX graph container.
|
||||
The default is Graph(), an undirected graph. To read the data as
|
||||
a directed graph use
|
||||
|
||||
>>> G = nx.read_multiline_adjlist("test.adjlist", create_using=nx.DiGraph)
|
||||
|
||||
Notes
|
||||
-----
|
||||
This format does not store graph, node, or edge data.
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_multiline_adjlist
|
||||
"""
|
||||
lines = (line.decode(encoding) for line in path)
|
||||
return parse_multiline_adjlist(
|
||||
lines,
|
||||
comments=comments,
|
||||
delimiter=delimiter,
|
||||
create_using=create_using,
|
||||
nodetype=nodetype,
|
||||
edgetype=edgetype,
|
||||
)
|
104
venv/Lib/site-packages/networkx/readwrite/p2g.py
Normal file
104
venv/Lib/site-packages/networkx/readwrite/p2g.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
"""
|
||||
This module provides the following: read and write of p2g format
|
||||
used in metabolic pathway studies.
|
||||
|
||||
See https://web.archive.org/web/20080626113807/http://www.cs.purdue.edu/homes/koyuturk/pathway/ for a description.
|
||||
|
||||
The summary is included here:
|
||||
|
||||
A file that describes a uniquely labeled graph (with extension ".gr")
|
||||
format looks like the following:
|
||||
|
||||
|
||||
name
|
||||
3 4
|
||||
a
|
||||
1 2
|
||||
b
|
||||
|
||||
c
|
||||
0 2
|
||||
|
||||
"name" is simply a description of what the graph corresponds to. The
|
||||
second line displays the number of nodes and number of edges,
|
||||
respectively. This sample graph contains three nodes labeled "a", "b",
|
||||
and "c". The rest of the graph contains two lines for each node. The
|
||||
first line for a node contains the node label. After the declaration
|
||||
of the node label, the out-edges of that node in the graph are
|
||||
provided. For instance, "a" is linked to nodes 1 and 2, which are
|
||||
labeled "b" and "c", while the node labeled "b" has no outgoing
|
||||
edges. Observe that node labeled "c" has an outgoing edge to
|
||||
itself. Indeed, self-loops are allowed. Node index starts from 0.
|
||||
|
||||
"""
|
||||
import networkx as nx
|
||||
from networkx.utils import open_file
|
||||
|
||||
|
||||
@open_file(1, mode="w")
|
||||
def write_p2g(G, path, encoding="utf-8"):
|
||||
"""Write NetworkX graph in p2g format.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This format is meant to be used with directed graphs with
|
||||
possible self loops.
|
||||
"""
|
||||
path.write((f"{G.name}\n").encode(encoding))
|
||||
path.write((f"{G.order()} {G.size()}\n").encode(encoding))
|
||||
nodes = list(G)
|
||||
# make dictionary mapping nodes to integers
|
||||
nodenumber = dict(zip(nodes, range(len(nodes))))
|
||||
for n in nodes:
|
||||
path.write((f"{n}\n").encode(encoding))
|
||||
for nbr in G.neighbors(n):
|
||||
path.write((f"{nodenumber[nbr]} ").encode(encoding))
|
||||
path.write("\n".encode(encoding))
|
||||
|
||||
|
||||
@open_file(0, mode="r")
|
||||
@nx._dispatch(graphs=None)
|
||||
def read_p2g(path, encoding="utf-8"):
|
||||
"""Read graph in p2g format from path.
|
||||
|
||||
Returns
|
||||
-------
|
||||
MultiDiGraph
|
||||
|
||||
Notes
|
||||
-----
|
||||
If you want a DiGraph (with no self loops allowed and no edge data)
|
||||
use D=nx.DiGraph(read_p2g(path))
|
||||
"""
|
||||
lines = (line.decode(encoding) for line in path)
|
||||
G = parse_p2g(lines)
|
||||
return G
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def parse_p2g(lines):
|
||||
"""Parse p2g format graph from string or iterable.
|
||||
|
||||
Returns
|
||||
-------
|
||||
MultiDiGraph
|
||||
"""
|
||||
description = next(lines).strip()
|
||||
# are multiedges (parallel edges) allowed?
|
||||
G = nx.MultiDiGraph(name=description, selfloops=True)
|
||||
nnodes, nedges = map(int, next(lines).split())
|
||||
nodelabel = {}
|
||||
nbrs = {}
|
||||
# loop over the nodes keeping track of node labels and out neighbors
|
||||
# defer adding edges until all node labels are known
|
||||
for i in range(nnodes):
|
||||
n = next(lines).strip()
|
||||
nodelabel[i] = n
|
||||
G.add_node(n)
|
||||
nbrs[n] = map(int, next(lines).split())
|
||||
# now we know all of the node labels so we can add the edges
|
||||
# with the correct labels
|
||||
for n in G:
|
||||
for nbr in nbrs[n]:
|
||||
G.add_edge(n, nodelabel[nbr])
|
||||
return G
|
286
venv/Lib/site-packages/networkx/readwrite/pajek.py
Normal file
286
venv/Lib/site-packages/networkx/readwrite/pajek.py
Normal file
|
@ -0,0 +1,286 @@
|
|||
"""
|
||||
*****
|
||||
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
|
||||
|
||||
|
||||
@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._dispatch(graphs=None)
|
||||
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._dispatch(graphs=None)
|
||||
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
|
376
venv/Lib/site-packages/networkx/readwrite/sparse6.py
Normal file
376
venv/Lib/site-packages/networkx/readwrite/sparse6.py
Normal file
|
@ -0,0 +1,376 @@
|
|||
# Original author: D. Eppstein, UC Irvine, August 12, 2003.
|
||||
# The original code at https://www.ics.uci.edu/~eppstein/PADS/ is public domain.
|
||||
"""Functions for reading and writing graphs in the *sparse6* format.
|
||||
|
||||
The *sparse6* file format is a space-efficient format for large sparse
|
||||
graphs. For small graphs or large dense graphs, use the *graph6* file
|
||||
format.
|
||||
|
||||
For more information, see the `sparse6`_ homepage.
|
||||
|
||||
.. _sparse6: https://users.cecs.anu.edu.au/~bdm/data/formats.html
|
||||
|
||||
"""
|
||||
import networkx as nx
|
||||
from networkx.exception import NetworkXError
|
||||
from networkx.readwrite.graph6 import data_to_n, n_to_data
|
||||
from networkx.utils import not_implemented_for, open_file
|
||||
|
||||
__all__ = ["from_sparse6_bytes", "read_sparse6", "to_sparse6_bytes", "write_sparse6"]
|
||||
|
||||
|
||||
def _generate_sparse6_bytes(G, nodes, header):
|
||||
"""Yield bytes in the sparse6 encoding of a graph.
|
||||
|
||||
`G` is an undirected simple graph. `nodes` is the list of nodes for
|
||||
which the node-induced subgraph will be encoded; if `nodes` is the
|
||||
list of all nodes in the graph, the entire graph will be
|
||||
encoded. `header` is a Boolean that specifies whether to generate
|
||||
the header ``b'>>sparse6<<'`` before the remaining data.
|
||||
|
||||
This function generates `bytes` objects in the following order:
|
||||
|
||||
1. the header (if requested),
|
||||
2. the encoding of the number of nodes,
|
||||
3. each character, one-at-a-time, in the encoding of the requested
|
||||
node-induced subgraph,
|
||||
4. a newline character.
|
||||
|
||||
This function raises :exc:`ValueError` if the graph is too large for
|
||||
the graph6 format (that is, greater than ``2 ** 36`` nodes).
|
||||
|
||||
"""
|
||||
n = len(G)
|
||||
if n >= 2**36:
|
||||
raise ValueError(
|
||||
"sparse6 is only defined if number of nodes is less " "than 2 ** 36"
|
||||
)
|
||||
if header:
|
||||
yield b">>sparse6<<"
|
||||
yield b":"
|
||||
for d in n_to_data(n):
|
||||
yield str.encode(chr(d + 63))
|
||||
|
||||
k = 1
|
||||
while 1 << k < n:
|
||||
k += 1
|
||||
|
||||
def enc(x):
|
||||
"""Big endian k-bit encoding of x"""
|
||||
return [1 if (x & 1 << (k - 1 - i)) else 0 for i in range(k)]
|
||||
|
||||
edges = sorted((max(u, v), min(u, v)) for u, v in G.edges())
|
||||
bits = []
|
||||
curv = 0
|
||||
for v, u in edges:
|
||||
if v == curv: # current vertex edge
|
||||
bits.append(0)
|
||||
bits.extend(enc(u))
|
||||
elif v == curv + 1: # next vertex edge
|
||||
curv += 1
|
||||
bits.append(1)
|
||||
bits.extend(enc(u))
|
||||
else: # skip to vertex v and then add edge to u
|
||||
curv = v
|
||||
bits.append(1)
|
||||
bits.extend(enc(v))
|
||||
bits.append(0)
|
||||
bits.extend(enc(u))
|
||||
if k < 6 and n == (1 << k) and ((-len(bits)) % 6) >= k and curv < (n - 1):
|
||||
# Padding special case: small k, n=2^k,
|
||||
# more than k bits of padding needed,
|
||||
# current vertex is not (n-1) --
|
||||
# appending 1111... would add a loop on (n-1)
|
||||
bits.append(0)
|
||||
bits.extend([1] * ((-len(bits)) % 6))
|
||||
else:
|
||||
bits.extend([1] * ((-len(bits)) % 6))
|
||||
|
||||
data = [
|
||||
(bits[i + 0] << 5)
|
||||
+ (bits[i + 1] << 4)
|
||||
+ (bits[i + 2] << 3)
|
||||
+ (bits[i + 3] << 2)
|
||||
+ (bits[i + 4] << 1)
|
||||
+ (bits[i + 5] << 0)
|
||||
for i in range(0, len(bits), 6)
|
||||
]
|
||||
|
||||
for d in data:
|
||||
yield str.encode(chr(d + 63))
|
||||
yield b"\n"
|
||||
|
||||
|
||||
@nx._dispatch(graphs=None)
|
||||
def from_sparse6_bytes(string):
|
||||
"""Read an undirected graph in sparse6 format from string.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
string : string
|
||||
Data in sparse6 format
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : Graph
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the string is unable to be parsed in sparse6 format
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.from_sparse6_bytes(b":A_")
|
||||
>>> sorted(G.edges())
|
||||
[(0, 1), (0, 1), (0, 1)]
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_sparse6, write_sparse6
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Sparse6 specification
|
||||
<https://users.cecs.anu.edu.au/~bdm/data/formats.html>
|
||||
|
||||
"""
|
||||
if string.startswith(b">>sparse6<<"):
|
||||
string = string[11:]
|
||||
if not string.startswith(b":"):
|
||||
raise NetworkXError("Expected leading colon in sparse6")
|
||||
|
||||
chars = [c - 63 for c in string[1:]]
|
||||
n, data = data_to_n(chars)
|
||||
k = 1
|
||||
while 1 << k < n:
|
||||
k += 1
|
||||
|
||||
def parseData():
|
||||
"""Returns stream of pairs b[i], x[i] for sparse6 format."""
|
||||
chunks = iter(data)
|
||||
d = None # partial data word
|
||||
dLen = 0 # how many unparsed bits are left in d
|
||||
|
||||
while 1:
|
||||
if dLen < 1:
|
||||
try:
|
||||
d = next(chunks)
|
||||
except StopIteration:
|
||||
return
|
||||
dLen = 6
|
||||
dLen -= 1
|
||||
b = (d >> dLen) & 1 # grab top remaining bit
|
||||
|
||||
x = d & ((1 << dLen) - 1) # partially built up value of x
|
||||
xLen = dLen # how many bits included so far in x
|
||||
while xLen < k: # now grab full chunks until we have enough
|
||||
try:
|
||||
d = next(chunks)
|
||||
except StopIteration:
|
||||
return
|
||||
dLen = 6
|
||||
x = (x << 6) + d
|
||||
xLen += 6
|
||||
x = x >> (xLen - k) # shift back the extra bits
|
||||
dLen = xLen - k
|
||||
yield b, x
|
||||
|
||||
v = 0
|
||||
|
||||
G = nx.MultiGraph()
|
||||
G.add_nodes_from(range(n))
|
||||
|
||||
multigraph = False
|
||||
for b, x in parseData():
|
||||
if b == 1:
|
||||
v += 1
|
||||
# padding with ones can cause overlarge number here
|
||||
if x >= n or v >= n:
|
||||
break
|
||||
elif x > v:
|
||||
v = x
|
||||
else:
|
||||
if G.has_edge(x, v):
|
||||
multigraph = True
|
||||
G.add_edge(x, v)
|
||||
if not multigraph:
|
||||
G = nx.Graph(G)
|
||||
return G
|
||||
|
||||
|
||||
def to_sparse6_bytes(G, nodes=None, header=True):
|
||||
"""Convert an undirected graph to bytes in sparse6 format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : Graph (undirected)
|
||||
|
||||
nodes: list or iterable
|
||||
Nodes are labeled 0...n-1 in the order provided. If None the ordering
|
||||
given by ``G.nodes()`` is used.
|
||||
|
||||
header: bool
|
||||
If True add '>>sparse6<<' bytes to head of data.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If the graph is directed.
|
||||
|
||||
ValueError
|
||||
If the graph has at least ``2 ** 36`` nodes; the sparse6 format
|
||||
is only defined for graphs of order less than ``2 ** 36``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> nx.to_sparse6_bytes(nx.path_graph(2))
|
||||
b'>>sparse6<<:An\\n'
|
||||
|
||||
See Also
|
||||
--------
|
||||
to_sparse6_bytes, read_sparse6, write_sparse6_bytes
|
||||
|
||||
Notes
|
||||
-----
|
||||
The returned bytes end with a newline character.
|
||||
|
||||
The format does not support edge or node labels.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Graph6 specification
|
||||
<https://users.cecs.anu.edu.au/~bdm/data/formats.html>
|
||||
|
||||
"""
|
||||
if nodes is not None:
|
||||
G = G.subgraph(nodes)
|
||||
G = nx.convert_node_labels_to_integers(G, ordering="sorted")
|
||||
return b"".join(_generate_sparse6_bytes(G, nodes, header))
|
||||
|
||||
|
||||
@open_file(0, mode="rb")
|
||||
@nx._dispatch(graphs=None)
|
||||
def read_sparse6(path):
|
||||
"""Read an undirected graph in sparse6 format from path.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : file or string
|
||||
File or filename to write.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : Graph/Multigraph or list of Graphs/MultiGraphs
|
||||
If the file contains multiple lines then a list of graphs is returned
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the string is unable to be parsed in sparse6 format
|
||||
|
||||
Examples
|
||||
--------
|
||||
You can read a sparse6 file by giving the path to the file::
|
||||
|
||||
>>> import tempfile
|
||||
>>> with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
... _ = f.write(b">>sparse6<<:An\\n")
|
||||
... _ = f.seek(0)
|
||||
... G = nx.read_sparse6(f.name)
|
||||
>>> list(G.edges())
|
||||
[(0, 1)]
|
||||
|
||||
You can also read a sparse6 file by giving an open file-like object::
|
||||
|
||||
>>> import tempfile
|
||||
>>> with tempfile.NamedTemporaryFile() as f:
|
||||
... _ = f.write(b">>sparse6<<:An\\n")
|
||||
... _ = f.seek(0)
|
||||
... G = nx.read_sparse6(f)
|
||||
>>> list(G.edges())
|
||||
[(0, 1)]
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_sparse6, from_sparse6_bytes
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Sparse6 specification
|
||||
<https://users.cecs.anu.edu.au/~bdm/data/formats.html>
|
||||
|
||||
"""
|
||||
glist = []
|
||||
for line in path:
|
||||
line = line.strip()
|
||||
if not len(line):
|
||||
continue
|
||||
glist.append(from_sparse6_bytes(line))
|
||||
if len(glist) == 1:
|
||||
return glist[0]
|
||||
else:
|
||||
return glist
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@open_file(1, mode="wb")
|
||||
def write_sparse6(G, path, nodes=None, header=True):
|
||||
"""Write graph G to given path in sparse6 format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : Graph (undirected)
|
||||
|
||||
path : file or string
|
||||
File or filename to write
|
||||
|
||||
nodes: list or iterable
|
||||
Nodes are labeled 0...n-1 in the order provided. If None the ordering
|
||||
given by G.nodes() is used.
|
||||
|
||||
header: bool
|
||||
If True add '>>sparse6<<' string to head of data
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the graph is directed
|
||||
|
||||
Examples
|
||||
--------
|
||||
You can write a sparse6 file by giving the path to the file::
|
||||
|
||||
>>> import tempfile
|
||||
>>> with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
... nx.write_sparse6(nx.path_graph(2), f.name)
|
||||
... print(f.read())
|
||||
b'>>sparse6<<:An\\n'
|
||||
|
||||
You can also write a sparse6 file by giving an open file-like object::
|
||||
|
||||
>>> with tempfile.NamedTemporaryFile() as f:
|
||||
... nx.write_sparse6(nx.path_graph(2), f)
|
||||
... _ = f.seek(0)
|
||||
... print(f.read())
|
||||
b'>>sparse6<<:An\\n'
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_sparse6, from_sparse6_bytes
|
||||
|
||||
Notes
|
||||
-----
|
||||
The format does not support edge or node labels.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Sparse6 specification
|
||||
<https://users.cecs.anu.edu.au/~bdm/data/formats.html>
|
||||
|
||||
"""
|
||||
if nodes is not None:
|
||||
G = G.subgraph(nodes)
|
||||
G = nx.convert_node_labels_to_integers(G, ordering="sorted")
|
||||
for b in _generate_sparse6_bytes(G, nodes, header):
|
||||
path.write(b)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
268
venv/Lib/site-packages/networkx/readwrite/tests/test_adjlist.py
Normal file
268
venv/Lib/site-packages/networkx/readwrite/tests/test_adjlist.py
Normal file
|
@ -0,0 +1,268 @@
|
|||
"""
|
||||
Unit tests for adjlist.
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import edges_equal, graphs_equal, nodes_equal
|
||||
|
||||
|
||||
class TestAdjlist:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.G = nx.Graph(name="test")
|
||||
e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")]
|
||||
cls.G.add_edges_from(e)
|
||||
cls.G.add_node("g")
|
||||
cls.DG = nx.DiGraph(cls.G)
|
||||
cls.XG = nx.MultiGraph()
|
||||
cls.XG.add_weighted_edges_from([(1, 2, 5), (1, 2, 5), (1, 2, 1), (3, 3, 42)])
|
||||
cls.XDG = nx.MultiDiGraph(cls.XG)
|
||||
|
||||
def test_read_multiline_adjlist_1(self):
|
||||
# Unit test for https://networkx.lanl.gov/trac/ticket/252
|
||||
s = b"""# comment line
|
||||
1 2
|
||||
# comment line
|
||||
2
|
||||
3
|
||||
"""
|
||||
bytesIO = io.BytesIO(s)
|
||||
G = nx.read_multiline_adjlist(bytesIO)
|
||||
adj = {"1": {"3": {}, "2": {}}, "3": {"1": {}}, "2": {"1": {}}}
|
||||
assert graphs_equal(G, nx.Graph(adj))
|
||||
|
||||
def test_unicode(self):
|
||||
G = nx.Graph()
|
||||
name1 = chr(2344) + chr(123) + chr(6543)
|
||||
name2 = chr(5543) + chr(1543) + chr(324)
|
||||
G.add_edge(name1, "Radiohead", **{name2: 3})
|
||||
fd, fname = tempfile.mkstemp()
|
||||
nx.write_multiline_adjlist(G, fname)
|
||||
H = nx.read_multiline_adjlist(fname)
|
||||
assert graphs_equal(G, H)
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_latin1_err(self):
|
||||
G = nx.Graph()
|
||||
name1 = chr(2344) + chr(123) + chr(6543)
|
||||
name2 = chr(5543) + chr(1543) + chr(324)
|
||||
G.add_edge(name1, "Radiohead", **{name2: 3})
|
||||
fd, fname = tempfile.mkstemp()
|
||||
pytest.raises(
|
||||
UnicodeEncodeError, nx.write_multiline_adjlist, G, fname, encoding="latin-1"
|
||||
)
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_latin1(self):
|
||||
G = nx.Graph()
|
||||
name1 = "Bj" + chr(246) + "rk"
|
||||
name2 = chr(220) + "ber"
|
||||
G.add_edge(name1, "Radiohead", **{name2: 3})
|
||||
fd, fname = tempfile.mkstemp()
|
||||
nx.write_multiline_adjlist(G, fname, encoding="latin-1")
|
||||
H = nx.read_multiline_adjlist(fname, encoding="latin-1")
|
||||
assert graphs_equal(G, H)
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_parse_adjlist(self):
|
||||
lines = ["1 2 5", "2 3 4", "3 5", "4", "5"]
|
||||
nx.parse_adjlist(lines, nodetype=int) # smoke test
|
||||
with pytest.raises(TypeError):
|
||||
nx.parse_adjlist(lines, nodetype="int")
|
||||
lines = ["1 2 5", "2 b", "c"]
|
||||
with pytest.raises(TypeError):
|
||||
nx.parse_adjlist(lines, nodetype=int)
|
||||
|
||||
def test_adjlist_graph(self):
|
||||
G = self.G
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_adjlist(G, fname)
|
||||
H = nx.read_adjlist(fname)
|
||||
H2 = nx.read_adjlist(fname)
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_adjlist_digraph(self):
|
||||
G = self.DG
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_adjlist(G, fname)
|
||||
H = nx.read_adjlist(fname, create_using=nx.DiGraph())
|
||||
H2 = nx.read_adjlist(fname, create_using=nx.DiGraph())
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_adjlist_integers(self):
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
G = nx.convert_node_labels_to_integers(self.G)
|
||||
nx.write_adjlist(G, fname)
|
||||
H = nx.read_adjlist(fname, nodetype=int)
|
||||
H2 = nx.read_adjlist(fname, nodetype=int)
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_adjlist_multigraph(self):
|
||||
G = self.XG
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_adjlist(G, fname)
|
||||
H = nx.read_adjlist(fname, nodetype=int, create_using=nx.MultiGraph())
|
||||
H2 = nx.read_adjlist(fname, nodetype=int, create_using=nx.MultiGraph())
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_adjlist_multidigraph(self):
|
||||
G = self.XDG
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_adjlist(G, fname)
|
||||
H = nx.read_adjlist(fname, nodetype=int, create_using=nx.MultiDiGraph())
|
||||
H2 = nx.read_adjlist(fname, nodetype=int, create_using=nx.MultiDiGraph())
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_adjlist_delimiter(self):
|
||||
fh = io.BytesIO()
|
||||
G = nx.path_graph(3)
|
||||
nx.write_adjlist(G, fh, delimiter=":")
|
||||
fh.seek(0)
|
||||
H = nx.read_adjlist(fh, nodetype=int, delimiter=":")
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
|
||||
|
||||
class TestMultilineAdjlist:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.G = nx.Graph(name="test")
|
||||
e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")]
|
||||
cls.G.add_edges_from(e)
|
||||
cls.G.add_node("g")
|
||||
cls.DG = nx.DiGraph(cls.G)
|
||||
cls.DG.remove_edge("b", "a")
|
||||
cls.DG.remove_edge("b", "c")
|
||||
cls.XG = nx.MultiGraph()
|
||||
cls.XG.add_weighted_edges_from([(1, 2, 5), (1, 2, 5), (1, 2, 1), (3, 3, 42)])
|
||||
cls.XDG = nx.MultiDiGraph(cls.XG)
|
||||
|
||||
def test_parse_multiline_adjlist(self):
|
||||
lines = [
|
||||
"1 2",
|
||||
"b {'weight':3, 'name': 'Frodo'}",
|
||||
"c {}",
|
||||
"d 1",
|
||||
"e {'weight':6, 'name': 'Saruman'}",
|
||||
]
|
||||
nx.parse_multiline_adjlist(iter(lines)) # smoke test
|
||||
with pytest.raises(TypeError):
|
||||
nx.parse_multiline_adjlist(iter(lines), nodetype=int)
|
||||
nx.parse_multiline_adjlist(iter(lines), edgetype=str) # smoke test
|
||||
with pytest.raises(TypeError):
|
||||
nx.parse_multiline_adjlist(iter(lines), nodetype=int)
|
||||
lines = ["1 a"]
|
||||
with pytest.raises(TypeError):
|
||||
nx.parse_multiline_adjlist(iter(lines))
|
||||
lines = ["a 2"]
|
||||
with pytest.raises(TypeError):
|
||||
nx.parse_multiline_adjlist(iter(lines), nodetype=int)
|
||||
lines = ["1 2"]
|
||||
with pytest.raises(TypeError):
|
||||
nx.parse_multiline_adjlist(iter(lines))
|
||||
lines = ["1 2", "2 {}"]
|
||||
with pytest.raises(TypeError):
|
||||
nx.parse_multiline_adjlist(iter(lines))
|
||||
|
||||
def test_multiline_adjlist_graph(self):
|
||||
G = self.G
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_multiline_adjlist(G, fname)
|
||||
H = nx.read_multiline_adjlist(fname)
|
||||
H2 = nx.read_multiline_adjlist(fname)
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_multiline_adjlist_digraph(self):
|
||||
G = self.DG
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_multiline_adjlist(G, fname)
|
||||
H = nx.read_multiline_adjlist(fname, create_using=nx.DiGraph())
|
||||
H2 = nx.read_multiline_adjlist(fname, create_using=nx.DiGraph())
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_multiline_adjlist_integers(self):
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
G = nx.convert_node_labels_to_integers(self.G)
|
||||
nx.write_multiline_adjlist(G, fname)
|
||||
H = nx.read_multiline_adjlist(fname, nodetype=int)
|
||||
H2 = nx.read_multiline_adjlist(fname, nodetype=int)
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_multiline_adjlist_multigraph(self):
|
||||
G = self.XG
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_multiline_adjlist(G, fname)
|
||||
H = nx.read_multiline_adjlist(fname, nodetype=int, create_using=nx.MultiGraph())
|
||||
H2 = nx.read_multiline_adjlist(
|
||||
fname, nodetype=int, create_using=nx.MultiGraph()
|
||||
)
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_multiline_adjlist_multidigraph(self):
|
||||
G = self.XDG
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_multiline_adjlist(G, fname)
|
||||
H = nx.read_multiline_adjlist(
|
||||
fname, nodetype=int, create_using=nx.MultiDiGraph()
|
||||
)
|
||||
H2 = nx.read_multiline_adjlist(
|
||||
fname, nodetype=int, create_using=nx.MultiDiGraph()
|
||||
)
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_multiline_adjlist_delimiter(self):
|
||||
fh = io.BytesIO()
|
||||
G = nx.path_graph(3)
|
||||
nx.write_multiline_adjlist(G, fh, delimiter=":")
|
||||
fh.seek(0)
|
||||
H = nx.read_multiline_adjlist(fh, nodetype=int, delimiter=":")
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
314
venv/Lib/site-packages/networkx/readwrite/tests/test_edgelist.py
Normal file
314
venv/Lib/site-packages/networkx/readwrite/tests/test_edgelist.py
Normal file
|
@ -0,0 +1,314 @@
|
|||
"""
|
||||
Unit tests for edgelists.
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import edges_equal, graphs_equal, nodes_equal
|
||||
|
||||
edges_no_data = textwrap.dedent(
|
||||
"""
|
||||
# comment line
|
||||
1 2
|
||||
# comment line
|
||||
2 3
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
edges_with_values = textwrap.dedent(
|
||||
"""
|
||||
# comment line
|
||||
1 2 2.0
|
||||
# comment line
|
||||
2 3 3.0
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
edges_with_weight = textwrap.dedent(
|
||||
"""
|
||||
# comment line
|
||||
1 2 {'weight':2.0}
|
||||
# comment line
|
||||
2 3 {'weight':3.0}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
edges_with_multiple_attrs = textwrap.dedent(
|
||||
"""
|
||||
# comment line
|
||||
1 2 {'weight':2.0, 'color':'green'}
|
||||
# comment line
|
||||
2 3 {'weight':3.0, 'color':'red'}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
edges_with_multiple_attrs_csv = textwrap.dedent(
|
||||
"""
|
||||
# comment line
|
||||
1, 2, {'weight':2.0, 'color':'green'}
|
||||
# comment line
|
||||
2, 3, {'weight':3.0, 'color':'red'}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
_expected_edges_weights = [(1, 2, {"weight": 2.0}), (2, 3, {"weight": 3.0})]
|
||||
_expected_edges_multiattr = [
|
||||
(1, 2, {"weight": 2.0, "color": "green"}),
|
||||
(2, 3, {"weight": 3.0, "color": "red"}),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("data", "extra_kwargs"),
|
||||
(
|
||||
(edges_no_data, {}),
|
||||
(edges_with_values, {}),
|
||||
(edges_with_weight, {}),
|
||||
(edges_with_multiple_attrs, {}),
|
||||
(edges_with_multiple_attrs_csv, {"delimiter": ","}),
|
||||
),
|
||||
)
|
||||
def test_read_edgelist_no_data(data, extra_kwargs):
|
||||
bytesIO = io.BytesIO(data.encode("utf-8"))
|
||||
G = nx.read_edgelist(bytesIO, nodetype=int, data=False, **extra_kwargs)
|
||||
assert edges_equal(G.edges(), [(1, 2), (2, 3)])
|
||||
|
||||
|
||||
def test_read_weighted_edgelist():
|
||||
bytesIO = io.BytesIO(edges_with_values.encode("utf-8"))
|
||||
G = nx.read_weighted_edgelist(bytesIO, nodetype=int)
|
||||
assert edges_equal(G.edges(data=True), _expected_edges_weights)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("data", "extra_kwargs", "expected"),
|
||||
(
|
||||
(edges_with_weight, {}, _expected_edges_weights),
|
||||
(edges_with_multiple_attrs, {}, _expected_edges_multiattr),
|
||||
(edges_with_multiple_attrs_csv, {"delimiter": ","}, _expected_edges_multiattr),
|
||||
),
|
||||
)
|
||||
def test_read_edgelist_with_data(data, extra_kwargs, expected):
|
||||
bytesIO = io.BytesIO(data.encode("utf-8"))
|
||||
G = nx.read_edgelist(bytesIO, nodetype=int, **extra_kwargs)
|
||||
assert edges_equal(G.edges(data=True), expected)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def example_graph():
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(1, 2, 3.0), (2, 3, 27.0), (3, 4, 3.0)])
|
||||
return G
|
||||
|
||||
|
||||
def test_parse_edgelist_no_data(example_graph):
|
||||
G = example_graph
|
||||
H = nx.parse_edgelist(["1 2", "2 3", "3 4"], nodetype=int)
|
||||
assert nodes_equal(G.nodes, H.nodes)
|
||||
assert edges_equal(G.edges, H.edges)
|
||||
|
||||
|
||||
def test_parse_edgelist_with_data_dict(example_graph):
|
||||
G = example_graph
|
||||
H = nx.parse_edgelist(
|
||||
["1 2 {'weight': 3}", "2 3 {'weight': 27}", "3 4 {'weight': 3.0}"], nodetype=int
|
||||
)
|
||||
assert nodes_equal(G.nodes, H.nodes)
|
||||
assert edges_equal(G.edges(data=True), H.edges(data=True))
|
||||
|
||||
|
||||
def test_parse_edgelist_with_data_list(example_graph):
|
||||
G = example_graph
|
||||
H = nx.parse_edgelist(
|
||||
["1 2 3", "2 3 27", "3 4 3.0"], nodetype=int, data=(("weight", float),)
|
||||
)
|
||||
assert nodes_equal(G.nodes, H.nodes)
|
||||
assert edges_equal(G.edges(data=True), H.edges(data=True))
|
||||
|
||||
|
||||
def test_parse_edgelist():
|
||||
# ignore lines with less than 2 nodes
|
||||
lines = ["1;2", "2 3", "3 4"]
|
||||
G = nx.parse_edgelist(lines, nodetype=int)
|
||||
assert list(G.edges()) == [(2, 3), (3, 4)]
|
||||
# unknown nodetype
|
||||
with pytest.raises(TypeError, match="Failed to convert nodes"):
|
||||
lines = ["1 2", "2 3", "3 4"]
|
||||
nx.parse_edgelist(lines, nodetype="nope")
|
||||
# lines have invalid edge format
|
||||
with pytest.raises(TypeError, match="Failed to convert edge data"):
|
||||
lines = ["1 2 3", "2 3", "3 4"]
|
||||
nx.parse_edgelist(lines, nodetype=int)
|
||||
# edge data and data_keys not the same length
|
||||
with pytest.raises(IndexError, match="not the same length"):
|
||||
lines = ["1 2 3", "2 3 27", "3 4 3.0"]
|
||||
nx.parse_edgelist(
|
||||
lines, nodetype=int, data=(("weight", float), ("capacity", int))
|
||||
)
|
||||
# edge data can't be converted to edge type
|
||||
with pytest.raises(TypeError, match="Failed to convert"):
|
||||
lines = ["1 2 't1'", "2 3 't3'", "3 4 't3'"]
|
||||
nx.parse_edgelist(lines, nodetype=int, data=(("weight", float),))
|
||||
|
||||
|
||||
def test_comments_None():
|
||||
edgelist = ["node#1 node#2", "node#2 node#3"]
|
||||
# comments=None supported to ignore all comment characters
|
||||
G = nx.parse_edgelist(edgelist, comments=None)
|
||||
H = nx.Graph([e.split(" ") for e in edgelist])
|
||||
assert edges_equal(G.edges, H.edges)
|
||||
|
||||
|
||||
class TestEdgelist:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.G = nx.Graph(name="test")
|
||||
e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")]
|
||||
cls.G.add_edges_from(e)
|
||||
cls.G.add_node("g")
|
||||
cls.DG = nx.DiGraph(cls.G)
|
||||
cls.XG = nx.MultiGraph()
|
||||
cls.XG.add_weighted_edges_from([(1, 2, 5), (1, 2, 5), (1, 2, 1), (3, 3, 42)])
|
||||
cls.XDG = nx.MultiDiGraph(cls.XG)
|
||||
|
||||
def test_write_edgelist_1(self):
|
||||
fh = io.BytesIO()
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(1, 2), (2, 3)])
|
||||
nx.write_edgelist(G, fh, data=False)
|
||||
fh.seek(0)
|
||||
assert fh.read() == b"1 2\n2 3\n"
|
||||
|
||||
def test_write_edgelist_2(self):
|
||||
fh = io.BytesIO()
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(1, 2), (2, 3)])
|
||||
nx.write_edgelist(G, fh, data=True)
|
||||
fh.seek(0)
|
||||
assert fh.read() == b"1 2 {}\n2 3 {}\n"
|
||||
|
||||
def test_write_edgelist_3(self):
|
||||
fh = io.BytesIO()
|
||||
G = nx.Graph()
|
||||
G.add_edge(1, 2, weight=2.0)
|
||||
G.add_edge(2, 3, weight=3.0)
|
||||
nx.write_edgelist(G, fh, data=True)
|
||||
fh.seek(0)
|
||||
assert fh.read() == b"1 2 {'weight': 2.0}\n2 3 {'weight': 3.0}\n"
|
||||
|
||||
def test_write_edgelist_4(self):
|
||||
fh = io.BytesIO()
|
||||
G = nx.Graph()
|
||||
G.add_edge(1, 2, weight=2.0)
|
||||
G.add_edge(2, 3, weight=3.0)
|
||||
nx.write_edgelist(G, fh, data=[("weight")])
|
||||
fh.seek(0)
|
||||
assert fh.read() == b"1 2 2.0\n2 3 3.0\n"
|
||||
|
||||
def test_unicode(self):
|
||||
G = nx.Graph()
|
||||
name1 = chr(2344) + chr(123) + chr(6543)
|
||||
name2 = chr(5543) + chr(1543) + chr(324)
|
||||
G.add_edge(name1, "Radiohead", **{name2: 3})
|
||||
fd, fname = tempfile.mkstemp()
|
||||
nx.write_edgelist(G, fname)
|
||||
H = nx.read_edgelist(fname)
|
||||
assert graphs_equal(G, H)
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_latin1_issue(self):
|
||||
G = nx.Graph()
|
||||
name1 = chr(2344) + chr(123) + chr(6543)
|
||||
name2 = chr(5543) + chr(1543) + chr(324)
|
||||
G.add_edge(name1, "Radiohead", **{name2: 3})
|
||||
fd, fname = tempfile.mkstemp()
|
||||
pytest.raises(
|
||||
UnicodeEncodeError, nx.write_edgelist, G, fname, encoding="latin-1"
|
||||
)
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_latin1(self):
|
||||
G = nx.Graph()
|
||||
name1 = "Bj" + chr(246) + "rk"
|
||||
name2 = chr(220) + "ber"
|
||||
G.add_edge(name1, "Radiohead", **{name2: 3})
|
||||
fd, fname = tempfile.mkstemp()
|
||||
nx.write_edgelist(G, fname, encoding="latin-1")
|
||||
H = nx.read_edgelist(fname, encoding="latin-1")
|
||||
assert graphs_equal(G, H)
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_edgelist_graph(self):
|
||||
G = self.G
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_edgelist(G, fname)
|
||||
H = nx.read_edgelist(fname)
|
||||
H2 = nx.read_edgelist(fname)
|
||||
assert H is not H2 # they should be different graphs
|
||||
G.remove_node("g") # isolated nodes are not written in edgelist
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_edgelist_digraph(self):
|
||||
G = self.DG
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_edgelist(G, fname)
|
||||
H = nx.read_edgelist(fname, create_using=nx.DiGraph())
|
||||
H2 = nx.read_edgelist(fname, create_using=nx.DiGraph())
|
||||
assert H is not H2 # they should be different graphs
|
||||
G.remove_node("g") # isolated nodes are not written in edgelist
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_edgelist_integers(self):
|
||||
G = nx.convert_node_labels_to_integers(self.G)
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_edgelist(G, fname)
|
||||
H = nx.read_edgelist(fname, nodetype=int)
|
||||
# isolated nodes are not written in edgelist
|
||||
G.remove_nodes_from(list(nx.isolates(G)))
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_edgelist_multigraph(self):
|
||||
G = self.XG
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_edgelist(G, fname)
|
||||
H = nx.read_edgelist(fname, nodetype=int, create_using=nx.MultiGraph())
|
||||
H2 = nx.read_edgelist(fname, nodetype=int, create_using=nx.MultiGraph())
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_edgelist_multidigraph(self):
|
||||
G = self.XDG
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
nx.write_edgelist(G, fname)
|
||||
H = nx.read_edgelist(fname, nodetype=int, create_using=nx.MultiDiGraph())
|
||||
H2 = nx.read_edgelist(fname, nodetype=int, create_using=nx.MultiDiGraph())
|
||||
assert H is not H2 # they should be different graphs
|
||||
assert nodes_equal(list(H), list(G))
|
||||
assert edges_equal(list(H.edges()), list(G.edges()))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
557
venv/Lib/site-packages/networkx/readwrite/tests/test_gexf.py
Normal file
557
venv/Lib/site-packages/networkx/readwrite/tests/test_gexf.py
Normal file
|
@ -0,0 +1,557 @@
|
|||
import io
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
class TestGEXF:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.simple_directed_data = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gexf xmlns="http://www.gexf.net/1.2draft" version="1.2">
|
||||
<graph mode="static" defaultedgetype="directed">
|
||||
<nodes>
|
||||
<node id="0" label="Hello" />
|
||||
<node id="1" label="Word" />
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge id="0" source="0" target="1" />
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>
|
||||
"""
|
||||
cls.simple_directed_graph = nx.DiGraph()
|
||||
cls.simple_directed_graph.add_node("0", label="Hello")
|
||||
cls.simple_directed_graph.add_node("1", label="World")
|
||||
cls.simple_directed_graph.add_edge("0", "1", id="0")
|
||||
|
||||
cls.simple_directed_fh = io.BytesIO(cls.simple_directed_data.encode("UTF-8"))
|
||||
|
||||
cls.attribute_data = """<?xml version="1.0" encoding="UTF-8"?>\
|
||||
<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi="http://www.w3.\
|
||||
org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gexf.net/\
|
||||
1.2draft http://www.gexf.net/1.2draft/gexf.xsd" version="1.2">
|
||||
<meta lastmodifieddate="2009-03-20">
|
||||
<creator>Gephi.org</creator>
|
||||
<description>A Web network</description>
|
||||
</meta>
|
||||
<graph defaultedgetype="directed">
|
||||
<attributes class="node">
|
||||
<attribute id="0" title="url" type="string"/>
|
||||
<attribute id="1" title="indegree" type="integer"/>
|
||||
<attribute id="2" title="frog" type="boolean">
|
||||
<default>true</default>
|
||||
</attribute>
|
||||
</attributes>
|
||||
<nodes>
|
||||
<node id="0" label="Gephi">
|
||||
<attvalues>
|
||||
<attvalue for="0" value="https://gephi.org"/>
|
||||
<attvalue for="1" value="1"/>
|
||||
<attvalue for="2" value="false"/>
|
||||
</attvalues>
|
||||
</node>
|
||||
<node id="1" label="Webatlas">
|
||||
<attvalues>
|
||||
<attvalue for="0" value="http://webatlas.fr"/>
|
||||
<attvalue for="1" value="2"/>
|
||||
<attvalue for="2" value="false"/>
|
||||
</attvalues>
|
||||
</node>
|
||||
<node id="2" label="RTGI">
|
||||
<attvalues>
|
||||
<attvalue for="0" value="http://rtgi.fr"/>
|
||||
<attvalue for="1" value="1"/>
|
||||
<attvalue for="2" value="true"/>
|
||||
</attvalues>
|
||||
</node>
|
||||
<node id="3" label="BarabasiLab">
|
||||
<attvalues>
|
||||
<attvalue for="0" value="http://barabasilab.com"/>
|
||||
<attvalue for="1" value="1"/>
|
||||
<attvalue for="2" value="true"/>
|
||||
</attvalues>
|
||||
</node>
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge id="0" source="0" target="1" label="foo"/>
|
||||
<edge id="1" source="0" target="2"/>
|
||||
<edge id="2" source="1" target="0"/>
|
||||
<edge id="3" source="2" target="1"/>
|
||||
<edge id="4" source="0" target="3"/>
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>
|
||||
"""
|
||||
cls.attribute_graph = nx.DiGraph()
|
||||
cls.attribute_graph.graph["node_default"] = {"frog": True}
|
||||
cls.attribute_graph.add_node(
|
||||
"0", label="Gephi", url="https://gephi.org", indegree=1, frog=False
|
||||
)
|
||||
cls.attribute_graph.add_node(
|
||||
"1", label="Webatlas", url="http://webatlas.fr", indegree=2, frog=False
|
||||
)
|
||||
cls.attribute_graph.add_node(
|
||||
"2", label="RTGI", url="http://rtgi.fr", indegree=1, frog=True
|
||||
)
|
||||
cls.attribute_graph.add_node(
|
||||
"3",
|
||||
label="BarabasiLab",
|
||||
url="http://barabasilab.com",
|
||||
indegree=1,
|
||||
frog=True,
|
||||
)
|
||||
cls.attribute_graph.add_edge("0", "1", id="0", label="foo")
|
||||
cls.attribute_graph.add_edge("0", "2", id="1")
|
||||
cls.attribute_graph.add_edge("1", "0", id="2")
|
||||
cls.attribute_graph.add_edge("2", "1", id="3")
|
||||
cls.attribute_graph.add_edge("0", "3", id="4")
|
||||
cls.attribute_fh = io.BytesIO(cls.attribute_data.encode("UTF-8"))
|
||||
|
||||
cls.simple_undirected_data = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gexf xmlns="http://www.gexf.net/1.2draft" version="1.2">
|
||||
<graph mode="static" defaultedgetype="undirected">
|
||||
<nodes>
|
||||
<node id="0" label="Hello" />
|
||||
<node id="1" label="Word" />
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge id="0" source="0" target="1" />
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>
|
||||
"""
|
||||
cls.simple_undirected_graph = nx.Graph()
|
||||
cls.simple_undirected_graph.add_node("0", label="Hello")
|
||||
cls.simple_undirected_graph.add_node("1", label="World")
|
||||
cls.simple_undirected_graph.add_edge("0", "1", id="0")
|
||||
|
||||
cls.simple_undirected_fh = io.BytesIO(
|
||||
cls.simple_undirected_data.encode("UTF-8")
|
||||
)
|
||||
|
||||
def test_read_simple_directed_graphml(self):
|
||||
G = self.simple_directed_graph
|
||||
H = nx.read_gexf(self.simple_directed_fh)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(G.edges()) == sorted(H.edges())
|
||||
assert sorted(G.edges(data=True)) == sorted(H.edges(data=True))
|
||||
self.simple_directed_fh.seek(0)
|
||||
|
||||
def test_write_read_simple_directed_graphml(self):
|
||||
G = self.simple_directed_graph
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(G.edges()) == sorted(H.edges())
|
||||
assert sorted(G.edges(data=True)) == sorted(H.edges(data=True))
|
||||
self.simple_directed_fh.seek(0)
|
||||
|
||||
def test_read_simple_undirected_graphml(self):
|
||||
G = self.simple_undirected_graph
|
||||
H = nx.read_gexf(self.simple_undirected_fh)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(sorted(e) for e in G.edges()) == sorted(
|
||||
sorted(e) for e in H.edges()
|
||||
)
|
||||
self.simple_undirected_fh.seek(0)
|
||||
|
||||
def test_read_attribute_graphml(self):
|
||||
G = self.attribute_graph
|
||||
H = nx.read_gexf(self.attribute_fh)
|
||||
assert sorted(G.nodes(True)) == sorted(H.nodes(data=True))
|
||||
ge = sorted(G.edges(data=True))
|
||||
he = sorted(H.edges(data=True))
|
||||
for a, b in zip(ge, he):
|
||||
assert a == b
|
||||
self.attribute_fh.seek(0)
|
||||
|
||||
def test_directed_edge_in_undirected(self):
|
||||
s = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gexf xmlns="http://www.gexf.net/1.2draft" version='1.2'>
|
||||
<graph mode="static" defaultedgetype="undirected" name="">
|
||||
<nodes>
|
||||
<node id="0" label="Hello" />
|
||||
<node id="1" label="Word" />
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge id="0" source="0" target="1" type="directed"/>
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>
|
||||
"""
|
||||
fh = io.BytesIO(s.encode("UTF-8"))
|
||||
pytest.raises(nx.NetworkXError, nx.read_gexf, fh)
|
||||
|
||||
def test_undirected_edge_in_directed(self):
|
||||
s = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gexf xmlns="http://www.gexf.net/1.2draft" version='1.2'>
|
||||
<graph mode="static" defaultedgetype="directed" name="">
|
||||
<nodes>
|
||||
<node id="0" label="Hello" />
|
||||
<node id="1" label="Word" />
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge id="0" source="0" target="1" type="undirected"/>
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>
|
||||
"""
|
||||
fh = io.BytesIO(s.encode("UTF-8"))
|
||||
pytest.raises(nx.NetworkXError, nx.read_gexf, fh)
|
||||
|
||||
def test_key_raises(self):
|
||||
s = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gexf xmlns="http://www.gexf.net/1.2draft" version='1.2'>
|
||||
<graph mode="static" defaultedgetype="directed" name="">
|
||||
<nodes>
|
||||
<node id="0" label="Hello">
|
||||
<attvalues>
|
||||
<attvalue for='0' value='1'/>
|
||||
</attvalues>
|
||||
</node>
|
||||
<node id="1" label="Word" />
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge id="0" source="0" target="1" type="undirected"/>
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>
|
||||
"""
|
||||
fh = io.BytesIO(s.encode("UTF-8"))
|
||||
pytest.raises(nx.NetworkXError, nx.read_gexf, fh)
|
||||
|
||||
def test_relabel(self):
|
||||
s = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gexf xmlns="http://www.gexf.net/1.2draft" version='1.2'>
|
||||
<graph mode="static" defaultedgetype="directed" name="">
|
||||
<nodes>
|
||||
<node id="0" label="Hello" />
|
||||
<node id="1" label="Word" />
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge id="0" source="0" target="1"/>
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>
|
||||
"""
|
||||
fh = io.BytesIO(s.encode("UTF-8"))
|
||||
G = nx.read_gexf(fh, relabel=True)
|
||||
assert sorted(G.nodes()) == ["Hello", "Word"]
|
||||
|
||||
def test_default_attribute(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(1, label="1", color="green")
|
||||
nx.add_path(G, [0, 1, 2, 3])
|
||||
G.add_edge(1, 2, foo=3)
|
||||
G.graph["node_default"] = {"color": "yellow"}
|
||||
G.graph["edge_default"] = {"foo": 7}
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(sorted(e) for e in G.edges()) == sorted(
|
||||
sorted(e) for e in H.edges()
|
||||
)
|
||||
# Reading a gexf graph always sets mode attribute to either
|
||||
# 'static' or 'dynamic'. Remove the mode attribute from the
|
||||
# read graph for the sake of comparing remaining attributes.
|
||||
del H.graph["mode"]
|
||||
assert G.graph == H.graph
|
||||
|
||||
def test_serialize_ints_to_strings(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(1, id=7, label=77)
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert list(H) == [7]
|
||||
assert H.nodes[7]["label"] == "77"
|
||||
|
||||
def test_write_with_node_attributes(self):
|
||||
# Addresses #673.
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (2, 3)])
|
||||
for i in range(4):
|
||||
G.nodes[i]["id"] = i
|
||||
G.nodes[i]["label"] = i
|
||||
G.nodes[i]["pid"] = i
|
||||
G.nodes[i]["start"] = i
|
||||
G.nodes[i]["end"] = i + 1
|
||||
|
||||
expected = f"""<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi\
|
||||
="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=\
|
||||
"http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/\
|
||||
gexf.xsd" version="1.2">
|
||||
<meta lastmodifieddate="{time.strftime('%Y-%m-%d')}">
|
||||
<creator>NetworkX {nx.__version__}</creator>
|
||||
</meta>
|
||||
<graph defaultedgetype="undirected" mode="dynamic" name="" timeformat="long">
|
||||
<nodes>
|
||||
<node id="0" label="0" pid="0" start="0" end="1" />
|
||||
<node id="1" label="1" pid="1" start="1" end="2" />
|
||||
<node id="2" label="2" pid="2" start="2" end="3" />
|
||||
<node id="3" label="3" pid="3" start="3" end="4" />
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge source="0" target="1" id="0" />
|
||||
<edge source="1" target="2" id="1" />
|
||||
<edge source="2" target="3" id="2" />
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>"""
|
||||
obtained = "\n".join(nx.generate_gexf(G))
|
||||
assert expected == obtained
|
||||
|
||||
def test_edge_id_construct(self):
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1, {"id": 0}), (1, 2, {"id": 2}), (2, 3)])
|
||||
|
||||
expected = f"""<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi\
|
||||
="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.\
|
||||
gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd" version="1.2">
|
||||
<meta lastmodifieddate="{time.strftime('%Y-%m-%d')}">
|
||||
<creator>NetworkX {nx.__version__}</creator>
|
||||
</meta>
|
||||
<graph defaultedgetype="undirected" mode="static" name="">
|
||||
<nodes>
|
||||
<node id="0" label="0" />
|
||||
<node id="1" label="1" />
|
||||
<node id="2" label="2" />
|
||||
<node id="3" label="3" />
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge source="0" target="1" id="0" />
|
||||
<edge source="1" target="2" id="2" />
|
||||
<edge source="2" target="3" id="1" />
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>"""
|
||||
|
||||
obtained = "\n".join(nx.generate_gexf(G))
|
||||
assert expected == obtained
|
||||
|
||||
def test_numpy_type(self):
|
||||
np = pytest.importorskip("numpy")
|
||||
G = nx.path_graph(4)
|
||||
nx.set_node_attributes(G, {n: n for n in np.arange(4)}, "number")
|
||||
G[0][1]["edge-number"] = np.float64(1.1)
|
||||
|
||||
expected = f"""<gexf xmlns="http://www.gexf.net/1.2draft"\
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation\
|
||||
="http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd"\
|
||||
version="1.2">
|
||||
<meta lastmodifieddate="{time.strftime('%Y-%m-%d')}">
|
||||
<creator>NetworkX {nx.__version__}</creator>
|
||||
</meta>
|
||||
<graph defaultedgetype="undirected" mode="static" name="">
|
||||
<attributes mode="static" class="edge">
|
||||
<attribute id="1" title="edge-number" type="float" />
|
||||
</attributes>
|
||||
<attributes mode="static" class="node">
|
||||
<attribute id="0" title="number" type="int" />
|
||||
</attributes>
|
||||
<nodes>
|
||||
<node id="0" label="0">
|
||||
<attvalues>
|
||||
<attvalue for="0" value="0" />
|
||||
</attvalues>
|
||||
</node>
|
||||
<node id="1" label="1">
|
||||
<attvalues>
|
||||
<attvalue for="0" value="1" />
|
||||
</attvalues>
|
||||
</node>
|
||||
<node id="2" label="2">
|
||||
<attvalues>
|
||||
<attvalue for="0" value="2" />
|
||||
</attvalues>
|
||||
</node>
|
||||
<node id="3" label="3">
|
||||
<attvalues>
|
||||
<attvalue for="0" value="3" />
|
||||
</attvalues>
|
||||
</node>
|
||||
</nodes>
|
||||
<edges>
|
||||
<edge source="0" target="1" id="0">
|
||||
<attvalues>
|
||||
<attvalue for="1" value="1.1" />
|
||||
</attvalues>
|
||||
</edge>
|
||||
<edge source="1" target="2" id="1" />
|
||||
<edge source="2" target="3" id="2" />
|
||||
</edges>
|
||||
</graph>
|
||||
</gexf>"""
|
||||
obtained = "\n".join(nx.generate_gexf(G))
|
||||
assert expected == obtained
|
||||
|
||||
def test_bool(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(1, testattr=True)
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert H.nodes[1]["testattr"]
|
||||
|
||||
# Test for NaN, INF and -INF
|
||||
def test_specials(self):
|
||||
from math import isnan
|
||||
|
||||
inf, nan = float("inf"), float("nan")
|
||||
G = nx.Graph()
|
||||
G.add_node(1, testattr=inf, strdata="inf", key="a")
|
||||
G.add_node(2, testattr=nan, strdata="nan", key="b")
|
||||
G.add_node(3, testattr=-inf, strdata="-inf", key="c")
|
||||
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
filetext = fh.read()
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
|
||||
assert b"INF" in filetext
|
||||
assert b"NaN" in filetext
|
||||
assert b"-INF" in filetext
|
||||
|
||||
assert H.nodes[1]["testattr"] == inf
|
||||
assert isnan(H.nodes[2]["testattr"])
|
||||
assert H.nodes[3]["testattr"] == -inf
|
||||
|
||||
assert H.nodes[1]["strdata"] == "inf"
|
||||
assert H.nodes[2]["strdata"] == "nan"
|
||||
assert H.nodes[3]["strdata"] == "-inf"
|
||||
|
||||
assert H.nodes[1]["networkx_key"] == "a"
|
||||
assert H.nodes[2]["networkx_key"] == "b"
|
||||
assert H.nodes[3]["networkx_key"] == "c"
|
||||
|
||||
def test_simple_list(self):
|
||||
G = nx.Graph()
|
||||
list_value = [(1, 2, 3), (9, 1, 2)]
|
||||
G.add_node(1, key=list_value)
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert H.nodes[1]["networkx_key"] == list_value
|
||||
|
||||
def test_dynamic_mode(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(1, label="1", color="green")
|
||||
G.graph["mode"] = "dynamic"
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(sorted(e) for e in G.edges()) == sorted(
|
||||
sorted(e) for e in H.edges()
|
||||
)
|
||||
|
||||
def test_multigraph_with_missing_attributes(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_node(0, label="1", color="green")
|
||||
G.add_node(1, label="2", color="green")
|
||||
G.add_edge(0, 1, id="0", weight=3, type="undirected", start=0, end=1)
|
||||
G.add_edge(0, 1, id="1", label="foo", start=0, end=1)
|
||||
G.add_edge(0, 1)
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(sorted(e) for e in G.edges()) == sorted(
|
||||
sorted(e) for e in H.edges()
|
||||
)
|
||||
|
||||
def test_missing_viz_attributes(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(0, label="1", color="green")
|
||||
G.nodes[0]["viz"] = {"size": 54}
|
||||
G.nodes[0]["viz"]["position"] = {"x": 0, "y": 1, "z": 0}
|
||||
G.nodes[0]["viz"]["color"] = {"r": 0, "g": 0, "b": 256}
|
||||
G.nodes[0]["viz"]["shape"] = "http://random.url"
|
||||
G.nodes[0]["viz"]["thickness"] = 2
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh, version="1.1draft")
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(sorted(e) for e in G.edges()) == sorted(
|
||||
sorted(e) for e in H.edges()
|
||||
)
|
||||
|
||||
# Test missing alpha value for version >draft1.1 - set default alpha value
|
||||
# to 1.0 instead of `None` when writing for better general compatibility
|
||||
fh = io.BytesIO()
|
||||
# G.nodes[0]["viz"]["color"] does not have an alpha value explicitly defined
|
||||
# so the default is used instead
|
||||
nx.write_gexf(G, fh, version="1.2draft")
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert H.nodes[0]["viz"]["color"]["a"] == 1.0
|
||||
|
||||
# Second graph for the other branch
|
||||
G = nx.Graph()
|
||||
G.add_node(0, label="1", color="green")
|
||||
G.nodes[0]["viz"] = {"size": 54}
|
||||
G.nodes[0]["viz"]["position"] = {"x": 0, "y": 1, "z": 0}
|
||||
G.nodes[0]["viz"]["color"] = {"r": 0, "g": 0, "b": 256, "a": 0.5}
|
||||
G.nodes[0]["viz"]["shape"] = "ftp://random.url"
|
||||
G.nodes[0]["viz"]["thickness"] = 2
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(sorted(e) for e in G.edges()) == sorted(
|
||||
sorted(e) for e in H.edges()
|
||||
)
|
||||
|
||||
def test_slice_and_spell(self):
|
||||
# Test spell first, so version = 1.2
|
||||
G = nx.Graph()
|
||||
G.add_node(0, label="1", color="green")
|
||||
G.nodes[0]["spells"] = [(1, 2)]
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(sorted(e) for e in G.edges()) == sorted(
|
||||
sorted(e) for e in H.edges()
|
||||
)
|
||||
|
||||
G = nx.Graph()
|
||||
G.add_node(0, label="1", color="green")
|
||||
G.nodes[0]["slices"] = [(1, 2)]
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh, version="1.1draft")
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(sorted(e) for e in G.edges()) == sorted(
|
||||
sorted(e) for e in H.edges()
|
||||
)
|
||||
|
||||
def test_add_parent(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(0, label="1", color="green", parents=[1, 2])
|
||||
fh = io.BytesIO()
|
||||
nx.write_gexf(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_gexf(fh, node_type=int)
|
||||
assert sorted(G.nodes()) == sorted(H.nodes())
|
||||
assert sorted(sorted(e) for e in G.edges()) == sorted(
|
||||
sorted(e) for e in H.edges()
|
||||
)
|
753
venv/Lib/site-packages/networkx/readwrite/tests/test_gml.py
Normal file
753
venv/Lib/site-packages/networkx/readwrite/tests/test_gml.py
Normal file
|
@ -0,0 +1,753 @@
|
|||
import codecs
|
||||
import io
|
||||
import math
|
||||
import os
|
||||
import tempfile
|
||||
from ast import literal_eval
|
||||
from contextlib import contextmanager
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.readwrite.gml import literal_destringizer, literal_stringizer
|
||||
|
||||
|
||||
class TestGraph:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.simple_data = """Creator "me"
|
||||
Version "xx"
|
||||
graph [
|
||||
comment "This is a sample graph"
|
||||
directed 1
|
||||
IsPlanar 1
|
||||
pos [ x 0 y 1 ]
|
||||
node [
|
||||
id 1
|
||||
label "Node 1"
|
||||
pos [ x 1 y 1 ]
|
||||
]
|
||||
node [
|
||||
id 2
|
||||
pos [ x 1 y 2 ]
|
||||
label "Node 2"
|
||||
]
|
||||
node [
|
||||
id 3
|
||||
label "Node 3"
|
||||
pos [ x 1 y 3 ]
|
||||
]
|
||||
edge [
|
||||
source 1
|
||||
target 2
|
||||
label "Edge from node 1 to node 2"
|
||||
color [line "blue" thickness 3]
|
||||
|
||||
]
|
||||
edge [
|
||||
source 2
|
||||
target 3
|
||||
label "Edge from node 2 to node 3"
|
||||
]
|
||||
edge [
|
||||
source 3
|
||||
target 1
|
||||
label "Edge from node 3 to node 1"
|
||||
]
|
||||
]
|
||||
"""
|
||||
|
||||
def test_parse_gml_cytoscape_bug(self):
|
||||
# example from issue #321, originally #324 in trac
|
||||
cytoscape_example = """
|
||||
Creator "Cytoscape"
|
||||
Version 1.0
|
||||
graph [
|
||||
node [
|
||||
root_index -3
|
||||
id -3
|
||||
graphics [
|
||||
x -96.0
|
||||
y -67.0
|
||||
w 40.0
|
||||
h 40.0
|
||||
fill "#ff9999"
|
||||
type "ellipse"
|
||||
outline "#666666"
|
||||
outline_width 1.5
|
||||
]
|
||||
label "node2"
|
||||
]
|
||||
node [
|
||||
root_index -2
|
||||
id -2
|
||||
graphics [
|
||||
x 63.0
|
||||
y 37.0
|
||||
w 40.0
|
||||
h 40.0
|
||||
fill "#ff9999"
|
||||
type "ellipse"
|
||||
outline "#666666"
|
||||
outline_width 1.5
|
||||
]
|
||||
label "node1"
|
||||
]
|
||||
node [
|
||||
root_index -1
|
||||
id -1
|
||||
graphics [
|
||||
x -31.0
|
||||
y -17.0
|
||||
w 40.0
|
||||
h 40.0
|
||||
fill "#ff9999"
|
||||
type "ellipse"
|
||||
outline "#666666"
|
||||
outline_width 1.5
|
||||
]
|
||||
label "node0"
|
||||
]
|
||||
edge [
|
||||
root_index -2
|
||||
target -2
|
||||
source -1
|
||||
graphics [
|
||||
width 1.5
|
||||
fill "#0000ff"
|
||||
type "line"
|
||||
Line [
|
||||
]
|
||||
source_arrow 0
|
||||
target_arrow 3
|
||||
]
|
||||
label "DirectedEdge"
|
||||
]
|
||||
edge [
|
||||
root_index -1
|
||||
target -1
|
||||
source -3
|
||||
graphics [
|
||||
width 1.5
|
||||
fill "#0000ff"
|
||||
type "line"
|
||||
Line [
|
||||
]
|
||||
source_arrow 0
|
||||
target_arrow 3
|
||||
]
|
||||
label "DirectedEdge"
|
||||
]
|
||||
]
|
||||
"""
|
||||
nx.parse_gml(cytoscape_example)
|
||||
|
||||
def test_parse_gml(self):
|
||||
G = nx.parse_gml(self.simple_data, label="label")
|
||||
assert sorted(G.nodes()) == ["Node 1", "Node 2", "Node 3"]
|
||||
assert sorted(G.edges()) == [
|
||||
("Node 1", "Node 2"),
|
||||
("Node 2", "Node 3"),
|
||||
("Node 3", "Node 1"),
|
||||
]
|
||||
|
||||
assert sorted(G.edges(data=True)) == [
|
||||
(
|
||||
"Node 1",
|
||||
"Node 2",
|
||||
{
|
||||
"color": {"line": "blue", "thickness": 3},
|
||||
"label": "Edge from node 1 to node 2",
|
||||
},
|
||||
),
|
||||
("Node 2", "Node 3", {"label": "Edge from node 2 to node 3"}),
|
||||
("Node 3", "Node 1", {"label": "Edge from node 3 to node 1"}),
|
||||
]
|
||||
|
||||
def test_read_gml(self):
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
fh = open(fname, "w")
|
||||
fh.write(self.simple_data)
|
||||
fh.close()
|
||||
Gin = nx.read_gml(fname, label="label")
|
||||
G = nx.parse_gml(self.simple_data, label="label")
|
||||
assert sorted(G.nodes(data=True)) == sorted(Gin.nodes(data=True))
|
||||
assert sorted(G.edges(data=True)) == sorted(Gin.edges(data=True))
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_labels_are_strings(self):
|
||||
# GML requires labels to be strings (i.e., in quotes)
|
||||
answer = """graph [
|
||||
node [
|
||||
id 0
|
||||
label "1203"
|
||||
]
|
||||
]"""
|
||||
G = nx.Graph()
|
||||
G.add_node(1203)
|
||||
data = "\n".join(nx.generate_gml(G, stringizer=literal_stringizer))
|
||||
assert data == answer
|
||||
|
||||
def test_relabel_duplicate(self):
|
||||
data = """
|
||||
graph
|
||||
[
|
||||
label ""
|
||||
directed 1
|
||||
node
|
||||
[
|
||||
id 0
|
||||
label "same"
|
||||
]
|
||||
node
|
||||
[
|
||||
id 1
|
||||
label "same"
|
||||
]
|
||||
]
|
||||
"""
|
||||
fh = io.BytesIO(data.encode("UTF-8"))
|
||||
fh.seek(0)
|
||||
pytest.raises(nx.NetworkXError, nx.read_gml, fh, label="label")
|
||||
|
||||
@pytest.mark.parametrize("stringizer", (None, literal_stringizer))
|
||||
def test_tuplelabels(self, stringizer):
|
||||
# https://github.com/networkx/networkx/pull/1048
|
||||
# Writing tuple labels to GML failed.
|
||||
G = nx.Graph()
|
||||
G.add_edge((0, 1), (1, 0))
|
||||
data = "\n".join(nx.generate_gml(G, stringizer=stringizer))
|
||||
answer = """graph [
|
||||
node [
|
||||
id 0
|
||||
label "(0,1)"
|
||||
]
|
||||
node [
|
||||
id 1
|
||||
label "(1,0)"
|
||||
]
|
||||
edge [
|
||||
source 0
|
||||
target 1
|
||||
]
|
||||
]"""
|
||||
assert data == answer
|
||||
|
||||
def test_quotes(self):
|
||||
# https://github.com/networkx/networkx/issues/1061
|
||||
# Encoding quotes as HTML entities.
|
||||
G = nx.path_graph(1)
|
||||
G.name = "path_graph(1)"
|
||||
attr = 'This is "quoted" and this is a copyright: ' + chr(169)
|
||||
G.nodes[0]["demo"] = attr
|
||||
fobj = tempfile.NamedTemporaryFile()
|
||||
nx.write_gml(G, fobj)
|
||||
fobj.seek(0)
|
||||
# Should be bytes in 2.x and 3.x
|
||||
data = fobj.read().strip().decode("ascii")
|
||||
answer = """graph [
|
||||
name "path_graph(1)"
|
||||
node [
|
||||
id 0
|
||||
label "0"
|
||||
demo "This is "quoted" and this is a copyright: ©"
|
||||
]
|
||||
]"""
|
||||
assert data == answer
|
||||
|
||||
def test_unicode_node(self):
|
||||
node = "node" + chr(169)
|
||||
G = nx.Graph()
|
||||
G.add_node(node)
|
||||
fobj = tempfile.NamedTemporaryFile()
|
||||
nx.write_gml(G, fobj)
|
||||
fobj.seek(0)
|
||||
# Should be bytes in 2.x and 3.x
|
||||
data = fobj.read().strip().decode("ascii")
|
||||
answer = """graph [
|
||||
node [
|
||||
id 0
|
||||
label "node©"
|
||||
]
|
||||
]"""
|
||||
assert data == answer
|
||||
|
||||
def test_float_label(self):
|
||||
node = 1.0
|
||||
G = nx.Graph()
|
||||
G.add_node(node)
|
||||
fobj = tempfile.NamedTemporaryFile()
|
||||
nx.write_gml(G, fobj)
|
||||
fobj.seek(0)
|
||||
# Should be bytes in 2.x and 3.x
|
||||
data = fobj.read().strip().decode("ascii")
|
||||
answer = """graph [
|
||||
node [
|
||||
id 0
|
||||
label "1.0"
|
||||
]
|
||||
]"""
|
||||
assert data == answer
|
||||
|
||||
def test_special_float_label(self):
|
||||
special_floats = [float("nan"), float("+inf"), float("-inf")]
|
||||
try:
|
||||
import numpy as np
|
||||
|
||||
special_floats += [np.nan, np.inf, np.inf * -1]
|
||||
except ImportError:
|
||||
special_floats += special_floats
|
||||
|
||||
G = nx.cycle_graph(len(special_floats))
|
||||
attrs = dict(enumerate(special_floats))
|
||||
nx.set_node_attributes(G, attrs, "nodefloat")
|
||||
edges = list(G.edges)
|
||||
attrs = {edges[i]: value for i, value in enumerate(special_floats)}
|
||||
nx.set_edge_attributes(G, attrs, "edgefloat")
|
||||
|
||||
fobj = tempfile.NamedTemporaryFile()
|
||||
nx.write_gml(G, fobj)
|
||||
fobj.seek(0)
|
||||
# Should be bytes in 2.x and 3.x
|
||||
data = fobj.read().strip().decode("ascii")
|
||||
answer = """graph [
|
||||
node [
|
||||
id 0
|
||||
label "0"
|
||||
nodefloat NAN
|
||||
]
|
||||
node [
|
||||
id 1
|
||||
label "1"
|
||||
nodefloat +INF
|
||||
]
|
||||
node [
|
||||
id 2
|
||||
label "2"
|
||||
nodefloat -INF
|
||||
]
|
||||
node [
|
||||
id 3
|
||||
label "3"
|
||||
nodefloat NAN
|
||||
]
|
||||
node [
|
||||
id 4
|
||||
label "4"
|
||||
nodefloat +INF
|
||||
]
|
||||
node [
|
||||
id 5
|
||||
label "5"
|
||||
nodefloat -INF
|
||||
]
|
||||
edge [
|
||||
source 0
|
||||
target 1
|
||||
edgefloat NAN
|
||||
]
|
||||
edge [
|
||||
source 0
|
||||
target 5
|
||||
edgefloat +INF
|
||||
]
|
||||
edge [
|
||||
source 1
|
||||
target 2
|
||||
edgefloat -INF
|
||||
]
|
||||
edge [
|
||||
source 2
|
||||
target 3
|
||||
edgefloat NAN
|
||||
]
|
||||
edge [
|
||||
source 3
|
||||
target 4
|
||||
edgefloat +INF
|
||||
]
|
||||
edge [
|
||||
source 4
|
||||
target 5
|
||||
edgefloat -INF
|
||||
]
|
||||
]"""
|
||||
assert data == answer
|
||||
|
||||
fobj.seek(0)
|
||||
graph = nx.read_gml(fobj)
|
||||
for indx, value in enumerate(special_floats):
|
||||
node_value = graph.nodes[str(indx)]["nodefloat"]
|
||||
if math.isnan(value):
|
||||
assert math.isnan(node_value)
|
||||
else:
|
||||
assert node_value == value
|
||||
|
||||
edge = edges[indx]
|
||||
string_edge = (str(edge[0]), str(edge[1]))
|
||||
edge_value = graph.edges[string_edge]["edgefloat"]
|
||||
if math.isnan(value):
|
||||
assert math.isnan(edge_value)
|
||||
else:
|
||||
assert edge_value == value
|
||||
|
||||
def test_name(self):
|
||||
G = nx.parse_gml('graph [ name "x" node [ id 0 label "x" ] ]')
|
||||
assert "x" == G.graph["name"]
|
||||
G = nx.parse_gml('graph [ node [ id 0 label "x" ] ]')
|
||||
assert "" == G.name
|
||||
assert "name" not in G.graph
|
||||
|
||||
def test_graph_types(self):
|
||||
for directed in [None, False, True]:
|
||||
for multigraph in [None, False, True]:
|
||||
gml = "graph ["
|
||||
if directed is not None:
|
||||
gml += " directed " + str(int(directed))
|
||||
if multigraph is not None:
|
||||
gml += " multigraph " + str(int(multigraph))
|
||||
gml += ' node [ id 0 label "0" ]'
|
||||
gml += " edge [ source 0 target 0 ]"
|
||||
gml += " ]"
|
||||
G = nx.parse_gml(gml)
|
||||
assert bool(directed) == G.is_directed()
|
||||
assert bool(multigraph) == G.is_multigraph()
|
||||
gml = "graph [\n"
|
||||
if directed is True:
|
||||
gml += " directed 1\n"
|
||||
if multigraph is True:
|
||||
gml += " multigraph 1\n"
|
||||
gml += """ node [
|
||||
id 0
|
||||
label "0"
|
||||
]
|
||||
edge [
|
||||
source 0
|
||||
target 0
|
||||
"""
|
||||
if multigraph:
|
||||
gml += " key 0\n"
|
||||
gml += " ]\n]"
|
||||
assert gml == "\n".join(nx.generate_gml(G))
|
||||
|
||||
def test_data_types(self):
|
||||
data = [
|
||||
True,
|
||||
False,
|
||||
10**20,
|
||||
-2e33,
|
||||
"'",
|
||||
'"&&&""',
|
||||
[{(b"\xfd",): "\x7f", chr(0x4444): (1, 2)}, (2, "3")],
|
||||
]
|
||||
data.append(chr(0x14444))
|
||||
data.append(literal_eval("{2.3j, 1 - 2.3j, ()}"))
|
||||
G = nx.Graph()
|
||||
G.name = data
|
||||
G.graph["data"] = data
|
||||
G.add_node(0, int=-1, data={"data": data})
|
||||
G.add_edge(0, 0, float=-2.5, data=data)
|
||||
gml = "\n".join(nx.generate_gml(G, stringizer=literal_stringizer))
|
||||
G = nx.parse_gml(gml, destringizer=literal_destringizer)
|
||||
assert data == G.name
|
||||
assert {"name": data, "data": data} == G.graph
|
||||
assert list(G.nodes(data=True)) == [(0, {"int": -1, "data": {"data": data}})]
|
||||
assert list(G.edges(data=True)) == [(0, 0, {"float": -2.5, "data": data})]
|
||||
G = nx.Graph()
|
||||
G.graph["data"] = "frozenset([1, 2, 3])"
|
||||
G = nx.parse_gml(nx.generate_gml(G), destringizer=literal_eval)
|
||||
assert G.graph["data"] == "frozenset([1, 2, 3])"
|
||||
|
||||
def test_escape_unescape(self):
|
||||
gml = """graph [
|
||||
name "&"䑄��&unknown;"
|
||||
]"""
|
||||
G = nx.parse_gml(gml)
|
||||
assert (
|
||||
'&"\x0f' + chr(0x4444) + "��&unknown;"
|
||||
== G.name
|
||||
)
|
||||
gml = "\n".join(nx.generate_gml(G))
|
||||
alnu = "#1234567890;&#x1234567890abcdef"
|
||||
answer = (
|
||||
"""graph [
|
||||
name "&"䑄&"""
|
||||
+ alnu
|
||||
+ """;&unknown;"
|
||||
]"""
|
||||
)
|
||||
assert answer == gml
|
||||
|
||||
def test_exceptions(self):
|
||||
pytest.raises(ValueError, literal_destringizer, "(")
|
||||
pytest.raises(ValueError, literal_destringizer, "frozenset([1, 2, 3])")
|
||||
pytest.raises(ValueError, literal_destringizer, literal_destringizer)
|
||||
pytest.raises(ValueError, literal_stringizer, frozenset([1, 2, 3]))
|
||||
pytest.raises(ValueError, literal_stringizer, literal_stringizer)
|
||||
with tempfile.TemporaryFile() as f:
|
||||
f.write(codecs.BOM_UTF8 + b"graph[]")
|
||||
f.seek(0)
|
||||
pytest.raises(nx.NetworkXError, nx.read_gml, f)
|
||||
|
||||
def assert_parse_error(gml):
|
||||
pytest.raises(nx.NetworkXError, nx.parse_gml, gml)
|
||||
|
||||
assert_parse_error(["graph [\n\n", "]"])
|
||||
assert_parse_error("")
|
||||
assert_parse_error('Creator ""')
|
||||
assert_parse_error("0")
|
||||
assert_parse_error("graph ]")
|
||||
assert_parse_error("graph [ 1 ]")
|
||||
assert_parse_error("graph [ 1.E+2 ]")
|
||||
assert_parse_error('graph [ "A" ]')
|
||||
assert_parse_error("graph [ ] graph ]")
|
||||
assert_parse_error("graph [ ] graph [ ]")
|
||||
assert_parse_error("graph [ data [1, 2, 3] ]")
|
||||
assert_parse_error("graph [ node [ ] ]")
|
||||
assert_parse_error("graph [ node [ id 0 ] ]")
|
||||
nx.parse_gml('graph [ node [ id "a" ] ]', label="id")
|
||||
assert_parse_error("graph [ node [ id 0 label 0 ] node [ id 0 label 1 ] ]")
|
||||
assert_parse_error("graph [ node [ id 0 label 0 ] node [ id 1 label 0 ] ]")
|
||||
assert_parse_error("graph [ node [ id 0 label 0 ] edge [ ] ]")
|
||||
assert_parse_error("graph [ node [ id 0 label 0 ] edge [ source 0 ] ]")
|
||||
nx.parse_gml("graph [edge [ source 0 target 0 ] node [ id 0 label 0 ] ]")
|
||||
assert_parse_error("graph [ node [ id 0 label 0 ] edge [ source 1 target 0 ] ]")
|
||||
assert_parse_error("graph [ node [ id 0 label 0 ] edge [ source 0 target 1 ] ]")
|
||||
assert_parse_error(
|
||||
"graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
|
||||
"edge [ source 0 target 1 ] edge [ source 1 target 0 ] ]"
|
||||
)
|
||||
nx.parse_gml(
|
||||
"graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
|
||||
"edge [ source 0 target 1 ] edge [ source 1 target 0 ] "
|
||||
"directed 1 ]"
|
||||
)
|
||||
nx.parse_gml(
|
||||
"graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
|
||||
"edge [ source 0 target 1 ] edge [ source 0 target 1 ]"
|
||||
"multigraph 1 ]"
|
||||
)
|
||||
nx.parse_gml(
|
||||
"graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
|
||||
"edge [ source 0 target 1 key 0 ] edge [ source 0 target 1 ]"
|
||||
"multigraph 1 ]"
|
||||
)
|
||||
assert_parse_error(
|
||||
"graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
|
||||
"edge [ source 0 target 1 key 0 ] edge [ source 0 target 1 key 0 ]"
|
||||
"multigraph 1 ]"
|
||||
)
|
||||
nx.parse_gml(
|
||||
"graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
|
||||
"edge [ source 0 target 1 key 0 ] edge [ source 1 target 0 key 0 ]"
|
||||
"directed 1 multigraph 1 ]"
|
||||
)
|
||||
|
||||
# Tests for string convertible alphanumeric id and label values
|
||||
nx.parse_gml("graph [edge [ source a target a ] node [ id a label b ] ]")
|
||||
nx.parse_gml(
|
||||
"graph [ node [ id n42 label 0 ] node [ id x43 label 1 ]"
|
||||
"edge [ source n42 target x43 key 0 ]"
|
||||
"edge [ source x43 target n42 key 0 ]"
|
||||
"directed 1 multigraph 1 ]"
|
||||
)
|
||||
assert_parse_error(
|
||||
"graph [edge [ source u'u\4200' target u'u\4200' ] "
|
||||
+ "node [ id u'u\4200' label b ] ]"
|
||||
)
|
||||
|
||||
def assert_generate_error(*args, **kwargs):
|
||||
pytest.raises(
|
||||
nx.NetworkXError, lambda: list(nx.generate_gml(*args, **kwargs))
|
||||
)
|
||||
|
||||
G = nx.Graph()
|
||||
G.graph[3] = 3
|
||||
assert_generate_error(G)
|
||||
G = nx.Graph()
|
||||
G.graph["3"] = 3
|
||||
assert_generate_error(G)
|
||||
G = nx.Graph()
|
||||
G.graph["data"] = frozenset([1, 2, 3])
|
||||
assert_generate_error(G, stringizer=literal_stringizer)
|
||||
|
||||
def test_label_kwarg(self):
|
||||
G = nx.parse_gml(self.simple_data, label="id")
|
||||
assert sorted(G.nodes) == [1, 2, 3]
|
||||
labels = [G.nodes[n]["label"] for n in sorted(G.nodes)]
|
||||
assert labels == ["Node 1", "Node 2", "Node 3"]
|
||||
|
||||
G = nx.parse_gml(self.simple_data, label=None)
|
||||
assert sorted(G.nodes) == [1, 2, 3]
|
||||
labels = [G.nodes[n]["label"] for n in sorted(G.nodes)]
|
||||
assert labels == ["Node 1", "Node 2", "Node 3"]
|
||||
|
||||
def test_outofrange_integers(self):
|
||||
# GML restricts integers to 32 signed bits.
|
||||
# Check that we honor this restriction on export
|
||||
G = nx.Graph()
|
||||
# Test export for numbers that barely fit or don't fit into 32 bits,
|
||||
# and 3 numbers in the middle
|
||||
numbers = {
|
||||
"toosmall": (-(2**31)) - 1,
|
||||
"small": -(2**31),
|
||||
"med1": -4,
|
||||
"med2": 0,
|
||||
"med3": 17,
|
||||
"big": (2**31) - 1,
|
||||
"toobig": 2**31,
|
||||
}
|
||||
G.add_node("Node", **numbers)
|
||||
|
||||
fd, fname = tempfile.mkstemp()
|
||||
try:
|
||||
nx.write_gml(G, fname)
|
||||
# Check that the export wrote the nonfitting numbers as strings
|
||||
G2 = nx.read_gml(fname)
|
||||
for attr, value in G2.nodes["Node"].items():
|
||||
if attr == "toosmall" or attr == "toobig":
|
||||
assert type(value) == str
|
||||
else:
|
||||
assert type(value) == int
|
||||
finally:
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_multiline(self):
|
||||
# example from issue #6836
|
||||
multiline_example = """
|
||||
graph
|
||||
[
|
||||
node
|
||||
[
|
||||
id 0
|
||||
label "multiline node"
|
||||
label2 "multiline1
|
||||
multiline2
|
||||
multiline3"
|
||||
alt_name "id 0"
|
||||
]
|
||||
]
|
||||
"""
|
||||
G = nx.parse_gml(multiline_example)
|
||||
assert G.nodes["multiline node"] == {
|
||||
"label2": "multiline1 multiline2 multiline3",
|
||||
"alt_name": "id 0",
|
||||
}
|
||||
|
||||
|
||||
@contextmanager
|
||||
def byte_file():
|
||||
_file_handle = io.BytesIO()
|
||||
yield _file_handle
|
||||
_file_handle.seek(0)
|
||||
|
||||
|
||||
class TestPropertyLists:
|
||||
def test_writing_graph_with_multi_element_property_list(self):
|
||||
g = nx.Graph()
|
||||
g.add_node("n1", properties=["element", 0, 1, 2.5, True, False])
|
||||
with byte_file() as f:
|
||||
nx.write_gml(g, f)
|
||||
result = f.read().decode()
|
||||
|
||||
assert result == dedent(
|
||||
"""\
|
||||
graph [
|
||||
node [
|
||||
id 0
|
||||
label "n1"
|
||||
properties "element"
|
||||
properties 0
|
||||
properties 1
|
||||
properties 2.5
|
||||
properties 1
|
||||
properties 0
|
||||
]
|
||||
]
|
||||
"""
|
||||
)
|
||||
|
||||
def test_writing_graph_with_one_element_property_list(self):
|
||||
g = nx.Graph()
|
||||
g.add_node("n1", properties=["element"])
|
||||
with byte_file() as f:
|
||||
nx.write_gml(g, f)
|
||||
result = f.read().decode()
|
||||
|
||||
assert result == dedent(
|
||||
"""\
|
||||
graph [
|
||||
node [
|
||||
id 0
|
||||
label "n1"
|
||||
properties "_networkx_list_start"
|
||||
properties "element"
|
||||
]
|
||||
]
|
||||
"""
|
||||
)
|
||||
|
||||
def test_reading_graph_with_list_property(self):
|
||||
with byte_file() as f:
|
||||
f.write(
|
||||
dedent(
|
||||
"""
|
||||
graph [
|
||||
node [
|
||||
id 0
|
||||
label "n1"
|
||||
properties "element"
|
||||
properties 0
|
||||
properties 1
|
||||
properties 2.5
|
||||
]
|
||||
]
|
||||
"""
|
||||
).encode("ascii")
|
||||
)
|
||||
f.seek(0)
|
||||
graph = nx.read_gml(f)
|
||||
assert graph.nodes(data=True)["n1"] == {"properties": ["element", 0, 1, 2.5]}
|
||||
|
||||
def test_reading_graph_with_single_element_list_property(self):
|
||||
with byte_file() as f:
|
||||
f.write(
|
||||
dedent(
|
||||
"""
|
||||
graph [
|
||||
node [
|
||||
id 0
|
||||
label "n1"
|
||||
properties "_networkx_list_start"
|
||||
properties "element"
|
||||
]
|
||||
]
|
||||
"""
|
||||
).encode("ascii")
|
||||
)
|
||||
f.seek(0)
|
||||
graph = nx.read_gml(f)
|
||||
assert graph.nodes(data=True)["n1"] == {"properties": ["element"]}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("coll", ([], ()))
|
||||
def test_stringize_empty_list_tuple(coll):
|
||||
G = nx.path_graph(2)
|
||||
G.nodes[0]["test"] = coll # test serializing an empty collection
|
||||
f = io.BytesIO()
|
||||
nx.write_gml(G, f) # Smoke test - should not raise
|
||||
f.seek(0)
|
||||
H = nx.read_gml(f)
|
||||
assert H.nodes["0"]["test"] == coll # Check empty list round-trips properly
|
||||
# Check full round-tripping. Note that nodes are loaded as strings by
|
||||
# default, so there needs to be some remapping prior to comparison
|
||||
H = nx.relabel_nodes(H, {"0": 0, "1": 1})
|
||||
assert nx.utils.graphs_equal(G, H)
|
||||
# Same as above, but use destringizer for node remapping. Should have no
|
||||
# effect on node attr
|
||||
f.seek(0)
|
||||
H = nx.read_gml(f, destringizer=int)
|
||||
assert nx.utils.graphs_equal(G, H)
|
169
venv/Lib/site-packages/networkx/readwrite/tests/test_graph6.py
Normal file
169
venv/Lib/site-packages/networkx/readwrite/tests/test_graph6.py
Normal file
|
@ -0,0 +1,169 @@
|
|||
import tempfile
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
import networkx.readwrite.graph6 as g6
|
||||
from networkx.utils import edges_equal, nodes_equal
|
||||
|
||||
|
||||
class TestGraph6Utils:
|
||||
def test_n_data_n_conversion(self):
|
||||
for i in [0, 1, 42, 62, 63, 64, 258047, 258048, 7744773, 68719476735]:
|
||||
assert g6.data_to_n(g6.n_to_data(i))[0] == i
|
||||
assert g6.data_to_n(g6.n_to_data(i))[1] == []
|
||||
assert g6.data_to_n(g6.n_to_data(i) + [42, 43])[1] == [42, 43]
|
||||
|
||||
|
||||
class TestFromGraph6Bytes:
|
||||
def test_from_graph6_bytes(self):
|
||||
data = b"DF{"
|
||||
G = nx.from_graph6_bytes(data)
|
||||
assert nodes_equal(G.nodes(), [0, 1, 2, 3, 4])
|
||||
assert edges_equal(
|
||||
G.edges(), [(0, 3), (0, 4), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
|
||||
)
|
||||
|
||||
def test_read_equals_from_bytes(self):
|
||||
data = b"DF{"
|
||||
G = nx.from_graph6_bytes(data)
|
||||
fh = BytesIO(data)
|
||||
Gin = nx.read_graph6(fh)
|
||||
assert nodes_equal(G.nodes(), Gin.nodes())
|
||||
assert edges_equal(G.edges(), Gin.edges())
|
||||
|
||||
|
||||
class TestReadGraph6:
|
||||
def test_read_many_graph6(self):
|
||||
"""Test for reading many graphs from a file into a list."""
|
||||
data = b"DF{\nD`{\nDqK\nD~{\n"
|
||||
fh = BytesIO(data)
|
||||
glist = nx.read_graph6(fh)
|
||||
assert len(glist) == 4
|
||||
for G in glist:
|
||||
assert sorted(G) == list(range(5))
|
||||
|
||||
|
||||
class TestWriteGraph6:
|
||||
"""Unit tests for writing a graph to a file in graph6 format."""
|
||||
|
||||
def test_null_graph(self):
|
||||
result = BytesIO()
|
||||
nx.write_graph6(nx.null_graph(), result)
|
||||
assert result.getvalue() == b">>graph6<<?\n"
|
||||
|
||||
def test_trivial_graph(self):
|
||||
result = BytesIO()
|
||||
nx.write_graph6(nx.trivial_graph(), result)
|
||||
assert result.getvalue() == b">>graph6<<@\n"
|
||||
|
||||
def test_complete_graph(self):
|
||||
result = BytesIO()
|
||||
nx.write_graph6(nx.complete_graph(4), result)
|
||||
assert result.getvalue() == b">>graph6<<C~\n"
|
||||
|
||||
def test_large_complete_graph(self):
|
||||
result = BytesIO()
|
||||
nx.write_graph6(nx.complete_graph(67), result, header=False)
|
||||
assert result.getvalue() == b"~?@B" + b"~" * 368 + b"w\n"
|
||||
|
||||
def test_no_header(self):
|
||||
result = BytesIO()
|
||||
nx.write_graph6(nx.complete_graph(4), result, header=False)
|
||||
assert result.getvalue() == b"C~\n"
|
||||
|
||||
def test_complete_bipartite_graph(self):
|
||||
result = BytesIO()
|
||||
G = nx.complete_bipartite_graph(6, 9)
|
||||
nx.write_graph6(G, result, header=False)
|
||||
# The expected encoding here was verified by Sage.
|
||||
assert result.getvalue() == b"N??F~z{~Fw^_~?~?^_?\n"
|
||||
|
||||
@pytest.mark.parametrize("G", (nx.MultiGraph(), nx.DiGraph()))
|
||||
def test_no_directed_or_multi_graphs(self, G):
|
||||
with pytest.raises(nx.NetworkXNotImplemented):
|
||||
nx.write_graph6(G, BytesIO())
|
||||
|
||||
def test_length(self):
|
||||
for i in list(range(13)) + [31, 47, 62, 63, 64, 72]:
|
||||
g = nx.random_graphs.gnm_random_graph(i, i * i // 4, seed=i)
|
||||
gstr = BytesIO()
|
||||
nx.write_graph6(g, gstr, header=False)
|
||||
# Strip the trailing newline.
|
||||
gstr = gstr.getvalue().rstrip()
|
||||
assert len(gstr) == ((i - 1) * i // 2 + 5) // 6 + (1 if i < 63 else 4)
|
||||
|
||||
def test_roundtrip(self):
|
||||
for i in list(range(13)) + [31, 47, 62, 63, 64, 72]:
|
||||
G = nx.random_graphs.gnm_random_graph(i, i * i // 4, seed=i)
|
||||
f = BytesIO()
|
||||
nx.write_graph6(G, f)
|
||||
f.seek(0)
|
||||
H = nx.read_graph6(f)
|
||||
assert nodes_equal(G.nodes(), H.nodes())
|
||||
assert edges_equal(G.edges(), H.edges())
|
||||
|
||||
def test_write_path(self):
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
g6.write_graph6_file(nx.null_graph(), f)
|
||||
f.seek(0)
|
||||
assert f.read() == b">>graph6<<?\n"
|
||||
|
||||
@pytest.mark.parametrize("edge", ((0, 1), (1, 2), (1, 42)))
|
||||
def test_relabeling(self, edge):
|
||||
G = nx.Graph([edge])
|
||||
f = BytesIO()
|
||||
nx.write_graph6(G, f)
|
||||
f.seek(0)
|
||||
assert f.read() == b">>graph6<<A_\n"
|
||||
|
||||
|
||||
class TestToGraph6Bytes:
|
||||
def test_null_graph(self):
|
||||
G = nx.null_graph()
|
||||
assert g6.to_graph6_bytes(G) == b">>graph6<<?\n"
|
||||
|
||||
def test_trivial_graph(self):
|
||||
G = nx.trivial_graph()
|
||||
assert g6.to_graph6_bytes(G) == b">>graph6<<@\n"
|
||||
|
||||
def test_complete_graph(self):
|
||||
assert g6.to_graph6_bytes(nx.complete_graph(4)) == b">>graph6<<C~\n"
|
||||
|
||||
def test_large_complete_graph(self):
|
||||
G = nx.complete_graph(67)
|
||||
assert g6.to_graph6_bytes(G, header=False) == b"~?@B" + b"~" * 368 + b"w\n"
|
||||
|
||||
def test_no_header(self):
|
||||
G = nx.complete_graph(4)
|
||||
assert g6.to_graph6_bytes(G, header=False) == b"C~\n"
|
||||
|
||||
def test_complete_bipartite_graph(self):
|
||||
G = nx.complete_bipartite_graph(6, 9)
|
||||
assert g6.to_graph6_bytes(G, header=False) == b"N??F~z{~Fw^_~?~?^_?\n"
|
||||
|
||||
@pytest.mark.parametrize("G", (nx.MultiGraph(), nx.DiGraph()))
|
||||
def test_no_directed_or_multi_graphs(self, G):
|
||||
with pytest.raises(nx.NetworkXNotImplemented):
|
||||
g6.to_graph6_bytes(G)
|
||||
|
||||
def test_length(self):
|
||||
for i in list(range(13)) + [31, 47, 62, 63, 64, 72]:
|
||||
G = nx.random_graphs.gnm_random_graph(i, i * i // 4, seed=i)
|
||||
# Strip the trailing newline.
|
||||
gstr = g6.to_graph6_bytes(G, header=False).rstrip()
|
||||
assert len(gstr) == ((i - 1) * i // 2 + 5) // 6 + (1 if i < 63 else 4)
|
||||
|
||||
def test_roundtrip(self):
|
||||
for i in list(range(13)) + [31, 47, 62, 63, 64, 72]:
|
||||
G = nx.random_graphs.gnm_random_graph(i, i * i // 4, seed=i)
|
||||
data = g6.to_graph6_bytes(G)
|
||||
H = nx.from_graph6_bytes(data.rstrip())
|
||||
assert nodes_equal(G.nodes(), H.nodes())
|
||||
assert edges_equal(G.edges(), H.edges())
|
||||
|
||||
@pytest.mark.parametrize("edge", ((0, 1), (1, 2), (1, 42)))
|
||||
def test_relabeling(self, edge):
|
||||
G = nx.Graph([edge])
|
||||
assert g6.to_graph6_bytes(G) == b">>graph6<<A_\n"
|
1529
venv/Lib/site-packages/networkx/readwrite/tests/test_graphml.py
Normal file
1529
venv/Lib/site-packages/networkx/readwrite/tests/test_graphml.py
Normal file
File diff suppressed because it is too large
Load diff
30
venv/Lib/site-packages/networkx/readwrite/tests/test_leda.py
Normal file
30
venv/Lib/site-packages/networkx/readwrite/tests/test_leda.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import io
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
class TestLEDA:
|
||||
def test_parse_leda(self):
|
||||
data = """#header section \nLEDA.GRAPH \nstring\nint\n-1\n#nodes section\n5 \n|{v1}| \n|{v2}| \n|{v3}| \n|{v4}| \n|{v5}| \n\n#edges section\n7 \n1 2 0 |{4}| \n1 3 0 |{3}| \n2 3 0 |{2}| \n3 4 0 |{3}| \n3 5 0 |{7}| \n4 5 0 |{6}| \n5 1 0 |{foo}|"""
|
||||
G = nx.parse_leda(data)
|
||||
G = nx.parse_leda(data.split("\n"))
|
||||
assert sorted(G.nodes()) == ["v1", "v2", "v3", "v4", "v5"]
|
||||
assert sorted(G.edges(data=True)) == [
|
||||
("v1", "v2", {"label": "4"}),
|
||||
("v1", "v3", {"label": "3"}),
|
||||
("v2", "v3", {"label": "2"}),
|
||||
("v3", "v4", {"label": "3"}),
|
||||
("v3", "v5", {"label": "7"}),
|
||||
("v4", "v5", {"label": "6"}),
|
||||
("v5", "v1", {"label": "foo"}),
|
||||
]
|
||||
|
||||
def test_read_LEDA(self):
|
||||
fh = io.BytesIO()
|
||||
data = """#header section \nLEDA.GRAPH \nstring\nint\n-1\n#nodes section\n5 \n|{v1}| \n|{v2}| \n|{v3}| \n|{v4}| \n|{v5}| \n\n#edges section\n7 \n1 2 0 |{4}| \n1 3 0 |{3}| \n2 3 0 |{2}| \n3 4 0 |{3}| \n3 5 0 |{7}| \n4 5 0 |{6}| \n5 1 0 |{foo}|"""
|
||||
G = nx.parse_leda(data)
|
||||
fh.write(data.encode("UTF-8"))
|
||||
fh.seek(0)
|
||||
Gin = nx.read_leda(fh)
|
||||
assert sorted(G.nodes()) == sorted(Gin.nodes())
|
||||
assert sorted(G.edges()) == sorted(Gin.edges())
|
62
venv/Lib/site-packages/networkx/readwrite/tests/test_p2g.py
Normal file
62
venv/Lib/site-packages/networkx/readwrite/tests/test_p2g.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
import io
|
||||
|
||||
import networkx as nx
|
||||
from networkx.readwrite.p2g import read_p2g, write_p2g
|
||||
from networkx.utils import edges_equal
|
||||
|
||||
|
||||
class TestP2G:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.G = nx.Graph(name="test")
|
||||
e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")]
|
||||
cls.G.add_edges_from(e)
|
||||
cls.G.add_node("g")
|
||||
cls.DG = nx.DiGraph(cls.G)
|
||||
|
||||
def test_read_p2g(self):
|
||||
s = b"""\
|
||||
name
|
||||
3 4
|
||||
a
|
||||
1 2
|
||||
b
|
||||
|
||||
c
|
||||
0 2
|
||||
"""
|
||||
bytesIO = io.BytesIO(s)
|
||||
G = read_p2g(bytesIO)
|
||||
assert G.name == "name"
|
||||
assert sorted(G) == ["a", "b", "c"]
|
||||
edges = [(str(u), str(v)) for u, v in G.edges()]
|
||||
assert edges_equal(G.edges(), [("a", "c"), ("a", "b"), ("c", "a"), ("c", "c")])
|
||||
|
||||
def test_write_p2g(self):
|
||||
s = b"""foo
|
||||
3 2
|
||||
1
|
||||
1
|
||||
2
|
||||
2
|
||||
3
|
||||
|
||||
"""
|
||||
fh = io.BytesIO()
|
||||
G = nx.DiGraph()
|
||||
G.name = "foo"
|
||||
G.add_edges_from([(1, 2), (2, 3)])
|
||||
write_p2g(G, fh)
|
||||
fh.seek(0)
|
||||
r = fh.read()
|
||||
assert r == s
|
||||
|
||||
def test_write_read_p2g(self):
|
||||
fh = io.BytesIO()
|
||||
G = nx.DiGraph()
|
||||
G.name = "foo"
|
||||
G.add_edges_from([("a", "b"), ("b", "c")])
|
||||
write_p2g(G, fh)
|
||||
fh.seek(0)
|
||||
H = read_p2g(fh)
|
||||
assert edges_equal(G.edges(), H.edges())
|
130
venv/Lib/site-packages/networkx/readwrite/tests/test_pajek.py
Normal file
130
venv/Lib/site-packages/networkx/readwrite/tests/test_pajek.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
"""
|
||||
Pajek tests
|
||||
"""
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import edges_equal, nodes_equal
|
||||
|
||||
|
||||
class TestPajek:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.data = """*network Tralala\n*vertices 4\n 1 "A1" 0.0938 0.0896 ellipse x_fact 1 y_fact 1\n 2 "Bb" 0.8188 0.2458 ellipse x_fact 1 y_fact 1\n 3 "C" 0.3688 0.7792 ellipse x_fact 1\n 4 "D2" 0.9583 0.8563 ellipse x_fact 1\n*arcs\n1 1 1 h2 0 w 3 c Blue s 3 a1 -130 k1 0.6 a2 -130 k2 0.6 ap 0.5 l "Bezier loop" lc BlueViolet fos 20 lr 58 lp 0.3 la 360\n2 1 1 h2 0 a1 120 k1 1.3 a2 -120 k2 0.3 ap 25 l "Bezier arc" lphi 270 la 180 lr 19 lp 0.5\n1 2 1 h2 0 a1 40 k1 2.8 a2 30 k2 0.8 ap 25 l "Bezier arc" lphi 90 la 0 lp 0.65\n4 2 -1 h2 0 w 1 k1 -2 k2 250 ap 25 l "Circular arc" c Red lc OrangeRed\n3 4 1 p Dashed h2 0 w 2 c OliveGreen ap 25 l "Straight arc" lc PineGreen\n1 3 1 p Dashed h2 0 w 5 k1 -1 k2 -20 ap 25 l "Oval arc" c Brown lc Black\n3 3 -1 h1 6 w 1 h2 12 k1 -2 k2 -15 ap 0.5 l "Circular loop" c Red lc OrangeRed lphi 270 la 180"""
|
||||
cls.G = nx.MultiDiGraph()
|
||||
cls.G.add_nodes_from(["A1", "Bb", "C", "D2"])
|
||||
cls.G.add_edges_from(
|
||||
[
|
||||
("A1", "A1"),
|
||||
("A1", "Bb"),
|
||||
("A1", "C"),
|
||||
("Bb", "A1"),
|
||||
("C", "C"),
|
||||
("C", "D2"),
|
||||
("D2", "Bb"),
|
||||
]
|
||||
)
|
||||
|
||||
cls.G.graph["name"] = "Tralala"
|
||||
(fd, cls.fname) = tempfile.mkstemp()
|
||||
with os.fdopen(fd, "wb") as fh:
|
||||
fh.write(cls.data.encode("UTF-8"))
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
os.unlink(cls.fname)
|
||||
|
||||
def test_parse_pajek_simple(self):
|
||||
# Example without node positions or shape
|
||||
data = """*Vertices 2\n1 "1"\n2 "2"\n*Edges\n1 2\n2 1"""
|
||||
G = nx.parse_pajek(data)
|
||||
assert sorted(G.nodes()) == ["1", "2"]
|
||||
assert edges_equal(G.edges(), [("1", "2"), ("1", "2")])
|
||||
|
||||
def test_parse_pajek(self):
|
||||
G = nx.parse_pajek(self.data)
|
||||
assert sorted(G.nodes()) == ["A1", "Bb", "C", "D2"]
|
||||
assert edges_equal(
|
||||
G.edges(),
|
||||
[
|
||||
("A1", "A1"),
|
||||
("A1", "Bb"),
|
||||
("A1", "C"),
|
||||
("Bb", "A1"),
|
||||
("C", "C"),
|
||||
("C", "D2"),
|
||||
("D2", "Bb"),
|
||||
],
|
||||
)
|
||||
|
||||
def test_parse_pajet_mat(self):
|
||||
data = """*Vertices 3\n1 "one"\n2 "two"\n3 "three"\n*Matrix\n1 1 0\n0 1 0\n0 1 0\n"""
|
||||
G = nx.parse_pajek(data)
|
||||
assert set(G.nodes()) == {"one", "two", "three"}
|
||||
assert G.nodes["two"] == {"id": "2"}
|
||||
assert edges_equal(
|
||||
set(G.edges()),
|
||||
{("one", "one"), ("two", "one"), ("two", "two"), ("two", "three")},
|
||||
)
|
||||
|
||||
def test_read_pajek(self):
|
||||
G = nx.parse_pajek(self.data)
|
||||
Gin = nx.read_pajek(self.fname)
|
||||
assert sorted(G.nodes()) == sorted(Gin.nodes())
|
||||
assert edges_equal(G.edges(), Gin.edges())
|
||||
assert self.G.graph == Gin.graph
|
||||
for n in G:
|
||||
assert G.nodes[n] == Gin.nodes[n]
|
||||
|
||||
def test_write_pajek(self):
|
||||
import io
|
||||
|
||||
G = nx.parse_pajek(self.data)
|
||||
fh = io.BytesIO()
|
||||
nx.write_pajek(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_pajek(fh)
|
||||
assert nodes_equal(list(G), list(H))
|
||||
assert edges_equal(list(G.edges()), list(H.edges()))
|
||||
# Graph name is left out for now, therefore it is not tested.
|
||||
# assert_equal(G.graph, H.graph)
|
||||
|
||||
def test_ignored_attribute(self):
|
||||
import io
|
||||
|
||||
G = nx.Graph()
|
||||
fh = io.BytesIO()
|
||||
G.add_node(1, int_attr=1)
|
||||
G.add_node(2, empty_attr=" ")
|
||||
G.add_edge(1, 2, int_attr=2)
|
||||
G.add_edge(2, 3, empty_attr=" ")
|
||||
|
||||
import warnings
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
nx.write_pajek(G, fh)
|
||||
assert len(w) == 4
|
||||
|
||||
def test_noname(self):
|
||||
# Make sure we can parse a line such as: *network
|
||||
# Issue #952
|
||||
line = "*network\n"
|
||||
other_lines = self.data.split("\n")[1:]
|
||||
data = line + "\n".join(other_lines)
|
||||
G = nx.parse_pajek(data)
|
||||
|
||||
def test_unicode(self):
|
||||
import io
|
||||
|
||||
G = nx.Graph()
|
||||
name1 = chr(2344) + chr(123) + chr(6543)
|
||||
name2 = chr(5543) + chr(1543) + chr(324)
|
||||
G.add_edge(name1, "Radiohead", foo=name2)
|
||||
fh = io.BytesIO()
|
||||
nx.write_pajek(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.read_pajek(fh)
|
||||
assert nodes_equal(list(G), list(H))
|
||||
assert edges_equal(list(G.edges()), list(H.edges()))
|
||||
assert G.graph == H.graph
|
173
venv/Lib/site-packages/networkx/readwrite/tests/test_sparse6.py
Normal file
173
venv/Lib/site-packages/networkx/readwrite/tests/test_sparse6.py
Normal file
|
@ -0,0 +1,173 @@
|
|||
import tempfile
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import edges_equal, nodes_equal
|
||||
|
||||
|
||||
class TestSparseGraph6:
|
||||
def test_from_sparse6_bytes(self):
|
||||
data = b":Q___eDcdFcDeFcE`GaJ`IaHbKNbLM"
|
||||
G = nx.from_sparse6_bytes(data)
|
||||
assert nodes_equal(
|
||||
sorted(G.nodes()),
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
|
||||
)
|
||||
assert edges_equal(
|
||||
G.edges(),
|
||||
[
|
||||
(0, 1),
|
||||
(0, 2),
|
||||
(0, 3),
|
||||
(1, 12),
|
||||
(1, 14),
|
||||
(2, 13),
|
||||
(2, 15),
|
||||
(3, 16),
|
||||
(3, 17),
|
||||
(4, 7),
|
||||
(4, 9),
|
||||
(4, 11),
|
||||
(5, 6),
|
||||
(5, 8),
|
||||
(5, 9),
|
||||
(6, 10),
|
||||
(6, 11),
|
||||
(7, 8),
|
||||
(7, 10),
|
||||
(8, 12),
|
||||
(9, 15),
|
||||
(10, 14),
|
||||
(11, 13),
|
||||
(12, 16),
|
||||
(13, 17),
|
||||
(14, 17),
|
||||
(15, 16),
|
||||
],
|
||||
)
|
||||
|
||||
def test_from_bytes_multigraph_graph(self):
|
||||
graph_data = b":An"
|
||||
G = nx.from_sparse6_bytes(graph_data)
|
||||
assert type(G) == nx.Graph
|
||||
multigraph_data = b":Ab"
|
||||
M = nx.from_sparse6_bytes(multigraph_data)
|
||||
assert type(M) == nx.MultiGraph
|
||||
|
||||
def test_read_sparse6(self):
|
||||
data = b":Q___eDcdFcDeFcE`GaJ`IaHbKNbLM"
|
||||
G = nx.from_sparse6_bytes(data)
|
||||
fh = BytesIO(data)
|
||||
Gin = nx.read_sparse6(fh)
|
||||
assert nodes_equal(G.nodes(), Gin.nodes())
|
||||
assert edges_equal(G.edges(), Gin.edges())
|
||||
|
||||
def test_read_many_graph6(self):
|
||||
# Read many graphs into list
|
||||
data = b":Q___eDcdFcDeFcE`GaJ`IaHbKNbLM\n" b":Q___dCfDEdcEgcbEGbFIaJ`JaHN`IM"
|
||||
fh = BytesIO(data)
|
||||
glist = nx.read_sparse6(fh)
|
||||
assert len(glist) == 2
|
||||
for G in glist:
|
||||
assert nodes_equal(
|
||||
G.nodes(),
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
|
||||
)
|
||||
|
||||
|
||||
class TestWriteSparse6:
|
||||
"""Unit tests for writing graphs in the sparse6 format.
|
||||
|
||||
Most of the test cases were checked against the sparse6 encoder in Sage.
|
||||
|
||||
"""
|
||||
|
||||
def test_null_graph(self):
|
||||
G = nx.null_graph()
|
||||
result = BytesIO()
|
||||
nx.write_sparse6(G, result)
|
||||
assert result.getvalue() == b">>sparse6<<:?\n"
|
||||
|
||||
def test_trivial_graph(self):
|
||||
G = nx.trivial_graph()
|
||||
result = BytesIO()
|
||||
nx.write_sparse6(G, result)
|
||||
assert result.getvalue() == b">>sparse6<<:@\n"
|
||||
|
||||
def test_empty_graph(self):
|
||||
G = nx.empty_graph(5)
|
||||
result = BytesIO()
|
||||
nx.write_sparse6(G, result)
|
||||
assert result.getvalue() == b">>sparse6<<:D\n"
|
||||
|
||||
def test_large_empty_graph(self):
|
||||
G = nx.empty_graph(68)
|
||||
result = BytesIO()
|
||||
nx.write_sparse6(G, result)
|
||||
assert result.getvalue() == b">>sparse6<<:~?@C\n"
|
||||
|
||||
def test_very_large_empty_graph(self):
|
||||
G = nx.empty_graph(258049)
|
||||
result = BytesIO()
|
||||
nx.write_sparse6(G, result)
|
||||
assert result.getvalue() == b">>sparse6<<:~~???~?@\n"
|
||||
|
||||
def test_complete_graph(self):
|
||||
G = nx.complete_graph(4)
|
||||
result = BytesIO()
|
||||
nx.write_sparse6(G, result)
|
||||
assert result.getvalue() == b">>sparse6<<:CcKI\n"
|
||||
|
||||
def test_no_header(self):
|
||||
G = nx.complete_graph(4)
|
||||
result = BytesIO()
|
||||
nx.write_sparse6(G, result, header=False)
|
||||
assert result.getvalue() == b":CcKI\n"
|
||||
|
||||
def test_padding(self):
|
||||
codes = (b":Cdv", b":DaYn", b":EaYnN", b":FaYnL", b":GaYnLz")
|
||||
for n, code in enumerate(codes, start=4):
|
||||
G = nx.path_graph(n)
|
||||
result = BytesIO()
|
||||
nx.write_sparse6(G, result, header=False)
|
||||
assert result.getvalue() == code + b"\n"
|
||||
|
||||
def test_complete_bipartite(self):
|
||||
G = nx.complete_bipartite_graph(6, 9)
|
||||
result = BytesIO()
|
||||
nx.write_sparse6(G, result)
|
||||
# Compared with sage
|
||||
expected = b">>sparse6<<:Nk" + b"?G`cJ" * 9 + b"\n"
|
||||
assert result.getvalue() == expected
|
||||
|
||||
def test_read_write_inverse(self):
|
||||
for i in list(range(13)) + [31, 47, 62, 63, 64, 72]:
|
||||
m = min(2 * i, i * i // 2)
|
||||
g = nx.random_graphs.gnm_random_graph(i, m, seed=i)
|
||||
gstr = BytesIO()
|
||||
nx.write_sparse6(g, gstr, header=False)
|
||||
# Strip the trailing newline.
|
||||
gstr = gstr.getvalue().rstrip()
|
||||
g2 = nx.from_sparse6_bytes(gstr)
|
||||
assert g2.order() == g.order()
|
||||
assert edges_equal(g2.edges(), g.edges())
|
||||
|
||||
def test_no_directed_graphs(self):
|
||||
with pytest.raises(nx.NetworkXNotImplemented):
|
||||
nx.write_sparse6(nx.DiGraph(), BytesIO())
|
||||
|
||||
def test_write_path(self):
|
||||
# On Windows, we can't reopen a file that is open
|
||||
# So, for test we get a valid name from tempfile but close it.
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
fullfilename = f.name
|
||||
# file should be closed now, so write_sparse6 can open it
|
||||
nx.write_sparse6(nx.null_graph(), fullfilename)
|
||||
fh = open(fullfilename, mode="rb")
|
||||
assert fh.read() == b">>sparse6<<:?\n"
|
||||
fh.close()
|
||||
import os
|
||||
|
||||
os.remove(fullfilename)
|
1809
venv/Lib/site-packages/networkx/readwrite/tests/test_text.py
Normal file
1809
venv/Lib/site-packages/networkx/readwrite/tests/test_text.py
Normal file
File diff suppressed because it is too large
Load diff
950
venv/Lib/site-packages/networkx/readwrite/text.py
Normal file
950
venv/Lib/site-packages/networkx/readwrite/text.py
Normal file
|
@ -0,0 +1,950 @@
|
|||
"""
|
||||
Text-based visual representations of graphs
|
||||
"""
|
||||
import sys
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import open_file
|
||||
|
||||
__all__ = ["forest_str", "generate_network_text", "write_network_text"]
|
||||
|
||||
|
||||
class BaseGlyphs:
|
||||
@classmethod
|
||||
def as_dict(cls):
|
||||
return {
|
||||
a: getattr(cls, a)
|
||||
for a in dir(cls)
|
||||
if not a.startswith("_") and a != "as_dict"
|
||||
}
|
||||
|
||||
|
||||
class AsciiBaseGlyphs(BaseGlyphs):
|
||||
empty: str = "+"
|
||||
newtree_last: str = "+-- "
|
||||
newtree_mid: str = "+-- "
|
||||
endof_forest: str = " "
|
||||
within_forest: str = ": "
|
||||
within_tree: str = "| "
|
||||
|
||||
|
||||
class AsciiDirectedGlyphs(AsciiBaseGlyphs):
|
||||
last: str = "L-> "
|
||||
mid: str = "|-> "
|
||||
backedge: str = "<-"
|
||||
vertical_edge: str = "!"
|
||||
|
||||
|
||||
class AsciiUndirectedGlyphs(AsciiBaseGlyphs):
|
||||
last: str = "L-- "
|
||||
mid: str = "|-- "
|
||||
backedge: str = "-"
|
||||
vertical_edge: str = "|"
|
||||
|
||||
|
||||
class UtfBaseGlyphs(BaseGlyphs):
|
||||
# Notes on available box and arrow characters
|
||||
# https://en.wikipedia.org/wiki/Box-drawing_character
|
||||
# https://stackoverflow.com/questions/2701192/triangle-arrow
|
||||
empty: str = "╙"
|
||||
newtree_last: str = "╙── "
|
||||
newtree_mid: str = "╟── "
|
||||
endof_forest: str = " "
|
||||
within_forest: str = "╎ "
|
||||
within_tree: str = "│ "
|
||||
|
||||
|
||||
class UtfDirectedGlyphs(UtfBaseGlyphs):
|
||||
last: str = "└─╼ "
|
||||
mid: str = "├─╼ "
|
||||
backedge: str = "╾"
|
||||
vertical_edge: str = "╽"
|
||||
|
||||
|
||||
class UtfUndirectedGlyphs(UtfBaseGlyphs):
|
||||
last: str = "└── "
|
||||
mid: str = "├── "
|
||||
backedge: str = "─"
|
||||
vertical_edge: str = "│"
|
||||
|
||||
|
||||
def generate_network_text(
|
||||
graph,
|
||||
with_labels=True,
|
||||
sources=None,
|
||||
max_depth=None,
|
||||
ascii_only=False,
|
||||
vertical_chains=False,
|
||||
):
|
||||
"""Generate lines in the "network text" format
|
||||
|
||||
This works via a depth-first traversal of the graph and writing a line for
|
||||
each unique node encountered. Non-tree edges are written to the right of
|
||||
each node, and connection to a non-tree edge is indicated with an ellipsis.
|
||||
This representation works best when the input graph is a forest, but any
|
||||
graph can be represented.
|
||||
|
||||
This notation is original to networkx, although it is simple enough that it
|
||||
may be known in existing literature. See #5602 for details. The procedure
|
||||
is summarized as follows:
|
||||
|
||||
1. Given a set of source nodes (which can be specified, or automatically
|
||||
discovered via finding the (strongly) connected components and choosing one
|
||||
node with minimum degree from each), we traverse the graph in depth first
|
||||
order.
|
||||
|
||||
2. Each reachable node will be printed exactly once on it's own line.
|
||||
|
||||
3. Edges are indicated in one of four ways:
|
||||
|
||||
a. a parent "L-style" connection on the upper left. This corresponds to
|
||||
a traversal in the directed DFS tree.
|
||||
|
||||
b. a backref "<-style" connection shown directly on the right. For
|
||||
directed graphs, these are drawn for any incoming edges to a node that
|
||||
is not a parent edge. For undirected graphs, these are drawn for only
|
||||
the non-parent edges that have already been represented (The edges that
|
||||
have not been represented will be handled in the recursive case).
|
||||
|
||||
c. a child "L-style" connection on the lower right. Drawing of the
|
||||
children are handled recursively.
|
||||
|
||||
d. if ``vertical_chains`` is true, and a parent node only has one child
|
||||
a "vertical-style" edge is drawn between them.
|
||||
|
||||
4. The children of each node (wrt the directed DFS tree) are drawn
|
||||
underneath and to the right of it. In the case that a child node has already
|
||||
been drawn the connection is replaced with an ellipsis ("...") to indicate
|
||||
that there is one or more connections represented elsewhere.
|
||||
|
||||
5. If a maximum depth is specified, an edge to nodes past this maximum
|
||||
depth will be represented by an ellipsis.
|
||||
|
||||
6. If a a node has a truthy "collapse" value, then we do not traverse past
|
||||
that node.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
graph : nx.DiGraph | nx.Graph
|
||||
Graph to represent
|
||||
|
||||
with_labels : bool | str
|
||||
If True will use the "label" attribute of a node to display if it
|
||||
exists otherwise it will use the node value itself. If given as a
|
||||
string, then that attribute name will be used instead of "label".
|
||||
Defaults to True.
|
||||
|
||||
sources : List
|
||||
Specifies which nodes to start traversal from. Note: nodes that are not
|
||||
reachable from one of these sources may not be shown. If unspecified,
|
||||
the minimal set of nodes needed to reach all others will be used.
|
||||
|
||||
max_depth : int | None
|
||||
The maximum depth to traverse before stopping. Defaults to None.
|
||||
|
||||
ascii_only : Boolean
|
||||
If True only ASCII characters are used to construct the visualization
|
||||
|
||||
vertical_chains : Boolean
|
||||
If True, chains of nodes will be drawn vertically when possible.
|
||||
|
||||
Yields
|
||||
------
|
||||
str : a line of generated text
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> graph = nx.path_graph(10)
|
||||
>>> graph.add_node('A')
|
||||
>>> graph.add_node('B')
|
||||
>>> graph.add_node('C')
|
||||
>>> graph.add_node('D')
|
||||
>>> graph.add_edge(9, 'A')
|
||||
>>> graph.add_edge(9, 'B')
|
||||
>>> graph.add_edge(9, 'C')
|
||||
>>> graph.add_edge('C', 'D')
|
||||
>>> graph.add_edge('C', 'E')
|
||||
>>> graph.add_edge('C', 'F')
|
||||
>>> nx.write_network_text(graph)
|
||||
╙── 0
|
||||
└── 1
|
||||
└── 2
|
||||
└── 3
|
||||
└── 4
|
||||
└── 5
|
||||
└── 6
|
||||
└── 7
|
||||
└── 8
|
||||
└── 9
|
||||
├── A
|
||||
├── B
|
||||
└── C
|
||||
├── D
|
||||
├── E
|
||||
└── F
|
||||
>>> nx.write_network_text(graph, vertical_chains=True)
|
||||
╙── 0
|
||||
│
|
||||
1
|
||||
│
|
||||
2
|
||||
│
|
||||
3
|
||||
│
|
||||
4
|
||||
│
|
||||
5
|
||||
│
|
||||
6
|
||||
│
|
||||
7
|
||||
│
|
||||
8
|
||||
│
|
||||
9
|
||||
├── A
|
||||
├── B
|
||||
└── C
|
||||
├── D
|
||||
├── E
|
||||
└── F
|
||||
"""
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
class StackFrame(NamedTuple):
|
||||
parent: Any
|
||||
node: Any
|
||||
indents: list
|
||||
this_islast: bool
|
||||
this_vertical: bool
|
||||
|
||||
collapse_attr = "collapse"
|
||||
|
||||
is_directed = graph.is_directed()
|
||||
|
||||
if is_directed:
|
||||
glyphs = AsciiDirectedGlyphs if ascii_only else UtfDirectedGlyphs
|
||||
succ = graph.succ
|
||||
pred = graph.pred
|
||||
else:
|
||||
glyphs = AsciiUndirectedGlyphs if ascii_only else UtfUndirectedGlyphs
|
||||
succ = graph.adj
|
||||
pred = graph.adj
|
||||
|
||||
if isinstance(with_labels, str):
|
||||
label_attr = with_labels
|
||||
elif with_labels:
|
||||
label_attr = "label"
|
||||
else:
|
||||
label_attr = None
|
||||
|
||||
if max_depth == 0:
|
||||
yield glyphs.empty + " ..."
|
||||
elif len(graph.nodes) == 0:
|
||||
yield glyphs.empty
|
||||
else:
|
||||
# If the nodes to traverse are unspecified, find the minimal set of
|
||||
# nodes that will reach the entire graph
|
||||
if sources is None:
|
||||
sources = _find_sources(graph)
|
||||
|
||||
# Populate the stack with each:
|
||||
# 1. parent node in the DFS tree (or None for root nodes),
|
||||
# 2. the current node in the DFS tree
|
||||
# 2. a list of indentations indicating depth
|
||||
# 3. a flag indicating if the node is the final one to be written.
|
||||
# Reverse the stack so sources are popped in the correct order.
|
||||
last_idx = len(sources) - 1
|
||||
stack = [
|
||||
StackFrame(None, node, [], (idx == last_idx), False)
|
||||
for idx, node in enumerate(sources)
|
||||
][::-1]
|
||||
|
||||
num_skipped_children = defaultdict(lambda: 0)
|
||||
seen_nodes = set()
|
||||
while stack:
|
||||
parent, node, indents, this_islast, this_vertical = stack.pop()
|
||||
|
||||
if node is not Ellipsis:
|
||||
skip = node in seen_nodes
|
||||
if skip:
|
||||
# Mark that we skipped a parent's child
|
||||
num_skipped_children[parent] += 1
|
||||
|
||||
if this_islast:
|
||||
# If we reached the last child of a parent, and we skipped
|
||||
# any of that parents children, then we should emit an
|
||||
# ellipsis at the end after this.
|
||||
if num_skipped_children[parent] and parent is not None:
|
||||
# Append the ellipsis to be emitted last
|
||||
next_islast = True
|
||||
try_frame = StackFrame(
|
||||
node, Ellipsis, indents, next_islast, False
|
||||
)
|
||||
stack.append(try_frame)
|
||||
|
||||
# Redo this frame, but not as a last object
|
||||
next_islast = False
|
||||
try_frame = StackFrame(
|
||||
parent, node, indents, next_islast, this_vertical
|
||||
)
|
||||
stack.append(try_frame)
|
||||
continue
|
||||
|
||||
if skip:
|
||||
continue
|
||||
seen_nodes.add(node)
|
||||
|
||||
if not indents:
|
||||
# Top level items (i.e. trees in the forest) get different
|
||||
# glyphs to indicate they are not actually connected
|
||||
if this_islast:
|
||||
this_vertical = False
|
||||
this_prefix = indents + [glyphs.newtree_last]
|
||||
next_prefix = indents + [glyphs.endof_forest]
|
||||
else:
|
||||
this_prefix = indents + [glyphs.newtree_mid]
|
||||
next_prefix = indents + [glyphs.within_forest]
|
||||
|
||||
else:
|
||||
# Non-top-level items
|
||||
if this_vertical:
|
||||
this_prefix = indents
|
||||
next_prefix = indents
|
||||
else:
|
||||
if this_islast:
|
||||
this_prefix = indents + [glyphs.last]
|
||||
next_prefix = indents + [glyphs.endof_forest]
|
||||
else:
|
||||
this_prefix = indents + [glyphs.mid]
|
||||
next_prefix = indents + [glyphs.within_tree]
|
||||
|
||||
if node is Ellipsis:
|
||||
label = " ..."
|
||||
suffix = ""
|
||||
children = []
|
||||
else:
|
||||
if label_attr is not None:
|
||||
label = str(graph.nodes[node].get(label_attr, node))
|
||||
else:
|
||||
label = str(node)
|
||||
|
||||
# Determine if we want to show the children of this node.
|
||||
if collapse_attr is not None:
|
||||
collapse = graph.nodes[node].get(collapse_attr, False)
|
||||
else:
|
||||
collapse = False
|
||||
|
||||
# Determine:
|
||||
# (1) children to traverse into after showing this node.
|
||||
# (2) parents to immediately show to the right of this node.
|
||||
if is_directed:
|
||||
# In the directed case we must show every successor node
|
||||
# note: it may be skipped later, but we don't have that
|
||||
# information here.
|
||||
children = list(succ[node])
|
||||
# In the directed case we must show every predecessor
|
||||
# except for parent we directly traversed from.
|
||||
handled_parents = {parent}
|
||||
else:
|
||||
# Showing only the unseen children results in a more
|
||||
# concise representation for the undirected case.
|
||||
children = [
|
||||
child for child in succ[node] if child not in seen_nodes
|
||||
]
|
||||
|
||||
# In the undirected case, parents are also children, so we
|
||||
# only need to immediately show the ones we can no longer
|
||||
# traverse
|
||||
handled_parents = {*children, parent}
|
||||
|
||||
if max_depth is not None and len(indents) == max_depth - 1:
|
||||
# Use ellipsis to indicate we have reached maximum depth
|
||||
if children:
|
||||
children = [Ellipsis]
|
||||
handled_parents = {parent}
|
||||
|
||||
if collapse:
|
||||
# Collapsing a node is the same as reaching maximum depth
|
||||
if children:
|
||||
children = [Ellipsis]
|
||||
handled_parents = {parent}
|
||||
|
||||
# The other parents are other predecessors of this node that
|
||||
# are not handled elsewhere.
|
||||
other_parents = [p for p in pred[node] if p not in handled_parents]
|
||||
if other_parents:
|
||||
if label_attr is not None:
|
||||
other_parents_labels = ", ".join(
|
||||
[
|
||||
str(graph.nodes[p].get(label_attr, p))
|
||||
for p in other_parents
|
||||
]
|
||||
)
|
||||
else:
|
||||
other_parents_labels = ", ".join(
|
||||
[str(p) for p in other_parents]
|
||||
)
|
||||
suffix = " ".join(["", glyphs.backedge, other_parents_labels])
|
||||
else:
|
||||
suffix = ""
|
||||
|
||||
# Emit the line for this node, this will be called for each node
|
||||
# exactly once.
|
||||
if this_vertical:
|
||||
yield "".join(this_prefix + [glyphs.vertical_edge])
|
||||
|
||||
yield "".join(this_prefix + [label, suffix])
|
||||
|
||||
if vertical_chains:
|
||||
if is_directed:
|
||||
num_children = len(set(children))
|
||||
else:
|
||||
num_children = len(set(children) - {parent})
|
||||
# The next node can be drawn vertically if it is the only
|
||||
# remaining child of this node.
|
||||
next_is_vertical = num_children == 1
|
||||
else:
|
||||
next_is_vertical = False
|
||||
|
||||
# Push children on the stack in reverse order so they are popped in
|
||||
# the original order.
|
||||
for idx, child in enumerate(children[::-1]):
|
||||
next_islast = idx == 0
|
||||
try_frame = StackFrame(
|
||||
node, child, next_prefix, next_islast, next_is_vertical
|
||||
)
|
||||
stack.append(try_frame)
|
||||
|
||||
|
||||
@open_file(1, "w")
|
||||
def write_network_text(
|
||||
graph,
|
||||
path=None,
|
||||
with_labels=True,
|
||||
sources=None,
|
||||
max_depth=None,
|
||||
ascii_only=False,
|
||||
end="\n",
|
||||
vertical_chains=False,
|
||||
):
|
||||
"""Creates a nice text representation of a graph
|
||||
|
||||
This works via a depth-first traversal of the graph and writing a line for
|
||||
each unique node encountered. Non-tree edges are written to the right of
|
||||
each node, and connection to a non-tree edge is indicated with an ellipsis.
|
||||
This representation works best when the input graph is a forest, but any
|
||||
graph can be represented.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
graph : nx.DiGraph | nx.Graph
|
||||
Graph to represent
|
||||
|
||||
path : string or file or callable or None
|
||||
Filename or file handle for data output.
|
||||
if a function, then it will be called for each generated line.
|
||||
if None, this will default to "sys.stdout.write"
|
||||
|
||||
with_labels : bool | str
|
||||
If True will use the "label" attribute of a node to display if it
|
||||
exists otherwise it will use the node value itself. If given as a
|
||||
string, then that attribute name will be used instead of "label".
|
||||
Defaults to True.
|
||||
|
||||
sources : List
|
||||
Specifies which nodes to start traversal from. Note: nodes that are not
|
||||
reachable from one of these sources may not be shown. If unspecified,
|
||||
the minimal set of nodes needed to reach all others will be used.
|
||||
|
||||
max_depth : int | None
|
||||
The maximum depth to traverse before stopping. Defaults to None.
|
||||
|
||||
ascii_only : Boolean
|
||||
If True only ASCII characters are used to construct the visualization
|
||||
|
||||
end : string
|
||||
The line ending character
|
||||
|
||||
vertical_chains : Boolean
|
||||
If True, chains of nodes will be drawn vertically when possible.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> graph = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
|
||||
>>> nx.write_network_text(graph)
|
||||
╙── 0
|
||||
├─╼ 1
|
||||
│ ├─╼ 3
|
||||
│ └─╼ 4
|
||||
└─╼ 2
|
||||
├─╼ 5
|
||||
└─╼ 6
|
||||
|
||||
>>> # A near tree with one non-tree edge
|
||||
>>> graph.add_edge(5, 1)
|
||||
>>> nx.write_network_text(graph)
|
||||
╙── 0
|
||||
├─╼ 1 ╾ 5
|
||||
│ ├─╼ 3
|
||||
│ └─╼ 4
|
||||
└─╼ 2
|
||||
├─╼ 5
|
||||
│ └─╼ ...
|
||||
└─╼ 6
|
||||
|
||||
>>> graph = nx.cycle_graph(5)
|
||||
>>> nx.write_network_text(graph)
|
||||
╙── 0
|
||||
├── 1
|
||||
│ └── 2
|
||||
│ └── 3
|
||||
│ └── 4 ─ 0
|
||||
└── ...
|
||||
|
||||
>>> graph = nx.cycle_graph(5, nx.DiGraph)
|
||||
>>> nx.write_network_text(graph, vertical_chains=True)
|
||||
╙── 0 ╾ 4
|
||||
╽
|
||||
1
|
||||
╽
|
||||
2
|
||||
╽
|
||||
3
|
||||
╽
|
||||
4
|
||||
└─╼ ...
|
||||
|
||||
>>> nx.write_network_text(graph, vertical_chains=True, ascii_only=True)
|
||||
+-- 0 <- 4
|
||||
!
|
||||
1
|
||||
!
|
||||
2
|
||||
!
|
||||
3
|
||||
!
|
||||
4
|
||||
L-> ...
|
||||
|
||||
>>> graph = nx.generators.barbell_graph(4, 2)
|
||||
>>> nx.write_network_text(graph, vertical_chains=False)
|
||||
╙── 4
|
||||
├── 5
|
||||
│ └── 6
|
||||
│ ├── 7
|
||||
│ │ ├── 8 ─ 6
|
||||
│ │ │ └── 9 ─ 6, 7
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
└── 3
|
||||
├── 0
|
||||
│ ├── 1 ─ 3
|
||||
│ │ └── 2 ─ 0, 3
|
||||
│ └── ...
|
||||
└── ...
|
||||
>>> nx.write_network_text(graph, vertical_chains=True)
|
||||
╙── 4
|
||||
├── 5
|
||||
│ │
|
||||
│ 6
|
||||
│ ├── 7
|
||||
│ │ ├── 8 ─ 6
|
||||
│ │ │ │
|
||||
│ │ │ 9 ─ 6, 7
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
└── 3
|
||||
├── 0
|
||||
│ ├── 1 ─ 3
|
||||
│ │ │
|
||||
│ │ 2 ─ 0, 3
|
||||
│ └── ...
|
||||
└── ...
|
||||
|
||||
>>> graph = nx.complete_graph(5, create_using=nx.Graph)
|
||||
>>> nx.write_network_text(graph)
|
||||
╙── 0
|
||||
├── 1
|
||||
│ ├── 2 ─ 0
|
||||
│ │ ├── 3 ─ 0, 1
|
||||
│ │ │ └── 4 ─ 0, 1, 2
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
└── ...
|
||||
|
||||
>>> graph = nx.complete_graph(3, create_using=nx.DiGraph)
|
||||
>>> nx.write_network_text(graph)
|
||||
╙── 0 ╾ 1, 2
|
||||
├─╼ 1 ╾ 2
|
||||
│ ├─╼ 2 ╾ 0
|
||||
│ │ └─╼ ...
|
||||
│ └─╼ ...
|
||||
└─╼ ...
|
||||
"""
|
||||
if path is None:
|
||||
# The path is unspecified, write to stdout
|
||||
_write = sys.stdout.write
|
||||
elif hasattr(path, "write"):
|
||||
# The path is already an open file
|
||||
_write = path.write
|
||||
elif callable(path):
|
||||
# The path is a custom callable
|
||||
_write = path
|
||||
else:
|
||||
raise TypeError(type(path))
|
||||
|
||||
for line in generate_network_text(
|
||||
graph,
|
||||
with_labels=with_labels,
|
||||
sources=sources,
|
||||
max_depth=max_depth,
|
||||
ascii_only=ascii_only,
|
||||
vertical_chains=vertical_chains,
|
||||
):
|
||||
_write(line + end)
|
||||
|
||||
|
||||
def _find_sources(graph):
|
||||
"""
|
||||
Determine a minimal set of nodes such that the entire graph is reachable
|
||||
"""
|
||||
# For each connected part of the graph, choose at least
|
||||
# one node as a starting point, preferably without a parent
|
||||
if graph.is_directed():
|
||||
# Choose one node from each SCC with minimum in_degree
|
||||
sccs = list(nx.strongly_connected_components(graph))
|
||||
# condensing the SCCs forms a dag, the nodes in this graph with
|
||||
# 0 in-degree correspond to the SCCs from which the minimum set
|
||||
# of nodes from which all other nodes can be reached.
|
||||
scc_graph = nx.condensation(graph, sccs)
|
||||
supernode_to_nodes = {sn: [] for sn in scc_graph.nodes()}
|
||||
# Note: the order of mapping differs between pypy and cpython
|
||||
# so we have to loop over graph nodes for consistency
|
||||
mapping = scc_graph.graph["mapping"]
|
||||
for n in graph.nodes:
|
||||
sn = mapping[n]
|
||||
supernode_to_nodes[sn].append(n)
|
||||
sources = []
|
||||
for sn in scc_graph.nodes():
|
||||
if scc_graph.in_degree[sn] == 0:
|
||||
scc = supernode_to_nodes[sn]
|
||||
node = min(scc, key=lambda n: graph.in_degree[n])
|
||||
sources.append(node)
|
||||
else:
|
||||
# For undirected graph, the entire graph will be reachable as
|
||||
# long as we consider one node from every connected component
|
||||
sources = [
|
||||
min(cc, key=lambda n: graph.degree[n])
|
||||
for cc in nx.connected_components(graph)
|
||||
]
|
||||
sources = sorted(sources, key=lambda n: graph.degree[n])
|
||||
return sources
|
||||
|
||||
|
||||
def forest_str(graph, with_labels=True, sources=None, write=None, ascii_only=False):
|
||||
"""Creates a nice utf8 representation of a forest
|
||||
|
||||
This function has been superseded by
|
||||
:func:`nx.readwrite.text.generate_network_text`, which should be used
|
||||
instead.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
graph : nx.DiGraph | nx.Graph
|
||||
Graph to represent (must be a tree, forest, or the empty graph)
|
||||
|
||||
with_labels : bool
|
||||
If True will use the "label" attribute of a node to display if it
|
||||
exists otherwise it will use the node value itself. Defaults to True.
|
||||
|
||||
sources : List
|
||||
Mainly relevant for undirected forests, specifies which nodes to list
|
||||
first. If unspecified the root nodes of each tree will be used for
|
||||
directed forests; for undirected forests this defaults to the nodes
|
||||
with the smallest degree.
|
||||
|
||||
write : callable
|
||||
Function to use to write to, if None new lines are appended to
|
||||
a list and returned. If set to the `print` function, lines will
|
||||
be written to stdout as they are generated. If specified,
|
||||
this function will return None. Defaults to None.
|
||||
|
||||
ascii_only : Boolean
|
||||
If True only ASCII characters are used to construct the visualization
|
||||
|
||||
Returns
|
||||
-------
|
||||
str | None :
|
||||
utf8 representation of the tree / forest
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> graph = nx.balanced_tree(r=2, h=3, create_using=nx.DiGraph)
|
||||
>>> print(nx.forest_str(graph))
|
||||
╙── 0
|
||||
├─╼ 1
|
||||
│ ├─╼ 3
|
||||
│ │ ├─╼ 7
|
||||
│ │ └─╼ 8
|
||||
│ └─╼ 4
|
||||
│ ├─╼ 9
|
||||
│ └─╼ 10
|
||||
└─╼ 2
|
||||
├─╼ 5
|
||||
│ ├─╼ 11
|
||||
│ └─╼ 12
|
||||
└─╼ 6
|
||||
├─╼ 13
|
||||
└─╼ 14
|
||||
|
||||
|
||||
>>> graph = nx.balanced_tree(r=1, h=2, create_using=nx.Graph)
|
||||
>>> print(nx.forest_str(graph))
|
||||
╙── 0
|
||||
└── 1
|
||||
└── 2
|
||||
|
||||
>>> print(nx.forest_str(graph, ascii_only=True))
|
||||
+-- 0
|
||||
L-- 1
|
||||
L-- 2
|
||||
"""
|
||||
msg = (
|
||||
"\nforest_str is deprecated as of version 3.1 and will be removed "
|
||||
"in version 3.3. Use generate_network_text or write_network_text "
|
||||
"instead.\n"
|
||||
)
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
|
||||
if len(graph.nodes) > 0:
|
||||
if not nx.is_forest(graph):
|
||||
raise nx.NetworkXNotImplemented("input must be a forest or the empty graph")
|
||||
|
||||
printbuf = []
|
||||
if write is None:
|
||||
_write = printbuf.append
|
||||
else:
|
||||
_write = write
|
||||
|
||||
write_network_text(
|
||||
graph,
|
||||
_write,
|
||||
with_labels=with_labels,
|
||||
sources=sources,
|
||||
ascii_only=ascii_only,
|
||||
end="",
|
||||
)
|
||||
|
||||
if write is None:
|
||||
# Only return a string if the custom write function was not specified
|
||||
return "\n".join(printbuf)
|
||||
|
||||
|
||||
def _parse_network_text(lines):
|
||||
"""Reconstructs a graph from a network text representation.
|
||||
|
||||
This is mainly used for testing. Network text is for display, not
|
||||
serialization, as such this cannot parse all network text representations
|
||||
because node labels can be ambiguous with the glyphs and indentation used
|
||||
to represent edge structure. Additionally, there is no way to determine if
|
||||
disconnected graphs were originally directed or undirected.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lines : list or iterator of strings
|
||||
Input data in network text format
|
||||
|
||||
Returns
|
||||
-------
|
||||
G: NetworkX graph
|
||||
The graph corresponding to the lines in network text format.
|
||||
"""
|
||||
from itertools import chain
|
||||
from typing import Any, NamedTuple, Union
|
||||
|
||||
class ParseStackFrame(NamedTuple):
|
||||
node: Any
|
||||
indent: int
|
||||
has_vertical_child: Union[int, None]
|
||||
|
||||
initial_line_iter = iter(lines)
|
||||
|
||||
is_ascii = None
|
||||
is_directed = None
|
||||
|
||||
##############
|
||||
# Initial Pass
|
||||
##############
|
||||
|
||||
# Do an initial pass over the lines to determine what type of graph it is.
|
||||
# Remember what these lines were, so we can reiterate over them in the
|
||||
# parsing pass.
|
||||
initial_lines = []
|
||||
try:
|
||||
first_line = next(initial_line_iter)
|
||||
except StopIteration:
|
||||
...
|
||||
else:
|
||||
initial_lines.append(first_line)
|
||||
# The first character indicates if it is an ASCII or UTF graph
|
||||
first_char = first_line[0]
|
||||
if first_char in {
|
||||
UtfBaseGlyphs.empty,
|
||||
UtfBaseGlyphs.newtree_mid[0],
|
||||
UtfBaseGlyphs.newtree_last[0],
|
||||
}:
|
||||
is_ascii = False
|
||||
elif first_char in {
|
||||
AsciiBaseGlyphs.empty,
|
||||
AsciiBaseGlyphs.newtree_mid[0],
|
||||
AsciiBaseGlyphs.newtree_last[0],
|
||||
}:
|
||||
is_ascii = True
|
||||
else:
|
||||
raise AssertionError(f"Unexpected first character: {first_char}")
|
||||
|
||||
if is_ascii:
|
||||
directed_glyphs = AsciiDirectedGlyphs.as_dict()
|
||||
undirected_glyphs = AsciiUndirectedGlyphs.as_dict()
|
||||
else:
|
||||
directed_glyphs = UtfDirectedGlyphs.as_dict()
|
||||
undirected_glyphs = UtfUndirectedGlyphs.as_dict()
|
||||
|
||||
# For both directed / undirected glyphs, determine which glyphs never
|
||||
# appear as substrings in the other undirected / directed glyphs. Glyphs
|
||||
# with this property unambiguously indicates if a graph is directed /
|
||||
# undirected.
|
||||
directed_items = set(directed_glyphs.values())
|
||||
undirected_items = set(undirected_glyphs.values())
|
||||
unambiguous_directed_items = []
|
||||
for item in directed_items:
|
||||
other_items = undirected_items
|
||||
other_supersets = [other for other in other_items if item in other]
|
||||
if not other_supersets:
|
||||
unambiguous_directed_items.append(item)
|
||||
unambiguous_undirected_items = []
|
||||
for item in undirected_items:
|
||||
other_items = directed_items
|
||||
other_supersets = [other for other in other_items if item in other]
|
||||
if not other_supersets:
|
||||
unambiguous_undirected_items.append(item)
|
||||
|
||||
for line in initial_line_iter:
|
||||
initial_lines.append(line)
|
||||
if any(item in line for item in unambiguous_undirected_items):
|
||||
is_directed = False
|
||||
break
|
||||
elif any(item in line for item in unambiguous_directed_items):
|
||||
is_directed = True
|
||||
break
|
||||
|
||||
if is_directed is None:
|
||||
# Not enough information to determine, choose undirected by default
|
||||
is_directed = False
|
||||
|
||||
glyphs = directed_glyphs if is_directed else undirected_glyphs
|
||||
|
||||
# the backedge symbol by itself can be ambiguous, but with spaces around it
|
||||
# becomes unambiguous.
|
||||
backedge_symbol = " " + glyphs["backedge"] + " "
|
||||
|
||||
# Reconstruct an iterator over all of the lines.
|
||||
parsing_line_iter = chain(initial_lines, initial_line_iter)
|
||||
|
||||
##############
|
||||
# Parsing Pass
|
||||
##############
|
||||
|
||||
edges = []
|
||||
nodes = []
|
||||
is_empty = None
|
||||
|
||||
noparent = object() # sentinel value
|
||||
|
||||
# keep a stack of previous nodes that could be parents of subsequent nodes
|
||||
stack = [ParseStackFrame(noparent, -1, None)]
|
||||
|
||||
for line in parsing_line_iter:
|
||||
if line == glyphs["empty"]:
|
||||
# If the line is the empty glyph, we are done.
|
||||
# There shouldn't be anything else after this.
|
||||
is_empty = True
|
||||
continue
|
||||
|
||||
if backedge_symbol in line:
|
||||
# This line has one or more backedges, separate those out
|
||||
node_part, backedge_part = line.split(backedge_symbol)
|
||||
backedge_nodes = [u.strip() for u in backedge_part.split(", ")]
|
||||
# Now the node can be parsed
|
||||
node_part = node_part.rstrip()
|
||||
prefix, node = node_part.rsplit(" ", 1)
|
||||
node = node.strip()
|
||||
# Add the backedges to the edge list
|
||||
edges.extend([(u, node) for u in backedge_nodes])
|
||||
else:
|
||||
# No backedge, the tail of this line is the node
|
||||
prefix, node = line.rsplit(" ", 1)
|
||||
node = node.strip()
|
||||
|
||||
prev = stack.pop()
|
||||
|
||||
if node in glyphs["vertical_edge"]:
|
||||
# Previous node is still the previous node, but we know it will
|
||||
# have exactly one child, which will need to have its nesting level
|
||||
# adjusted.
|
||||
modified_prev = ParseStackFrame(
|
||||
prev.node,
|
||||
prev.indent,
|
||||
True,
|
||||
)
|
||||
stack.append(modified_prev)
|
||||
continue
|
||||
|
||||
# The length of the string before the node characters give us a hint
|
||||
# about our nesting level. The only case where this doesn't work is
|
||||
# when there are vertical chains, which is handled explicitly.
|
||||
indent = len(prefix)
|
||||
curr = ParseStackFrame(node, indent, None)
|
||||
|
||||
if prev.has_vertical_child:
|
||||
# In this case we know prev must be the parent of our current line,
|
||||
# so we don't have to search the stack. (which is good because the
|
||||
# indentation check wouldn't work in this case).
|
||||
...
|
||||
else:
|
||||
# If the previous node nesting-level is greater than the current
|
||||
# nodes nesting-level than the previous node was the end of a path,
|
||||
# and is not our parent. We can safely pop nodes off the stack
|
||||
# until we find one with a comparable nesting-level, which is our
|
||||
# parent.
|
||||
while curr.indent <= prev.indent:
|
||||
prev = stack.pop()
|
||||
|
||||
if node == "...":
|
||||
# The current previous node is no longer a valid parent,
|
||||
# keep it popped from the stack.
|
||||
stack.append(prev)
|
||||
else:
|
||||
# The previous and current nodes may still be parents, so add them
|
||||
# back onto the stack.
|
||||
stack.append(prev)
|
||||
stack.append(curr)
|
||||
|
||||
# Add the node and the edge to its parent to the node / edge lists.
|
||||
nodes.append(curr.node)
|
||||
if prev.node is not noparent:
|
||||
edges.append((prev.node, curr.node))
|
||||
|
||||
if is_empty:
|
||||
# Sanity check
|
||||
assert len(nodes) == 0
|
||||
|
||||
# Reconstruct the graph
|
||||
cls = nx.DiGraph if is_directed else nx.Graph
|
||||
new = cls()
|
||||
new.add_nodes_from(nodes)
|
||||
new.add_edges_from(edges)
|
||||
return new
|
Loading…
Add table
Add a link
Reference in a new issue