Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into forceatlas2-networkx
Browse files Browse the repository at this point in the history
  • Loading branch information
cvanelteren committed May 2, 2024
2 parents 367ff4a + 655f465 commit 47d46b6
Show file tree
Hide file tree
Showing 14 changed files with 120 additions and 43 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ jobs:
run: |
pip install --upgrade pip
pip install -r requirements/default.txt -r requirements/test.txt
pip install --global-option=build_ext --global-option="-I/usr/local/include/" --global-option="-L/usr/local/lib/" pygraphviz
pip install --no-cache-dir \
--config-settings="--global-option=build_ext" \
--config-settings="--global-option=-I$(brew --prefix graphviz)/include/" \
--config-settings="--global-option=-L$(brew --prefix graphviz)/lib/" \
pygraphviz
pip install -r requirements/extra.txt
pip install .
pip list
Expand Down
30 changes: 30 additions & 0 deletions benchmarks/benchmarks/benchmark_to_networkx_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import networkx as nx


class ToNetworkXGraphBenchmark:
params = [nx.Graph, nx.DiGraph]
param_names = ["graph_type"]

def setup(self, graph_type):
self.edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]

def time_to_networkx_graph_direct(self, graph_type):
_ = nx.to_networkx_graph(self.edges, create_using=graph_type)

def time_to_networkx_graph_via_constructor(self, graph_type):
_ = graph_type(self.edges)

### NOTE: Multi-instance checks are explicitly included to cover the case
# where many graph instances are created, which is not uncommon in graph
# analysis. The reason why multi-instance is explicitly probed (rather than
# relying solely on the number of repeats/runs from `timeit` in the benchmark
# suite) is to capture/amplify any distinctions from potential import
# cacheing of the try-excepts in the *same* run

def time_to_networkx_graph_direct_multi_instance(self, graph_type):
for _ in range(500): # Creating many graph instances
_ = nx.to_networkx_graph(self.edges, create_using=graph_type)

def time_to_networkx_graph_via_constructor_multi_instance(self, graph_type):
for _ in range(500): # Creating many graph instances
_ = graph_type(self.edges)
1 change: 1 addition & 0 deletions doc/developer/about_us.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ to add your name to the bottom of the list.
- Sebastiano Vigna, Github: `https://github.com/vigna`
- Aaron Zolnai-Lucas, GitHub: `aaronzo <https://github.com/aaronzo>`_, LinkedIn: `aaronzolnailucas <https://www.linkedin.com/in/aaronzolnailucas/>`_
- Erik Welch, GitHub: `eriknw <https://github.com/eriknw>`_, LinkedIn: `eriknwelch <https://www.linkedin.com/in/eriknwelch/>`_
- Mohamed Rezk, Github: `mohamedrezk122 <https://github.com/mohamedrezk122>`_

A supplementary (but still incomplete) list of contributors is given by the
list of names that have commits in ``networkx``'s
Expand Down
44 changes: 27 additions & 17 deletions examples/algorithms/plot_lca.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,44 @@
import networkx as nx
import matplotlib.pyplot as plt

# Generate a random tree with its node positions
G = nx.random_tree(14, seed=100, create_using=nx.DiGraph)
G = nx.DiGraph(
[
(0, 2),
(2, 7),
(2, 11),
(5, 6),
(6, 9),
(6, 8),
(7, 1),
(7, 3),
(8, 12),
(11, 10),
(11, 5),
(12, 4),
(12, 13),
]
)
pos = nx.nx_agraph.graphviz_layout(G, prog="dot")

# Compute lowest-common ancestors for certain node pairs
ancestors = list(nx.all_pairs_lowest_common_ancestor(G, ((1, 3), (4, 9), (13, 10))))

# Create node color and edge color lists
node_color_map = []
edge_color_map = []
for i in range(14):
node_color_map.append("#D5D7D8")
edge_color_map.append("None")
template = ["#FFE799", "#FFD23F", "#CEB6E2", "#A77CCB", "#88DFE7", "#45CDD9"]
x = 0
for i in ancestors:
for j in i[0]:
node_color_map[j] = template[x]
x += 1
node_color_map[i[1]] = template[x]
edge_color_map[i[1]] = "black"
x += 1
node_colors = ["#D5D7D8" for _ in G]
node_edge_colors = ["None" for _ in G]
node_seq = list(G.nodes)
clr_pairs = (("cyan", "tab:blue"), ("moccasin", "tab:orange"), ("lime", "tab:green"))
for (children, ancestor), (child_clr, anc_clr) in zip(ancestors, clr_pairs):
for c in children:
node_colors[node_seq.index(c)] = child_clr
node_colors[node_seq.index(ancestor)] = anc_clr
node_edge_colors[node_seq.index(ancestor)] = "black"

