Fixed database typo and removed unnecessary class identifier.

This commit is contained in:
Batuhan Berk Başoğlu 2020-10-14 10:10:37 -04:00
parent 00ad49a143
commit 45fb349a7d
5098 changed files with 952558 additions and 85 deletions

View 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.gpickle import *
from networkx.readwrite.pajek import *
from networkx.readwrite.leda import *
from networkx.readwrite.sparse6 import *
from networkx.readwrite.graph6 import *
from networkx.readwrite.nx_yaml import *
from networkx.readwrite.gml import *
from networkx.readwrite.graphml import *
from networkx.readwrite.gexf import *
from networkx.readwrite.nx_shp import *
from networkx.readwrite.json_graph import *

View file

@ -0,0 +1,296 @@
"""
**************
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"]
from networkx.utils import open_file
import networkx as nx
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
"""
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
-----
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))
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 e:
raise TypeError(
f"Failed to convert node ({u}) to type " f"{nodetype}"
) from e
G.add_node(u)
if nodetype is not None:
try:
vlist = list(map(nodetype, vlist))
except BaseException as e:
raise TypeError(
f"Failed to convert nodes ({','.join(vlist)}) "
f"to type {nodetype}"
) from e
G.add_edges_from([(u, v) for v in vlist])
return G
@open_file(0, mode="rb")
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,
)

View file

