chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
import json
|
||||
import requests
|
||||
import fastjsonschema
|
||||
from copy import deepcopy
|
||||
from typing import Optional, Union, List, Dict, Any
|
||||
from pathlib import Path
|
||||
from easygraph.classes.hypergraph import Hypergraph
|
||||
|
||||
schema_url = "https://raw.githubusercontent.com/pszufe/HIF_validators/main/schemas/hif_schema_v0.1.0.json"
|
||||
|
||||
class EasyGraphHIFError(Exception):
|
||||
"""Custom exception for HIF conversion errors."""
|
||||
pass
|
||||
|
||||
_hif_validator = None
|
||||
|
||||
def _get_hif_validator():
|
||||
global _hif_validator
|
||||
if _hif_validator is None:
|
||||
try:
|
||||
resp = requests.get(schema_url, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
schema = json.loads(resp.text)
|
||||
_hif_validator = fastjsonschema.compile(schema)
|
||||
except Exception:
|
||||
print("Warning: HIF Schema could not be fetched. Validation skipped.")
|
||||
_hif_validator = lambda x: True
|
||||
|
||||
return _hif_validator if _hif_validator else (lambda x: True)
|
||||
|
||||
def hypergraph_to_hif(
|
||||
hg: Hypergraph,
|
||||
filename: Optional[Union[str, Path]] = None,
|
||||
node_label: str = "name",
|
||||
edge_label: str = "name",
|
||||
) -> dict:
|
||||
"""
|
||||
Converts an EasyGraph Hypergraph to HIF JSON.
|
||||
Correctly handles hg.e tuple structure ((edges), (weights), (props)).
|
||||
"""
|
||||
|
||||
if hasattr(hg, "custom_hif_nodes"):
|
||||
nodj = hg.custom_hif_nodes
|
||||
else:
|
||||
nodj = []
|
||||
num_v = hg.num_v if hasattr(hg, "num_v") else len(hg.v_property) if hasattr(hg, "v_property") else 0
|
||||
v_props = getattr(hg, "v_property", [{} for _ in range(num_v)])
|
||||
if not v_props and num_v > 0: v_props = [{} for _ in range(num_v)]
|
||||
|
||||
for i in range(num_v):
|
||||
props = v_props[i] if i < len(v_props) and isinstance(v_props[i], dict) else {}
|
||||
p = props.copy()
|
||||
weight = p.pop("weight", 1.0)
|
||||
if node_label in p:
|
||||
node_id = str(p.get(node_label))
|
||||
if node_label == "name":
|
||||
p.pop("name", None)
|
||||
else:
|
||||
node_id = p.pop("name", str(i))
|
||||
nodj.append({"node": node_id, "weight": weight, "attrs": p})
|
||||
|
||||
e_structure = []
|
||||
e_weights = []
|
||||
e_props = []
|
||||
|
||||
if hasattr(hg, "e") and isinstance(hg.e, tuple) and len(hg.e) == 3 and \
|
||||
isinstance(hg.e[0], (list, tuple)) and isinstance(hg.e[1], (list, tuple)):
|
||||
e_structure = hg.e[0]
|
||||
e_weights = hg.e[1]
|
||||
e_props = hg.e[2]
|
||||
|
||||
elif hasattr(hg, "e_list") and hg.e_list:
|
||||
e_structure = hg.e_list
|
||||
e_weights = getattr(hg, "e_weight", [1.0] * len(e_structure))
|
||||
e_props = getattr(hg, "e_property_full", [{} for _ in range(len(e_structure))])
|
||||
|
||||
elif hasattr(hg, "e") and isinstance(hg.e, (list, tuple)):
|
||||
e_structure = hg.e
|
||||
e_weights = getattr(hg, "e_weight", [1.0] * len(e_structure))
|
||||
e_props = getattr(hg, "e_property_full", [{} for _ in range(len(e_structure))])
|
||||
|
||||
num_e = len(e_structure)
|
||||
|
||||
if len(e_weights) < num_e: e_weights = [1.0] * num_e
|
||||
if len(e_props) < num_e: e_props = [{} for _ in range(num_e)]
|
||||
|
||||
if hasattr(hg, "custom_hif_edges"):
|
||||
edgj = hg.custom_hif_edges
|
||||
else:
|
||||
edgj = []
|
||||
for i in range(num_e):
|
||||
props = e_props[i].copy() if isinstance(e_props[i], dict) else {}
|
||||
# edge_id = props.pop("name", str(i))
|
||||
weight = e_weights[i]
|
||||
props.pop("weight", None)
|
||||
if edge_label in props:
|
||||
edge_id = str(props.get(edge_label))
|
||||
if edge_label == "name":
|
||||
props.pop("name", None)
|
||||
else:
|
||||
edge_id = props.pop("name", str(i))
|
||||
edgj.append({"edge": edge_id, "weight": weight, "attrs": props})
|
||||
|
||||
if hasattr(hg, "custom_hif_incidences"):
|
||||
incj = hg.custom_hif_incidences
|
||||
else:
|
||||
incj = []
|
||||
node_id_list = [n["node"] for n in nodj]
|
||||
edge_id_list = [e["edge"] for e in edgj]
|
||||
|
||||
for e_idx, nodes_in_edge in enumerate(e_structure):
|
||||
if e_idx >= len(edge_id_list): break
|
||||
edge_name = edge_id_list[e_idx]
|
||||
|
||||
flat_nodes = []
|
||||
if isinstance(nodes_in_edge, (list, tuple)):
|
||||
for item in nodes_in_edge:
|
||||
if isinstance(item, (list, tuple)):
|
||||
flat_nodes.extend(item)
|
||||
else:
|
||||
flat_nodes.append(item)
|
||||
else:
|
||||
flat_nodes = [nodes_in_edge]
|
||||
|
||||
for n_idx in flat_nodes:
|
||||
try:
|
||||
n_idx_int = int(n_idx)
|
||||
if 0 <= n_idx_int < len(node_id_list):
|
||||
incj.append({
|
||||
"edge": edge_name,
|
||||
"node": node_id_list[n_idx_int],
|
||||
"weight": 1.0,
|
||||
})
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
metadata = getattr(hg, "metadata", {})
|
||||
network_type = getattr(hg, "network_type", "undirected")
|
||||
|
||||
hif = {
|
||||
"nodes": nodj,
|
||||
"edges": edgj,
|
||||
"incidences": incj,
|
||||
"network-type": network_type,
|
||||
"metadata": metadata
|
||||
}
|
||||
|
||||
try:
|
||||
validator = _get_hif_validator()
|
||||
validator(hif)
|
||||
except Exception as e:
|
||||
print(f"Validation Warning: {e}")
|
||||
|
||||
if filename:
|
||||
with open(filename, "w", encoding='utf-8') as f:
|
||||
json.dump(hif, f, indent=4, ensure_ascii=False)
|
||||
|
||||
return hif
|
||||
|
||||
|
||||
def hif_to_hypergraph(
|
||||
hif: dict = None,
|
||||
filename: Optional[Union[str, Path]] = None,
|
||||
node_label: str = "name",
|
||||
edge_label: str = "name",
|
||||
):
|
||||
"""
|
||||
Reads HIF JSON and returns an EasyGraph Hypergraph.
|
||||
Attaches original JSON parts to 'custom_hif_*' attributes to preserve
|
||||
structure during round-trips.
|
||||
"""
|
||||
if hif is None:
|
||||
if filename is None:
|
||||
raise EasyGraphHIFError("No HIF data or filename provided.")
|
||||
try:
|
||||
with open(filename, "r", encoding='utf-8') as f:
|
||||
hif = json.load(f)
|
||||
except Exception as e:
|
||||
raise EasyGraphHIFError(f"Failed to load HIF file {filename}: {e}")
|
||||
|
||||
nodes_list = hif.get("nodes", [])
|
||||
node_name_to_idx = {rec["node"]: i for i, rec in enumerate(nodes_list)}
|
||||
num_v = len(nodes_list)
|
||||
|
||||
edges_list = hif.get("edges", [])
|
||||
edge_name_to_idx = {rec["edge"]: i for i, rec in enumerate(edges_list)}
|
||||
num_e = len(edges_list)
|
||||
|
||||
v_property = [{} for _ in range(num_v)]
|
||||
for rec in nodes_list:
|
||||
idx = node_name_to_idx.get(rec["node"])
|
||||
if idx is not None:
|
||||
|
||||
prop = rec.get("attrs", {}).copy()
|
||||
if node_label in prop:
|
||||
prop["name"] = str(prop[node_label])
|
||||
else:
|
||||
prop["name"] = rec["node"]
|
||||
prop["weight"] = rec.get("weight", 1.0)
|
||||
v_property[idx] = prop
|
||||
|
||||
e_property_full = [{} for _ in range(num_e)]
|
||||
e_weight = [1.0] * num_e
|
||||
|
||||
for rec in edges_list:
|
||||
idx = edge_name_to_idx.get(rec["edge"])
|
||||
if idx is not None:
|
||||
prop = rec.get("attrs", {}).copy()
|
||||
# if "name" not in prop:
|
||||
# prop["name"] = rec["edge"]
|
||||
if edge_label in prop:
|
||||
prop["name"] = str(prop[edge_label])
|
||||
else:
|
||||
prop["name"] = rec["edge"]
|
||||
prop["weight"] = rec.get("weight", 1.0)
|
||||
e_property_full[idx] = prop
|
||||
e_weight[idx] = prop["weight"]
|
||||
|
||||
raw_groups = [[] for _ in range(num_e)]
|
||||
|
||||
incidences_list = hif.get("incidences", [])
|
||||
|
||||
for inc in incidences_list:
|
||||
e_name = inc.get("edge")
|
||||
n_name = inc.get("node")
|
||||
|
||||
e_idx = edge_name_to_idx.get(e_name)
|
||||
n_idx = node_name_to_idx.get(n_name)
|
||||
|
||||
if e_idx is not None and n_idx is not None:
|
||||
raw_groups[e_idx].append(n_idx)
|
||||
|
||||
hg = Hypergraph(
|
||||
num_v=num_v,
|
||||
e_list=raw_groups,
|
||||
e_weight=e_weight,
|
||||
v_property=v_property
|
||||
)
|
||||
|
||||
hg.node_label_index = {}
|
||||
for i in range(num_v):
|
||||
name = v_property[i].get("name")
|
||||
if name:
|
||||
hg.node_label_index[name] = i
|
||||
|
||||
hg.edge_label_index = {}
|
||||
for i in range(num_e):
|
||||
name = e_property_full[i].get("name")
|
||||
if name:
|
||||
hg.edge_label_index[name] = i
|
||||
|
||||
hg.custom_hif_nodes = deepcopy(nodes_list)
|
||||
hg.custom_hif_edges = deepcopy(edges_list)
|
||||
hg.custom_hif_incidences = deepcopy(incidences_list)
|
||||
|
||||
if "metadata" in hif:
|
||||
hg.metadata = deepcopy(hif["metadata"])
|
||||
else:
|
||||
hg.metadata = {}
|
||||
|
||||
if "network-type" in hif:
|
||||
hg.network_type = hif["network-type"]
|
||||
|
||||
hg.e_property_full = e_property_full
|
||||
|
||||
return hg
|
||||
@@ -0,0 +1,14 @@
|
||||
from easygraph.utils.alias import *
|
||||
from easygraph.utils.convert_class import *
|
||||
from easygraph.utils.convert_to_matrix import *
|
||||
from easygraph.utils.decorators import *
|
||||
from easygraph.utils.download import *
|
||||
from easygraph.utils.exception import *
|
||||
from easygraph.utils.index_of_node import *
|
||||
from easygraph.utils.logging import *
|
||||
from easygraph.utils.mapped_queue import *
|
||||
from easygraph.utils.misc import *
|
||||
from easygraph.utils.relabel import *
|
||||
from easygraph.utils.sparse import *
|
||||
from easygraph.utils.type_change import *
|
||||
from easygraph.utils.HIF import *
|
||||
@@ -0,0 +1,119 @@
|
||||
__all__ = ["create_alias_table", "alias_sample", "alias_setup", "alias_draw"]
|
||||
|
||||
|
||||
def create_alias_table(area_ratio):
|
||||
"""
|
||||
Parameters
|
||||
---------
|
||||
area_ratio :
|
||||
sum(area_ratio)=1
|
||||
|
||||
Returns
|
||||
----------
|
||||
1. accept
|
||||
2. alias
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
l = len(area_ratio)
|
||||
accept, alias = [0] * l, [0] * l
|
||||
small, large = [], []
|
||||
area_ratio_ = np.array(area_ratio) * l
|
||||
for i, prob in enumerate(area_ratio_):
|
||||
if prob < 1.0:
|
||||
small.append(i)
|
||||
else:
|
||||
large.append(i)
|
||||
|
||||
while small and large:
|
||||
small_idx, large_idx = small.pop(), large.pop()
|
||||
accept[small_idx] = area_ratio_[small_idx]
|
||||
alias[small_idx] = large_idx
|
||||
area_ratio_[large_idx] = area_ratio_[large_idx] - (1 - area_ratio_[small_idx])
|
||||
if area_ratio_[large_idx] < 1.0:
|
||||
small.append(large_idx)
|
||||
else:
|
||||
large.append(large_idx)
|
||||
|
||||
while large:
|
||||
large_idx = large.pop()
|
||||
accept[large_idx] = 1
|
||||
while small:
|
||||
small_idx = small.pop()
|
||||
accept[small_idx] = 1
|
||||
|
||||
return accept, alias
|
||||
|
||||
|
||||
def alias_sample(accept, alias):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
accept :
|
||||
|
||||
alias :
|
||||
|
||||
Returns
|
||||
----------
|
||||
sample index
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
N = len(accept)
|
||||
i = int(np.random.random() * N)
|
||||
r = np.random.random()
|
||||
if r < accept[i]:
|
||||
return i
|
||||
else:
|
||||
return alias[i]
|
||||
|
||||
|
||||
def alias_draw(J, q):
|
||||
import numpy as np
|
||||
|
||||
"""
|
||||
Draw sample from a non-uniform discrete distribution using alias sampling.
|
||||
"""
|
||||
K = len(J)
|
||||
|
||||
kk = int(np.floor(np.random.rand() * K))
|
||||
if np.random.rand() < q[kk]:
|
||||
return kk
|
||||
else:
|
||||
return J[kk]
|
||||
|
||||
|
||||
def alias_setup(probs):
|
||||
import numpy as np
|
||||
|
||||
"""
|
||||
Compute utility lists for non-uniform sampling from discrete distributions.
|
||||
Refer to https://hips.seas.harvard.edu/blog/2013/03/03/the-alias-method-efficient-sampling-with-many-discrete-outcomes/
|
||||
for details
|
||||
"""
|
||||
K = len(probs)
|
||||
q = np.zeros(K)
|
||||
J = np.zeros(K, dtype=int)
|
||||
|
||||
smaller = []
|
||||
larger = []
|
||||
for kk, prob in enumerate(probs):
|
||||
q[kk] = K * prob
|
||||
if q[kk] < 1.0:
|
||||
smaller.append(kk)
|
||||
else:
|
||||
larger.append(kk)
|
||||
|
||||
while len(smaller) > 0 and len(larger) > 0:
|
||||
small = smaller.pop()
|
||||
large = larger.pop()
|
||||
|
||||
J[small] = large
|
||||
q[large] = q[large] + q[small] - 1.0
|
||||
if q[large] < 1.0:
|
||||
smaller.append(large)
|
||||
else:
|
||||
larger.append(large)
|
||||
|
||||
return J, q
|
||||
@@ -0,0 +1,19 @@
|
||||
__all__ = [
|
||||
"convert_graph_class",
|
||||
]
|
||||
|
||||
|
||||
def convert_graph_class(G, graph_class):
|
||||
_G = graph_class()
|
||||
_G.graph.update(G.graph)
|
||||
for node, node_attrs in G.nodes.items():
|
||||
dict_attrs = {}
|
||||
for key, value in node_attrs:
|
||||
dict_attrs[key] = value
|
||||
_G.add_node(node, **dict_attrs)
|
||||
for u, v, edge_attrs in G.edges:
|
||||
dict_attrs = {}
|
||||
for key, value in edge_attrs.items():
|
||||
dict_attrs[key] = value
|
||||
_G.add_edge(u, v, **dict_attrs)
|
||||
return _G
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,95 @@
|
||||
import hashlib
|
||||
import warnings
|
||||
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
__all__ = [
|
||||
"check_file",
|
||||
"download_file",
|
||||
"download_and_check",
|
||||
]
|
||||
|
||||
|
||||
def download_file(url: str, file_path: Path):
|
||||
r"""Download a file from a url.
|
||||
|
||||
Args:
|
||||
``url`` (``str``): the url of the file
|
||||
``file_path`` (``str``): the path to the file
|
||||
"""
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
r = requests.get(url, stream=True, verify=True)
|
||||
if r.status_code != 200:
|
||||
raise requests.HTTPError(f"{url} is not accessible.")
|
||||
with open(file_path, "wb") as f:
|
||||
for chunk in r.iter_content(chunk_size=1024):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
|
||||
def check_file(file_path: Path, md5: str):
|
||||
r"""Check if a file is valid.
|
||||
|
||||
Args:
|
||||
``file_path`` (``Path``): The local path of the file.
|
||||
``md5`` (``str``): The md5 of the file.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: Not found the file.
|
||||
"""
|
||||
if not file_path.exists():
|
||||
raise FileNotFoundError(f"{file_path} does not exist.")
|
||||
else:
|
||||
with open(file_path, "rb") as f:
|
||||
data = f.read()
|
||||
cur_md5 = hashlib.md5(data).hexdigest()
|
||||
return cur_md5 == md5
|
||||
|
||||
|
||||
def _retry(n: int, exception_type=requests.HTTPError):
|
||||
r"""A decorator for retrying a function for n times.
|
||||
|
||||
Args:
|
||||
``n`` (``int``): The number of times to retry.
|
||||
"""
|
||||
|
||||
def decorator(fetcher):
|
||||
@wraps(fetcher)
|
||||
def wrapper(*args, **kwargs):
|
||||
for i in range(n - 1):
|
||||
try:
|
||||
return fetcher(*args, **kwargs)
|
||||
except exception_type as e:
|
||||
warnings.warn(f"Retry downloading({i + 1}/{n}): {str(e)}")
|
||||
except Exception as e:
|
||||
raise e
|
||||
return fetcher(*args, **kwargs)
|
||||
# raise FileNotFoundError
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@_retry(3)
|
||||
def download_and_check(url: str, file_path: Path, md5: str):
|
||||
r"""Download a file from a url and check its integrity.
|
||||
|
||||
Args:
|
||||
``url`` (``str``): The url of the file.
|
||||
``file_path`` (``Path``): The path to the file.
|
||||
``md5`` (``str``): The md5 of the file.
|
||||
"""
|
||||
if not file_path.exists():
|
||||
download_file(url, file_path)
|
||||
if not check_file(file_path, md5):
|
||||
file_path.unlink()
|
||||
raise ValueError(
|
||||
f"{file_path} is corrupted. We will delete it, and try to download it"
|
||||
" again."
|
||||
)
|
||||
return True
|
||||
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
**********
|
||||
Exceptions
|
||||
**********
|
||||
|
||||
Base exceptions and errors for EasyGraph.
|
||||
"""
|
||||
|
||||
|
||||
__all__ = [
|
||||
"EasyGraphException",
|
||||
"EasyGraphError",
|
||||
"EasyGraphNotImplemented",
|
||||
"EasyGraphPointlessConcept",
|
||||
]
|
||||
|
||||
|
||||
class EasyGraphException(Exception):
|
||||
"""Base class for exceptions in EasyGraph."""
|
||||
|
||||
|
||||
class EasyGraphError(EasyGraphException):
|
||||
"""Exception for a serious error in EasyGraph"""
|
||||
|
||||
|
||||
class EasyGraphNotImplemented(EasyGraphException):
|
||||
"""Exception raised by algorithms not implemented for a type of graph."""
|
||||
|
||||
|
||||
class EasyGraphPointlessConcept(EasyGraphException):
|
||||
"""Raised when a null graph is provided as input to an algorithm
|
||||
that cannot use it.
|
||||
|
||||
The null graph is sometimes considered a pointless concept [1]_,
|
||||
thus the name of the exception.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Harary, F. and Read, R. "Is the Null Graph a Pointless
|
||||
Concept?" In Graphs and Combinatorics Conference, George
|
||||
Washington University. New York: Springer-Verlag, 1973.
|
||||
|
||||
"""
|
||||
@@ -0,0 +1,12 @@
|
||||
__all__ = ["get_relation_of_index_and_node"]
|
||||
|
||||
|
||||
def get_relation_of_index_and_node(graph):
|
||||
node2idx = {}
|
||||
idx2node = []
|
||||
node_size = 0
|
||||
for node in graph.nodes:
|
||||
node2idx[node] = node_size
|
||||
idx2node.append(node)
|
||||
node_size += 1
|
||||
return idx2node, node2idx
|
||||
@@ -0,0 +1,44 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
|
||||
__all__ = ["default_log_formatter", "simple_stdout2file"]
|
||||
|
||||
|
||||
def default_log_formatter() -> logging.Formatter:
|
||||
r"""Create a default formatter of log messages for logging."""
|
||||
|
||||
return logging.Formatter("[%(levelname)s %(asctime)s]-> %(message)s")
|
||||
|
||||
|
||||
def simple_stdout2file(file_path: Union[str, Path]) -> None:
|
||||
r"""This function simply wraps the ``sys.stdout`` stream, and outputs messages to the ``sys.stdout`` and a specified file, simultaneously.
|
||||
|
||||
Parameters:
|
||||
``file_path`` (``file_path: Union[str, Path]``): The path of the file to output the messages.
|
||||
"""
|
||||
|
||||
class SimpleLogger:
|
||||
def __init__(self, file_path: Path):
|
||||
file_path = Path(file_path).absolute()
|
||||
assert (
|
||||
file_path.parent.exists()
|
||||
), f"The parent directory of {file_path} does not exist."
|
||||
self.file_path = file_path
|
||||
self.terminal = sys.stdout
|
||||
self.file = open(file_path, "a")
|
||||
|
||||
def write(self, message):
|
||||
self.terminal.write(message)
|
||||
self.file.write(message)
|
||||
self.flush()
|
||||
|
||||
def flush(self):
|
||||
self.terminal.flush()
|
||||
self.file.flush()
|
||||
|
||||
file_path = Path(file_path)
|
||||
sys.stdout = SimpleLogger(file_path)
|
||||
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Priority queue class with updatable priorities.
|
||||
Codes from NetworkX - http://networkx.github.io/
|
||||
"""
|
||||
|
||||
|
||||
import heapq
|
||||
|
||||
|
||||
__all__ = ["MappedQueue"]
|
||||
|
||||
|
||||
class MappedQueue:
|
||||
"""
|
||||
The MappedQueue class implements an efficient minimum heap. The
|
||||
smallest element can be popped in O(1) time, new elements can be pushed
|
||||
in O(log n) time, and any element can be removed or updated in O(log n)
|
||||
time. The queue cannot contain duplicate elements and an attempt to push an
|
||||
element already in the queue will have no effect.
|
||||
|
||||
MappedQueue complements the heapq package from the python standard
|
||||
library. While MappedQueue is designed for maximum compatibility with
|
||||
heapq, it has slightly different functionality.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
A `MappedQueue` can be created empty or optionally given an array of
|
||||
initial elements. Calling `push()` will add an element and calling `pop()`
|
||||
will remove and return the smallest element.
|
||||
|
||||
>>> q = MappedQueue([916, 50, 4609, 493, 237])
|
||||
>>> q.push(1310)
|
||||
True
|
||||
>>> x = [q.pop() for i in range(len(q.h))]
|
||||
>>> x
|
||||
[50, 237, 493, 916, 1310, 4609]
|
||||
|
||||
Elements can also be updated or removed from anywhere in the queue.
|
||||
|
||||
>>> q = MappedQueue([916, 50, 4609, 493, 237])
|
||||
>>> q.remove(493)
|
||||
>>> q.update(237, 1117)
|
||||
>>> x = [q.pop() for i in range(len(q.h))]
|
||||
>>> x
|
||||
[50, 916, 1117, 4609]
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2001).
|
||||
Introduction to algorithms second edition.
|
||||
.. [2] Knuth, D. E. (1997). The art of computer programming (Vol. 3).
|
||||
Pearson Education.
|
||||
"""
|
||||
|
||||
def __init__(self, data=[]):
|
||||
"""Priority queue class with updatable priorities."""
|
||||
self.h = list(data)
|
||||
self.d = dict()
|
||||
self._heapify()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.h)
|
||||
|
||||
def _heapify(self):
|
||||
"""Restore heap invariant and recalculate map."""
|
||||
heapq.heapify(self.h)
|
||||
self.d = {elt: pos for pos, elt in enumerate(self.h)}
|
||||
if len(self.h) != len(self.d):
|
||||
raise AssertionError("Heap contains duplicate elements")
|
||||
|
||||
def push(self, elt):
|
||||
"""Add an element to the queue."""
|
||||
# If element is already in queue, do nothing
|
||||
if elt in self.d:
|
||||
return False
|
||||
# Add element to heap and dict
|
||||
pos = len(self.h)
|
||||
self.h.append(elt)
|
||||
self.d[elt] = pos
|
||||
# Restore invariant by sifting down
|
||||
self._siftdown(pos)
|
||||
return True
|
||||
|
||||
def pop(self):
|
||||
"""Remove and return the smallest element in the queue."""
|
||||
# Remove smallest element
|
||||
elt = self.h[0]
|
||||
del self.d[elt]
|
||||
# If elt is last item, remove and return
|
||||
if len(self.h) == 1:
|
||||
self.h.pop()
|
||||
return elt
|
||||
# Replace root with last element
|
||||
last = self.h.pop()
|
||||
self.h[0] = last
|
||||
self.d[last] = 0
|
||||
# Restore invariant by sifting up, then down
|
||||
pos = self._siftup(0)
|
||||
self._siftdown(pos)
|
||||
# Return smallest element
|
||||
return elt
|
||||
|
||||
def update(self, elt, new):
|
||||
"""Replace an element in the queue with a new one."""
|
||||
# Replace
|
||||
pos = self.d[elt]
|
||||
self.h[pos] = new
|
||||
del self.d[elt]
|
||||
self.d[new] = pos
|
||||
# Restore invariant by sifting up, then down
|
||||
pos = self._siftup(pos)
|
||||
self._siftdown(pos)
|
||||
|
||||
def remove(self, elt):
|
||||
"""Remove an element from the queue."""
|
||||
# Find and remove element
|
||||
try:
|
||||
pos = self.d[elt]
|
||||
del self.d[elt]
|
||||
except KeyError:
|
||||
# Not in queue
|
||||
raise
|
||||
# If elt is last item, remove and return
|
||||
if pos == len(self.h) - 1:
|
||||
self.h.pop()
|
||||
return
|
||||
# Replace elt with last element
|
||||
last = self.h.pop()
|
||||
self.h[pos] = last
|
||||
self.d[last] = pos
|
||||
# Restore invariant by sifting up, then down
|
||||
pos = self._siftup(pos)
|
||||
self._siftdown(pos)
|
||||
|
||||
def _siftup(self, pos):
|
||||
"""Move element at pos down to a leaf by repeatedly moving the smaller
|
||||
child up."""
|
||||
h, d = self.h, self.d
|
||||
elt = h[pos]
|
||||
# Continue until element is in a leaf
|
||||
end_pos = len(h)
|
||||
left_pos = (pos << 1) + 1
|
||||
while left_pos < end_pos:
|
||||
# Left child is guaranteed to exist by loop predicate
|
||||
left = h[left_pos]
|
||||
try:
|
||||
right_pos = left_pos + 1
|
||||
right = h[right_pos]
|
||||
# Out-of-place, swap with left unless right is smaller
|
||||
if right < left:
|
||||
h[pos], h[right_pos] = right, elt
|
||||
pos, right_pos = right_pos, pos
|
||||
d[elt], d[right] = pos, right_pos
|
||||
else:
|
||||
h[pos], h[left_pos] = left, elt
|
||||
pos, left_pos = left_pos, pos
|
||||
d[elt], d[left] = pos, left_pos
|
||||
except IndexError:
|
||||
# Left leaf is the end of the heap, swap
|
||||
h[pos], h[left_pos] = left, elt
|
||||
pos, left_pos = left_pos, pos
|
||||
d[elt], d[left] = pos, left_pos
|
||||
# Update left_pos
|
||||
left_pos = (pos << 1) + 1
|
||||
return pos
|
||||
|
||||
def _siftdown(self, pos):
|
||||
"""Restore invariant by repeatedly replacing out-of-place element with
|
||||
its parent."""
|
||||
h, d = self.h, self.d
|
||||
elt = h[pos]
|
||||
# Continue until element is at root
|
||||
while pos > 0:
|
||||
parent_pos = (pos - 1) >> 1
|
||||
parent = h[parent_pos]
|
||||
if parent > elt:
|
||||
# Swap out-of-place element with parent
|
||||
h[parent_pos], h[pos] = elt, parent
|
||||
parent_pos, pos = pos, parent_pos
|
||||
d[elt] = pos
|
||||
d[parent] = parent_pos
|
||||
else:
|
||||
# Invariant is satisfied
|
||||
break
|
||||
return pos
|
||||
@@ -0,0 +1,222 @@
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Iterator
|
||||
from itertools import chain
|
||||
from itertools import tee
|
||||
|
||||
|
||||
__all__ = [
|
||||
"split_len",
|
||||
"split",
|
||||
"nodes_equal",
|
||||
"edges_equal",
|
||||
"pairwise",
|
||||
"graphs_equal",
|
||||
# "arbitrary_element"
|
||||
]
|
||||
|
||||
|
||||
def split_len(nodes, step=30000):
|
||||
ret = []
|
||||
length = len(nodes)
|
||||
for i in range(0, length, step):
|
||||
ret.append(nodes[i : i + step])
|
||||
if len(ret[-1]) * 3 < step:
|
||||
ret[-2] = ret[-2] + ret[-1]
|
||||
ret = ret[:-1]
|
||||
return ret
|
||||
|
||||
|
||||
def split(nodes, n):
|
||||
ret = []
|
||||
length = len(nodes) # 总长
|
||||
step = int(length / n) + 1 # 每份的长度
|
||||
for i in range(0, length, step):
|
||||
ret.append(nodes[i : i + step])
|
||||
return ret
|
||||
|
||||
|
||||
def nodes_equal(nodes1, nodes2):
|
||||
"""Check if nodes are equal.
|
||||
|
||||
Equality here means equal as Python objects.
|
||||
Node data must match if included.
|
||||
The order of nodes is not relevant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nodes1, nodes2 : iterables of nodes, or (node, datadict) tuples
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if nodes are equal, False otherwise.
|
||||
"""
|
||||
nlist1 = list(nodes1)
|
||||
nlist2 = list(nodes2)
|
||||
try:
|
||||
d1 = dict(nlist1)
|
||||
d2 = dict(nlist2)
|
||||
except (ValueError, TypeError):
|
||||
d1 = dict.fromkeys(nlist1)
|
||||
d2 = dict.fromkeys(nlist2)
|
||||
return d1 == d2
|
||||
|
||||
|
||||
def edges_equal(edges1, edges2, need_data=True):
|
||||
"""Check if edges are equal.
|
||||
|
||||
Equality here means equal as Python objects.
|
||||
Edge data must match if included.
|
||||
The order of the edges is not relevant.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
edges1, edges2 : iterables of with u, v nodes as
|
||||
edge tuples (u, v), or
|
||||
edge tuples with data dicts (u, v, d), or
|
||||
edge tuples with keys and data dicts (u, v, k, d)
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if edges are equal, False otherwise.
|
||||
"""
|
||||
from collections import defaultdict
|
||||
|
||||
d1 = defaultdict(dict)
|
||||
d2 = defaultdict(dict)
|
||||
c1 = 0
|
||||
for c1, e in enumerate(edges1):
|
||||
u, v = e[0], e[1]
|
||||
data = []
|
||||
if need_data == True:
|
||||
data = [e[2:]]
|
||||
if v in d1[u]:
|
||||
data = d1[u][v] + data
|
||||
d1[u][v] = data
|
||||
d1[v][u] = data
|
||||
c2 = 0
|
||||
for c2, e in enumerate(edges2):
|
||||
u, v = e[0], e[1]
|
||||
data = []
|
||||
if need_data == True:
|
||||
data = [e[2:]]
|
||||
if v in d2[u]:
|
||||
data = d2[u][v] + data
|
||||
d2[u][v] = data
|
||||
d2[v][u] = data
|
||||
if c1 != c2:
|
||||
return False
|
||||
# can check one direction because lengths are the same.
|
||||
for n, nbrdict in d1.items():
|
||||
for nbr, datalist in nbrdict.items():
|
||||
if n not in d2:
|
||||
return False
|
||||
if nbr not in d2[n]:
|
||||
return False
|
||||
d2datalist = d2[n][nbr]
|
||||
for data in datalist:
|
||||
if datalist.count(data) != d2datalist.count(data):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# Recipe from the itertools documentation.
|
||||
def pairwise(iterable, cyclic=False):
|
||||
"s -> (s0, s1), (s1, s2), (s2, s3), ..."
|
||||
a, b = tee(iterable)
|
||||
first = next(b, None)
|
||||
if cyclic is True:
|
||||
return zip(a, chain(b, (first,)))
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
def graphs_equal(graph1, graph2):
|
||||
"""Check if graphs are equal.
|
||||
|
||||
Equality here means equal as Python objects (not isomorphism).
|
||||
Node, edge and graph data must match.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
graph1, graph2 : graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if graphs are equal, False otherwise.
|
||||
"""
|
||||
return (
|
||||
graph1.adj == graph2.adj
|
||||
and graph1.nodes == graph2.nodes
|
||||
and graph1.graph == graph2.graph
|
||||
)
|
||||
|
||||
|
||||
# def arbitrary_element(iterable):
|
||||
# """Returns an arbitrary element of `iterable` without removing it.
|
||||
|
||||
# This is most useful for "peeking" at an arbitrary element of a set,
|
||||
# but can be used for any list, dictionary, etc., as well.
|
||||
|
||||
# Parameters
|
||||
# ----------
|
||||
# iterable : `abc.collections.Iterable` instance
|
||||
# Any object that implements ``__iter__``, e.g. set, dict, list, tuple,
|
||||
# etc.
|
||||
|
||||
# Returns
|
||||
# -------
|
||||
# The object that results from ``next(iter(iterable))``
|
||||
|
||||
# Raises
|
||||
# ------
|
||||
# ValueError
|
||||
# If `iterable` is an iterator (because the current implementation of
|
||||
# this function would consume an element from the iterator).
|
||||
|
||||
# Examples
|
||||
# --------
|
||||
# Arbitrary elements from common Iterable objects:
|
||||
|
||||
# >>> eg.utils.arbitrary_element([1, 2, 3]) # list
|
||||
# 1
|
||||
# >>> eg.utils.arbitrary_element((1, 2, 3)) # tuple
|
||||
# 1
|
||||
# >>> eg.utils.arbitrary_element({1, 2, 3}) # set
|
||||
# 1
|
||||
# >>> d = {k: v for k, v in zip([1, 2, 3], [3, 2, 1])}
|
||||
# >>> eg.utils.arbitrary_element(d) # dict_keys
|
||||
# 1
|
||||
# >>> eg.utils.arbitrary_element(d.values()) # dict values
|
||||
# 3
|
||||
|
||||
# `str` is also an Iterable:
|
||||
|
||||
# >>> eg.utils.arbitrary_element("hello")
|
||||
# 'h'
|
||||
|
||||
# :exc:`ValueError` is raised if `iterable` is an iterator:
|
||||
|
||||
# >>> iterator = iter([1, 2, 3]) # Iterator, *not* Iterable
|
||||
# >>> eg.utils.arbitrary_element(iterator)
|
||||
# Traceback (most recent call last):
|
||||
# ...
|
||||
# ValueError: cannot return an arbitrary item from an iterator
|
||||
|
||||
# Notes
|
||||
# -----
|
||||
# This function does not return a *random* element. If `iterable` is
|
||||
# ordered, sequential calls will return the same value::
|
||||
|
||||
# >>> l = [1, 2, 3]
|
||||
# >>> eg.utils.arbitrary_element(l)
|
||||
# 1
|
||||
# >>> eg.utils.arbitrary_element(l)
|
||||
# 1
|
||||
|
||||
# """
|
||||
# if isinstance(iterable, Iterator):
|
||||
# raise ValueError("cannot return an arbitrary item from an iterator")
|
||||
# # Another possible implementation is ``for x in iterable: return x``.
|
||||
# return next(iter(iterable))
|
||||
@@ -0,0 +1,106 @@
|
||||
import easygraph as eg
|
||||
|
||||
|
||||
__all__ = ["relabel_nodes", "convert_node_labels_to_integers"]
|
||||
|
||||
|
||||
def relabel_nodes(G, mapping):
|
||||
if not hasattr(mapping, "__getitem__"):
|
||||
m = {n: mapping(n) for n in G}
|
||||
else:
|
||||
m = mapping
|
||||
return _relabel_copy(G, m)
|
||||
|
||||
|
||||
def _relabel_copy(G, mapping):
|
||||
H = G.__class__()
|
||||
H.add_nodes_from(mapping.get(n, n) for n in G)
|
||||
H._node.update((mapping.get(n, n), d.copy()) for n, d in G.nodes.items())
|
||||
if G.is_multigraph():
|
||||
new_edges = [
|
||||
(mapping.get(n1, n1), mapping.get(n2, n2), k, d.copy())
|
||||
for (n1, n2, k, d) in G.edges
|
||||
]
|
||||
|
||||
# check for conflicting edge-keys
|
||||
undirected = not G.is_directed()
|
||||
seen_edges = set()
|
||||
for i, (source, target, key, data) in enumerate(new_edges):
|
||||
while (source, target, key) in seen_edges:
|
||||
if not isinstance(key, (int, float)):
|
||||
key = 0
|
||||
key += 1
|
||||
seen_edges.add((source, target, key))
|
||||
if undirected:
|
||||
seen_edges.add((target, source, key))
|
||||
new_edges[i] = (source, target, key, data)
|
||||
|
||||
H.add_edges_from(new_edges)
|
||||
else:
|
||||
H.add_edges_from(
|
||||
(mapping.get(n1, n1), mapping.get(n2, n2), d.copy())
|
||||
for (n1, n2, d) in G.edges
|
||||
)
|
||||
H.graph.update(G.graph)
|
||||
return H
|
||||
|
||||
|
||||
def convert_node_labels_to_integers(
|
||||
G, first_label=0, ordering="default", label_attribute=None
|
||||
):
|
||||
"""Returns a copy of the graph G with the nodes relabeled using
|
||||
consecutive integers.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A easygraph graph
|
||||
|
||||
first_label : int, optional (default=0)
|
||||
An integer specifying the starting offset in numbering nodes.
|
||||
The new integer labels are numbered first_label, ..., n-1+first_label.
|
||||
|
||||
ordering : string
|
||||
"default" : inherit node ordering from G.nodes
|
||||
"sorted" : inherit node ordering from sorted(G.nodes)
|
||||
"increasing degree" : nodes are sorted by increasing degree
|
||||
"decreasing degree" : nodes are sorted by decreasing degree
|
||||
|
||||
label_attribute : string, optional (default=None)
|
||||
Name of node attribute to store old label. If None no attribute
|
||||
is created.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Node and edge attribute data are copied to the new (relabeled) graph.
|
||||
|
||||
There is no guarantee that the relabeling of nodes to integers will
|
||||
give the same two integers for two (even identical graphs).
|
||||
Use the `ordering` argument to try to preserve the order.
|
||||
|
||||
See Also
|
||||
--------
|
||||
relabel_nodes
|
||||
"""
|
||||
N = G.number_of_nodes() + first_label
|
||||
if ordering == "default":
|
||||
mapping = dict(zip(G.nodes, range(first_label, N)))
|
||||
elif ordering == "sorted":
|
||||
nlist = sorted(G.nodes)
|
||||
mapping = dict(zip(nlist, range(first_label, N)))
|
||||
elif ordering == "increasing degree":
|
||||
dv_pairs = [(d, n) for (n, d) in G.degree()]
|
||||
dv_pairs.sort() # in-place sort from lowest to highest degree
|
||||
mapping = dict(zip([n for d, n in dv_pairs], range(first_label, N)))
|
||||
elif ordering == "decreasing degree":
|
||||
dv_pairs = [(d, n) for (n, d) in G.degree()]
|
||||
dv_pairs.sort() # in-place sort from lowest to highest degree
|
||||
dv_pairs.reverse()
|
||||
mapping = dict(zip([n for d, n in dv_pairs], range(first_label, N)))
|
||||
else:
|
||||
raise eg.EasyGraphError(f"Unknown node ordering: {ordering}")
|
||||
H = relabel_nodes(G, mapping)
|
||||
# create node attribute with the old label
|
||||
if label_attribute is not None:
|
||||
eg.set_node_attributes(H, {v: k for k, v in mapping.items()}, label_attribute)
|
||||
return H
|
||||
@@ -0,0 +1,39 @@
|
||||
__all__ = ["sparse_dropout"]
|
||||
|
||||
|
||||
# if not type checking
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import torch
|
||||
|
||||
|
||||
def sparse_dropout(
|
||||
sp_mat: "torch.Tensor", p: float, fill_value: float = 0.0
|
||||
) -> "torch.Tensor":
|
||||
import torch
|
||||
|
||||
r"""Dropout function for sparse matrix. This function will return a new sparse matrix with the same shape as the input sparse matrix, but with some elements dropped out.
|
||||
|
||||
Args:
|
||||
``sp_mat`` (``torch.Tensor``): The sparse matrix with format ``torch.sparse_coo_tensor``.
|
||||
``p`` (``float``): Probability of an element to be dropped.
|
||||
``fill_value`` (``float``): The fill value for dropped elements. Defaults to ``0.0``.
|
||||
"""
|
||||
device = sp_mat.device
|
||||
sp_mat = sp_mat.coalesce()
|
||||
assert 0 <= p <= 1
|
||||
if p == 0:
|
||||
return sp_mat
|
||||
p = torch.ones(sp_mat._nnz(), device=device) * p
|
||||
keep_mask = torch.bernoulli(1 - p).to(device)
|
||||
fill_values = torch.logical_not(keep_mask) * fill_value
|
||||
new_sp_mat = torch.sparse_coo_tensor(
|
||||
sp_mat._indices(),
|
||||
sp_mat._values() * keep_mask + fill_values,
|
||||
size=sp_mat.size(),
|
||||
device=sp_mat.device,
|
||||
dtype=sp_mat.dtype,
|
||||
)
|
||||
return new_sp_mat
|
||||
@@ -0,0 +1,147 @@
|
||||
import easygraph as eg
|
||||
|
||||
|
||||
__all__ = [
|
||||
"from_pyGraphviz_agraph",
|
||||
"to_pyGraphviz_agraph",
|
||||
]
|
||||
|
||||
|
||||
def from_pyGraphviz_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_pyGraphviz_agraph(K5)
|
||||
>>> G = eg.from_pyGraphviz_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_pyGraphviz_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_pyGraphviz_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(data=True):
|
||||
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(data=True, keys=True):
|
||||
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(data=True):
|
||||
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
|
||||
Reference in New Issue
Block a user