Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAINT: Minor touchups to tadpole and lollipop graph #7049

Merged
merged 7 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 31 additions & 23 deletions networkx/generators/classic.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,27 +584,32 @@ def ladder_graph(n, create_using=None):
@nodes_or_number([0, 1])
@nx._dispatch(graphs=None)
def lollipop_graph(m, n, create_using=None):
"""Returns the Lollipop Graph; `K_m` connected to `P_n`.
"""Returns the Lollipop Graph; ``K_m`` connected to ``P_n``.

This is the Barbell Graph without the right barbell.

Parameters
----------
m, n : int or iterable container of nodes (default = 0)
If an integer, nodes are from `range(m)` and `range(m,m+n)`.
m, n : int or iterable container of nodes
If an integer, nodes are from ``range(m)`` and ``range(m, m+n)``.
If a container of nodes, those nodes appear in the graph.
Warning: m and n are not checked for duplicates and if present the
Warning: `m` and `n` are not checked for duplicates and if present the
resulting graph may not be as desired. Make sure you have no duplicates.

The nodes for m appear in the complete graph $K_m$ and the nodes
for n appear in the path $P_n$
The nodes for `m` appear in the complete graph $K_m$ and the nodes
for `n` appear in the path $P_n$
create_using : NetworkX graph constructor, optional (default=nx.Graph)
Graph type to create. If graph instance, then cleared before populated.

Returns
-------
Networkx graph
A complete graph with `m` nodes connected to a path of length `n`.

Notes
-----
The 2 subgraphs are joined via an edge (m-1, m).
If n=0, this is merely a complete graph.
The 2 subgraphs are joined via an edge ``(m-1, m)``.
If ``n=0``, this is merely a complete graph.