@ -0,0 +1,483 @@
"""
**********
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",
]
from networkx.utils import open_file
import networkx as nx
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))
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 `'#'`
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:
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 e:
raise TypeError(
f"Failed to convert nodes {u},{v} to type {nodetype}."
) from e
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 e:
raise TypeError(
f"Failed to convert edge data ({d}) to dictionary."
) from e
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 e:
raise TypeError(
f"Failed to convert {edge_key} data {edge_value} "
f"to type {edge_type}."
) from e
edgedata.update({edge_key: edge_value})
G.add_edge(u, v, **edgedata)
return G
@open_file(0, mode="rb")
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.
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,
)
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,
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,824 @@
"""
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
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For other data types, you need to explicitly supply a
`stringizer`/`destringizer`.
For additional documentation on the GML file format, please see the
`GML website <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
Several example graphs in GML format may be found on Mark Newman's
`Network data page <http://www-personal.umich.edu/~mejn/netdata/>`_.
"""
from io import StringIO
from ast import literal_eval
from collections import defaultdict
from enum import Enum
from typing import Any, NamedTuple
import networkx as nx
from networkx.exception import NetworkXError
from networkx.utils import open_file
import warnings
import re
import html.entities as htmlentitydefs
__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.
"""
msg = "literal_destringizer is deprecated and will be removed in 3.0."
warnings.warn(msg, DeprecationWarning)
if isinstance(rep, str):
orig_rep = rep
try:
return literal_eval(rep)
except SyntaxError as e:
raise ValueError(f"{orig_rep!r} is not a valid Python literal") from e
else:
raise ValueError(f"{rep!r} is not a string")
@open_file(0, mode="rb")
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
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
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For other data types, you need to explicitly supply a
`stringizer`/`destringizer`.
For additional documentation on the GML file format, please see the
`GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
See the module docstring :mod:`networkx.readwrite.gml` for more details.
Examples
--------
>>> G = nx.path_graph(4)
>>> nx.write_gml(G, "test.gml")
>>> H = nx.read_gml("test.gml")
"""
def filter_lines(lines):
for line in lines:
try:
line = line.decode("ascii")
except UnicodeDecodeError as e:
raise NetworkXError("input is not ASCII-encoded") from e
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
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
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For other data types, you need to explicitly supply a
`stringizer`/`destringizer`.
For additional documentation on the GML file format, please see the
`GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
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 e:
raise NetworkXError("input is not ASCII-encoded") from e
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]*)(?:[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
for line in lines:
length = len(line)
pos = 0
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
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"
+ " convertable ASCII value for node id or label"
)
unexpected(curr_token, msg)
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 e:
raise NetworkXError(f"{category} #{i} has no '{attr}' attribute") from e
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
-----
`literal_stringizer` is largely the same as `repr` in terms of
functionality but attempts prefix `unicode` and `bytes` literals with
`u` and `b` to provide better interoperability of data generated by
Python 2 and Python 3.
The original value can be recovered using the
:func:`networkx.readwrite.gml.literal_destringizer` function.
"""
msg = "literal_stringizer is deprecated and will be removed in 3.0."
warnings.warn(msg, DeprecationWarning)
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 = "{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.
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
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For other data types, you need to explicitly supply a
`stringizer`/`destringizer`.
For additional documentation on the GML file format, please see the
`GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
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.OrderedMultiGraph([("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 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, (list, tuple))
and key != "label"
and value
and not in_list
):
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 e:
raise NetworkXError(
f"{value!r} cannot be converted into a string"
) from e
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
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
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For other data types, 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 <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
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"))

View file

@ -0,0 +1,90 @@
"""
**************
Pickled Graphs
**************
Read and write NetworkX graphs as Python pickles.
"The pickle module implements a fundamental, but powerful algorithm
for serializing and de-serializing a Python object
structure. "Pickling" is the process whereby a Python object hierarchy
is converted into a byte stream, and "unpickling" is the inverse
operation, whereby a byte stream is converted back into an object
hierarchy."
Note that NetworkX graphs can contain any hashable Python object as
node (not just integers and strings). For arbitrary data types it may
be difficult to represent the data as text. In that case using Python
pickles to store the graph data can be used.
Format
------
See https://docs.python.org/3/library/pickle.html
"""
__all__ = ["read_gpickle", "write_gpickle"]
from networkx.utils import open_file
import pickle
@open_file(1, mode="wb")
def write_gpickle(G, path, protocol=pickle.HIGHEST_PROTOCOL):
"""Write graph in Python pickle format.
Pickles are a serialized byte stream of a Python object [1]_.
This format will preserve Python objects used as nodes or edges.
Parameters
----------
G : graph
A NetworkX graph
path : file or string
File or filename to write.
Filenames ending in .gz or .bz2 will be compressed.
protocol : integer
Pickling protocol to use. Default value: ``pickle.HIGHEST_PROTOCOL``.
Examples
--------
>>> G = nx.path_graph(4)
>>> nx.write_gpickle(G, "test.gpickle")
References
----------
.. [1] https://docs.python.org/3/library/pickle.html
"""
pickle.dump(G, path, protocol)
@open_file(0, mode="rb")
def read_gpickle(path):
"""Read graph object in Python pickle format.
Pickles are a serialized byte stream of a Python object [1]_.
This format will preserve Python objects used as nodes or edges.
Parameters
----------
path : file or string
File or filename to write.
Filenames ending in .gz or .bz2 will be uncompressed.
Returns
-------
G : graph
A NetworkX graph
Examples
--------
>>> G = nx.path_graph(4)
>>> nx.write_gpickle(G, "test.gpickle")
>>> G = nx.read_gpickle("test.gpickle")
References
----------
.. [1] https://docs.python.org/3/library/pickle.html
"""
return pickle.load(path)

View file

@ -0,0 +1,412 @@
# 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 open_file, not_implemented_for
__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"
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
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")
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() 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() 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,
]

View file

@ -0,0 +1,958 @@
"""
*******
GraphML
*******
Read and write graphs in GraphML format.
This implementation does not support mixed graphs (directed and unidirected
edges together), hyperedges, nested graphs, or ports.
"GraphML is a comprehensive and easy-to-use file format for graphs. It
consists of a language core to describe the structural properties of a
graph and a flexible extension mechanism to add application-specific
data. Its main features include support of
* directed, undirected, and mixed graphs,
* hypergraphs,
* hierarchical graphs,
* graphical representations,
* references to external data,
* application-specific attribute data, and
* light-weight parsers.
Unlike many other file formats for graphs, GraphML does not use a
custom syntax. Instead, it is based on XML and hence ideally suited as
a common denominator for all kinds of services generating, archiving,
or processing graphs."
http://graphml.graphdrawing.org/
Format
------
GraphML is an XML format. See
http://graphml.graphdrawing.org/specification.html for the specification and
http://graphml.graphdrawing.org/primer/graphml-primer.html
for examples.
"""
import warnings
from collections import defaultdict
from xml.etree.ElementTree import Element, ElementTree, tostring, fromstring
try:
import lxml.etree as lxmletree
except ImportError:
lxmletree = None
import networkx as nx
from networkx.utils import open_file
__all__ = [
"write_graphml",
"read_graphml",
"generate_graphml",
"write_graphml_xml",
"write_graphml_lxml",
"parse_graphml",
"GraphMLWriter",
"GraphMLReader",
]
@open_file(1, mode="wb")
def write_graphml_xml(
G,
path,
encoding="utf-8",
prettyprint=True,
infer_numeric_types=False,
named_key_ids=False,
):
"""Write G in GraphML XML 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.
encoding : string (optional)
Encoding for text data.
prettyprint : bool (optional)
If True use line breaks and indenting in output XML.
infer_numeric_types : boolean
Determine if numeric types should be generalized.
For example, if edges have both int and float 'weight' attributes,
we infer in GraphML that both are floats.
named_key_ids : bool (optional)
If True use attr.name as value for key elements' id attribute.
Examples
--------
>>> G = nx.path_graph(4)
>>> nx.write_graphml(G, "test.graphml")
Notes
-----
This implementation does not support mixed graphs (directed
and unidirected edges together) hyperedges, nested graphs, or ports.
"""
writer = GraphMLWriter(
encoding=encoding,
prettyprint=prettyprint,
infer_numeric_types=infer_numeric_types,
named_key_ids=named_key_ids,
)
writer.add_graph_element(G)
writer.dump(path)
@open_file(1, mode="wb")
def write_graphml_lxml(
G,
path,
encoding="utf-8",
prettyprint=True,
infer_numeric_types=False,
named_key_ids=False,
):
"""Write G in GraphML XML format to path
This function uses the LXML framework and should be faster than
the version using the xml library.
Parameters
----------
G : graph
A networkx graph
path : file or string
File or filename to write.
Filenames ending in .gz or .bz2 will be compressed.
encoding : string (optional)
Encoding for text data.
prettyprint : bool (optional)
If True use line breaks and indenting in output XML.
infer_numeric_types : boolean
Determine if numeric types should be generalized.
For example, if edges have both int and float 'weight' attributes,
we infer in GraphML that both are floats.
named_key_ids : bool (optional)
If True use attr.name as value for key elements' id attribute.
Examples
--------
>>> G = nx.path_graph(4)
>>> nx.write_graphml_lxml(G, "fourpath.graphml") # doctest: +SKIP
Notes
-----
This implementation does not support mixed graphs (directed
and unidirected edges together) hyperedges, nested graphs, or ports.
"""
writer = GraphMLWriterLxml(
path,
graph=G,
encoding=encoding,
prettyprint=prettyprint,
infer_numeric_types=infer_numeric_types,
named_key_ids=named_key_ids,
)
writer.dump()
def generate_graphml(G, encoding="utf-8", prettyprint=True, named_key_ids=False):
"""Generate GraphML lines for G
Parameters
----------
G : graph
A networkx graph
encoding : string (optional)
Encoding for text data.
prettyprint : bool (optional)
If True use line breaks and indenting in output XML.
named_key_ids : bool (optional)
If True use attr.name as value for key elements' id attribute.
Examples
--------
>>> G = nx.path_graph(4)
>>> linefeed = chr(10) # linefeed = \n
>>> s = linefeed.join(nx.generate_graphml(G)) # doctest: +SKIP
>>> for line in nx.generate_graphml(G): # doctest: +SKIP
... print(line)
Notes
-----
This implementation does not support mixed graphs (directed and unidirected
edges together) hyperedges, nested graphs, or ports.
"""
writer = GraphMLWriter(
encoding=encoding, prettyprint=prettyprint, named_key_ids=named_key_ids
)
writer.add_graph_element(G)
yield from str(writer).splitlines()
@open_file(0, mode="rb")
def read_graphml(path, node_type=str, edge_key_type=int, force_multigraph=False):
"""Read graph in GraphML format from path.
Parameters
----------
path : file or string
File or filename to write.
Filenames ending in .gz or .bz2 will be compressed.
node_type: Python type (default: str)
Convert node ids to this type
edge_key_type: Python type (default: int)
Convert graphml edge ids to this type. Multigraphs use id as edge key.
Non-multigraphs add to edge attribute dict with name "id".
force_multigraph : bool (default: False)
If True, return a multigraph with edge keys. If False (the default)
return a multigraph when multiedges are in the graph.
Returns
-------
graph: NetworkX graph
If parallel edges are present or `force_multigraph=True` then
a MultiGraph or MultiDiGraph is returned. Otherwise a Graph/DiGraph.
The returned graph is directed if the file indicates it should be.
Notes
-----
Default node and edge attributes are not propagated to each node and edge.
They can be obtained from `G.graph` and applied to node and edge attributes
if desired using something like this:
>>> default_color = G.graph["node_default"]["color"] # doctest: +SKIP
>>> for node, data in G.nodes(data=True): # doctest: +SKIP
... if "color" not in data:
... data["color"] = default_color
>>> default_color = G.graph["edge_default"]["color"] # doctest: +SKIP
>>> for u, v, data in G.edges(data=True): # doctest: +SKIP
... if "color" not in data:
... data["color"] = default_color
This implementation does not support mixed graphs (directed and unidirected
edges together), hypergraphs, nested graphs, or ports.
For multigraphs the GraphML edge "id" will be used as the edge
key. If not specified then they "key" attribute will be used. If
there is no "key" attribute a default NetworkX multigraph edge key
will be provided.
Files with the yEd "yfiles" extension will can be read but the graphics
information is discarded.
yEd compressed files ("file.graphmlz" extension) can be read by renaming
the file to "file.graphml.gz".
"""
reader = GraphMLReader(node_type, edge_key_type, force_multigraph)
# need to check for multiple graphs
glist = list(reader(path=path))
if len(glist) == 0:
# If no graph comes back, try looking for an incomplete header
header = b'<graphml xmlns="http://graphml.graphdrawing.org/xmlns">'
path.seek(0)
old_bytes = path.read()
new_bytes = old_bytes.replace(b"<graphml>", header)
glist = list(reader(string=new_bytes))
if len(glist) == 0:
raise nx.NetworkXError("file not successfully read as graphml")
return glist[0]
def parse_graphml(
graphml_string, node_type=str, edge_key_type=int, force_multigraph=False
):
"""Read graph in GraphML format from string.
Parameters
----------
graphml_string : string
String containing graphml information
(e.g., contents of a graphml file).
node_type: Python type (default: str)
Convert node ids to this type
edge_key_type: Python type (default: int)
Convert graphml edge ids to this type. Multigraphs use id as edge key.
Non-multigraphs add to edge attribute dict with name "id".
force_multigraph : bool (default: False)
If True, return a multigraph with edge keys. If False (the default)
return a multigraph when multiedges are in the graph.
Returns
-------
graph: NetworkX graph
If no parallel edges are found a Graph or DiGraph is returned.
Otherwise a MultiGraph or MultiDiGraph is returned.
Examples
--------
>>> G = nx.path_graph(4)
>>> linefeed = chr(10) # linefeed = \n
>>> s = linefeed.join(nx.generate_graphml(G))
>>> H = nx.parse_graphml(s)
Notes
-----
Default node and edge attributes are not propagated to each node and edge.
They can be obtained from `G.graph` and applied to node and edge attributes
if desired using something like this:
>>> default_color = G.graph["node_default"]["color"] # doctest: +SKIP
>>> for node, data in G.nodes(data=True): # doctest: +SKIP
... if "color" not in data:
... data["color"] = default_color
>>> default_color = G.graph["edge_default"]["color"] # doctest: +SKIP
>>> for u, v, data in G.edges(data=True): # doctest: +SKIP
... if "color" not in data:
... data["color"] = default_color
This implementation does not support mixed graphs (directed and unidirected
edges together), hypergraphs, nested graphs, or ports.
For multigraphs the GraphML edge "id" will be used as the edge
key. If not specified then they "key" attribute will be used. If
there is no "key" attribute a default NetworkX multigraph edge key
will be provided.
"""
reader = GraphMLReader(node_type, edge_key_type, force_multigraph)
# need to check for multiple graphs
glist = list(reader(string=graphml_string))
if len(glist) == 0:
# If no graph comes back, try looking for an incomplete header
header = '<graphml xmlns="http://graphml.graphdrawing.org/xmlns">'
new_string = graphml_string.replace("<graphml>", header)
glist = list(reader(string=new_string))
if len(glist) == 0:
raise nx.NetworkXError("file not successfully read as graphml")
return glist[0]
class GraphML:
NS_GRAPHML = "http://graphml.graphdrawing.org/xmlns"
NS_XSI = "http://www.w3.org/2001/XMLSchema-instance"
# xmlns:y="http://www.yworks.com/xml/graphml"
NS_Y = "http://www.yworks.com/xml/graphml"
SCHEMALOCATION = " ".join(
[
"http://graphml.graphdrawing.org/xmlns",
"http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd",
]
)
types = [
(int, "integer"), # for Gephi GraphML bug
(str, "yfiles"),
(str, "string"),
(int, "int"),
(float, "float"),
(float, "double"),
(bool, "boolean"),
]
# These additions to types allow writing numpy types
try:
import numpy as np
except:
pass
else:
# prepend so that python types are created upon read (last entry wins)
types = [
(np.float64, "float"),
(np.float32, "float"),
(np.float16, "float"),
(np.float_, "float"),
(np.int_, "int"),
(np.int8, "int"),
(np.int16, "int"),
(np.int32, "int"),
(np.int64, "int"),
(np.uint8, "int"),
(np.uint16, "int"),
(np.uint32, "int"),
(np.uint64, "int"),
(np.int_, "int"),
(np.intc, "int"),
(np.intp, "int"),
] + types
xml_type = dict(types)
python_type = dict(reversed(a) for a in types)
# This page says that data types in GraphML follow Java(TM).
# http://graphml.graphdrawing.org/primer/graphml-primer.html#AttributesDefinition
# true and false are the only boolean literals:
# http://en.wikibooks.org/wiki/Java_Programming/Literals#Boolean_Literals
convert_bool = {
# We use data.lower() in actual use.
"true": True,
"false": False,
# Include integer strings for convenience.
"0": False,
0: False,
"1": True,
1: True,
}
class GraphMLWriter(GraphML):
def __init__(
self,
graph=None,
encoding="utf-8",
prettyprint=True,
infer_numeric_types=False,
named_key_ids=False,
):
self.myElement = Element
self.infer_numeric_types = infer_numeric_types
self.prettyprint = prettyprint
self.named_key_ids = named_key_ids
self.encoding = encoding
self.xml = self.myElement(
"graphml",
{
"xmlns": self.NS_GRAPHML,
"xmlns:xsi": self.NS_XSI,
"xsi:schemaLocation": self.SCHEMALOCATION,
},
)
self.keys = {}
self.attributes = defaultdict(list)
self.attribute_types = defaultdict(set)
if graph is not None:
self.add_graph_element(graph)
def __str__(self):
if self.prettyprint:
self.indent(self.xml)
s = tostring(self.xml).decode(self.encoding)
return s
def attr_type(self, name, scope, value):
"""Infer the attribute type of data named name. Currently this only
supports inference of numeric types.
If self.infer_numeric_types is false, type is used. Otherwise, pick the
most general of types found across all values with name and scope. This
means edges with data named 'weight' are treated separately from nodes
with data named 'weight'.
"""
if self.infer_numeric_types:
types = self.attribute_types[(name, scope)]
if len(types) > 1:
types = {self.xml_type[t] for t in types}
if "string" in types:
return str
elif "float" in types or "double" in types:
return float
else:
return int
else:
return list(types)[0]
else:
return type(value)
def get_key(self, name, attr_type, scope, default):
keys_key = (name, attr_type, scope)
try:
return self.keys[keys_key]
except KeyError:
if self.named_key_ids:
new_id = name
else:
new_id = f"d{len(list(self.keys))}"
self.keys[keys_key] = new_id
key_kwargs = {
"id": new_id,
"for": scope,
"attr.name": name,
"attr.type": attr_type,
}
key_element = self.myElement("key", **key_kwargs)
# add subelement for data default value if present
if default is not None:
default_element = self.myElement("default")
default_element.text = str(default)
key_element.append(default_element)
self.xml.insert(0, key_element)
return new_id
def add_data(self, name, element_type, value, scope="all", default=None):
"""
Make a data element for an edge or a node. Keep a log of the
type in the keys table.
"""
if element_type not in self.xml_type:
msg = f"GraphML writer does not support {element_type} as data values."
raise nx.NetworkXError(msg)
keyid = self.get_key(name, self.xml_type[element_type], scope, default)
data_element = self.myElement("data", key=keyid)
data_element.text = str(value)
return data_element
def add_attributes(self, scope, xml_obj, data, default):
"""Appends attribute data to edges or nodes, and stores type information
to be added later. See add_graph_element.
"""
for k, v in data.items():
self.attribute_types[(str(k), scope)].add(type(v))
self.attributes[xml_obj].append([k, v, scope, default.get(k)])
def add_nodes(self, G, graph_element):
default = G.graph.get("node_default", {})
for node, data in G.nodes(data=True):
node_element = self.myElement("node", id=str(node))
self.add_attributes("node", node_element, data, default)
graph_element.append(node_element)
def add_edges(self, G, graph_element):
if G.is_multigraph():
for u, v, key, data in G.edges(data=True, keys=True):
edge_element = self.myElement(
"edge", source=str(u), target=str(v), id=str(key)
)
default = G.graph.get("edge_default", {})
self.add_attributes("edge", edge_element, data, default)
graph_element.append(edge_element)
else:
for u, v, data in G.edges(data=True):
edge_element = self.myElement("edge", source=str(u), target=str(v))
default = G.graph.get("edge_default", {})
self.add_attributes("edge", edge_element, data, default)
graph_element.append(edge_element)
def add_graph_element(self, G):
"""
Serialize graph G in GraphML to the stream.
"""
if G.is_directed():
default_edge_type = "directed"
else:
default_edge_type = "undirected"
graphid = G.graph.pop("id", None)
if graphid is None:
graph_element = self.myElement("graph", edgedefault=default_edge_type)
else:
graph_element = self.myElement(
"graph", edgedefault=default_edge_type, id=graphid
)
default = {}
data = {
k: v
for (k, v) in G.graph.items()
if k not in ["node_default", "edge_default"]
}
self.add_attributes("graph", graph_element, data, default)
self.add_nodes(G, graph_element)
self.add_edges(G, graph_element)
# self.attributes contains a mapping from XML Objects to a list of
# data that needs to be added to them.
# We postpone processing in order to do type inference/generalization.
# See self.attr_type
for (xml_obj, data) in self.attributes.items():
for (k, v, scope, default) in data:
xml_obj.append(
self.add_data(
str(k), self.attr_type(k, scope, v), str(v), scope, default
)
)
self.xml.append(graph_element)
def add_graphs(self, graph_list):
""" Add many graphs to this GraphML document. """
for G in graph_list:
self.add_graph_element(G)
def dump(self, stream):
if self.prettyprint:
self.indent(self.xml)
document = ElementTree(self.xml)
document.write(stream, encoding=self.encoding, xml_declaration=True)
def indent(self, elem, level=0):
# in-place prettyprint formatter
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
self.indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
class IncrementalElement:
"""Wrapper for _IncrementalWriter providing an Element like interface.
This wrapper does not intend to be a complete implementation but rather to
deal with those calls used in GraphMLWriter.
"""
def __init__(self, xml, prettyprint):
self.xml = xml
self.prettyprint = prettyprint
def append(self, element):
self.xml.write(element, pretty_print=self.prettyprint)
class GraphMLWriterLxml(GraphMLWriter):
def __init__(
self,
path,
graph=None,
encoding="utf-8",
prettyprint=True,
infer_numeric_types=False,
named_key_ids=False,
):
self.myElement = lxmletree.Element
self._encoding = encoding
self._prettyprint = prettyprint
self.named_key_ids = named_key_ids
self.infer_numeric_types = infer_numeric_types
self._xml_base = lxmletree.xmlfile(path, encoding=encoding)
self._xml = self._xml_base.__enter__()
self._xml.write_declaration()
# We need to have a xml variable that support insertion. This call is
# used for adding the keys to the document.
# We will store those keys in a plain list, and then after the graph
# element is closed we will add them to the main graphml element.
self.xml = []
self._keys = self.xml
self._graphml = self._xml.element(
"graphml",
{
"xmlns": self.NS_GRAPHML,
"xmlns:xsi": self.NS_XSI,
"xsi:schemaLocation": self.SCHEMALOCATION,
},
)
self._graphml.__enter__()
self.keys = {}
self.attribute_types = defaultdict(set)
if graph is not None:
self.add_graph_element(graph)
def add_graph_element(self, G):
"""
Serialize graph G in GraphML to the stream.
"""
if G.is_directed():
default_edge_type = "directed"
else:
default_edge_type = "undirected"
graphid = G.graph.pop("id", None)
if graphid is None:
graph_element = self._xml.element("graph", edgedefault=default_edge_type)
else:
graph_element = self._xml.element(
"graph", edgedefault=default_edge_type, id=graphid
)
# gather attributes types for the whole graph
# to find the most general numeric format needed.
# Then pass through attributes to create key_id for each.
graphdata = {
k: v
for k, v in G.graph.items()
if k not in ("node_default", "edge_default")
}
node_default = G.graph.get("node_default", {})
edge_default = G.graph.get("edge_default", {})
# Graph attributes
for k, v in graphdata.items():
self.attribute_types[(str(k), "graph")].add(type(v))
for k, v in graphdata.items():
element_type = self.xml_type[self.attr_type(k, "graph", v)]
self.get_key(str(k), element_type, "graph", None)
# Nodes and data
for node, d in G.nodes(data=True):
for k, v in d.items():
self.attribute_types[(str(k), "node")].add(type(v))
for node, d in G.nodes(data=True):
for k, v in d.items():
T = self.xml_type[self.attr_type(k, "node", v)]
self.get_key(str(k), T, "node", node_default.get(k))
# Edges and data
if G.is_multigraph():
for u, v, ekey, d in G.edges(keys=True, data=True):
for k, v in d.items():
self.attribute_types[(str(k), "edge")].add(type(v))
for u, v, ekey, d in G.edges(keys=True, data=True):
for k, v in d.items():
T = self.xml_type[self.attr_type(k, "edge", v)]
self.get_key(str(k), T, "edge", edge_default.get(k))
else:
for u, v, d in G.edges(data=True):
for k, v in d.items():
self.attribute_types[(str(k), "edge")].add(type(v))
for u, v, d in G.edges(data=True):
for k, v in d.items():
T = self.xml_type[self.attr_type(k, "edge", v)]
self.get_key(str(k), T, "edge", edge_default.get(k))
# Now add attribute keys to the xml file
for key in self.xml:
self._xml.write(key, pretty_print=self._prettyprint)
# The incremental_writer writes each node/edge as it is created
incremental_writer = IncrementalElement(self._xml, self._prettyprint)
with graph_element:
self.add_attributes("graph", incremental_writer, graphdata, {})
self.add_nodes(G, incremental_writer) # adds attributes too
self.add_edges(G, incremental_writer) # adds attributes too
def add_attributes(self, scope, xml_obj, data, default):
"""Appends attribute data."""
for k, v in data.items():
data_element = self.add_data(
str(k), self.attr_type(str(k), scope, v), str(v), scope, default.get(k)
)
xml_obj.append(data_element)
def __str__(self):
return object.__str__(self)
def dump(self):
self._graphml.__exit__(None, None, None)
self._xml_base.__exit__(None, None, None)
# Choose a writer function for default
if lxmletree is None:
write_graphml = write_graphml_xml
else:
write_graphml = write_graphml_lxml
class GraphMLReader(GraphML):
"""Read a GraphML document. Produces NetworkX graph objects."""
def __init__(self, node_type=str, edge_key_type=int, force_multigraph=False):
self.node_type = node_type
self.edge_key_type = edge_key_type
self.multigraph = force_multigraph # If False, test for multiedges
self.edge_ids = {} # dict mapping (u,v) tuples to edge id attributes
def __call__(self, path=None, string=None):
if path is not None:
self.xml = ElementTree(file=path)
elif string is not None:
self.xml = fromstring(string)
else:
raise ValueError("Must specify either 'path' or 'string' as kwarg")
(keys, defaults) = self.find_graphml_keys(self.xml)
for g in self.xml.findall(f"{{{self.NS_GRAPHML}}}graph"):
yield self.make_graph(g, keys, defaults)
def make_graph(self, graph_xml, graphml_keys, defaults, G=None):
# set default graph type
edgedefault = graph_xml.get("edgedefault", None)
if G is None:
if edgedefault == "directed":
G = nx.MultiDiGraph()
else:
G = nx.MultiGraph()
# set defaults for graph attributes
G.graph["node_default"] = {}
G.graph["edge_default"] = {}
for key_id, value in defaults.items():
key_for = graphml_keys[key_id]["for"]
name = graphml_keys[key_id]["name"]
python_type = graphml_keys[key_id]["type"]
if key_for == "node":
G.graph["node_default"].update({name: python_type(value)})
if key_for == "edge":
G.graph["edge_default"].update({name: python_type(value)})
# hyperedges are not supported
hyperedge = graph_xml.find(f"{{{self.NS_GRAPHML}}}hyperedge")
if hyperedge is not None:
raise nx.NetworkXError("GraphML reader doesn't support hyperedges")
# add nodes
for node_xml in graph_xml.findall(f"{{{self.NS_GRAPHML}}}node"):
self.add_node(G, node_xml, graphml_keys, defaults)
# add edges
for edge_xml in graph_xml.findall(f"{{{self.NS_GRAPHML}}}edge"):
self.add_edge(G, edge_xml, graphml_keys)
# add graph data
data = self.decode_data_elements(graphml_keys, graph_xml)
G.graph.update(data)
# switch to Graph or DiGraph if no parallel edges were found
if self.multigraph:
return G
G = nx.DiGraph(G) if G.is_directed() else nx.Graph(G)
# add explicit edge "id" from file as attribute in NX graph.
nx.set_edge_attributes(G, values=self.edge_ids, name="id")
return G
def add_node(self, G, node_xml, graphml_keys, defaults):
"""Add a node to the graph.
"""
# warn on finding unsupported ports tag
ports = node_xml.find(f"{{{self.NS_GRAPHML}}}port")
if ports is not None:
warnings.warn("GraphML port tag not supported.")
# find the node by id and cast it to the appropriate type
node_id = self.node_type(node_xml.get("id"))
# get data/attributes for node
data = self.decode_data_elements(graphml_keys, node_xml)
G.add_node(node_id, **data)
# get child nodes
if node_xml.attrib.get("yfiles.foldertype") == "group":
graph_xml = node_xml.find(f"{{{self.NS_GRAPHML}}}graph")
self.make_graph(graph_xml, graphml_keys, defaults, G)
def add_edge(self, G, edge_element, graphml_keys):
"""Add an edge to the graph.
"""
# warn on finding unsupported ports tag
ports = edge_element.find(f"{{{self.NS_GRAPHML}}}port")
if ports is not None:
warnings.warn("GraphML port tag not supported.")
# raise error if we find mixed directed and undirected edges
directed = edge_element.get("directed")
if G.is_directed() and directed == "false":
msg = "directed=false edge found in directed graph."
raise nx.NetworkXError(msg)
if (not G.is_directed()) and directed == "true":
msg = "directed=true edge found in undirected graph."
raise nx.NetworkXError(msg)
source = self.node_type(edge_element.get("source"))
target = self.node_type(edge_element.get("target"))
data = self.decode_data_elements(graphml_keys, edge_element)
# GraphML stores edge ids as an attribute
# NetworkX uses them as keys in multigraphs too if no key
# attribute is specified
edge_id = edge_element.get("id")
if edge_id:
# self.edge_ids is used by `make_graph` method for non-multigraphs
self.edge_ids[source, target] = edge_id
try:
edge_id = self.edge_key_type(edge_id)
except ValueError: # Could not convert.
pass
else:
edge_id = data.get("key")
if G.has_edge(source, target):
# mark this as a multigraph
self.multigraph = True
# Use add_edges_from to avoid error with add_edge when `'key' in data`
# Note there is only one edge here...
G.add_edges_from([(source, target, edge_id, data)])
def decode_data_elements(self, graphml_keys, obj_xml):
"""Use the key information to decode the data XML if present."""
data = {}
for data_element in obj_xml.findall(f"{{{self.NS_GRAPHML}}}data"):
key = data_element.get("key")
try:
data_name = graphml_keys[key]["name"]
data_type = graphml_keys[key]["type"]
except KeyError as e:
raise nx.NetworkXError(f"Bad GraphML data: no key {key}") from e
text = data_element.text
# assume anything with subelements is a yfiles extension
if text is not None and len(list(data_element)) == 0:
if data_type == bool:
# Ignore cases.
# http://docs.oracle.com/javase/6/docs/api/java/lang/
# Boolean.html#parseBoolean%28java.lang.String%29
data[data_name] = self.convert_bool[text.lower()]
else:
data[data_name] = data_type(text)
elif len(list(data_element)) > 0:
# Assume yfiles as subelements, try to extract node_label
node_label = None
for node_type in ["ShapeNode", "SVGNode", "ImageNode"]:
pref = f"{{{self.NS_Y}}}{node_type}/{{{self.NS_Y}}}"
geometry = data_element.find(f"{pref}Geometry")
if geometry is not None:
data["x"] = geometry.get("x")
data["y"] = geometry.get("y")
if node_label is None:
node_label = data_element.find(f"{pref}NodeLabel")
if node_label is not None:
data["label"] = node_label.text
# check all the different types of edges avaivable in yEd.
for e in [
"PolyLineEdge",
"SplineEdge",
"QuadCurveEdge",
"BezierEdge",
"ArcEdge",
]:
pref = f"{{{self.NS_Y}}}{e}/{{{self.NS_Y}}}"
edge_label = data_element.find(f"{pref}EdgeLabel")
if edge_label is not None:
break
if edge_label is not None:
data["label"] = edge_label.text
return data
def find_graphml_keys(self, graph_element):
"""Extracts all the keys and key defaults from the xml.
"""
graphml_keys = {}
graphml_key_defaults = {}
for k in graph_element.findall(f"{{{self.NS_GRAPHML}}}key"):
attr_id = k.get("id")
attr_type = k.get("attr.type")
attr_name = k.get("attr.name")
yfiles_type = k.get("yfiles.type")
if yfiles_type is not None:
attr_name = yfiles_type
attr_type = "yfiles"
if attr_type is None:
attr_type = "string"
warnings.warn(f"No key type for id {attr_id}. Using string")
if attr_name is None:
raise nx.NetworkXError(f"Unknown key for id {attr_id}.")
graphml_keys[attr_id] = {
"name": attr_name,
"type": self.python_type[attr_type],
"for": k.get("for"),
}
# check for "default" subelement of key element
default = k.find(f"{{{self.NS_GRAPHML}}}default")
if default is not None:
graphml_key_defaults[attr_id] = default.text
return graphml_keys, graphml_key_defaults

View file

@ -0,0 +1,19 @@
"""
*********
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.jit import *
from networkx.readwrite.json_graph.cytoscape import *

View file

@ -0,0 +1,156 @@
from itertools import chain
import networkx as nx
__all__ = ["adjacency_data", "adjacency_graph"]
_attrs = dict(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(dict(chain(G.nodes[n].items(), [(id_, n)])))
adj = []
if multigraph:
for nbr, keys in nbrdict.items():
for k, d in keys.items():
adj.append(dict(chain(d.items(), [(id_, nbr), (key, k)])))
else:
for nbr, d in nbrdict.items():
adj.append(dict(chain(d.items(), [(id_, nbr)])))
data["adjacency"].append(adj)
return data
def adjacency_graph(data, directed=False, multigraph=True, attrs=_attrs):
"""Returns graph from adjacency data format.
Parameters
----------
data : dict
Adjacency list formatted graph data
Returns
-------
G : NetworkX graph
A NetworkX graph object
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')`.
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(tdata)
else:
ky = target_data.pop(key, None)
graph.add_edge(source, target, key=ky)
graph[source][target][ky].update(tdata)
return graph

View file

@ -0,0 +1,110 @@
import networkx as nx
__all__ = ["cytoscape_data", "cytoscape_graph"]
_attrs = dict(name="name", ident="id")
def cytoscape_data(G, attrs=None):
"""Returns data in Cytoscape JSON format (cyjs).
Parameters
----------
G : NetworkX Graph
Returns
-------
data: dict
A dictionary with cyjs formatted data.
Raises
------
NetworkXError
If values in attrs are not unique.
"""
if not attrs:
attrs = _attrs
else:
attrs.update({k: v for (k, v) in _attrs.items() if k not in attrs})
name = attrs["name"]
ident = attrs["ident"]
if len({name, ident}) < 2:
raise nx.NetworkXError("Attribute names are not unique.")
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
def cytoscape_graph(data, attrs=None):
if not attrs:
attrs = _attrs
else:
attrs.update({k: v for (k, v) in _attrs.items() if k not in attrs})
name = attrs["name"]
ident = attrs["ident"]
if len({ident, name}) < 2:
raise nx.NetworkXError("Attribute names are not unique.")
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"].pop("source")
targ = d["data"].pop("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

View file

@ -0,0 +1,103 @@
"""
Read and write NetworkX graphs as JavaScript InfoVis Toolkit (JIT) format JSON.
See the `JIT documentation`_ for more examples.
Format
------
var json = [
{
"id": "aUniqueIdentifier",
"name": "usually a nodes name",
"data": {
"some key": "some value",
"some other key": "some other value"
},
"adjacencies": [
{
nodeTo:"aNodeId",
data: {} //put whatever you want here
},
'other adjacencies go here...'
},
'other nodes go here...'
];
.. _JIT documentation: http://thejit.org
"""
import json
import networkx as nx
from networkx.utils.decorators import not_implemented_for
__all__ = ["jit_graph", "jit_data"]
def jit_graph(data, create_using=None):
"""Read a graph from JIT JSON.
Parameters
----------
data : JSON Graph Object
create_using : Networkx Graph, optional (default: Graph())
Return graph of this type. The provided instance will be cleared.
Returns
-------
G : NetworkX Graph built from create_using if provided.
"""
if create_using is None:
G = nx.Graph()
else:
G = create_using
G.clear()
if isinstance(data, str):
data = json.loads(data)
for node in data:
G.add_node(node["id"], **node["data"])
if node.get("adjacencies") is not None:
for adj in node["adjacencies"]:
G.add_edge(node["id"], adj["nodeTo"], **adj["data"])
return G
@not_implemented_for("multigraph")
def jit_data(G, indent=None, default=None):
"""Returns data in JIT JSON format.
Parameters
----------
G : NetworkX Graph
indent: optional, default=None
If indent is a non-negative integer, then JSON array elements and
object members will be pretty-printed with that indent level.
An indent level of 0, or negative, will only insert newlines.
None (the default) selects the most compact representation.
default: optional, default=None
It will pass the value to the json.dumps function in order to
be able to serialize custom objects used as nodes.
Returns
-------
data: JIT JSON string
"""
json_graph = []
for node in G.nodes():
json_node = {"id": node, "name": node}
# node data
json_node["data"] = G.nodes[node]
# adjacencies
if G[node]:
json_node["adjacencies"] = []
for neighbour in G[node]:
adjacency = {"nodeTo": neighbour}
# adjacency data
adjacency["data"] = G.edges[node, neighbour]
json_node["adjacencies"].append(adjacency)
json_graph.append(json_node)
return json.dumps(json_graph, indent=indent, default=default)

View file

@ -0,0 +1,184 @@
from itertools import chain, count
import networkx as nx
from networkx.utils import to_tuple
__all__ = ["node_link_data", "node_link_graph"]
_attrs = dict(source="source", target="target", name="id", key="key", link="links")
def node_link_data(G, attrs=None):
"""Returns data in node-link format that is suitable for JSON serialization
and use in Javascript documents.
Parameters
----------
G : NetworkX graph
attrs : dict
A dictionary that contains five keys 'source', 'target', 'name',
'key' and 'link'. The corresponding values provide the attribute
names for storing NetworkX-internal graph data. The values should
be unique. Default value::
dict(source='source', target='target', name='id',
key='key', link='links')
If some user-defined graph data use these attribute names as data keys,
they may be silently dropped.
Returns
-------
data : dict
A dictionary with node-link formatted data.
Raises
------
NetworkXError
If values in attrs are not unique.
Examples
--------
>>> from networkx.readwrite import json_graph
>>> G = nx.Graph([("A", "B")])
>>> data1 = json_graph.node_link_data(G)
>>> H = nx.gn_graph(2)
>>> data2 = json_graph.node_link_data(
... H, {"link": "edges", "source": "from", "target": "to"}
... )
To serialize with json
>>> import json
>>> s1 = json.dumps(data1)
>>> s2 = json.dumps(
... data2, default={"link": "edges", "source": "from", "target": "to"}
... )
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.
See Also
--------
node_link_graph, adjacency_data, tree_data
"""
multigraph = G.is_multigraph()
# Allow 'attrs' to keep default values.
if attrs is None:
attrs = _attrs
else:
attrs.update({k: v for (k, v) in _attrs.items() if k not in attrs})
name = attrs["name"]
source = attrs["source"]
target = attrs["target"]
links = attrs["link"]
# Allow 'key' to be omitted from attrs if the graph is not a multigraph.
key = None if not multigraph else attrs["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": [dict(chain(G.nodes[n].items(), [(name, n)])) for n in G],
}
if multigraph:
data[links] = [
dict(chain(d.items(), [(source, u), (target, v), (key, k)]))
for u, v, k, d in G.edges(keys=True, data=True)
]
else:
data[links] = [
dict(chain(d.items(), [(source, u), (target, v)]))
for u, v, d in G.edges(data=True)
]
return data
def node_link_graph(data, directed=False, multigraph=True, attrs=None):
"""Returns graph from node-link data format.
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.
attrs : dict
A dictionary that contains five keys 'source', 'target', 'name',
'key' and 'link'. The corresponding values provide the attribute
names for storing NetworkX-internal graph data. Default value:
dict(source='source', target='target', name='id',
key='key', link='links')
Returns
-------
G : NetworkX graph
A NetworkX graph object
Examples
--------
>>> from networkx.readwrite import json_graph
>>> G = nx.Graph([("A", "B")])
>>> data = json_graph.node_link_data(G)
>>> H = json_graph.node_link_graph(data)
Notes
-----
Attribute 'key' is only used for multigraphs.
See Also
--------
node_link_data, adjacency_data, tree_data
"""
# Allow 'attrs' to keep default values.
if attrs is None:
attrs = _attrs
else:
attrs.update({k: v for k, v in _attrs.items() if k not in attrs})
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()
name = attrs["name"]
source = attrs["source"]
target = attrs["target"]
links = attrs["link"]
# Allow 'key' to be omitted from attrs if the graph is not a multigraph.
key = None if not multigraph else attrs["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[links]:
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

View file

@ -0,0 +1,58 @@
import json
import pytest
import networkx as nx
from networkx.readwrite.json_graph import adjacency_data, adjacency_graph
class TestAdjacency:
def test_graph(self):
G = nx.path_graph(4)
H = adjacency_graph(adjacency_data(G))
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["foo"] = "bar"
G.graph[1] = "one"
H = adjacency_graph(adjacency_data(G))
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 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()
nx.is_isomorphic(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()
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))
nx.is_isomorphic(G, H)
assert H[1][2]["second"]["color"] == "blue"
def test_exception(self):
with pytest.raises(nx.NetworkXError):
G = nx.MultiDiGraph()
attrs = dict(id="node", key="node")
adjacency_data(G, attrs)

View file

@ -0,0 +1,63 @@
import json
import pytest
import networkx as nx
from networkx.readwrite.json_graph import cytoscape_data, cytoscape_graph
class TestCytoscape:
def test_graph(self):
G = nx.path_graph(4)
H = cytoscape_graph(cytoscape_data(G))
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["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(self):
G = nx.DiGraph()
nx.add_path(G, [1, 2, 3])
H = cytoscape_graph(cytoscape_data(G))
assert H.is_directed()
nx.is_isomorphic(G, H)
def test_multidigraph(self):
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(self):
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(self):
with pytest.raises(nx.NetworkXError):
G = nx.MultiDiGraph()
attrs = dict(name="node", ident="node")
cytoscape_data(G, attrs)

View file

@ -0,0 +1,64 @@
import json
import pytest
import networkx as nx
from networkx.readwrite.json_graph import jit_data, jit_graph
class TestJIT:
def test_jit(self):
G = nx.Graph()
G.add_node("Node1", node_data="foobar")
G.add_node("Node3", node_data="bar")
G.add_node("Node4")
G.add_edge("Node1", "Node2", weight=9, something="isSomething")
G.add_edge("Node2", "Node3", weight=4, something="isNotSomething")
G.add_edge("Node1", "Node2")
d = jit_data(G)
K = jit_graph(json.loads(d))
assert nx.is_isomorphic(G, K)
def test_jit_2(self):
G = nx.Graph()
G.add_node(1, node_data=3)
G.add_node(3, node_data=0)
G.add_edge(1, 2, weight=9, something=0)
G.add_edge(2, 3, weight=4, something=3)
G.add_edge(1, 2)
d = jit_data(G)
K = jit_graph(json.loads(d))
assert nx.is_isomorphic(G, K)
def test_jit_directed(self):
G = nx.DiGraph()
G.add_node(1, node_data=3)
G.add_node(3, node_data=0)
G.add_edge(1, 2, weight=9, something=0)
G.add_edge(2, 3, weight=4, something=3)
G.add_edge(1, 2)
d = jit_data(G)
K = jit_graph(json.loads(d), create_using=nx.DiGraph())
assert nx.is_isomorphic(G, K)
def test_jit_multi_directed(self):
G = nx.MultiDiGraph()
G.add_node(1, node_data=3)
G.add_node(3, node_data=0)
G.add_edge(1, 2, weight=9, something=0)
G.add_edge(2, 3, weight=4, something=3)
G.add_edge(1, 2)
pytest.raises(nx.NetworkXNotImplemented, jit_data, G)
H = nx.DiGraph(G)
d = jit_data(H)
K = jit_graph(json.loads(d), create_using=nx.MultiDiGraph())
assert nx.is_isomorphic(H, K)
K.add_edge(1, 2)
assert not nx.is_isomorphic(H, K)
assert nx.is_isomorphic(G, K)
def test_jit_round_trip(self):
G = nx.Graph()
d = nx.jit_data(G)
H = jit_graph(json.loads(d))
K = jit_graph(d)
assert nx.is_isomorphic(H, K)

View file

@ -0,0 +1,104 @@
import json
import pytest
import networkx as nx
from networkx.readwrite.json_graph import node_link_data, node_link_graph
class TestNodeLink:
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))
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 = dict(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 = dict(
source="c_source",
target="c_target",
name="c_id",
key="c_key",
link="c_links",
)
H = node_link_graph(
node_link_data(G, attrs=attrs), multigraph=False, attrs=attrs
)
assert nx.is_isomorphic(G, H)
assert H.graph["foo"] == "bar"
assert H.nodes[1]["color"] == "red"
assert H[1][2]["width"] == 7

View file

@ -0,0 +1,35 @@
import json
import pytest
import networkx as nx
from networkx.readwrite.json_graph import tree_data, tree_graph
class TestTree:
def test_graph(self):
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))
nx.is_isomorphic(G, H)
def test_graph_attributes(self):
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_exception(self):
with pytest.raises(nx.NetworkXError):
G = nx.MultiDiGraph()
G.add_node(0)
attrs = dict(id="node", children="node")
tree_data(G, 0, attrs)

View file

@ -0,0 +1,146 @@
from itertools import chain
import networkx as nx
__all__ = ["tree_data", "tree_graph"]
_attrs = dict(id="id", children="children")
def tree_data(G, root, attrs=_attrs):
"""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
attrs : dict
A dictionary that contains two keys 'id' and 'children'. The
corresponding values provide the attribute names for storing
NetworkX-internal graph data. The values should be unique. Default
value: :samp:`dict(id='id', children='children')`.
If some user-defined graph data use these attribute names as data keys,
they may be silently dropped.
Returns
-------
data : dict
A dictionary with node-link formatted data.
Raises
------
NetworkXError
If values in attrs are not unique.
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.
The default value of attrs will be changed in a future release of NetworkX.
See Also
--------
tree_graph, node_link_data, node_link_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.")
id_ = attrs["id"]
children = attrs["children"]
if id_ == children:
raise nx.NetworkXError("Attribute names are not unique.")
def add_children(n, G):
nbrs = G[n]
if len(nbrs) == 0:
return []
children_ = []
for child in nbrs:
d = dict(chain(G.nodes[child].items(), [(id_, child)]))
c = add_children(child, G)
if c:
d[children] = c
children_.append(d)
return children_
data = dict(chain(G.nodes[root].items(), [(id_, root)]))
data[children] = add_children(root, G)
return data
def tree_graph(data, attrs=_attrs):
"""Returns graph from tree data format.
Parameters
----------
data : dict
Tree formatted graph data
Returns
-------
G : NetworkX DiGraph
attrs : dict
A dictionary that contains two keys 'id' and 'children'. The
corresponding values provide the attribute names for storing
NetworkX-internal graph data. The values should be unique. Default
value: :samp:`dict(id='id', children='children')`.
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)
Notes
-----
The default value of attrs will be changed in a future release of NetworkX.
See Also
--------
tree_graph, node_link_data, adjacency_data
"""
graph = nx.DiGraph()
id_ = attrs["id"]
children = attrs["children"]
def add_children(parent, children_):
for data in children_:
child = data[id_]
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 != id_ and k != children
}
graph.add_node(child, **nodedata)
root = data[id_]
children_ = data.get(children, [])
nodedata = {str(k): v for k, v in data.items() if k != id_ and k != children}
graph.add_node(root, **nodedata)
add_children(root, children_)
return graph

View file

@ -0,0 +1,106 @@
"""
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")
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
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("#") or 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 e:
raise NetworkXError(f"Too few fields in LEDA.GRAPH edge {i+1}") from e
# BEWARE: no handling of reversal edges
G.add_edge(node[int(s)], node[int(t)], label=label[2:-2])
return G

View file

@ -0,0 +1,384 @@
"""
*************************
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",
]
from networkx.utils import open_file
import networkx as nx
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
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))
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.
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 e:
raise TypeError(f"Failed to read node and degree on line ({line})") from e
if nodetype is not None:
try:
u = nodetype(u)
except BaseException as e:
raise TypeError(
f"Failed to convert node ({u}) to " f"type {nodetype}"
) from e
G.add_node(u)
for i in range(deg):
while True:
try:
line = next(lines)
except StopIteration as e:
msg = f"Failed to find neighbor for node ({u})"
raise TypeError(msg) from e
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 e:
raise TypeError(
f"Failed to convert node ({v}) " f"to type {nodetype}"
) from e
if edgetype is not None:
try:
edgedata = {"weight": edgetype(data)}
except BaseException as e:
raise TypeError(
f"Failed to convert edge data ({data}) " f"to type {edgetype}"
) from e
else:
try: # try to evaluate
edgedata = literal_eval(data)
except:
edgedata = {}
G.add_edge(u, v, **edgedata)
return G
@open_file(0, mode="rb")
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,
)

