Source code for networkx_temporal.utils.convert.graph_tool
from typing import Any, Union
from ...classes.types import is_static_graph, is_temporal_graph
from ...typing import StaticGraph, TemporalGraph
INT16, INT32, INT64 = 2**15, 2**31, 2**63
NAME_TYPE = {
"bytes": "object",
"dict": "object",
"str": "string",
}
NUM_TYPE = {
"short": 0,
"int": 1,
"long": 2,
"double": 3,
"long double": 4,
}
[docs]
def to_graph_tool(
G: Union[StaticGraph, TemporalGraph, list],
index: str = "id",
encoding: str = "ascii",
errors: str = "strict",
):
""" Convert from NetworkX to `graph-tool <https://graph-tool.skewed.de/>`__.
:param object G: Graph object. Accepts a :class:`~networkx_temporal.classes.TemporalGraph`, a
single static NetworkX graph, or a list of static NetworkX graphs as input.
:param index: Property name to use as the node identifier.
Default is ``'id'``.
:param encoding: Encoding to use for string conversion.
Default is ``'ascii'``.
:param errors: `Error handler <https://docs.python.org/3/library/codecs.html#error-handlers>`__
for string conversion. Default is ``'strict'``.
:rtype: graph_tool.Graph
:note: Original implementation by
`bbengfort (GitHub)
<https://gist.github.com/bbengfort/a430d460966d64edc6cad71c502d7005>`__.
"""
import graph_tool as gt
if not (is_temporal_graph(G) or is_static_graph(G)):
raise TypeError("Input must be a temporal or static NetworkX graph.")
if is_temporal_graph(G) or type(G) == list:
return [to_graph_tool(H, index=index, encoding=encoding, errors=errors) for H in G]
gtG = gt.Graph(directed=G.is_directed())
# Map graph properties.
for key, value in G.graph.items():
type_, key, value = _get_prop(key, value, encoding=encoding, errors=errors)
gtG.graph_properties[key] = gtG.new_graph_property(type_)
gtG.graph_properties[key] = value
# Map node properties.
vs, vp = {}, {}
for node, data in G.nodes(data=True):
type_, key, value = _get_prop(index, node, encoding=encoding, errors=errors)
vp[index].add(type_) if index in vp else vp.__setitem__(index, {type_})
for key, value in data.items():
type_, key, value = _get_prop(key, value, encoding=encoding, errors=errors)
vp[key].add(type_) if key in vp else vp.__setitem__(key, {type_})
# Verify numeric type and ensure each property has a single type.
for key, types in vp.items():
types_ = _get_types(types)
if len(types_) != 1:
raise TypeError(f"Multiple types for node property '{key}': {types}.")
gtG.vp[key] = gtG.new_vertex_property(types_[0])
# Add nodes and their properties.
gtG.vp[index] = gtG.new_vertex_property("string")
for node, data in G.nodes(data=True):
v = gtG.add_vertex()
gtG.vp[index][v] = str(node)
for key, value in data.items():
gtG.vp[key][v] = value
vs[node] = v
# Map edge properties.
ep = {}
for src, dst, data in G.edges(data=True):
for key, value in data.items():
type_, key, value = _get_prop(key, value, encoding=encoding, errors=errors)
ep[key].add(type_) if key in ep else ep.__setitem__(key, {type_})
# Verify numeric type and ensure each property has a single type.
for key, types in ep.items():
types_ = _get_types(types)
if len(types_) != 1:
raise TypeError(f"Multiple types for edge property '{key}': {types}.")
gtG.ep[key] = gtG.new_edge_property(types_[0])
# Add edges and their properties.
for src, dst, data in G.edges(data=True):
e = gtG.add_edge(vs[src], vs[dst])
for key, value in data.items():
gtG.ep[key][e] = value
return gtG
def _get_prop(key: Any, value: Any, encoding: str = "ascii", errors: str = "strict") -> tuple:
""" Performs typing and key/value conversion for graph-tool's `PropertyMap` class.
:param key: Attribute key.
:param value: Attribute value.
:param encoding: Encoding to use for string conversion.
Default is ``'ascii'``.
:param errors: `Error handler <https://docs.python.org/3/library/codecs.html#error-handlers>`__
for string conversion. Default is ``'strict'``.
"""
key = key.encode(encoding, errors=errors).decode(encoding)
type_ = type(value).__name__
type_ = NAME_TYPE.get(type_, type_)
if type_ in ("int", "float"):
if -INT16 <= value < INT16:
type_ = "short"
elif -INT32 <= value < INT32:
type_ = "int"
elif -INT64 <= value < INT64:
type_ = "long"
elif len(str(value)) <= 8:
type_ = "double"
else:
type_ = "long double"
return type_, key, value
def _get_types(types: list) -> list:
""" Returns the type of a property based on list of observed types.
:param types: List of types.
"""
type_ = set(types)
if all(type_ in NUM_TYPE for type_ in types):
type_ = [max(types, key=NUM_TYPE.get)]
return list(type_)