(This graph is an extremal example in David Aldous and Jim
Fill's etext on Random Walks on Graphs.)
Expand Down Expand Up @@ -710,35 +715,39 @@ def star_graph(n, create_using=None):
@nodes_or_number([0, 1])
@nx._dispatch(graphs=None)
def tadpole_graph(m, n, create_using=None):
"""Returns the (m,n)-tadpole graph; `C_m` connected to `P_n`.
"""Returns the (m,n)-tadpole graph; ``C_m`` connected to ``P_n``.

This graph on m+n nodes connects a cycle of size m to a path of length n.
It looks like a tadpole.
It is also called a kite graph or a dragon graph.
This graph on m+n nodes connects a cycle of size `m` to a path of length `n`.
It looks like a tadpole. It is also called a kite graph or a dragon graph.

Parameters
----------
m, n : int or iterable container of nodes (default = 0)
If an integer, nodes are from `range(m)` and `range(m,m+n)`.
m, n : int or iterable container of nodes
If an integer, nodes are from ``range(m)`` and ``range(m,m+n)``.
If a container of nodes, those nodes appear in the graph.
Warning: m and n are not checked for duplicates and if present the
Warning: `m` and `n` are not checked for duplicates and if present the
resulting graph may not be as desired.

The nodes for m appear in the cycle graph $C_m$ and the nodes
for n appear in the path $P_n$.
The nodes for `m` appear in the cycle graph $C_m$ and the nodes
for `n` appear in the path $P_n$.
create_using : NetworkX graph constructor, optional (default=nx.Graph)
Graph type to create. If graph instance, then cleared before populated.

Raises
Returns
-------
Networkx graph
A cycle of size `m` connected to a path of length `n`.

Raises
------
NetworkXError
If `m < 2`. The tadpole graph is undefined for `m<2`.
If ``m < 2``. The tadpole graph is undefined for ``m<2``.

Notes
-----
The 2 subgraphs are joined via an edge (m-1, m).
If n=0, this is a cycle graph.
m and/or n can be a container of nodes instead of an integer.
The 2 subgraphs are joined via an edge ``(m-1, m)``.
If ``n=0``, this is a cycle graph.
`m` and/or `n` can be a container of nodes instead of an integer.

"""
m, m_nodes = m
Expand All @@ -749,7 +758,6 @@ def tadpole_graph(m, n, create_using=None):
n, n_nodes = n
if isinstance(m, numbers.Integral) and isinstance(n, numbers.Integral):
n_nodes = list(range(M, M + n))
N = len(n_nodes)

# the circle
G = cycle_graph(m_nodes, create_using)
Expand Down
119 changes: 58 additions & 61 deletions networkx/generators/tests/test_classic.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,18 +343,17 @@ def test_ladder_graph(self):
mg = nx.ladder_graph(2, create_using=nx.MultiGraph)
assert edges_equal(mg.edges(), g.edges())

def test_lollipop_graph_right_sizes(self):
# number of nodes = m1 + m2
# number of edges = nx.number_of_edges(nx.complete_graph(m1)) + m2
for m1, m2 in [(3, 5), (4, 10), (3, 20)]:
G = nx.lollipop_graph(m1, m2)
assert nx.number_of_nodes(G) == m1 + m2
assert nx.number_of_edges(G) == m1 * (m1 - 1) / 2 + m2
for first, second in [("ab", ""), ("abc", "defg")]:
m1, m2 = len(first), len(second)
G = nx.lollipop_graph(first, second)
assert nx.number_of_nodes(G) == m1 + m2
assert nx.number_of_edges(G) == m1 * (m1 - 1) / 2 + m2
@pytest.mark.parametrize(("m", "n"), [(3, 5), (4, 10), (3, 20)])
def test_lollipop_graph_right_sizes(self, m, n):
G = nx.lollipop_graph(m, n)
assert nx.number_of_nodes(G) == m + n
assert nx.number_of_edges(G) == m * (m - 1) / 2 + n

@pytest.mark.parametrize(("m", "n"), [("ab", ""), ("abc", "defg")])
def test_lollipop_graph_size_node_sequence(self, m, n):
G = nx.lollipop_graph(m, n)
assert nx.number_of_nodes(G) == len(m) + len(n)
assert nx.number_of_edges(G) == len(m) * (len(m) - 1) / 2 + len(n)

def test_lollipop_graph_exceptions(self):
# Raise NetworkXError if m<2
Expand All @@ -372,30 +371,31 @@ def test_lollipop_graph_exceptions(self):
with pytest.raises(nx.NetworkXError):
nx.lollipop_graph(2, 20, create_using=nx.MultiDiGraph)

def test_lollipop_graph_same_as_path_when_m1_is_2(self):
# lollipop_graph(2,m) = path_graph(m+2)
for m1, m2 in [(2, 0), (2, 5), (2, 10), ("ab", 20)]:
G = nx.lollipop_graph(m1, m2)
assert is_isomorphic(G, nx.path_graph(m2 + 2))
@pytest.mark.parametrize(("m", "n"), [(2, 0), (2, 5), (2, 10), ("ab", 20)])
def test_lollipop_graph_same_as_path_when_m1_is_2(self, m, n):
G = nx.lollipop_graph(m, n)
assert is_isomorphic(G, nx.path_graph(n + 2))

def test_lollipop_graph_for_multigraph(self):
G = nx.lollipop_graph(5, 20)
MG = nx.lollipop_graph(5, 20, create_using=nx.MultiGraph)
assert edges_equal(MG.edges(), G.edges())

def test_lollipop_graph_mixing_input_types(self):
cases = [(4, "abc"), ("abcd", 3), ([1, 2, 3, 4], "abc"), ("abcd", [1, 2, 3])]
for m1, m2 in cases:
G = nx.lollipop_graph(m1, m2)
assert len(G) == 7
assert G.size() == 9
@pytest.mark.parametrize(
("m", "n"),
[(4, "abc"), ("abcd", 3), ([1, 2, 3, 4], "abc"), ("abcd", [1, 2, 3])],
)
def test_lollipop_graph_mixing_input_types(self, m, n):
expected = nx.compose(nx.complete_graph(4), nx.path_graph(range(100, 103)))
expected.add_edge(0, 100) # Connect complete graph and path graph
assert is_isomorphic(nx.lollipop_graph(m, n), expected)

def test_lollipop_graph_not_int_integer_inputs(self):
# test non-int integers
def test_lollipop_graph_non_builtin_ints(self):
np = pytest.importorskip("numpy")
G = nx.lollipop_graph(np.int32(4), np.int64(3))
assert len(G) == 7
assert G.size() == 9
expected = nx.compose(nx.complete_graph(4), nx.path_graph(range(100, 103)))
expected.add_edge(0, 100) # Connect complete graph and path graph
assert is_isomorphic(G, expected)

def test_null_graph(self):
assert nx.number_of_nodes(nx.null_graph()) == 0
Expand Down Expand Up @@ -473,20 +473,17 @@ def test_non_int_integers_for_star_graph(self):
assert len(G) == 4
assert G.size() == 3

def test_tadpole_graph_right_sizes(self):
# number of nodes = m1 + m2
# number of edges = m1 + m2 - (m1 == 2)
for m1, m2 in [(3, 0), (3, 5), (4, 10), (3, 20)]:
G = nx.tadpole_graph(m1, m2)
assert nx.number_of_nodes(G) == m1 + m2
assert nx.number_of_edges(G) == m1 + m2 - (m1 == 2)
for first, second in [("ab", ""), ("ab", "c"), ("abc", "defg")]:
m1, m2 = len(first), len(second)
print(first, second)
G = nx.tadpole_graph(first, second)
print(G.edges())
assert nx.number_of_nodes(G) == m1 + m2
assert nx.number_of_edges(G) == m1 + m2 - (m1 == 2)
@pytest.mark.parametrize(("m", "n"), [(3, 0), (3, 5), (4, 10), (3, 20)])
def test_tadpole_graph_right_sizes(self, m, n):
G = nx.tadpole_graph(m, n)
assert nx.number_of_nodes(G) == m + n
assert nx.number_of_edges(G) == m + n - (m == 2)

@pytest.mark.parametrize(("m", "n"), [("ab", ""), ("ab", "c"), ("abc", "defg")])
def test_tadpole_graph_size_node_sequences(self, m, n):
G = nx.tadpole_graph(m, n)
assert nx.number_of_nodes(G) == len(m) + len(n)
assert nx.number_of_edges(G) == len(m) + len(n) - (len(m) == 2)

def test_tadpole_graph_exceptions(self):
# Raise NetworkXError if m<2
Expand All @@ -503,36 +500,36 @@ def test_tadpole_graph_exceptions(self):
with pytest.raises(nx.NetworkXError):
nx.tadpole_graph(2, 20, create_using=nx.MultiDiGraph)

def test_tadpole_graph_same_as_path_when_m1_is_2_or_0(self):
# tadpole_graph(2,m) = path_graph(m+2)
for m1, m2 in [(2, 0), (2, 5), (2, 10), ("ab", 20)]:
G = nx.tadpole_graph(m1, m2)
assert is_isomorphic(G, nx.path_graph(m2 + 2))
@pytest.mark.parametrize(("m", "n"), [(2, 0), (2, 5), (2, 10), ("ab", 20)])
def test_tadpole_graph_same_as_path_when_m_is_2(self, m, n):
G = nx.tadpole_graph(m, n)
assert is_isomorphic(G, nx.path_graph(n + 2))

def test_tadpole_graph_same_as_cycle_when_m2_is_0(self):
# tadpole_graph(m,0) = cycle_(m)
for m1, m2 in [(4, 0), (7, 0)]:
G = nx.tadpole_graph(m1, m2)
assert is_isomorphic(G, nx.cycle_graph(m1))
@pytest.mark.parametrize("m", [4, 7])
def test_tadpole_graph_same_as_cycle_when_m2_is_0(self, m):
G = nx.tadpole_graph(m, 0)
assert is_isomorphic(G, nx.cycle_graph(m))

def test_tadpole_graph_for_multigraph(self):
G = nx.tadpole_graph(5, 20)
MG = nx.tadpole_graph(5, 20, create_using=nx.MultiGraph)
assert edges_equal(MG.edges(), G.edges())

def test_tadpole_graph_mixing_input_types(self):
cases = [(4, "abc"), ("abcd", 3), ([1, 2, 3, 4], "abc"), ("abcd", [1, 2, 3])]
for m1, m2 in cases:
G = nx.tadpole_graph(m1, m2)
assert len(G) == 7
assert G.size() == 7
@pytest.mark.parametrize(
("m", "n"),
[(4, "abc"), ("abcd", 3), ([1, 2, 3, 4], "abc"), ("abcd", [1, 2, 3])],
)
def test_tadpole_graph_mixing_input_types(self, m, n):
expected = nx.compose(nx.cycle_graph(4), nx.path_graph(range(100, 103)))
expected.add_edge(0, 100) # Connect cycle and path
assert is_isomorphic(nx.tadpole_graph(m, n), expected)

def test_tadpole_graph_not_int_integer_inputs(self):
# test non-int integers
def test_tadpole_graph_non_builtin_integers(self):
np = pytest.importorskip("numpy")
G = nx.tadpole_graph(np.int32(4), np.int64(3))
assert len(G) == 7
assert G.size() == 7
expected = nx.compose(nx.cycle_graph(4), nx.path_graph(range(100, 103)))
expected.add_edge(0, 100) # Connect cycle and path
assert is_isomorphic(G, expected)

def test_trivial_graph(self):
assert nx.number_of_nodes(nx.trivial_graph()) == 1
Expand Down