View file

@ -0,0 +1,316 @@
"""
*********
Shapefile
*********
Generates a networkx.DiGraph from point and line shapefiles.
"The Esri Shapefile or simply a shapefile is a popular geospatial vector
data format for geographic information systems software. It is developed
and regulated by Esri as a (mostly) open specification for data
interoperability among Esri and other software products."
See https://en.wikipedia.org/wiki/Shapefile for additional information.
"""
import networkx as nx
__all__ = ["read_shp", "write_shp"]
def read_shp(path, simplify=True, geom_attrs=True, strict=True):
"""Generates a networkx.DiGraph from shapefiles. Point geometries are
translated into nodes, lines into edges. Coordinate tuples are used as
keys. Attributes are preserved, line geometries are simplified into start
and end coordinates. Accepts a single shapefile or directory of many
shapefiles.
"The Esri Shapefile or simply a shapefile is a popular geospatial vector
data format for geographic information systems software [1]_."
Parameters
----------
path : file or string
File, directory, or filename to read.
simplify: bool
If True, simplify line geometries to start and end coordinates.
If False, and line feature geometry has multiple segments, the
non-geometric attributes for that feature will be repeated for each
edge comprising that feature.
geom_attrs: bool
If True, include the Wkb, Wkt and Json geometry attributes with
each edge.
NOTE: if these attributes are available, write_shp will use them
to write the geometry. If nodes store the underlying coordinates for
the edge geometry as well (as they do when they are read via
this method) and they change, your geomety will be out of sync.
strict: bool
If True, raise NetworkXError when feature geometry is missing or
GeometryType is not supported.
If False, silently ignore missing or unsupported geometry in features.
Returns
-------
G : NetworkX graph
Raises
------
ImportError
If ogr module is not available.
RuntimeError
If file cannot be open or read.
NetworkXError
If strict=True and feature is missing geometry or GeometryType is
not supported.
Examples
--------
>>> G = nx.read_shp("test.shp") # doctest: +SKIP
References
----------
.. [1] https://en.wikipedia.org/wiki/Shapefile
"""
try:
from osgeo import ogr
except ImportError as e:
raise ImportError("read_shp requires OGR: http://www.gdal.org/") from e
if not isinstance(path, str):
return
net = nx.DiGraph()
shp = ogr.Open(path)
if shp is None:
raise RuntimeError(f"Unable to open {path}")
for lyr in shp:
fields = [x.GetName() for x in lyr.schema]
for f in lyr:
g = f.geometry()
if g is None:
if strict:
raise nx.NetworkXError("Bad data: feature missing geometry")
else:
continue
flddata = [f.GetField(f.GetFieldIndex(x)) for x in fields]
attributes = dict(zip(fields, flddata))
attributes["ShpName"] = lyr.GetName()
# Note: Using layer level geometry type
if g.GetGeometryType() == ogr.wkbPoint:
net.add_node((g.GetPoint_2D(0)), **attributes)
elif g.GetGeometryType() in (ogr.wkbLineString, ogr.wkbMultiLineString):
for edge in edges_from_line(g, attributes, simplify, geom_attrs):
e1, e2, attr = edge
net.add_edge(e1, e2)
net[e1][e2].update(attr)
else:
if strict:
raise nx.NetworkXError(
"GeometryType {} not supported".format(g.GetGeometryType())
)
return net
def edges_from_line(geom, attrs, simplify=True, geom_attrs=True):
"""
Generate edges for each line in geom
Written as a helper for read_shp
Parameters
----------
geom: ogr line geometry
To be converted into an edge or edges
attrs: dict
Attributes to be associated with all geoms
simplify: bool
If True, simplify the line as in read_shp
geom_attrs: bool
If True, add geom attributes to edge as in read_shp
Returns
-------
edges: generator of edges
each edge is a tuple of form
(node1_coord, node2_coord, attribute_dict)
suitable for expanding into a networkx Graph add_edge call
"""
try:
from osgeo import ogr
except ImportError as e:
raise ImportError(
"edges_from_line requires OGR: " "http://www.gdal.org/"
) from e
if geom.GetGeometryType() == ogr.wkbLineString:
if simplify:
edge_attrs = attrs.copy()
last = geom.GetPointCount() - 1
if geom_attrs:
edge_attrs["Wkb"] = geom.ExportToWkb()
edge_attrs["Wkt"] = geom.ExportToWkt()
edge_attrs["Json"] = geom.ExportToJson()
yield (geom.GetPoint_2D(0), geom.GetPoint_2D(last), edge_attrs)
else:
for i in range(0, geom.GetPointCount() - 1):
pt1 = geom.GetPoint_2D(i)
pt2 = geom.GetPoint_2D(i + 1)
edge_attrs = attrs.copy()
if geom_attrs:
segment = ogr.Geometry(ogr.wkbLineString)
segment.AddPoint_2D(pt1[0], pt1[1])
segment.AddPoint_2D(pt2[0], pt2[1])
edge_attrs["Wkb"] = segment.ExportToWkb()
edge_attrs["Wkt"] = segment.ExportToWkt()
edge_attrs["Json"] = segment.ExportToJson()
del segment
yield (pt1, pt2, edge_attrs)
elif geom.GetGeometryType() == ogr.wkbMultiLineString:
for i in range(geom.GetGeometryCount()):
geom_i = geom.GetGeometryRef(i)
yield from edges_from_line(geom_i, attrs, simplify, geom_attrs)
def write_shp(G, outdir):
"""Writes a networkx.DiGraph to two shapefiles, edges and nodes.
Nodes and edges are expected to have a Well Known Binary (Wkb) or
Well Known Text (Wkt) key in order to generate geometries. Also
acceptable are nodes with a numeric tuple key (x,y).
"The Esri Shapefile or simply a shapefile is a popular geospatial vector
data format for geographic information systems software [1]_."
Parameters
----------
outdir : directory path
Output directory for the two shapefiles.
Returns
-------
None
Examples
--------
nx.write_shp(digraph, '/shapefiles') # doctest +SKIP
References
----------
.. [1] https://en.wikipedia.org/wiki/Shapefile
"""
try:
from osgeo import ogr
except ImportError as e:
raise ImportError("write_shp requires OGR: http://www.gdal.org/") from e
# easier to debug in python if ogr throws exceptions
ogr.UseExceptions()
def netgeometry(key, data):
if "Wkb" in data:
geom = ogr.CreateGeometryFromWkb(data["Wkb"])
elif "Wkt" in data:
geom = ogr.CreateGeometryFromWkt(data["Wkt"])
elif type(key[0]).__name__ == "tuple": # edge keys are packed tuples
geom = ogr.Geometry(ogr.wkbLineString)
_from, _to = key[0], key[1]
try:
geom.SetPoint(0, *_from)
geom.SetPoint(1, *_to)
except TypeError:
# assume user used tuple of int and choked ogr
_ffrom = [float(x) for x in _from]
_fto = [float(x) for x in _to]
geom.SetPoint(0, *_ffrom)
geom.SetPoint(1, *_fto)
else:
geom = ogr.Geometry(ogr.wkbPoint)
try:
geom.SetPoint(0, *key)
except TypeError:
# assume user used tuple of int and choked ogr
fkey = [float(x) for x in key]
geom.SetPoint(0, *fkey)
return geom
# Create_feature with new optional attributes arg (should be dict type)
def create_feature(geometry, lyr, attributes=None):
feature = ogr.Feature(lyr.GetLayerDefn())
feature.SetGeometry(g)
if attributes is not None:
# Loop through attributes, assigning data to each field
for field, data in attributes.items():
feature.SetField(field, data)
lyr.CreateFeature(feature)
feature.Destroy()
# Conversion dict between python and ogr types
OGRTypes = {int: ogr.OFTInteger, str: ogr.OFTString, float: ogr.OFTReal}
# Check/add fields from attribute data to Shapefile layers
def add_fields_to_layer(key, value, fields, layer):
# Field not in previous edges so add to dict
if type(value) in OGRTypes:
fields[key] = OGRTypes[type(value)]
else:
# Data type not supported, default to string (char 80)
fields[key] = ogr.OFTString
# Create the new field
newfield = ogr.FieldDefn(key, fields[key])
layer.CreateField(newfield)
drv = ogr.GetDriverByName("ESRI Shapefile")
shpdir = drv.CreateDataSource(outdir)
# delete pre-existing output first otherwise ogr chokes
try:
shpdir.DeleteLayer("nodes")
except:
pass
nodes = shpdir.CreateLayer("nodes", None, ogr.wkbPoint)
# Storage for node field names and their data types
node_fields = {}
def create_attributes(data, fields, layer):
attributes = {} # storage for attribute data (indexed by field names)
for key, value in data.items():
# Reject spatial data not required for attribute table
if key != "Json" and key != "Wkt" and key != "Wkb" and key != "ShpName":
# Check/add field and data type to fields dict
if key not in fields:
add_fields_to_layer(key, value, fields, layer)
# Store the data from new field to dict for CreateLayer()
attributes[key] = value
return attributes, layer
for n in G:
data = G.nodes[n]
g = netgeometry(n, data)
attributes, nodes = create_attributes(data, node_fields, nodes)
create_feature(g, nodes, attributes)
try:
shpdir.DeleteLayer("edges")
except:
pass
edges = shpdir.CreateLayer("edges", None, ogr.wkbLineString)
# New edge attribute write support merged into edge loop
edge_fields = {} # storage for field names and their data types
for e in G.edges(data=True):
data = G.get_edge_data(*e)
g = netgeometry(e, data)
attributes, edges = create_attributes(e[2], edge_fields, edges)
create_feature(g, edges, attributes)
nodes, edges = None, None