# Plot tree
plt.figure(figsize=(15, 15))
plt.title("Visualize Lowest Common Ancestors of node pairs")
nx.draw_networkx_nodes(
G, pos, node_color=node_color_map, node_size=2000, edgecolors=edge_color_map
G, pos, node_color=node_colors, node_size=2000, edgecolors=node_edge_colors
)
nx.draw_networkx_edges(G, pos)
nx.draw_networkx_labels(G, pos, font_size=15)
Expand Down
5 changes: 4 additions & 1 deletion networkx/algorithms/non_randomness.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def non_randomness(G, k=None, weight="weight"):
NetworkXException
if the input graph is not connected.
NetworkXError
if the input graph contains self-loops.
if the input graph contains self-loops or if graph has no edges.
Examples
--------
Expand All @@ -74,6 +74,9 @@ def non_randomness(G, k=None, weight="weight"):
"""
import numpy as np

# corner case: graph has no edges
if nx.is_empty(G):
raise nx.NetworkXError("non_randomness not applicable to empty graphs")
if not nx.is_connected(G):
raise nx.NetworkXException("Non connected graph.")
if len(list(nx.selfloop_edges(G))) > 0:
Expand Down
2 changes: 1 addition & 1 deletion networkx/algorithms/tests/test_cycles.py
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,7 @@ class TestGirth:
(nx.petersen_graph(), 5),
(nx.heawood_graph(), 6),
(nx.pappus_graph(), 6),
(nx.random_tree(10, seed=42), inf),
(nx.random_labeled_tree(10, seed=42), inf),
(nx.empty_graph(10), inf),
(nx.Graph(chain(cycle_edges(range(5)), cycle_edges(range(6, 10)))), 4),
(
Expand Down
13 changes: 9 additions & 4 deletions networkx/algorithms/tests/test_non_randomness.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,21 @@ def test_non_randomness(k, weight, expected):


def test_non_connected():
G = nx.Graph()
G.add_edge(1, 2)
G = nx.Graph([(1, 2)])
G.add_node(3)
with pytest.raises(nx.NetworkXException):
with pytest.raises(nx.NetworkXException, match="Non connected"):
nx.non_randomness(G)


def test_self_loops():
G = nx.Graph()
G.add_edge(1, 2)
G.add_edge(1, 1)
with pytest.raises(nx.NetworkXError):
with pytest.raises(nx.NetworkXError, match="Graph must not contain self-loops"):
nx.non_randomness(G)


def test_empty_graph():
G = nx.empty_graph(1)
with pytest.raises(nx.NetworkXError, match=".*not applicable to empty graphs"):
nx.non_randomness(G)
3 changes: 2 additions & 1 deletion networkx/algorithms/tests/test_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import networkx as nx

cycle = nx.cycle_graph(5, create_using=nx.DiGraph)
tree = nx.random_tree(10, create_using=nx.DiGraph, seed=42)
tree = nx.DiGraph()
tree.add_edges_from(nx.random_labeled_tree(10, seed=42).edges)
path = nx.path_graph(5, create_using=nx.DiGraph)
binomial = nx.binomial_tree(3, create_using=nx.DiGraph)
HH = nx.directed_havel_hakimi_graph([1, 2, 1, 2, 2, 2], [3, 1, 0, 1, 2, 3])
Expand Down
12 changes: 10 additions & 2 deletions networkx/classes/reportviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
The argument `nbunch` restricts edges to those incident to nodes in nbunch.
"""
from abc import ABC
from collections.abc import Mapping, Set

import networkx as nx
Expand Down Expand Up @@ -734,8 +735,15 @@ def __iter__(self):
yield (n, deg)


# A base class for all edge views. Ensures all edge view and edge data view
# objects/classes are captured by `isinstance(obj, EdgeViewABC)` and
# `issubclass(cls, EdgeViewABC)` respectively
class EdgeViewABC(ABC):
pass


