chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:36:30 +08:00
commit 55ab4e4a73
473 changed files with 72932 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
from easygraph.readwrite.edgelist import *
from easygraph.readwrite.gexf import *
from easygraph.readwrite.gml import *
from easygraph.readwrite.graphml import *
from easygraph.readwrite.graphviz import *
from easygraph.readwrite.json_graph import *
from easygraph.readwrite.pajek import *
from easygraph.readwrite.pickle import *
from easygraph.readwrite.ucinet import *
+463
View File
@@ -0,0 +1,463 @@
import easygraph as eg
from easygraph.utils import open_file
__all__ = [
"parse_edgelist",
"generate_edgelist",
"write_edgelist",
"read_edgelist",
"read_weighted_edgelist",
"write_weighted_edgelist",
]
def parse_edgelist(
lines, comments="#", delimiter=None, create_using=None, nodetype=None, data=True
):
"""Parse lines of an edge list representation of a graph.
Parameters
----------
lines : list or iterator of strings
Input data in edgelist format
comments : string, optional
Marker for comment lines. Default is `'#'`. To specify that no character
should be treated as a comment, use ``comments=None``.
delimiter : string, optional
Separator for node labels. Default is `None`, meaning any whitespace.
create_using : EasyGraph graph constructor, optional (default=eg.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: EasyGraph Graph
The graph corresponding to lines
Examples
--------
Edgelist with no data:
>>> lines = ["1 2", "2 3", "3 4"]
>>> G = eg.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 = eg.parse_edgelist(lines, nodetype=int)
>>> list(G)
[1, 2, 3, 4]
>>> list(G.edges)
[(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 = eg.parse_edgelist(lines, nodetype=int, data=(("weight", float),))
>>> list(G)
[1, 2, 3, 4]
>>> list(G.edges)
[(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 = eg.empty_graph(0, create_using)
for line in lines:
if comments is not None:
p = line.find(comments)
if p >= 0:
line = line[:p]
if not line:
continue
# split line, should have 2 or more
s = line.strip().split(delimiter)
if len(s) < 2:
continue
u = s.pop(0)
v = s.pop(0)
d = s
if nodetype is not None:
try:
u = nodetype(u)
v = nodetype(v)
except Exception as err:
raise TypeError(
f"Failed to convert nodes {u},{v} to type {nodetype}."
) from err
if len(d) == 0 or data is False:
# no data or data type specified
edgedata = {}
elif data is True:
# no edge types specified
try: # try to evaluate as dictionary
if delimiter == ",":
edgedata_str = ",".join(d)
else:
edgedata_str = " ".join(d)
edgedata = dict(literal_eval(edgedata_str.strip()))
except Exception as err:
raise TypeError(
f"Failed to convert edge data ({d}) to dictionary."
) from err
else:
# convert edge data to dictionary with specified keys and type
if len(d) != len(data):
raise IndexError(
f"Edge data {d} and data_keys {data} are not the same length"
)
edgedata = {}
for (edge_key, edge_type), edge_value in zip(data, d):
try:
edge_value = edge_type(edge_value)
except Exception as err:
raise TypeError(
f"Failed to convert {edge_key} data {edge_value} "
f"to type {edge_type}."
) from err
edgedata.update({edge_key: edge_value})
G.add_edge(u, v, **edgedata)
return G
def generate_edgelist(G, delimiter=" ", data=True):
"""Generate a single line of the graph G in edge list format.
Parameters
----------
G : EasyGraph 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 = eg.lollipop_graph(4, 3)
>>> G[1][2]["weight"] = 3
>>> G[3][4]["capacity"] = 12
>>> for line in eg.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 eg.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 eg.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
"""
edges = G.edges
if edges and len(edges[0]) > 3:
# multigraph
edges = ((u, v, d) for u, v, _, d in edges)
if data is True:
for u, v, d in edges:
e = u, v, dict(d)
yield delimiter.join(map(str, e))
elif data is False:
for u, v, _ in edges:
e = u, v
yield delimiter.join(map(str, e))
else:
for u, v, d in edges:
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 EasyGraph 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 = eg.path_graph(4)
>>> eg.write_edgelist(G, "test.edgelist")
>>> G = eg.path_graph(4)
>>> fh = open("test.edgelist", "wb")
>>> eg.write_edgelist(G, fh)
>>> eg.write_edgelist(G, "test.edgelist.gz")
>>> eg.write_edgelist(G, "test.edgelist.gz", data=False)
>>> G = eg.Graph()
>>> G.add_edge(1, 2, weight=7, color="red")
>>> eg.write_edgelist(G, "test.edgelist", data=False)
>>> eg.write_edgelist(G, "test.edgelist", data=["color"])
>>> eg.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))
@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. To specify that
no character should be treated as a comment, use ``comments=None``.
delimiter : string, optional
The string used to separate values. The default is whitespace.
create_using : EasyGraph graph constructor, optional (default=eg.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 easygraph Graph or other type specified with create_using
Examples
--------
>>> eg.write_edgelist(eg.path_graph(4), "test.edgelist")
>>> G = eg.read_edgelist("test.edgelist")
>>> fh = open("test.edgelist", "rb")
>>> G = eg.read_edgelist(fh)
>>> fh.close()
>>> G = eg.read_edgelist("test.edgelist", nodetype=int)
>>> G = eg.read_edgelist("test.edgelist", create_using=eg.DiGraph)
Edgelist with data in a list:
>>> textline = "1 2 3"
>>> fh = open("test.edgelist", "w")
>>> d = fh.write(textline)
>>> fh.close()
>>> G = eg.read_edgelist("test.edgelist", nodetype=int, data=(("weight", float),))
>>> list(G)
[1, 2]
>>> list(G.edges)
[(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 EasyGraph 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 = eg.Graph()
>>> G.add_edge(1, 2, weight=7)
>>> eg.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 : EasyGraph graph constructor, optional (default=eg.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 easygraph 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=eg.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
+803
View File
@@ -0,0 +1,803 @@
"""
Read graphs in GML format.
"GML, the Graph Modelling Language, is our proposal for a portable
file format for graphs. GML's key features are portability, simple
syntax, extensibility and flexibility. A GML file consists of a
hierarchical key-value lists. Graphs can be annotated with arbitrary
data structures. The idea for a common file format was born at the
GD'95; this proposal is the outcome of many discussions. GML is the
standard file format in the Graphlet graph editor system. It has been
overtaken and adapted by several other systems for drawing graphs."
GML files are stored using a 7-bit ASCII encoding with any extended
ASCII characters (iso8859-1) appearing as HTML character entities.
You will need to give some thought into how the exported data should
interact with different languages and even different Python versions.
Re-importing from gml is also a concern.
Without specifying a `stringizer`/`destringizer`, the code is capable of
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For writing other data types, and for reading data other
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
For additional documentation on the GML file format, please see the
`GML website <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
Several example graphs in GML format may be found on Mark Newman's
`Network data page <http://www-personal.umich.edu/~mejn/netdata/>`_.
"""
import html.entities as htmlentitydefs
import re
import warnings
from ast import literal_eval
from collections import defaultdict
from enum import Enum
from io import StringIO
from typing import Any
from typing import NamedTuple
from unicodedata import category
import easygraph as eg
from easygraph.utils import open_file
from easygraph.utils.exception import EasyGraphError
__all__ = ["read_gml", "parse_gml", "generate_gml", "write_gml"]
LIST_START_VALUE = "_easygraph_list_start"
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 err:
raise ValueError(f"{orig_rep!r} is not a valid Python literal") from err
else:
raise ValueError(f"{rep!r} is not a string")
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
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 : EasyGraph graph
The parsed graph.
Raises
------
EasyGraphError
If the input cannot be parsed.
See Also
--------
write_gml, read_gml
Notes
-----
This stores nested GML attributes as dictionaries in the EasyGraph graph,
node, and edge attribute structures.
GML files are stored using a 7-bit ASCII encoding with any extended
ASCII characters (iso8859-1) appearing as HTML character entities.
Without specifying a `stringizer`/`destringizer`, the code is capable of
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For writing other data types, and for reading data other
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
For additional documentation on the GML file format, please see the
`GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
See the module docstring :mod:`easygraph.readwrite.gml` for more details.
"""
def decode_line(line):
if isinstance(line, bytes):
try:
line.decode("ascii")
except UnicodeDecodeError as err:
raise EasyGraphError("input is not ASCII-encoded") from err
if not isinstance(line, str):
line = str(line)
return line
def filter_lines(lines):
if isinstance(lines, str):
lines = decode_line(lines)
lines = lines.splitlines()
yield from lines
else:
for line in lines:
line = decode_line(line)
if line and line[-1] == "\n":
line = line[:-1]
if line.find("\n") != -1:
raise EasyGraphError("input line contains newline")
yield line
G = parse_gml_lines(filter_lines(lines), label, destringizer)
return G
def parse_gml_lines(lines, label, destringizer):
"""Parse GML `lines` into a graph."""
def tokenize():
patterns = [
r"[A-Za-z][0-9A-Za-z_]*\b", # keys
# reals
r"[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.[0-9]*|INF)(?:[Ee][+-]?[0-9]+)?",
r"[+-]?[0-9]+", # ints
r'".*?"', # strings
r"\[", # dict start
r"\]", # dict end
r"#.*$|\s+", # comments and whitespaces
]
tokens = re.compile("|".join(f"({pattern})" for pattern in patterns))
lineno = 0
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 EasyGraphError(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 EasyGraphError(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_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_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:
if key in ("id", "label", "source", "target"):
try:
# String convert the token value
value = unescape(str(curr_token.value))
if destringizer:
try:
value = destringizer(value)
except ValueError:
pass
curr_token = next(tokens)
except Exception:
msg = (
"an int, float, string, '[' or string"
+ " convertible ASCII value for node id or label"
)
unexpected(curr_token, msg)
elif curr_token.value in {"NAN", "INF"}:
value = float(curr_token.value)
curr_token = next(tokens)
else: # Otherwise error out
unexpected(curr_token, "an int, float, string or '['")
dct[key].append(value)
def clean_dict_value(value):
if not isinstance(value, list):
return value
if len(value) == 1:
return value[0]
if value[0] == LIST_START_VALUE:
return value[1:]
return value
dct = {key: clean_dict_value(value) for key, value in dct.items()}
return curr_token, dct
def parse_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 EasyGraphError("input contains no graph")
graph = dct["graph"]
if isinstance(graph, list):
raise EasyGraphError("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 = eg.DiGraph() if directed else eg.Graph()
else:
G = eg.MultiDiGraph() if directed else eg.MultiGraph()
graph_attr = {k: v for k, v in graph.items() if k not in ("node", "edge")}
G.graph.update(graph_attr)
def pop_attr(dct, category, attr, i):
try:
return dct.pop(attr)
except KeyError as err:
raise EasyGraphError(f"{category} #{i} has no {attr!r} attribute") from err
nodes = graph.get("node", [])
mapping = {}
node_labels = set()
for i, node in enumerate(nodes if isinstance(nodes, list) else [nodes]):
id = pop_attr(node, "node", "id", i)
if id in G:
raise EasyGraphError(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 EasyGraphError(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 EasyGraphError(f"edge #{i} has undefined source {source!r}")
if target not in G:
raise EasyGraphError(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 EasyGraphError(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 EasyGraphError(msg + " is duplicated\n" + msg2)
G.add_edge(source, target, key, **edge)
if label is not None and label != "id":
G = eg.relabel_nodes(G, mapping)
return G
def generate_gml(G, stringizer=None):
r"""Generate a single entry of the graph `G` in GML format.
Parameters
----------
G : EasyGraph 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
------
EasyGraphError
If `stringizer` cannot convert a value into a string, or the value to
convert is not a string while `stringizer` is None.
See Also
--------
literal_stringizer
Notes
-----
Graph attributes named 'directed', 'multigraph', 'node' or
'edge', node attributes named 'id' or 'label', edge attributes
named 'source' or 'target' (or 'key' if `G` is a multigraph)
are ignored because these attribute names are used to encode the graph
structure.
GML files are stored using a 7-bit ASCII encoding with any extended
ASCII characters (iso8859-1) appearing as HTML character entities.
Without specifying a `stringizer`/`destringizer`, the code is capable of
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For writing other data types, and for reading data other
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
For additional documentation on the GML file format, please see the
`GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
See the module docstring :mod:`easygraph.readwrite.gml` for more details.
Examples
--------
>>> G = eg.Graph()
>>> G.add_node("1")
>>> print("\n".join(eg.generate_gml(G)))
graph [
node [
id 0
label "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 EasyGraphError(f"{key!r} is not a string")
if not valid_keys.match(key):
raise EasyGraphError(f"{key!r} is not a valid key")
if not isinstance(key, str):
key = str(key)
if key not in ignored_keys:
if isinstance(value, (int, bool)):
if key == "label":
yield indent + key + ' "' + str(value) + '"'
elif value is True:
# python bool is an instance of int
yield indent + key + " 1"
elif value is False:
yield indent + key + " 0"
# GML only supports signed 32-bit integers
elif value < -(2**31) or value >= 2**31:
yield indent + key + ' "' + str(value) + '"'
else:
yield indent + key + " " + str(value)
elif isinstance(value, float):
text = repr(value).upper()
# GML matches INF to keys, so prepend + to INF. Use repr(float(*))
# instead of string literal to future proof against changes to repr.
if text == repr(float("inf")).upper():
text = "+" + text
else:
# GML requires that a real literal contain a decimal point, but
# repr may not output a decimal point when the mantissa is
# integral and hence needs fixing.
epos = text.rfind("E")
if epos != -1 and text.find(".", 0, epos) == -1:
text = text[:epos] + "." + text[epos:]
if key == "label":
yield indent + key + ' "' + text + '"'
else:
yield indent + key + " " + text
elif isinstance(value, dict):
yield indent + key + " ["
next_indent = indent + " "
for key, value in value.items():
yield from stringize(key, value, (), next_indent)
yield indent + "]"
elif (
isinstance(value, (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 err:
raise EasyGraphError(
f"{value!r} cannot be converted into a string"
) from err
if not isinstance(value, str):
raise EasyGraphError(f"{value!r} is not a string")
yield indent + key + ' "' + escape(value) + '"'
yield "graph ["
# Output graph attributes
multigraph = G.is_multigraph()
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:
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(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 : EasyGraph graph
The parsed graph.
Raises
------
EasyGraphError
If the input cannot be parsed.
See Also
--------
write_gml, parse_gml
literal_destringizer
Notes
-----
GML files are stored using a 7-bit ASCII encoding with any extended
ASCII characters (iso8859-1) appearing as HTML character entities.
Without specifying a `stringizer`/`destringizer`, the code is capable of
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For writing other data types, and for reading data other
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
For additional documentation on the GML file format, please see the
`GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
See the module docstring :mod:`easygraph.readwrite.gml` for more details.
Examples
--------
>>> G = eg.path_graph(4)
>>> eg.write_gml(G, "test.gml")
GML values are interpreted as strings by default:
>>> H = eg.read_gml("test.gml")
>>> H.nodes
NodeView(('0', '1', '2', '3'))
When a `destringizer` is provided, GML values are converted to the provided type.
For example, integer nodes can be recovered as shown below:
>>> J = eg.read_gml("test.gml", destringizer=int)
>>> J.nodes
NodeView((0, 1, 2, 3))
"""
def filter_lines(lines):
for line in lines:
try:
line = line.decode("ascii")
except UnicodeDecodeError as err:
raise EasyGraphError("input is not ASCII-encoded") from err
if not isinstance(line, str):
lines = str(lines)
if line and line[-1] == "\n":
line = line[:-1]
yield line
G = parse_gml_lines(filter_lines(path), label, destringizer)
return G
@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 : EasyGraph 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
------
EasyGraphError
If `stringizer` cannot convert a value into a string, or the value to
convert is not a string while `stringizer` is None.
See Also
--------
read_gml, generate_gml
literal_stringizer
Notes
-----
Graph attributes named 'directed', 'multigraph', 'node' or
'edge', node attributes named 'id' or 'label', edge attributes
named 'source' or 'target' (or 'key' if `G` is a multigraph)
are ignored because these attribute names are used to encode the graph
structure.
GML files are stored using a 7-bit ASCII encoding with any extended
ASCII characters (iso8859-1) appearing as HTML character entities.
Without specifying a `stringizer`/`destringizer`, the code is capable of
writing `int`/`float`/`str`/`dict`/`list` data as required by the GML
specification. For writing other data types, and for reading data other
than `str` you need to explicitly supply a `stringizer`/`destringizer`.
Note that while we allow non-standard GML to be read from a file, we make
sure to write GML format. In particular, underscores are not allowed in
attribute names.
For additional documentation on the GML file format, please see the
`GML url <https://web.archive.org/web/20190207140002/http://www.fim.uni-passau.de/index.php?id=17297&L=1>`_.
See the module docstring :mod:`easygraph.readwrite.gml` for more details.
Examples
--------
>>> G = eg.path_graph(4)
>>> eg.write_gml(G, "test.gml")
Filenames ending in .gz or .bz2 will be compressed.
>>> eg.write_gml(G, "test.gml.gz")
"""
for line in generate_gml(G, stringizer):
path.write((line + "\n").encode("ascii"))
def literal_stringizer(value):
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()
File diff suppressed because it is too large Load Diff
+180
View File
@@ -0,0 +1,180 @@
import easygraph as eg
__all__ = ["write_dot", "read_dot", "from_agraph", "to_agraph"]
def from_agraph(A, create_using=None):
"""Returns a EasyGraph Graph or DiGraph from a PyGraphviz graph.
Parameters
----------
A : PyGraphviz AGraph
A graph created with PyGraphviz
create_using : EasyGraph graph constructor, optional (default=None)
Graph type to create. If graph instance, then cleared before populated.
If `None`, then the appropriate Graph type is inferred from `A`.
Examples
--------
>>> K5 = eg.complete_graph(5)
>>> A = eg.to_agraph(K5)
>>> G = eg.from_agraph(A)
Notes
-----
The Graph G will have a dictionary G.graph_attr containing
the default graphviz attributes for graphs, nodes and edges.
Default node attributes will be in the dictionary G.node_attr
which is keyed by node.
Edge attributes will be returned as edge data in G. With
edge_attr=False the edge data will be the Graphviz edge weight
attribute or the value 1 if no edge weight attribute is found.
"""
if create_using is None:
if A.is_directed():
if A.is_strict():
create_using = eg.DiGraph
else:
create_using = eg.MultiDiGraph
else:
if A.is_strict():
create_using = eg.Graph
else:
create_using = eg.MultiGraph
# assign defaults
N = eg.empty_graph(0, create_using)
if A.name is not None:
N.name = A.name
# add graph attributes
N.graph.update(A.graph_attr)
# add nodes, attributes to N.node_attr
for n in A.nodes():
str_attr = {str(k): v for k, v in n.attr.items()}
N.add_node(str(n), **str_attr)
# add edges, assign edge data as dictionary of attributes
for e in A.edges():
u, v = str(e[0]), str(e[1])
attr = dict(e.attr)
str_attr = {str(k): v for k, v in attr.items()}
if not N.is_multigraph():
if e.name is not None:
str_attr["key"] = e.name
N.add_edge(u, v, **str_attr)
else:
N.add_edge(u, v, key=e.name, **str_attr)
# add default attributes for graph, nodes, and edges
# hang them on N.graph_attr
N.graph["graph"] = dict(A.graph_attr)
N.graph["node"] = dict(A.node_attr)
N.graph["edge"] = dict(A.edge_attr)
return N
def to_agraph(N):
"""Returns a pygraphviz graph from a EasyGraph graph N.
Parameters
----------
N : EasyGraph graph
A graph created with EasyGraph
Examples
--------
>>> K5 = eg.complete_graph(5)
>>> A = eg.to_agraph(K5)
Notes
-----
If N has an dict N.graph_attr an attempt will be made first
to copy properties attached to the graph (see from_agraph)
and then updated with the calling arguments if any.
"""
try:
import pygraphviz
except ImportError as err:
raise ImportError("requires pygraphviz http://pygraphviz.github.io/") from err
directed = N.is_directed()
strict = eg.number_of_selfloops(N) == 0 and not N.is_multigraph()
A = pygraphviz.AGraph(name=N.name, strict=strict, directed=directed)
# default graph attributes
A.graph_attr.update(N.graph.get("graph", {}))
A.node_attr.update(N.graph.get("node", {}))
A.edge_attr.update(N.graph.get("edge", {}))
A.graph_attr.update(
(k, v) for k, v in N.graph.items() if k not in ("graph", "node", "edge")
)
# add nodes
for n, nodedata in N.nodes.items():
A.add_node(n)
# Add node data
a = A.get_node(n)
a.attr.update({k: str(v) for k, v in nodedata.items()})
# loop over edges
if N.is_multigraph():
for u, v, key, edgedata in N.edges:
str_edgedata = {k: str(v) for k, v in edgedata.items() if k != "key"}
A.add_edge(u, v, key=str(key))
# Add edge data
a = A.get_edge(u, v)
a.attr.update(str_edgedata)
else:
for u, v, edgedata in N.edges:
str_edgedata = {k: str(v) for k, v in edgedata.items()}
A.add_edge(u, v)
# Add edge data
a = A.get_edge(u, v)
a.attr.update(str_edgedata)
return A
def write_dot(G, path):
"""Write EasyGraph graph G to Graphviz dot format on path.
Parameters
----------
G : graph
A easygraph graph
path : filename
Filename or file handle to write
"""
A = to_agraph(G)
A.write(path)
A.clear()
return
def read_dot(path):
"""Returns a EasyGraph graph from a dot file on path.
Parameters
----------
path : file or string
File name or file handle to read.
"""
try:
import pygraphviz
except ImportError as err:
raise ImportError(
"read_dot() requires pygraphviz http://pygraphviz.github.io/"
) from err
A = pygraphviz.AGraph(file=path)
gr = from_agraph(A)
A.clear()
return gr
@@ -0,0 +1,16 @@
"""
*********
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 easygraph.readwrite.json_graph.node_link import *
+111
View File
@@ -0,0 +1,111 @@
from itertools import chain
from itertools import count
import easygraph as eg
__all__ = ["node_link_graph"]
_attrs = dict(source="source", target="target", name="id", key="key", link="links")
def _to_tuple(x):
"""Converts lists to tuples, including nested lists.
All other non-list inputs are passed through unmodified. This function is
intended to be used to convert potentially nested lists from json files
into valid nodes.
Examples
--------
>>> _to_tuple([1, 2, [3, 4]])
(1, 2, (3, 4))
"""
if not isinstance(x, (tuple, list)):
return x
return tuple(map(_to_tuple, x))
def node_link_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 : EasyGraph graph
A EasyGraph graph object
Examples
--------
>>> from easygraph.readwrite import json_graph
>>> G = eg.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 = eg.MultiGraph()
else:
graph = eg.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
+337
View File
@@ -0,0 +1,337 @@
# This file is part of the NetworkX distribution.
# NetworkX is distributed with the 3-clause BSD license.
# ::
# Copyright (C) 2004-2022, NetworkX Developers
# Aric Hagberg <hagberg@lanl.gov>
# Dan Schult <dschult@colgate.edu>
# Pieter Swart <swart@lanl.gov>
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of the NetworkX Developers nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
*****
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 easygraph as eg
# import networkx as nx
from easygraph.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 EasyGraph graph
References
----------
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
for format information.
"""
if G.name == "":
name = "EasyGraph"
else:
name = G.name
# Apparently many Pajek format readers can't process this line
# So we'll leave it out for now.
# yield '*network %s'%name
# write nodes with attributes
yield f"*vertices {G.order()}"
nodes = list(G)
# make dictionary mapping nodes to integers
nodenumber = dict(zip(nodes, range(1, len(nodes) + 1)))
for n in nodes:
# copy node attributes and pop mandatory attributes
# to avoid duplication.
na = G.nodes.get(n, {}).copy()
x = na.pop("x", 0.0)
y = na.pop("y", 0.0)
try:
id = int(na.pop("id", nodenumber[n]))
except ValueError as err:
err.args += (
"Pajek format requires 'id' to be an int()."
" Refer to the 'Relabeling nodes' section.",
)
raise
nodenumber[n] = id
shape = na.pop("shape", "ellipse")
s = " ".join(map(make_qstr, (id, n, x, y, shape)))
# only optional attributes are left in na.
for k, v in na.items():
if isinstance(v, str) and v.strip() != "":
s += f" {make_qstr(k)} {make_qstr(v)}"
else:
warnings.warn(
f"Node attribute {k} is not processed."
f" {('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"
# from icecream import ic
# ic(G.edges)
# if isinstance(G, MultiGraph)
for u, v, *edgedata in G.edges:
# if len(edgedata) > 1:
# edgedata = edgedata[1]
# else:
# edgedata = edgedata[0]
edgedata = edgedata[-1]
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."
f" {('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 EasyGraph graph
path : file or string
File or filename to write.
Filenames ending in .gz or .bz2 will be compressed.
Examples
--------
>>> G = eg.path_graph(4)
>>> eg.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):
"""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 : EasyGraph MultiGraph or MultiDiGraph.
Examples
--------
>>> G = eg.path_graph(4)
>>> eg.write_pajek(G, "test.net")
>>> G = eg.read_pajek("test.net")
To create a Graph instead of a MultiGraph use
>>> G1 = eg.Graph(G)
References
----------
See http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/draweps.htm
for format information.
"""
lines = (line.decode() for line in path)
# with open(path) as f:
# lines = f.readlines()
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 : EasyGraph graph
See Also
--------
read_pajek
"""
import shlex
# multigraph=False
if isinstance(lines, str):
lines = iter(lines.split("\n"))
# from itertools import tee
# lines, lines2 = tee(lines)
# from icecream import ic
# ic(next(lines2))
lines = iter([line.rstrip("\n") for line in lines])
G = eg.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 for x in shlex.split(str(l))]
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 = eg.MultiGraph(G)
if l.lower().startswith("*arcs"):
# switch to directed with multiple arcs for each existing edge
# G = G.to_directed()
pass
for l in lines:
try:
splitline = [x for x in shlex.split(str(l))]
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 = eg.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
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env python3
def read_pickle(file_name):
import pickle
with open(file_name, "rb") as f:
return pickle.load(f)
def write_pickle(file_name, obj):
import pickle
with open(file_name, "wb") as f:
pickle.dump(obj, f)
+318
View File
@@ -0,0 +1,318 @@
"""
Unit tests for edgelists.
"""
import io
import os
import tempfile
import textwrap
import easygraph as eg
import pytest
from easygraph.utils import edges_equal
from easygraph.utils import graphs_equal
from easygraph.utils import nodes_equal
edges_no_data = textwrap.dedent(
"""
# comment line
1 2
# comment line
2 3
"""
)
edges_with_values = textwrap.dedent(
"""
# comment line
1 2 2.0
# comment line
2 3 3.0
"""
)
edges_with_weight = textwrap.dedent(
"""
# comment line
1 2 {'weight':2.0}
# comment line
2 3 {'weight':3.0}
"""
)
edges_with_multiple_attrs = textwrap.dedent(
"""
# comment line
1 2 {'weight':2.0, 'color':'green'}
# comment line
2 3 {'weight':3.0, 'color':'red'}
"""
)
edges_with_multiple_attrs_csv = textwrap.dedent(
"""
# comment line
1, 2, {'weight':2.0, 'color':'green'}
# comment line
2, 3, {'weight':3.0, 'color':'red'}
"""
)
_expected_edges_weights = [(1, 2, {"weight": 2.0}), (2, 3, {"weight": 3.0})]
_expected_edges_multiattr = [
(1, 2, {"weight": 2.0, "color": "green"}),
(2, 3, {"weight": 3.0, "color": "red"}),
]
@pytest.mark.parametrize(
("data", "extra_kwargs"),
(
(edges_no_data, {}),
(edges_with_values, {}),
(edges_with_weight, {}),
(edges_with_multiple_attrs, {}),
(edges_with_multiple_attrs_csv, {"delimiter": ","}),
),
)
def test_read_edgelist_no_data(data, extra_kwargs):
bytesIO = io.BytesIO(data.encode("utf-8"))
G = eg.read_edgelist(bytesIO, nodetype=int, data=False, **extra_kwargs)
assert edges_equal(G.edges, [(1, 2, {}), (2, 3, {})])
def test_read_weighted_edgelist():
bytesIO = io.BytesIO(edges_with_values.encode("utf-8"))
G = eg.read_weighted_edgelist(bytesIO, nodetype=int)
assert edges_equal(G.edges, _expected_edges_weights)
@pytest.mark.parametrize(
("data", "extra_kwargs", "expected"),
(
(edges_with_weight, {}, _expected_edges_weights),
(edges_with_multiple_attrs, {}, _expected_edges_multiattr),
(edges_with_multiple_attrs_csv, {"delimiter": ","}, _expected_edges_multiattr),
),
)
def test_read_edgelist_with_data(data, extra_kwargs, expected):
bytesIO = io.BytesIO(data.encode("utf-8"))
G = eg.read_edgelist(bytesIO, nodetype=int, **extra_kwargs)
assert edges_equal(G.edges, expected)
@pytest.fixture
def example_graph():
G = eg.Graph()
G.add_weighted_edges_from([(1, 2, 3.0), (2, 3, 27.0), (3, 4, 3.0)])
return G
def test_parse_edgelist_no_data(example_graph):
G = example_graph
H = eg.parse_edgelist(["1 2", "2 3", "3 4"], nodetype=int)
assert nodes_equal(G.nodes, H.nodes)
assert edges_equal(G.edges, H.edges, need_data=False)
def test_parse_edgelist_with_data_dict(example_graph):
G = example_graph
H = eg.parse_edgelist(
["1 2 {'weight': 3}", "2 3 {'weight': 27}", "3 4 {'weight': 3.0}"], nodetype=int
)
assert nodes_equal(G.nodes, H.nodes)
assert edges_equal(G.edges, H.edges)
def test_parse_edgelist_with_data_list(example_graph):
G = example_graph
H = eg.parse_edgelist(
["1 2 3", "2 3 27", "3 4 3.0"], nodetype=int, data=(("weight", float),)
)
assert nodes_equal(G.nodes, H.nodes)
assert edges_equal(G.edges, H.edges)
def test_parse_edgelist():
# ignore lines with less than 2 nodes
lines = ["1;2", "2 3", "3 4"]
G = eg.parse_edgelist(lines, nodetype=int)
# assert list(G.edges) == [(2, 3), (3, 4)]
assert edges_equal(G.edges, [(2, 3), (3, 4)], need_data=False)
# unknown nodetype
with pytest.raises(TypeError, match="Failed to convert nodes"):
lines = ["1 2", "2 3", "3 4"]
eg.parse_edgelist(lines, nodetype="nope")
# lines have invalid edge format
with pytest.raises(TypeError, match="Failed to convert edge data"):
lines = ["1 2 3", "2 3", "3 4"]
eg.parse_edgelist(lines, nodetype=int)
# edge data and data_keys not the same length
with pytest.raises(IndexError, match="not the same length"):
lines = ["1 2 3", "2 3 27", "3 4 3.0"]
eg.parse_edgelist(
lines, nodetype=int, data=(("weight", float), ("capacity", int))
)
# edge data can't be converted to edge type
with pytest.raises(TypeError, match="Failed to convert"):
lines = ["1 2 't1'", "2 3 't3'", "3 4 't3'"]
eg.parse_edgelist(lines, nodetype=int, data=(("weight", float),))
def test_comments_None():
edgelist = ["node#1 node#2", "node#2 node#3"]
# comments=None supported to ignore all comment characters
G = eg.parse_edgelist(edgelist, comments=None)
H = eg.Graph([e.split(" ") for e in edgelist])
assert edges_equal(G.edges, H.edges)
class TestEdgelist:
@classmethod
def setup_class(cls):
cls.G = eg.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 = eg.DiGraph(cls.G)
cls.XG = eg.MultiGraph()
cls.XG.add_weighted_edges_from([(1, 2, 5), (1, 2, 5), (1, 2, 1), (3, 3, 42)])
cls.XDG = eg.MultiDiGraph(cls.XG)
def test_write_edgelist_1(self):
fh = io.BytesIO()
G = eg.Graph()
G.add_edges_from([(1, 2), (2, 3)])
eg.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 = eg.Graph()
G.add_edges_from([(1, 2), (2, 3)])
eg.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 = eg.Graph()
G.add_edge(1, 2, weight=2.0)
G.add_edge(2, 3, weight=3.0)
eg.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 = eg.Graph()
G.add_edge(1, 2, weight=2.0)
G.add_edge(2, 3, weight=3.0)
eg.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 = eg.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()
eg.write_edgelist(G, fname)
H = eg.read_edgelist(fname)
assert graphs_equal(G, H)
os.close(fd)
os.unlink(fname)
def test_latin1_issue(self):
G = eg.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, eg.write_edgelist, G, fname, encoding="latin-1"
)
os.close(fd)
os.unlink(fname)
def test_latin1(self):
G = eg.Graph()
name1 = "Bj" + chr(246) + "rk"
name2 = chr(220) + "ber"
G.add_edge(name1, "Radiohead", **{name2: 3})
fd, fname = tempfile.mkstemp()
eg.write_edgelist(G, fname, encoding="latin-1")
H = eg.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()
eg.write_edgelist(G, fname)
H = eg.read_edgelist(fname)
H2 = eg.read_edgelist(fname)
assert H is not H2 # they should be different graphs
G.remove_node("g") # isolated nodes are not written in edgelist
assert nodes_equal(list(H), list(G))
assert edges_equal(list(H.edges), list(G.edges))
os.close(fd)
os.unlink(fname)
def test_edgelist_digraph(self):
G = self.DG
(fd, fname) = tempfile.mkstemp()
eg.write_edgelist(G, fname)
H = eg.read_edgelist(fname, create_using=eg.DiGraph())
H2 = eg.read_edgelist(fname, create_using=eg.DiGraph())
assert H is not H2 # they should be different graphs
G.remove_node("g") # isolated nodes are not written in edgelist
assert nodes_equal(list(H), list(G))
assert edges_equal(list(H.edges), list(G.edges))
os.close(fd)
os.unlink(fname)
def test_edgelist_integers(self):
G = eg.convert_node_labels_to_integers(self.G)
(fd, fname) = tempfile.mkstemp()
eg.write_edgelist(G, fname)
H = eg.read_edgelist(fname, nodetype=int)
# isolated nodes are not written in edgelist
G.remove_nodes_from(list(eg.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()
eg.write_edgelist(G, fname)
H = eg.read_edgelist(fname, nodetype=int, create_using=eg.MultiGraph())
H2 = eg.read_edgelist(fname, nodetype=int, create_using=eg.MultiGraph())
assert H is not H2 # they should be different graphs
assert nodes_equal(list(H), list(G))
assert edges_equal(list(H.edges), list(G.edges), need_data=False)
os.close(fd)
os.unlink(fname)
def test_edgelist_multidigraph(self):
G = self.XDG
(fd, fname) = tempfile.mkstemp()
eg.write_edgelist(G, fname)
H = eg.read_edgelist(fname, nodetype=int, create_using=eg.MultiDiGraph())
H2 = eg.read_edgelist(fname, nodetype=int, create_using=eg.MultiDiGraph())
assert H is not H2 # they should be different graphs
assert nodes_equal(list(H), list(G))
assert edges_equal(list(H.edges), list(G.edges), need_data=False)
os.close(fd)
os.unlink(fname)
+447
View File
@@ -0,0 +1,447 @@
import io
import sys
import time
import easygraph as eg
import pytest
class TestGEXF:
@classmethod
def setup_class(cls):
cls.simple_directed_data = """<?xml version="1.0" encoding="UTF-8"?>
<gexf xmlns="http://www.gexf.net/1.2draft" version="1.2">
<graph mode="static" defaultedgetype="directed">
<nodes>
<node id="0" label="Hello" />
<node id="1" label="Word" />
</nodes>
<edges>
<edge id="0" source="0" target="1" />
</edges>
</graph>
</gexf>
"""
cls.simple_directed_graph = eg.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 = eg.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 = eg.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 = eg.read_gexf(self.simple_directed_fh)
assert sorted(G.nodes) == sorted(H.nodes)
assert sorted(G.edges) == sorted(H.edges)
self.simple_directed_fh.seek(0)
def test_write_read_simple_directed_graphml(self):
G = self.simple_directed_graph
fh = io.BytesIO()
eg.write_gexf(G, fh)
fh.seek(0)
H = eg.read_gexf(fh)
assert sorted(G.nodes) == sorted(H.nodes)
assert sorted(G.edges) == sorted(H.edges)
self.simple_directed_fh.seek(0)
def test_read_simple_undirected_graphml(self):
G = self.simple_undirected_graph
H = eg.read_gexf(self.simple_undirected_fh)
assert sorted(G.nodes) == sorted(H.nodes)
assert sorted(G.edges) == sorted(H.edges)
self.simple_undirected_fh.seek(0)
def test_read_attribute_graphml(self):
G = self.attribute_graph
H = eg.read_gexf(self.attribute_fh)
assert sorted(G.nodes) == sorted(H.nodes)
ge = sorted(G.edges)
he = sorted(H.edges)
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(eg.EasyGraphError, eg.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(eg.EasyGraphError, eg.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(eg.EasyGraphError, eg.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 = eg.read_gexf(fh, relabel=True)
assert sorted(G.nodes) == ["Hello", "Word"]
def test_default_attribute(self):
G = eg.Graph()
G.add_node(1, label="1", color="green")
eg.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()
eg.write_gexf(G, fh)
fh.seek(0)
H = eg.read_gexf(fh, node_type=int)
assert sorted(G.nodes) == sorted(H.nodes)
# 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 = eg.Graph()
G.add_node(1, id=7, label=77)
fh = io.BytesIO()
eg.write_gexf(G, fh)
fh.seek(0)
H = eg.read_gexf(fh, node_type=int)
assert list(H) == [7]
assert H.nodes[7]["label"] == "77"
@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires >= python3.8")
def test_edge_id_construct(self):
G = eg.Graph()
G.add_edges_from([(0, 1, {"id": 0}), (1, 2, {"id": 2}), (2, 3)])
expected = f"""<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:xsi\
="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.\
gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd" version="1.2">
<meta lastmodifieddate="{time.strftime('%Y-%m-%d')}">
<creator>EasyGraph</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(eg.generate_gexf(G))
assert expected == obtained
@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires >= python3.8")
def test_numpy_type(self):
np = pytest.importorskip("numpy")
G = eg.path_graph(4)
eg.set_node_attributes(G, {n: n for n in np.arange(4)}, "number")
G[0][1]["edge-number"] = np.float64(1.1)
expected = f"""<gexf xmlns="http://www.gexf.net/1.2draft"\
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation\
="http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd"\
version="1.2">
<meta lastmodifieddate="{time.strftime('%Y-%m-%d')}">
<creator>EasyGraph</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(eg.generate_gexf(G))
assert expected == obtained
def test_bool(self):
G = eg.Graph()
G.add_node(1, testattr=True)
fh = io.BytesIO()
eg.write_gexf(G, fh)
fh.seek(0)
H = eg.read_gexf(fh, node_type=int)
assert H.nodes[1]["testattr"]
def test_specials(self):
from math import isnan
inf, nan = float("inf"), float("nan")
G = eg.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()
eg.write_gexf(G, fh)
fh.seek(0)
filetext = fh.read()
fh.seek(0)
H = eg.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]["easygraph_key"] == "a"
assert H.nodes[2]["easygraph_key"] == "b"
assert H.nodes[3]["easygraph_key"] == "c"
def test_simple_list(self):
G = eg.Graph()
list_value = [(1, 2, 3), (9, 1, 2)]
G.add_node(1, key=list_value)
fh = io.BytesIO()
eg.write_gexf(G, fh)
fh.seek(0)
H = eg.read_gexf(fh, node_type=int)
assert H.nodes[1]["easygraph_key"] == list_value
def test_dynamic_mode(self):
G = eg.Graph()
G.add_node(1, label="1", color="green")
G.graph["mode"] = "dynamic"
fh = io.BytesIO()
eg.write_gexf(G, fh)
fh.seek(0)
H = eg.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 = eg.Graph()
G.add_node(0, label="1", color="green")
G.nodes[0]["spells"] = [(1, 2)]
fh = io.BytesIO()
eg.write_gexf(G, fh)
fh.seek(0)
H = eg.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 = eg.Graph()
G.add_node(0, label="1", color="green")
G.nodes[0]["slices"] = [(1, 2)]
fh = io.BytesIO()
eg.write_gexf(G, fh, version="1.1draft")
fh.seek(0)
H = eg.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 = eg.Graph()
G.add_node(0, label="1", color="green", parents=[1, 2])
fh = io.BytesIO()
eg.write_gexf(G, fh)
fh.seek(0)
H = eg.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)
+589
View File
@@ -0,0 +1,589 @@
import codecs
import io
import os
import tempfile
from ast import literal_eval
from contextlib import contextmanager
from textwrap import dedent
import easygraph as eg
import pytest
from easygraph.readwrite.gml import literal_destringizer
from easygraph.readwrite.gml import literal_stringizer
class TestGraph:
@classmethod
def setup_class(cls):
cls.simple_data = """Creator "me"
Version "xx"
graph [
comment "This is a sample graph"
directed 1
IsPlanar 1
pos [ x 0 y 1 ]
node [
id 1
label "Node 1"
pos [ x 1 y 1 ]
]
node [
id 2
pos [ x 1 y 2 ]
label "Node 2"
]
node [
id 3
label "Node 3"
pos [ x 1 y 3 ]
]
edge [
source 1
target 2
label "Edge from node 1 to node 2"
color [line "blue" thickness 3]
]
edge [
source 2
target 3
label "Edge from node 2 to node 3"
]
edge [
source 3
target 1
label "Edge from node 3 to node 1"
]
]
"""
def test_parse_gml_cytoscape_bug(self):
# example from issue #321, originally #324 in trac
cytoscape_example = """
Creator "Cytoscape"
Version 1.0
graph [
node [
root_index -3
id -3
graphics [
x -96.0
y -67.0
w 40.0
h 40.0
fill "#ff9999"
type "ellipse"
outline "#666666"
outline_width 1.5
]
label "node2"
]
node [
root_index -2
id -2
graphics [
x 63.0
y 37.0
w 40.0
h 40.0
fill "#ff9999"
type "ellipse"
outline "#666666"
outline_width 1.5
]
label "node1"
]
node [
root_index -1
id -1
graphics [
x -31.0
y -17.0
w 40.0
h 40.0
fill "#ff9999"
type "ellipse"
outline "#666666"
outline_width 1.5
]
label "node0"
]
edge [
root_index -2
target -2
source -1
graphics [
width 1.5
fill "#0000ff"
type "line"
Line [
]
source_arrow 0
target_arrow 3
]
label "DirectedEdge"
]
edge [
root_index -1
target -1
source -3
graphics [
width 1.5
fill "#0000ff"
type "line"
Line [
]
source_arrow 0
target_arrow 3
]
label "DirectedEdge"
]
]
"""
eg.parse_gml(cytoscape_example)
def test_parse_gml(self):
G = eg.parse_gml(self.simple_data, label="label")
assert sorted(G.nodes) == ["Node 1", "Node 2", "Node 3"]
assert [e[:2] 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)] == [
(
"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 = eg.read_gml(fname, label="label")
G = eg.parse_gml(self.simple_data, label="label")
assert sorted(G.nodes) == sorted(Gin.nodes)
assert sorted(G.edges) == sorted(Gin.edges)
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 = eg.Graph()
G.add_node(1203)
data = "\n".join(eg.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(eg.EasyGraphError, eg.read_gml, fh, label="label")
def test_quotes(self):
G = eg.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()
eg.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 = eg.Graph()
G.add_node(node)
fobj = tempfile.NamedTemporaryFile()
eg.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 = eg.Graph()
G.add_node(node)
fobj = tempfile.NamedTemporaryFile()
eg.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 = eg.parse_gml('graph [ name "x" node [ id 0 label "x" ] ]')
assert "x" == G.graph["name"]
G = eg.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 = eg.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(eg.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")],
]
data.append(chr(0x14444))
data.append(literal_eval("{2.3j, 1 - 2.3j, ()}"))
G = eg.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(eg.generate_gml(G, stringizer=literal_stringizer))
G = eg.parse_gml(gml, destringizer=literal_destringizer)
assert data == G.name
assert {"name": data, "data": data} == G.graph
assert G.nodes == {0: dict(int=-1, data=dict(data=data))}
assert list(G.edges) == [(0, 0, dict(float=-2.5, data=data))]
G = eg.Graph()
G.graph["data"] = "frozenset([1, 2, 3])"
G = eg.parse_gml(eg.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 = eg.parse_gml(gml)
assert (
'&"\x0f' + chr(0x4444) + "&#1234567890;&#x1234567890abcdef;&unknown;"
== G.name
)
gml = "\n".join(eg.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(eg.EasyGraphError, eg.read_gml, f)
def assert_parse_error(gml):
pytest.raises(eg.EasyGraphError, eg.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 ] ]")
eg.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 ] ]")
eg.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 ] ]"
)
eg.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 ]"
)
eg.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 ]"
)
eg.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 ]"
)
eg.parse_gml(
"graph [ node [ id 0 label 0 ] node [ id 1 label 1 ] "
"edge [ source 0 target 1 key 0 ] edge [ source 1 target 0 key 0 ]"
"directed 1 multigraph 1 ]"
)
# Tests for string convertible alphanumeric id and label values
eg.parse_gml("graph [edge [ source a target a ] node [ id a label b ] ]")
eg.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(
eg.EasyGraphError, lambda: list(eg.generate_gml(*args, **kwargs))
)
G = eg.Graph()
G.graph[3] = 3
assert_generate_error(G)
G = eg.Graph()
G.graph["3"] = 3
assert_generate_error(G)
G = eg.Graph()
G.graph["data"] = frozenset([1, 2, 3])
assert_generate_error(G, stringizer=literal_stringizer)
G = eg.Graph()
G.graph["data"] = []
assert_generate_error(G)
assert_generate_error(G, stringizer=len)
def test_label_kwarg(self):
G = eg.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 = eg.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 = eg.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:
eg.write_gml(G, fname)
# Check that the export wrote the nonfitting numbers as strings
G2 = eg.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 = eg.Graph()
g.add_node("n1", properties=["element", 0, 1, 2.5, True, False])
with byte_file() as f:
eg.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 = eg.Graph()
g.add_node("n1", properties=["element"])
with byte_file() as f:
eg.write_gml(g, f)
result = f.read().decode()
assert result == dedent(
"""\
graph [
node [
id 0
label "n1"
properties "_easygraph_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 = eg.read_gml(f)
assert graph.nodes["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 "_easygraph_list_start"
properties "element"
]
]
"""
).encode("ascii")
)
f.seek(0)
graph = eg.read_gml(f)
assert graph.nodes["n1"] == {"properties": ["element"]}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
import os
import tempfile
import pytest
pygraphviz = pytest.importorskip("pygraphviz")
import easygraph as eg
from easygraph.utils import edges_equal
from easygraph.utils import nodes_equal
class TestAGraph:
def build_graph(self, G):
edges = [("A", "B"), ("A", "C"), ("A", "C"), ("B", "C"), ("A", "D")]
G.add_edges_from(edges)
G.add_node("E")
G.graph["metal"] = "bronze"
return G
def assert_equal(self, G1, G2):
assert nodes_equal(G1.nodes, G2.nodes)
assert edges_equal(G1.edges, G2.edges)
assert G1.graph["metal"] == G2.graph["metal"]
def agraph_checks(self, G):
G = self.build_graph(G)
A = eg.to_agraph(G)
H = eg.from_agraph(A)
self.assert_equal(G, H)
fd, fname = tempfile.mkstemp()
eg.write_dot(H, fname)
Hin = eg.read_dot(fname)
self.assert_equal(H, Hin)
os.close(fd)
os.unlink(fname)
(fd, fname) = tempfile.mkstemp()
with open(fname, "w") as fh:
eg.write_dot(H, fh)
with open(fname) as fh:
Hin = eg.read_dot(fh)
os.close(fd)
os.unlink(fname)
self.assert_equal(H, Hin)
def test_from_agraph_name(self):
G = eg.Graph(name="test")
A = eg.to_agraph(G)
H = eg.from_agraph(A)
assert G.name == "test"
def test_undirected(self):
self.agraph_checks(eg.Graph())
+307
View File
@@ -0,0 +1,307 @@
# # This file is part of the NetworkX distribution.
# NetworkX is distributed with the 3-clause BSD license.
# ::
# Copyright (C) 2004-2022, NetworkX Developers
# Aric Hagberg <hagberg@lanl.gov>
# Dan Schult <dschult@colgate.edu>
# Pieter Swart <swart@lanl.gov>
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of the NetworkX Developers nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Pajek tests
"""
import easygraph as eg
print(eg)
import os
import tempfile
from easygraph.utils import edges_equal
from easygraph.utils import nodes_equal
# from rich import print
test_parse_pajek_edges = [
(
"A1",
"A1",
0,
{
"weight": 1.0,
"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",
},
),
(
"A1",
"Bb",
0,
{
"weight": 1.0,
"h2": "0",
"a1": "40",
"k1": "2.8",
"a2": "30",
"k2": "0.8",
"ap": "25",
"l": "Bezier arc",
"lphi": "90",
"la": "0",
"lp": "0.65",
},
),
(
"A1",
"C",
0,
{
"weight": 1.0,
"p": "Dashed",
"h2": "0",
"w": "5",
"k1": "-1",
"k2": "-20",
"ap": "25",
"l": "Oval arc",
"c": "Brown",
"lc": "Black",
},
),
(
"Bb",
"A1",
0,
{
"weight": 1.0,
"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",
},
),
(
"C",
"D2",
0,
{
"weight": 1.0,
"p": "Dashed",
"h2": "0",
"w": "2",
"c": "OliveGreen",
"ap": "25",
"l": "Straight arc",
"lc": "PineGreen",
},
),
(
"C",
"C",
0,
{
"weight": -1.0,
"h1": "6",
"w": "1",
"h2": "12",
"k1": "-2",
"k2": "-15",
"ap": "0.5",
"l": "Circular loop",
"c": "Red",
"lc": "OrangeRed",
"lphi": "270",
"la": "180",
},
),
(
"D2",
"Bb",
0,
{
"weight": -1.0,
"h2": "0",
"w": "1",
"k1": "-2",
"k2": "250",
"ap": "25",
"l": "Circular arc",
"c": "Red",
"lc": "OrangeRed",
},
),
]
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 = eg.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 = eg.parse_pajek(data)
assert sorted(G.nodes) == ["1", "2"]
assert edges_equal(G.edges, [("1", "2", 0, {}), ("1", "2", 1, {})])
def test_parse_pajek(self):
G = eg.parse_pajek(self.data)
assert sorted(G.nodes) == ["A1", "Bb", "C", "D2"]
# print(G.edges)
assert edges_equal(G.edges, test_parse_pajek_edges)
def test_parse_pajek_mat(self):
data = """*Vertices 3\n1 "one"\n2 "two"\n3 "three"\n*Matrix\n1 1 0\n0 1 0\n0 1 0\n"""
G = eg.parse_pajek(data)
assert set(G.nodes) == {"one", "two", "three"}
assert G.nodes["two"] == {"id": "2"}
assert edges_equal(
# set(G.edges),
G.edges,
[
("one", "one", {"weight": 1}),
("one", "two", {"weight": 1}),
("two", "two", {"weight": 1}),
("three", "two", {"weight": 1}),
],
)
def test_read_pajek(self):
G = eg.parse_pajek(self.data)
Gin = eg.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 = eg.parse_pajek(self.data)
fh = io.BytesIO()
eg.write_pajek(G, fh)
fh.seek(0)
H = eg.read_pajek(fh)
assert nodes_equal(G.nodes, list(H))
assert edges_equal(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 = eg.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:
eg.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 = eg.parse_pajek(data)
def test_unicode(self):
import io
G = eg.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()
eg.write_pajek(G, fh)
fh.seek(0)
H = eg.read_pajek(fh)
assert nodes_equal(list(G), list(H))
# from icecream import ic
# ic(G.edges)
# ic(H.edges)
# ic(G.graph)
# ic(H.graph)
# assert edges_equal(list(G.edges), list(H.edges))
assert G.graph == H.graph
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""
pickle read / write tests
"""
import os
import pickle
import tempfile
import easygraph as eg
from easygraph.utils import edges_equal
class TestPickle:
@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 = eg.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(pickle.dumps(cls.G))
@classmethod
def teardown_class(cls):
os.unlink(cls.fname)
def test_read_pickle(self):
G = eg.read_pickle(self.fname)
assert G.nodes == self.G.nodes
assert G.edges == self.G.edges
def test_write_pickle(self):
G = eg.parse_pajek(self.data)
eg.write_pickle(self.fname, G)
Gin = eg.read_pickle(self.fname)
assert sorted(G.nodes) == sorted(Gin.nodes)
assert edges_equal(G.edges, Gin.edges)
assert self.G.graph == Gin.graph
+340
View File
@@ -0,0 +1,340 @@
"""
UCINET tests
"""
import io
import easygraph as eg
# from nose import SkipTest
# from nose.tools import *
def filterEdges(edges):
return [e[:3] for e in edges]
class TestUcinet:
@classmethod
def setup_class(self):
self.G = eg.MultiDiGraph()
self.G.add_nodes_from(["a", "b", "c", "d", "e"])
self.G.add_edges_from(
[
("a", "b"),
("a", "c"),
("a", "d"),
("a", "e"),
("b", "a"),
("b", "c"),
("b", "d"),
("c", "a"),
("c", "b"),
("d", "a"),
("d", "b"),
("e", "a"),
]
)
try:
pass
except ImportError:
print("NumPy not available.")
# raise SkipTest("NumPy not available.")
def test_generate_ucinet(self):
Gout = eg.generate_ucinet(self.G)
s = ""
for line in Gout:
s += line + "\n"
G_generated = eg.parse_ucinet(s)
data = """\
dl n=5 format=fullmatrix
labels:
a,b,c,d,e
data:
0 1 1 1 1
1 0 1 1 0
1 1 0 0 0
1 1 0 0 0
1 0 0 0 0"""
G = eg.parse_ucinet(data)
assert sorted(G.nodes) == sorted(G_generated.nodes)
assert sorted(G.edges) == sorted(G_generated.edges)
def test_parse_ucinet(self):
data = """
DL N = 5
Data:
0 1 1 1 1
1 0 1 0 0
1 1 0 0 1
1 0 0 0 0
1 0 1 0 0
"""
graph = eg.MultiDiGraph()
graph.add_nodes_from([0, 1, 2, 3, 4])
graph.add_edges_from(
[
(0, 1),
(0, 2),
(0, 3),
(0, 4),
(1, 0),
(1, 2),
(2, 0),
(2, 1),
(2, 4),
(3, 0),
(4, 0),
(4, 2),
]
)
G = eg.parse_ucinet(data)
assert sorted(G.nodes) == sorted(graph.nodes)
assert sorted(filterEdges(G.edges)) == sorted(filterEdges(graph.edges))
# print [n for n in G.nodes(data=True)]
# print [e for e in G.edges]
def test_parse_ucinet_labels(self):
"""
Test parsing of labels : single line (data1), multiple lines (data2), embedded (data3)
Labels must be separated by spaces, carriage returns, equal signs or commas.
Labels with embedded spaces are not advisable, but can be entered by
surrounding the label in quotes (e.g., "Humpty Dumpty").
"""
data1 = """
dl n=5
format = fullmatrix
labels:
barry,david,lin,pat,russ
data:
0 1 1 1 0
1 0 0 0 1
1 0 0 1 0
1 0 1 0 1
0 1 0 1 0
"""
data2 = """
dl n=5
format = fullmatrix
labels:
barry,david
lin,pat
russ
data:
0 1 1 1 0
1 0 0 0 1
1 0 0 1 0
1 0 1 0 1
0 1 0 1 0
"""
data3 = """\
dl n=5
format = fullmatrix
labels embedded
data:
barry david lin pat russ
Barry 0 1 1 1 0
david 1 0 0 0 1
Lin 1 0 0 1 0
Pat 1 0 1 0 1
Russ 0 1 0 1 0
"""
G = eg.MultiDiGraph()
G.add_nodes_from(["russ", "barry", "lin", "pat", "david"])
G.add_edges_from(
[
("russ", "pat"),
("russ", "david"),
("barry", "lin"),
("barry", "pat"),
("barry", "david"),
("lin", "barry"),
("lin", "pat"),
("pat", "barry"),
("pat", "lin"),
("pat", "russ"),
("david", "barry"),
("david", "russ"),
]
)
G1 = eg.parse_ucinet(data1)
G2 = eg.parse_ucinet(data2)
G3 = eg.parse_ucinet(data3)
assert sorted(G1.nodes) == sorted(G.nodes)
assert sorted(G2.nodes) == sorted(G.nodes)
assert sorted(G3.nodes) == sorted(G.nodes)
assert sorted(e[:3] for e in G1.edges) == sorted(e[:3] for e in G.edges)
assert sorted(e[:3] for e in G2.edges) == sorted(e[:3] for e in G.edges)
assert sorted(e[:3] for e in G3.edges) == sorted(e[:3] for e in G.edges)
# print [n for n in G.nodes]
# print [e for e in G.edges]
def test_parse_ucinet_nodelist1(self):
data1 = """
DL n=4
format = nodelist1
data:
1 3 2 1
4 1 4
2 2 4 1
"""
data2 = """
DL n=4
format = nodelist1b
data:
3 1 2 3
3 1 2 4
0
2 1 4
"""
G = eg.MultiDiGraph()
G.add_nodes_from([0, 1, 2, 3])
G.add_edges_from(
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 3), (3, 0), (3, 3)]
)
G1 = eg.parse_ucinet(data1)
G2 = eg.parse_ucinet(data2)
assert sorted(G1.nodes) == sorted(G.nodes)
assert sorted(G2.nodes) == sorted(G.nodes)
assert sorted(filterEdges(G1.edges)) == sorted(filterEdges(G.edges))
assert sorted(filterEdges(G2.edges)) == sorted(filterEdges(G.edges))
def test_parse_ucinet_nodelist1_labels(self):
data1 = """
DL n=5
format = nodelist1
labels:
george, sally, jim, billy, jane
data:
1 2 3
2 3
4 1
5 3
"""
data2 = """
DL n=5
format = nodelist1
labels embedded:
data:
george sally jim
sally jim
billy george
jane jim
"""
G = eg.MultiDiGraph()
G.add_nodes_from(["george", "sally", "jim", "billy", "jane"])
G.add_edges_from(
[
("billy", "george"),
("jane", "jim"),
("sally", "jim"),
("george", "jim"),
("george", "sally"),
]
)
G1 = eg.parse_ucinet(data1)
G2 = eg.parse_ucinet(data2)
assert sorted(G1.nodes) == sorted(G.nodes)
assert sorted(G2.nodes) == sorted(G.nodes)
assert sorted(G1.edges) == sorted(G.edges)
assert sorted(G2.edges) == sorted(G.edges)
def test_read_ucinet(self):
fh = io.BytesIO()
data = """
DL N = 5
Data:
0 1 1 1 1
1 0 1 0 0
1 1 0 0 1
1 0 0 0 0
1 0 1 0 0
"""
Gin = eg.parse_ucinet(data)
fh.write(data.encode("UTF-8"))
fh.seek(0)
Gout = eg.read_ucinet(fh)
assert sorted(Gout.nodes) == sorted(Gin.nodes)
assert sorted(e[:3] for e in Gout.edges) == sorted(e[:3] for e in Gin.edges)
def test_write_ucinet(self):
fh = io.BytesIO()
data = """\
dl n=5 format=fullmatrix
data:
0 1 1 1 1
1 0 1 0 0
1 1 0 0 1
1 0 0 0 0
1 0 1 0 0
"""
graph = eg.MultiDiGraph()
graph.add_nodes_from([0, 1, 2, 3, 4])
graph.add_edges_from(
[
(0, 1),
(0, 2),
(0, 3),
(0, 4),
(1, 0),
(1, 2),
(2, 0),
(2, 1),
(2, 4),
(3, 0),
(4, 0),
(4, 2),
]
)
eg.write_ucinet(graph, fh)
fh.seek(0)
G = eg.parse_ucinet(fh.readlines())
assert sorted(G.nodes) == sorted(graph.nodes)
assert sorted(e[:3] for e in G.edges) == sorted(e[:3] for e in graph.edges)
def test_parse_ucinet_edgelist1(self):
data1 = """
DL n=5
format = edgelist1
labels:
george, sally, jim, billy, jane
data:
1 2
1 3
2 3
3 1
5 4
"""
data2 = """
DL n=5
format = edgelist1
labels embedded:
data:
george sally
george jim
sally jim
jim george
jane billy
"""
G = eg.MultiDiGraph()
G.add_nodes_from(["george", "sally", "jim", "billy", "jane"])
G.add_edges_from(
[
("jim", "george"),
("jane", "billy"),
("sally", "jim"),
("george", "jim"),
("george", "sally"),
]
)
G1 = eg.parse_ucinet(data1)
G2 = eg.parse_ucinet(data2)
assert sorted(G1.nodes) == sorted(G.nodes)
assert sorted(G2.nodes) == sorted(G.nodes)
assert sorted(G1.edges) == sorted(G.edges)
assert sorted(G2.edges) == sorted(G.edges)
+327
View File
@@ -0,0 +1,327 @@
"""
**************
UCINET DL
**************
Read and write graphs in UCINET DL format.
This implementation currently supports only the 'fullmatrix' data format.
Format
------
The UCINET DL format is the most common file format used by UCINET package.
Basic example:
DL N = 5
Data:
0 1 1 1 1
1 0 1 0 0
1 1 0 0 1
1 0 0 0 0
1 0 1 0 0
References
----------
See UCINET User Guide or http://www.analytictech.com/ucinet/help/hs5000.htm
for full format information. Short version on http://www.analytictech.com/networks/dataentry.htm
"""
import re
import shlex
import easygraph as eg
import numpy as np
from easygraph.utils import open_file
__all__ = ["generate_ucinet", "read_ucinet", "parse_ucinet", "write_ucinet"]
def generate_ucinet(G):
"""Generate lines in UCINET graph format.
Parameters
----------
G : graph
A EasyGraph graph
Examples
--------
Notes
-----
The default format 'fullmatrix' is used (for UCINET DL format).
References
----------
See UCINET User Guide or http://www.analytictech.com/ucinet/help/hs5000.htm
for full format information. Short version on http://www.analytictech.com/networks/dataentry.htm
"""
n = G.number_of_nodes()
nodes = sorted(list(G.nodes))
yield "dl n=%i format=fullmatrix" % n
# Labels
try:
int(nodes[0])
except ValueError:
s = "labels:\n"
for label in nodes:
s += label + " "
yield s
yield "data:"
yield str(np.asmatrix(eg.to_numpy_array(G, nodelist=nodes, dtype=int))).replace(
"[", " "
).replace("]", " ").lstrip().rstrip()
@open_file(0, mode="rb")
def read_ucinet(path, encoding="UTF-8"):
"""Read graph in UCINET format from path.
Parameters
----------
path : file or string
File or filename to read.
Filenames ending in .gz or .bz2 will be uncompressed.
Returns
-------
G : EasyGraph MultiGraph or MultiDiGraph.
Examples
--------
>>> G=eg.path_graph(4)
>>> eg.write_ucinet(G, "test.dl")
>>> G=eg.read_ucinet("test.dl")
To create a Graph instead of a MultiGraph use
>>> G1=eg.Graph(G)
See Also
--------
parse_ucinet()
References
----------
See UCINET User Guide or http://www.analytictech.com/ucinet/help/hs5000.htm
for full format information. Short version on http://www.analytictech.com/networks/dataentry.htm
"""
lines = (line.decode(encoding) for line in path)
return parse_ucinet(lines)
@open_file(1, mode="wb")
def write_ucinet(G, path, encoding="UTF-8"):
"""Write graph in UCINET format to path.
Parameters
----------
G : graph
A EasyGraph graph
path : file or string
File or filename to write.
Filenames ending in .gz or .bz2 will be compressed.
Examples
--------
>>> G=eg.path_graph(4)
>>> eg.write_ucinet(G, "test.net")
References
----------
See UCINET User Guide or http://www.analytictech.com/ucinet/help/hs5000.htm
for full format information. Short version on http://www.analytictech.com/networks/dataentry.htm
"""
for line in generate_ucinet(G):
line += "\n"
path.write(line.encode(encoding))
def parse_ucinet(lines):
"""Parse UCINET format graph from string or iterable.
Currently only the 'fullmatrix', 'nodelist1' and 'nodelist1b' formats are supported.
Parameters
----------
lines : string or iterable
Data in UCINET format.
Returns
-------
G : EasyGraph graph
See Also
--------
read_ucinet()
References
----------
See UCINET User Guide or http://www.analytictech.com/ucinet/help/hs5000.htm
for full format information. Short version on http://www.analytictech.com/networks/dataentry.htm
"""
from numpy import genfromtxt
from numpy import isnan
from numpy import reshape
G = eg.MultiDiGraph()
if not isinstance(lines, str):
s = ""
for line in lines:
if type(line) == bytes:
s += line.decode("utf-8")
else:
s += line
lines = s
lexer = shlex.shlex(lines.lower())
lexer.whitespace += ",="
lexer.whitespace_split = True
number_of_nodes = 0
number_of_matrices = 0
nr = 0 # number of rows (rectangular matrix)
nc = 0 # number of columns (rectangular matrix)
ucinet_format = "fullmatrix" # Format by default
labels = {} # Contains labels of nodes
row_labels_embedded = False # Whether labels are embedded in data or not
cols_labels_embedded = False
diagonal = True # whether the main diagonal is present or absent
KEYWORDS = ("format", "data:", "labels:") # TODO remove ':' in keywords
while lexer:
try:
token = next(lexer)
except StopIteration:
break
# print "Token : %s" % token
if token.startswith("n"):
if token.startswith("nr"):
nr = int(get_param(r"\d+", token, lexer))
number_of_nodes = max(nr, nc)
elif token.startswith("nc"):
nc = int(get_param(r"\d+", token, lexer))
number_of_nodes = max(nr, nc)
elif token.startswith("nm"):
number_of_matrices = int(get_param(r"\d+", token, lexer))
else:
number_of_nodes = int(get_param(r"\d+", token, lexer))
nr = number_of_nodes
nc = number_of_nodes
elif token.startswith("diagonal"):
diagonal = get_param("present|absent", token, lexer)
elif token.startswith("format"):
ucinet_format = get_param(
"""^(fullmatrix|upperhalf|lowerhalf|nodelist1|nodelist2|nodelist1b|\
edgelist1|edgelist2|blockmatrix|partition)$""",
token,
lexer,
)
# TODO : row and columns labels
elif token.startswith("row"): # Row labels
pass
elif token.startswith("column"): # Columns labels
pass
elif token.startswith("labels"):
token = next(lexer)
i = 0
while token not in KEYWORDS:
if token.startswith("embedded"):
row_labels_embedded = True
cols_labels_embedded = True
break
else:
labels[i] = token.replace(
'"', ""
) # for labels with embedded spaces
i += 1
try:
token = next(lexer)
except StopIteration:
break
elif token.startswith("data"):
break
data_lines = lines.lower().split("data:", 1)[1]
# Generate edges
params = {}
if cols_labels_embedded:
# params['names'] = True
labels = dict(zip(range(0, nc), data_lines.splitlines()[1].split()))
# params['skip_header'] = 2 # First character is \n
if row_labels_embedded: # Skip first column
# TODO rectangular case : labels can differ from rows to columns
# params['usecols'] = range(1, nc + 1)
pass
if ucinet_format == "fullmatrix":
# In Python3 genfromtxt requires bytes string
try:
data_lines = bytes(data_lines, "utf-8")
except TypeError:
pass
# Do not use splitlines() because it is not necessarily written as a square matrix
data = genfromtxt([data_lines], case_sensitive=False, **params)
if cols_labels_embedded or row_labels_embedded:
# data = insert(data, 0, float('nan'))
data = data[~isnan(data)]
mat = reshape(data, (max(number_of_nodes, nr), -1))
G = eg.from_numpy_array(mat, create_using=eg.MultiDiGraph())
elif ucinet_format in (
"nodelist1",
"nodelist1b",
): # Since genfromtxt only accepts square matrix...
s = ""
for i, line in enumerate(data_lines.splitlines()):
row = line.split()
if row:
if ucinet_format == "nodelist1b" and row[0] == "0":
pass
else:
for neighbor in row[1:]:
if ucinet_format == "nodelist1":
source = row[0]
else:
source = str(i)
s += source + " " + neighbor + "\n"
G = eg.parse_edgelist(
s.splitlines(),
nodetype=str if row_labels_embedded and cols_labels_embedded else int,
create_using=eg.MultiDiGraph(),
)
if not row_labels_embedded or not cols_labels_embedded:
G = eg.relabel_nodes(G, dict(zip(list(G.nodes), [i - 1 for i in G.nodes])))
elif ucinet_format == "edgelist1":
G = eg.parse_edgelist(
data_lines.splitlines(),
nodetype=str if row_labels_embedded and cols_labels_embedded else int,
create_using=eg.MultiDiGraph(),
)
if not row_labels_embedded or not cols_labels_embedded:
G = eg.relabel_nodes(G, dict(zip(list(G.nodes), [i - 1 for i in G.nodes])))
# Relabel nodes
if labels:
try:
if len(list(G.nodes)) < number_of_nodes:
G.add_nodes_from(
labels.values() if labels else range(0, number_of_nodes)
)
G = eg.relabel_nodes(G, labels)
except KeyError:
pass # Nodes already labelled
return G
def get_param(regex, token, lines):
"""
Get a parameter value in UCINET DL file
:param regex: string with the regex matching the parameter value
:param token: token (string) in which we search for the parameter
:param lines: to iterate through the next tokens
:return:
"""
n = token
query = re.search(regex, n)
while query is None:
try:
n = next(lines)
except StopIteration:
raise Exception("Parameter %s value not recognized" % token)
query = re.search(regex, n)
return query.group()