View file

@ -0,0 +1,92 @@
"""
****
YAML
****
Read and write NetworkX graphs in YAML format.
"YAML is a data serialization format designed for human readability
and interaction with scripting languages."
See http://www.yaml.org for documentation.
Format
------
http://pyyaml.org/wiki/PyYAML
"""
__all__ = ["read_yaml", "write_yaml"]
from networkx.utils import open_file
@open_file(1, mode="w")
def write_yaml(G_to_be_yaml, path_for_yaml_output, **kwds):
"""Write graph G in YAML format to path.
YAML is a data serialization format designed for human readability
and interaction with scripting languages [1]_.
Parameters
----------
G : graph
A NetworkX graph
path : file or string
File or filename to write.
Filenames ending in .gz or .bz2 will be compressed.
Notes
-----
To use encoding on the output file include e.g. `encoding='utf-8'`
in the keyword arguments.
Examples
--------
>>> G = nx.path_graph(4)
>>> nx.write_yaml(G, "test.yaml")
References
----------
.. [1] http://www.yaml.org
"""
try:
import yaml
except ImportError as e:
raise ImportError("write_yaml() requires PyYAML: http://pyyaml.org/") from e
yaml.dump(G_to_be_yaml, path_for_yaml_output, **kwds)
@open_file(0, mode="r")
def read_yaml(path):
"""Read graph in YAML format from path.
YAML is a data serialization format designed for human readability
and interaction with scripting languages [1]_.
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.path_graph(4)
>>> nx.write_yaml(G, "test.yaml")
>>> G = nx.read_yaml("test.yaml")
References
----------
.. [1] http://www.yaml.org
"""
try:
import yaml
except ImportError as e:
raise ImportError("read_yaml() requires PyYAML: http://pyyaml.org/") from e
G = yaml.load(path, Loader=yaml.FullLoader)
return G