# EdgeDataViews
class OutEdgeDataView:
class OutEdgeDataView(EdgeViewABC):
"""EdgeDataView for outward edges of DiGraph; See EdgeDataView"""

__slots__ = (
Expand Down Expand Up @@ -1036,7 +1044,7 @@ def __contains__(self, e):


# EdgeViews have set operations and no data reported
class OutEdgeView(Set, Mapping):
class OutEdgeView(Set, Mapping, EdgeViewABC):
"""A EdgeView class for outward edges of a DiGraph"""

__slots__ = ("_adjdict", "_graph", "_nodes_nbrs")
Expand Down
8 changes: 8 additions & 0 deletions networkx/classes/tests/test_reportviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -1425,3 +1425,11 @@ def test_cache_dict_get_set_state(graph):
# Raises error if the cached properties and views do not work
pickle.loads(pickle.dumps(G, -1))
deepcopy(G)


def test_edge_views_inherit_from_EdgeViewABC():
all_edge_view_classes = (v for v in dir(nx.reportviews) if "Edge" in v)
for eview_class in all_edge_view_classes:
assert issubclass(
getattr(nx.reportviews, eview_class), nx.reportviews.EdgeViewABC
)
21 changes: 14 additions & 7 deletions networkx/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,6 @@ def to_networkx_graph(data, create_using=None, multigraph_input=False):
except Exception as err:
raise nx.NetworkXError("Input is not a correct NetworkX graph.") from err

# pygraphviz agraph
if hasattr(data, "is_strict"):
try:
return nx.nx_agraph.from_agraph(data, create_using=create_using)
except Exception as err:
raise nx.NetworkXError("Input is not a correct pygraphviz graph.") from err

# dict of dicts/lists
if isinstance(data, dict):
try:
Expand All @@ -113,6 +106,20 @@ def to_networkx_graph(data, create_using=None, multigraph_input=False):
except Exception as err2:
raise TypeError("Input is not known type.") from err2

# edgelists
if isinstance(data, list | tuple | nx.reportviews.EdgeViewABC | Iterator):
try:
return from_edgelist(data, create_using=create_using)
except:
pass

# pygraphviz agraph
if hasattr(data, "is_strict"):
try:
return nx.nx_agraph.from_agraph(data, create_using=create_using)
except Exception as err:
raise nx.NetworkXError("Input is not a correct pygraphviz graph.") from err

# Pandas DataFrame
try:
import pandas as pd
Expand Down
2 changes: 1 addition & 1 deletion networkx/tests/test_all_random_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def kernel_integral(u, w, z):
t(nx.random_lobster, n, p1, p2, seed=seed)
t(nx.random_powerlaw_tree, n, seed=seed, tries=5000)
t(nx.random_powerlaw_tree_sequence, 10, seed=seed, tries=5000)
t(nx.random_tree, n, seed=seed)
t(nx.random_labeled_tree, n, seed=seed)
t(nx.utils.powerlaw_sequence, n, seed=seed)
t(nx.utils.zipf_rv, 2.3, seed=seed)
cdist = [0.2, 0.4, 0.5, 0.7, 0.9, 1.0]
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ developer = [
'rtoml',
]
doc = [
'sphinx>=7',
'pydata-sphinx-theme>=0.14',
'sphinx-gallery>=0.14',
'sphinx>=7.3',
'pydata-sphinx-theme>=0.15',
'sphinx-gallery>=0.16',
'numpydoc>=1.7',
'pillow>=9.4',
'texext>=0.6.7',
'myst-nb>=1.0',
'myst-nb>=1.1',
]
extra = [
'lxml>=4.6',
Expand Down
8 changes: 4 additions & 4 deletions requirements/doc.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Generated via tools/generate_requirements.py and pre-commit hook.
# Do not edit this file; modify pyproject.toml instead.
sphinx>=7
pydata-sphinx-theme>=0.14
sphinx-gallery>=0.14
sphinx>=7.3
pydata-sphinx-theme>=0.15
sphinx-gallery>=0.16
numpydoc>=1.7
pillow>=9.4
texext>=0.6.7
myst-nb>=1.0
myst-nb>=1.1

0 comments on commit 47d46b6

Please sign in to comment.