View file

@ -0,0 +1,102 @@
"""
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
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")
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=networkx.DiGraph(read_p2g(path))
"""
lines = (line.decode(encoding) for line in path)
G = parse_p2g(lines)
return G
def parse_p2g(lines):
"""Parse p2g format graph from string or iterable.
Returns
-------
MultiDiGraph
"""
description = next(lines).strip()
# are multiedges (parallel edges) allowed?
G = networkx.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

View file

@ -0,0 +1,284 @@
"""
*****
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 e:
e.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")
def read_pajek(path, encoding="UTF-8"):
"""Read graph in Pajek format from path.
Parameters
----------
path : file or string
File or filename to write.
Filenames ending in .gz or .bz2 will be uncompressed.
Returns
-------
G : NetworkX MultiGraph or MultiDiGraph.
Examples
--------
>>> G = nx.path_graph(4)
>>> nx.write_pajek(G, "test.net")
>>> G = nx.read_pajek("test.net")
To create a Graph instead of a MultiGraph use
>>> G1 = nx.Graph(G)
References
----------
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
for format information.
"""
lines = (line.decode(encoding) for line in path)
return parse_pajek(lines)
def parse_pajek(lines):
"""Parse Pajek format graph from string or iterable.
Parameters
----------
lines : string or iterable
Data in Pajek format.
Returns
-------
G : NetworkX graph
See Also
--------
read_pajek()
"""
import shlex
# multigraph=False
if isinstance(lines, str):
lines = iter(lines.split("\n"))
lines = iter([line.rstrip("\n") for line in lines])
G = nx.MultiDiGraph() # are multiedges allowed in Pajek? assume yes
labels = [] # in the order of the file, needed for matrix
while lines:
try:
l = next(lines)
except: # EOF
break
if l.lower().startswith("*network"):
try:
label, name = l.split(None, 1)
except ValueError:
# Line was not of the form: *network NAME
pass
else:
G.graph["name"] = name
elif l.lower().startswith("*vertices"):
nodelabels = {}
l, nnodes = l.split()
for i in range(int(nnodes)):
l = next(lines)
try:
splitline = [
x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8"))
]
except AttributeError:
splitline = shlex.split(str(l))
id, label = splitline[0:2]
labels.append(label)
G.add_node(label)
nodelabels[id] = label
G.nodes[label]["id"] = id
try:
x, y, shape = splitline[2:5]
G.nodes[label].update(
{"x": float(x), "y": float(y), "shape": shape}
)
except:
pass
extra_attr = zip(splitline[5::2], splitline[6::2])
G.nodes[label].update(extra_attr)
elif l.lower().startswith("*edges") or l.lower().startswith("*arcs"):
if l.lower().startswith("*edge"):
# switch from multidigraph to multigraph
G = nx.MultiGraph(G)
if l.lower().startswith("*arcs"):
# switch to directed with multiple arcs for each existing edge
G = G.to_directed()
for l in lines:
try:
splitline = [
x.decode("utf-8") for x in shlex.split(str(l).encode("utf-8"))
]
except AttributeError:
splitline = shlex.split(str(l))
if len(splitline) < 2:
continue
ui, vi = splitline[0:2]
u = nodelabels.get(ui, ui)
v = nodelabels.get(vi, vi)
# parse the data attached to this edge and put in a dictionary
edge_data = {}
try:
# there should always be a single value on the edge?
w = splitline[2:3]
edge_data.update({"weight": float(w[0])})
except:
pass
# if there isn't, just assign a 1
# edge_data.update({'value':1})
extra_attr = zip(splitline[3::2], splitline[4::2])
edge_data.update(extra_attr)
# if G.has_edge(u,v):
# multigraph=True
G.add_edge(u, v, **edge_data)
elif l.lower().startswith("*matrix"):
G = nx.DiGraph(G)
adj_list = (
(labels[row], labels[col], {"weight": int(data)})
for (row, line) in enumerate(lines)
for (col, data) in enumerate(line.split())
if int(data) != 0
)
G.add_edges_from(adj_list)
return G
def make_qstr(t):
"""Returns the string representation of t.
Add outer double-quotes if the string has a space.
"""
if not isinstance(t, str):
t = str(t)
if " " in t:
t = f'"{t}"'
return t

View file

@ -0,0 +1,374 @@
# 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 *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: http://users.cecs.anu.edu.au/~bdm/data/formats.html
"""
import networkx as nx
from networkx.exception import NetworkXError
from networkx.utils import open_file, not_implemented_for
from networkx.readwrite.graph6 import data_to_n, n_to_data
__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"
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
<http://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
<http://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")
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() 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
<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_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() 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
<http://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)

View file

@ -0,0 +1,266 @@
"""
Unit tests for adjlist.
"""
import io
import pytest
import os
import tempfile
import networkx as nx
from networkx.testing import assert_nodes_equal, assert_edges_equal, assert_graphs_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 != 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 != 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 != 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 != 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 != 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 != 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 != 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 != 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 != 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 != 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()))

View file

@ -0,0 +1,275 @@
"""
Unit tests for edgelists.
"""
import pytest
import io
import tempfile
import os
import networkx as nx
from networkx.testing import assert_edges_equal, assert_nodes_equal, assert_graphs_equal
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_read_edgelist_1(self):
s = b"""\
# comment line
1 2
# comment line
2 3
"""
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int)
assert_edges_equal(G.edges(), [(1, 2), (2, 3)])
def test_read_edgelist_2(self):
s = b"""\
# comment line
1 2 2.0
# comment line
2 3 3.0
"""
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int, data=False)
assert_edges_equal(G.edges(), [(1, 2), (2, 3)])
bytesIO = io.BytesIO(s)
G = nx.read_weighted_edgelist(bytesIO, nodetype=int)
assert_edges_equal(
G.edges(data=True), [(1, 2, {"weight": 2.0}), (2, 3, {"weight": 3.0})]
)
def test_read_edgelist_3(self):
s = b"""\
# comment line
1 2 {'weight':2.0}
# comment line
2 3 {'weight':3.0}
"""
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int, data=False)
assert_edges_equal(G.edges(), [(1, 2), (2, 3)])
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int, data=True)
assert_edges_equal(
G.edges(data=True), [(1, 2, {"weight": 2.0}), (2, 3, {"weight": 3.0})]
)
def test_read_edgelist_4(self):
s = b"""\
# comment line
1 2 {'weight':2.0}
# comment line
2 3 {'weight':3.0}
"""
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int, data=False)
assert_edges_equal(G.edges(), [(1, 2), (2, 3)])
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int, data=True)
assert_edges_equal(
G.edges(data=True), [(1, 2, {"weight": 2.0}), (2, 3, {"weight": 3.0})]
)
s = """\
# comment line
1 2 {'weight':2.0}
# comment line
2 3 {'weight':3.0}
"""
StringIO = io.StringIO(s)
G = nx.read_edgelist(StringIO, nodetype=int, data=False)
assert_edges_equal(G.edges(), [(1, 2), (2, 3)])
StringIO = io.StringIO(s)
G = nx.read_edgelist(StringIO, nodetype=int, data=True)
assert_edges_equal(
G.edges(data=True), [(1, 2, {"weight": 2.0}), (2, 3, {"weight": 3.0})]
)
def test_read_edgelist_5(self):
s = b"""\
# comment line
1 2 {'weight':2.0, 'color':'green'}
# comment line
2 3 {'weight':3.0, 'color':'red'}
"""
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int, data=False)
assert_edges_equal(G.edges(), [(1, 2), (2, 3)])
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int, data=True)
assert_edges_equal(
G.edges(data=True),
[
(1, 2, {"weight": 2.0, "color": "green"}),
(2, 3, {"weight": 3.0, "color": "red"}),
],
)
def test_read_edgelist_6(self):
s = b"""\
# comment line
1, 2, {'weight':2.0, 'color':'green'}
# comment line
2, 3, {'weight':3.0, 'color':'red'}
"""
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int, data=False, delimiter=",")
assert_edges_equal(G.edges(), [(1, 2), (2, 3)])
bytesIO = io.BytesIO(s)
G = nx.read_edgelist(bytesIO, nodetype=int, data=True, delimiter=",")
assert_edges_equal(
G.edges(data=True),
[
(1, 2, {"weight": 2.0, "color": "green"}),
(2, 3, {"weight": 3.0, "color": "red"}),
],
)
def test_write_edgelist_1(self):
fh = io.BytesIO()
G = nx.OrderedGraph()
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.OrderedGraph()
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.OrderedGraph()
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.OrderedGraph()
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 != 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 != 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 != 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 != 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)

View file

@ -0,0 +1,658 @@
import io
import sys
import time
import pytest
import networkx as nx
class TestGEXF:
@classmethod
def setup_class(cls):
_ = pytest.importorskip("xml.etree.ElementTree")
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"
# FIXME: We should test xml without caring about their order This is causing a
# problem b/c of a change in Python 3.8
#
# "Prior to Python 3.8, the serialisation order of the XML attributes of
# elements was artificially made predictable by sorting the attributes by their
# name. Based on the now guaranteed ordering of dicts, this arbitrary
# reordering was removed in Python 3.8 to preserve the order in which
# attributes were originally parsed or created by user code."
#
# https://docs.python.org/3.8/library/xml.etree.elementtree.html
# https://bugs.python.org/issue34160
def test_write_with_node_attributes(self):
# Addresses #673.
G = nx.OrderedGraph()
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
if sys.version_info < (3, 8):
expected = f"""<gexf version="1.2" xmlns="http://www.gexf.net/1.2\
draft" 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">
<meta lastmodifieddate="{time.strftime('%Y-%m-%d')}">
<creator>NetworkX {nx.__version__}</creator>
</meta>
<graph defaultedgetype="undirected" mode="dynamic" name="" timeformat="long">
<nodes>
<node end="1" id="0" label="0" pid="0" start="0" />
<node end="2" id="1" label="1" pid="1" start="1" />
<node end="3" id="2" label="2" pid="2" start="2" />
<node end="4" id="3" label="3" pid="3" start="3" />
</nodes>
<edges>
<edge id="0" source="0" target="1" />
<edge id="1" source="1" target="2" />
<edge id="2" source="2" target="3" />
</edges>
</graph>
</gexf>"""
else:
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)])
if sys.version_info < (3, 8):
expected = f"""<gexf version="1.2" 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">
<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 id="0" source="0" target="1" />
<edge id="2" source="1" target="2" />
<edge id="1" source="2" target="3" />
</edges>
</graph>
</gexf>"""
else:
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):
G = nx.path_graph(4)
try:
import numpy
except ImportError:
return
nx.set_node_attributes(G, {n: n for n in numpy.arange(4)}, "number")
G[0][1]["edge-number"] = numpy.float64(1.1)
if sys.version_info < (3, 8):
expected = f"""<gexf version="1.2" 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">
<meta lastmodifieddate="{time.strftime('%Y-%m-%d')}">
<creator>NetworkX {nx.__version__}</creator>
</meta>
<graph defaultedgetype="undirected" mode="static" name="">
<attributes class="edge" mode="static">
<attribute id="1" title="edge-number" type="float" />
</attributes>
<attributes class="node" mode="static">
<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 id="0" source="0" target="1">
<attvalues>
<attvalue for="1" value="1.1" />
</attvalues>
</edge>
<edge id="1" source="1" target="2" />
<edge id="2" source="2" target="3" />
</edges>
</graph>
</gexf>"""
else:
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", wight=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()
)
# 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()
)

View file

@ -0,0 +1,612 @@
from ast import literal_eval
import codecs
from contextlib import contextmanager
import io
import pytest
import networkx as nx
from networkx.readwrite.gml import literal_stringizer, literal_destringizer
import os
import tempfile
from textwrap import dedent
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 [e for e in sorted(G.edges())] == [
("Node 1", "Node 2"),
("Node 2", "Node 3"),
("Node 3", "Node 1"),
]
assert [e for e in 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")
def test_tuplelabels(self):
# https://github.com/networkx/networkx/pull/1048
# Writing tuple labels to GML failed.
G = nx.OrderedGraph()
G.add_edge((0, 1), (1, 0))
data = "\n".join(nx.generate_gml(G, stringizer=literal_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 &#34;quoted&#34; and this is a copyright: &#169;"
]
]"""
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&#169;"
]
]"""
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_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,
"'",
'"&&amp;&&#34;"',
[{(b"\xfd",): "\x7f", chr(0x4444): (1, 2)}, (2, "3")],
]
try: # fails under IronPython
data.append(chr(0x14444))
except ValueError:
data.append(chr(0x1444))
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=dict(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, dict(int=-1, data=dict(data=data)))]
assert list(G.edges(data=True)) == [(0, 0, dict(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 "&amp;&#34;&#xf;&#x4444;&#1234567890;&#x1234567890abcdef;&unknown;"
]"""
G = nx.parse_gml(gml)
assert (
'&"\x0f' + chr(0x4444) + "&#1234567890;&#x1234567890abcdef;&unknown;"
== G.name
)
gml = "\n".join(nx.generate_gml(G))
alnu = "#1234567890;&#38;#x1234567890abcdef"
answer = (
"""graph [
name "&#38;&#34;&#15;&#17476;&#38;"""
+ alnu
+ """;&#38;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 convertable 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)
G = nx.Graph()
G.graph["data"] = []
assert_generate_error(G)
assert_generate_error(G, stringizer=len)
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)
@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"]}

View file

@ -0,0 +1,79 @@
import os
import tempfile
import networkx as nx
from networkx.testing.utils import (
assert_graphs_equal,
assert_edges_equal,
assert_nodes_equal,
)
class TestGpickle:
@classmethod
def setup_class(cls):
G = nx.Graph(name="test")
e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")]
G.add_edges_from(e, width=10)
G.add_node("g", color="green")
G.graph["number"] = 1
DG = nx.DiGraph(G)
MG = nx.MultiGraph(G)
MG.add_edge("a", "a")
MDG = nx.MultiDiGraph(G)
MDG.add_edge("a", "a")
fG = G.copy()
fDG = DG.copy()
fMG = MG.copy()
fMDG = MDG.copy()
nx.freeze(fG)
nx.freeze(fDG)
nx.freeze(fMG)
nx.freeze(fMDG)
cls.G = G
cls.DG = DG
cls.MG = MG
cls.MDG = MDG
cls.fG = fG
cls.fDG = fDG
cls.fMG = fMG
cls.fMDG = fMDG
def test_gpickle(self):
for G in [
self.G,
self.DG,
self.MG,
self.MDG,
self.fG,
self.fDG,
self.fMG,
self.fMDG,
]:
(fd, fname) = tempfile.mkstemp()
nx.write_gpickle(G, fname)
Gin = nx.read_gpickle(fname)
assert_nodes_equal(list(G.nodes(data=True)), list(Gin.nodes(data=True)))
assert_edges_equal(list(G.edges(data=True)), list(Gin.edges(data=True)))
assert_graphs_equal(G, Gin)
os.close(fd)
os.unlink(fname)
def test_protocol(self):
for G in [
self.G,
self.DG,
self.MG,
self.MDG,
self.fG,
self.fDG,
self.fMG,
self.fMDG,
]:
with tempfile.TemporaryFile() as f:
nx.write_gpickle(G, f, 0)
f.seek(0)
Gin = nx.read_gpickle(f)
assert_nodes_equal(list(G.nodes(data=True)), list(Gin.nodes(data=True)))
assert_edges_equal(list(G.edges(data=True)), list(Gin.edges(data=True)))
assert_graphs_equal(G, Gin)

View file

@ -0,0 +1,118 @@
from io import BytesIO
import tempfile
import pytest
import networkx as nx
import networkx.readwrite.graph6 as g6
from networkx.testing.utils import assert_edges_equal
from networkx.testing.utils import assert_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"
def test_no_directed_graphs(self):
with pytest.raises(nx.NetworkXNotImplemented):
nx.write_graph6(nx.DiGraph(), 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"
def test_relabeling(self):
G = nx.Graph([(0, 1)])
assert g6.to_graph6_bytes(G) == b">>graph6<<A_\n"
G = nx.Graph([(1, 2)])
assert g6.to_graph6_bytes(G) == b">>graph6<<A_\n"
G = nx.Graph([(1, 42)])
assert g6.to_graph6_bytes(G) == b">>graph6<<A_\n"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
import networkx as nx
import io
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())

View file

@ -0,0 +1,61 @@
import networkx as nx
import io
from networkx.readwrite.p2g import read_p2g, write_p2g
from networkx.testing import assert_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.OrderedDiGraph()
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())

View file

@ -0,0 +1,129 @@
"""
Pajek tests
"""
import networkx as nx
import os
import tempfile
from networkx.testing import assert_edges_equal, assert_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

View file

@ -0,0 +1,287 @@
"""Unit tests for shp.
"""
import os
import tempfile
import pytest
ogr = pytest.importorskip("osgeo.ogr")
import networkx as nx
class TestShp:
def setup_method(self):
def createlayer(driver, layerType=ogr.wkbLineString):
lyr = driver.CreateLayer("edges", None, layerType)
namedef = ogr.FieldDefn("Name", ogr.OFTString)
namedef.SetWidth(32)
lyr.CreateField(namedef)
return lyr
drv = ogr.GetDriverByName("ESRI Shapefile")
testdir = os.path.join(tempfile.gettempdir(), "shpdir")
shppath = os.path.join(tempfile.gettempdir(), "tmpshp.shp")
multi_shppath = os.path.join(tempfile.gettempdir(), "tmp_mshp.shp")
self.deletetmp(drv, testdir, shppath, multi_shppath)
os.mkdir(testdir)
self.names = ["a", "b", "c", "c"] # edgenames
self.paths = (
[(1.0, 1.0), (2.0, 2.0)],
[(2.0, 2.0), (3.0, 3.0)],
[(0.9, 0.9), (4.0, 0.9), (4.0, 2.0)],
)
self.simplified_names = ["a", "b", "c"] # edgenames
self.simplified_paths = (
[(1.0, 1.0), (2.0, 2.0)],
[(2.0, 2.0), (3.0, 3.0)],
[(0.9, 0.9), (4.0, 2.0)],
)
self.multi_names = ["a", "a", "a", "a"] # edgenames
shp = drv.CreateDataSource(shppath)
lyr = createlayer(shp)
for path, name in zip(self.paths, self.names):
feat = ogr.Feature(lyr.GetLayerDefn())
g = ogr.Geometry(ogr.wkbLineString)
for p in path:
g.AddPoint_2D(*p)
feat.SetGeometry(g)
feat.SetField("Name", name)
lyr.CreateFeature(feat)
# create single record multiline shapefile for testing
multi_shp = drv.CreateDataSource(multi_shppath)
multi_lyr = createlayer(multi_shp, ogr.wkbMultiLineString)
multi_g = ogr.Geometry(ogr.wkbMultiLineString)
for path in self.paths:
g = ogr.Geometry(ogr.wkbLineString)
for p in path:
g.AddPoint_2D(*p)
multi_g.AddGeometry(g)
multi_feat = ogr.Feature(multi_lyr.GetLayerDefn())
multi_feat.SetGeometry(multi_g)
multi_feat.SetField("Name", "a")
multi_lyr.CreateFeature(multi_feat)
self.shppath = shppath
self.multi_shppath = multi_shppath
self.testdir = testdir
self.drv = drv
def deletetmp(self, drv, *paths):
for p in paths:
if os.path.exists(p):
drv.DeleteDataSource(p)
def testload(self):
def compare_graph_paths_names(g, paths, names):
expected = nx.DiGraph()
for p in paths:
nx.add_path(expected, p)
assert sorted(expected.nodes) == sorted(g.nodes)
assert sorted(expected.edges()) == sorted(g.edges())
g_names = [g.get_edge_data(s, e)["Name"] for s, e in g.edges()]
assert names == sorted(g_names)
# simplified
G = nx.read_shp(self.shppath)
compare_graph_paths_names(G, self.simplified_paths, self.simplified_names)
# unsimplified
G = nx.read_shp(self.shppath, simplify=False)
compare_graph_paths_names(G, self.paths, self.names)
# multiline unsimplified
G = nx.read_shp(self.multi_shppath, simplify=False)
compare_graph_paths_names(G, self.paths, self.multi_names)
def checkgeom(self, lyr, expected):
feature = lyr.GetNextFeature()
actualwkt = []
while feature:
actualwkt.append(feature.GetGeometryRef().ExportToWkt())
feature = lyr.GetNextFeature()
assert sorted(expected) == sorted(actualwkt)
def test_geometryexport(self):
expectedpoints_simple = (
"POINT (1 1)",
"POINT (2 2)",
"POINT (3 3)",
"POINT (0.9 0.9)",
"POINT (4 2)",
)
expectedlines_simple = (
"LINESTRING (1 1,2 2)",
"LINESTRING (2 2,3 3)",
"LINESTRING (0.9 0.9,4.0 0.9,4 2)",
)
expectedpoints = (
"POINT (1 1)",
"POINT (2 2)",
"POINT (3 3)",
"POINT (0.9 0.9)",
"POINT (4.0 0.9)",
"POINT (4 2)",
)
expectedlines = (
"LINESTRING (1 1,2 2)",
"LINESTRING (2 2,3 3)",
"LINESTRING (0.9 0.9,4.0 0.9)",
"LINESTRING (4.0 0.9,4 2)",
)
tpath = os.path.join(tempfile.gettempdir(), "shpdir")
G = nx.read_shp(self.shppath)
nx.write_shp(G, tpath)
shpdir = ogr.Open(tpath)
self.checkgeom(shpdir.GetLayerByName("nodes"), expectedpoints_simple)
self.checkgeom(shpdir.GetLayerByName("edges"), expectedlines_simple)
# Test unsimplified
# Nodes should have additional point,
# edges should be 'flattened'
G = nx.read_shp(self.shppath, simplify=False)
nx.write_shp(G, tpath)
shpdir = ogr.Open(tpath)
self.checkgeom(shpdir.GetLayerByName("nodes"), expectedpoints)
self.checkgeom(shpdir.GetLayerByName("edges"), expectedlines)
def test_attributeexport(self):
def testattributes(lyr, graph):
feature = lyr.GetNextFeature()
while feature:
coords = []
ref = feature.GetGeometryRef()
last = ref.GetPointCount() - 1
edge_nodes = (ref.GetPoint_2D(0), ref.GetPoint_2D(last))
name = feature.GetFieldAsString("Name")
assert graph.get_edge_data(*edge_nodes)["Name"] == name
feature = lyr.GetNextFeature()
tpath = os.path.join(tempfile.gettempdir(), "shpdir")
G = nx.read_shp(self.shppath)
nx.write_shp(G, tpath)
shpdir = ogr.Open(tpath)
edges = shpdir.GetLayerByName("edges")
testattributes(edges, G)
# Test export of node attributes in nx.write_shp (#2778)
def test_nodeattributeexport(self):
tpath = os.path.join(tempfile.gettempdir(), "shpdir")
G = nx.DiGraph()
A = (0, 0)
B = (1, 1)
C = (2, 2)
G.add_edge(A, B)
G.add_edge(A, C)
label = "node_label"
for n, d in G.nodes(data=True):
d["label"] = label
nx.write_shp(G, tpath)
H = nx.read_shp(tpath)
for n, d in H.nodes(data=True):
assert d["label"] == label
def test_wkt_export(self):
G = nx.DiGraph()
tpath = os.path.join(tempfile.gettempdir(), "shpdir")
points = ("POINT (0.9 0.9)", "POINT (4 2)")
line = ("LINESTRING (0.9 0.9,4 2)",)
G.add_node(1, Wkt=points[0])
G.add_node(2, Wkt=points[1])
G.add_edge(1, 2, Wkt=line[0])
try:
nx.write_shp(G, tpath)
except Exception as e:
assert False, e
shpdir = ogr.Open(tpath)
self.checkgeom(shpdir.GetLayerByName("nodes"), points)
self.checkgeom(shpdir.GetLayerByName("edges"), line)
def teardown_method(self):
self.deletetmp(self.drv, self.testdir, self.shppath)
def test_read_shp_nofile():
with pytest.raises(RuntimeError):
G = nx.read_shp("hopefully_this_file_will_not_be_available")
class TestMissingGeometry:
def setup_method(self):
self.setup_path()
self.delete_shapedir()
self.create_shapedir()
def teardown_method(self):
self.delete_shapedir()
def setup_path(self):
self.path = os.path.join(tempfile.gettempdir(), "missing_geometry")
def create_shapedir(self):
drv = ogr.GetDriverByName("ESRI Shapefile")
shp = drv.CreateDataSource(self.path)
lyr = shp.CreateLayer("nodes", None, ogr.wkbPoint)
feature = ogr.Feature(lyr.GetLayerDefn())
feature.SetGeometry(None)
lyr.CreateFeature(feature)
feature.Destroy()
def delete_shapedir(self):
drv = ogr.GetDriverByName("ESRI Shapefile")
if os.path.exists(self.path):
drv.DeleteDataSource(self.path)
def test_missing_geometry(self):
with pytest.raises(nx.NetworkXError):
G = nx.read_shp(self.path)
class TestMissingAttrWrite:
def setup_method(self):
self.setup_path()
self.delete_shapedir()
def teardown_method(self):
self.delete_shapedir()
def setup_path(self):
self.path = os.path.join(tempfile.gettempdir(), "missing_attributes")
def delete_shapedir(self):
drv = ogr.GetDriverByName("ESRI Shapefile")
if os.path.exists(self.path):
drv.DeleteDataSource(self.path)
def test_missing_attributes(self):
G = nx.DiGraph()
A = (0, 0)
B = (1, 1)
C = (2, 2)
G.add_edge(A, B, foo=100)
G.add_edge(A, C)
nx.write_shp(G, self.path)
H = nx.read_shp(self.path)
for u, v, d in H.edges(data=True):
if u == A and v == B:
assert d["foo"] == 100
if u == A and v == C:
assert d["foo"] is None

View file

@ -0,0 +1,173 @@
from io import BytesIO
import tempfile
import pytest
import networkx as nx
from networkx.testing.utils import assert_edges_equal
from networkx.testing.utils import assert_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)

View file

@ -0,0 +1,50 @@
"""
Unit tests for yaml.
"""
import os
import tempfile
import pytest
yaml = pytest.importorskip("yaml")
import networkx as nx
from networkx.testing import assert_edges_equal, assert_nodes_equal
class TestYaml:
@classmethod
def setup_class(cls):
cls.build_graphs()
@classmethod
def build_graphs(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.MG = nx.MultiGraph()
cls.MG.add_weighted_edges_from([(1, 2, 5), (1, 2, 5), (1, 2, 1), (3, 3, 42)])
def assert_equal(self, G, data=False):
(fd, fname) = tempfile.mkstemp()
nx.write_yaml(G, fname)
Gin = nx.read_yaml(fname)
assert_nodes_equal(list(G), list(Gin))
assert_edges_equal(G.edges(data=data), Gin.edges(data=data))
os.close(fd)
os.unlink(fname)
def testUndirected(self):
self.assert_equal(self.G, data=False)
def testDirected(self):
self.assert_equal(self.DG, data=False)
def testMultiGraph(self):
self.assert_equal(self.MG, data=True)