Skip to content

Commit 818ef98

Browse files
ekanshulclaude
andcommitted
[networkx] Accept any node/edge data types in graph parameters outside algorithms
Graph parameters annotated as `Graph[_Node]` only accept the default `dict[str, Any]` node and edge data, so graphs with other `Mapping` data types are rejected by functions that never look at the data. Type them as `Graph[_Node, _NodeData, _EdgeData]` in `classes.function`, `convert`, `convert_matrix`, `generators`, `linalg`, `readwrite` and `utils`, as already done for `classes` and `drawing`. Return types of graph-building functions are unchanged; `edge_subgraph()` and `restricted_view()` now return the input graph's type. Part of #16365. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 778a456 commit 818ef98

34 files changed

Lines changed: 247 additions & 157 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Iterator, Mapping
4+
from typing import Any
5+
6+
from typing_extensions import assert_type
7+
8+
import networkx as nx
9+
from networkx.utils.rcm import reverse_cuthill_mckee_ordering
10+
11+
12+
class NodeData(Mapping[str, Any]):
13+
def __getitem__(self, key: str) -> Any: ...
14+
def __iter__(self) -> Iterator[str]: ...
15+
def __len__(self) -> int: ...
16+
17+
18+
# Functions that only read a graph accept any node/edge data types that satisfy
19+
# the `Mapping[str, Any]` bound, not just the `dict[str, Any]` default.
20+
G = nx.Graph[int, NodeData, dict[str, Any]]()
21+
assert_type(nx.degree_histogram(G), list[int])
22+
assert_type(nx.to_dict_of_lists(G), dict[int, list[int]])
23+
assert_type(nx.is_weighted(G), bool)
24+
nx.number_of_nodes(G)
25+
nx.write_gml(G, "graph.gml")
26+
nx.generate_adjlist(G)
27+
nx.node_link_data(G)
28+
nx.adjacency_matrix(G)
29+
nx.laplacian_spectrum(G)
30+
reverse_cuthill_mckee_ordering(G)
31+
32+
D = nx.DiGraph[str, NodeData, NodeData]()
33+
nx.number_of_edges(D)
34+
nx.write_edgelist(D, "graph.edgelist")
35+
nx.directed_laplacian_matrix(D)
36+
37+
# Views keep the data types of the graph they come from.
38+
assert_type(nx.edge_subgraph(G, [(1, 2)]), nx.Graph[int, NodeData, dict[str, Any]])
39+
assert_type(nx.restricted_view(G, [1], [(1, 2)]), nx.Graph[int, NodeData, dict[str, Any]])
40+
41+
# The default data types work as before.
42+
H = nx.Graph[str]()
43+
assert_type(nx.to_dict_of_lists(H), dict[str, list[str]])
44+
nx.write_gml(H, "graph.gml")

stubs/networkx/networkx/classes/function.pyi

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from _typeshed import Incomplete, SupportsItems, SupportsKeysAndGetItem, Unused
22
from collections.abc import Callable, Collection, Generator, Hashable, Iterable, Iterator
3-
from typing import Literal, TypeVar, overload
3+
from typing import Any, Literal, TypeVar, overload
44

55
from networkx import _dispatchable
66
from networkx.algorithms.planarity import PlanarEmbedding
@@ -53,34 +53,34 @@ __all__ = [
5353

5454
_U = TypeVar("_U")
5555

56-
def nodes(G: Graph[_Node]): ...
57-
def edges(G: Graph[_Node], nbunch=None): ...
58-
def degree(G: Graph[_Node], nbunch=None, weight=None): ...
59-
def neighbors(G: Graph[_Node], n): ...
60-
def number_of_nodes(G: Graph[_Node]): ...
61-
def number_of_edges(G: Graph[_Node]): ...
62-
def density(G: Graph[_Node]): ...
63-
def degree_histogram(G: Graph[_Node]) -> list[int]: ...
64-
56+
def nodes(G: Graph[_Node, _NodeData, _EdgeData]): ...
57+
def edges(G: Graph[_Node, _NodeData, _EdgeData], nbunch=None): ...
58+
def degree(G: Graph[_Node, _NodeData, _EdgeData], nbunch=None, weight=None): ...
59+
def neighbors(G: Graph[_Node, _NodeData, _EdgeData], n): ...
60+
def number_of_nodes(G: Graph[_Node, _NodeData, _EdgeData]): ...
61+
def number_of_edges(G: Graph[_Node, _NodeData, _EdgeData]): ...
62+
def density(G: Graph[_Node, _NodeData, _EdgeData]): ...
63+
def degree_histogram(G: Graph[_Node, _NodeData, _EdgeData]) -> list[int]: ...
6564
@overload
6665
def is_directed(G: PlanarEmbedding[Hashable]) -> Literal[False]: ... # type: ignore[misc] # Incompatible return types
6766
@overload
6867
def is_directed(G: DiGraph[Hashable]) -> Literal[True]: ... # type: ignore[misc] # Incompatible return types
6968
@overload
7069
def is_directed(G: Graph[Hashable]) -> Literal[False]: ...
71-
72-
def freeze(G: Graph[_Node]): ...
70+
def freeze(G: Graph[_Node, _NodeData, _EdgeData]): ...
7371
def is_frozen(G: Graph[Incomplete]) -> bool: ...
7472
def add_star(G_to_add_to: Graph[Incomplete], nodes_for_star: Iterable[Incomplete], **attr) -> None: ...
7573
def add_path(G_to_add_to: Graph[Incomplete], nodes_for_path: Iterable[Incomplete], **attr) -> None: ...
7674
def add_cycle(G_to_add_to: Graph[Incomplete], nodes_for_cycle: Iterable[Incomplete], **attr) -> None: ...
77-
def subgraph(G: Graph[_Node], nbunch: Iterable[Incomplete]): ...
75+
def subgraph(G: Graph[_Node, _NodeData, _EdgeData], nbunch: Iterable[Incomplete]): ...
7876
def induced_subgraph(G: Graph[_Node, _NodeData, _EdgeData], nbunch: _NBunch[_Node]) -> Graph[_Node, _NodeData, _EdgeData]: ...
79-
def edge_subgraph(G: Graph[_Node], edges: Iterable[Incomplete]) -> Graph[Incomplete]: ...
80-
def restricted_view(G: Graph[_Node], nodes: Iterable[Incomplete], edges: Iterable[Incomplete]) -> Graph[Incomplete]: ...
77+
def edge_subgraph(G: Graph[_Node, _NodeData, _EdgeData], edges: Iterable[Incomplete]) -> Graph[_Node, _NodeData, _EdgeData]: ...
78+
def restricted_view(
79+
G: Graph[_Node, _NodeData, _EdgeData], nodes: Iterable[Incomplete], edges: Iterable[Incomplete]
80+
) -> Graph[_Node, _NodeData, _EdgeData]: ...
8181
def to_directed(graph): ...
8282
def to_undirected(graph): ...
83-
def create_empty_copy(G: Graph[_Node], with_data: bool = True): ...
83+
def create_empty_copy(G: Graph[_Node, _NodeData, _EdgeData], with_data: bool = True): ...
8484

8585
# incomplete: Can "Any scalar value" be enforced?
8686
@overload
@@ -94,22 +94,20 @@ def set_node_attributes(
9494
) -> None: ...
9595
@overload
9696
def set_node_attributes(
97-
G: Graph[_Node],
97+
G: Graph[_Node, _NodeData, _EdgeData],
9898
values: SupportsItems[_Node, SupportsKeysAndGetItem[Incomplete, Incomplete] | Iterable[tuple[Incomplete, Incomplete]]],
9999
name: None = None,
100100
*,
101101
backend=None,
102102
**backend_kwargs,
103103
) -> None: ...
104-
105104
@_dispatchable
106-
def get_node_attributes(G: Graph[_Node], name: str, default=None) -> dict[_Node, Incomplete]: ...
105+
def get_node_attributes(G: Graph[_Node, _NodeData, _EdgeData], name: str, default=None) -> dict[_Node, Incomplete]: ...
107106
@_dispatchable
108-
def remove_node_attributes(G: Graph[_Node], *attr_names, nbunch=None) -> None: ...
109-
107+
def remove_node_attributes(G: Graph[_Node, _NodeData, _EdgeData], *attr_names, nbunch=None) -> None: ...
110108
@overload
111109
def set_edge_attributes(
112-
G: Graph[_Node],
110+
G: Graph[_Node, _NodeData, _EdgeData],
113111
values: SupportsItems[tuple[_Node, _Node], Incomplete],
114112
name: str,
115113
*,
@@ -118,7 +116,7 @@ def set_edge_attributes(
118116
) -> None: ...
119117
@overload
120118
def set_edge_attributes(
121-
G: MultiGraph[_Node],
119+
G: MultiGraph[_Node, _NodeData, _EdgeData],
122120
values: dict[tuple[_Node, _Node, Incomplete], Incomplete],
123121
name: str,
124122
*,
@@ -129,54 +127,60 @@ def set_edge_attributes(
129127
def set_edge_attributes(
130128
G: Graph[Hashable], values, name: None = None, *, backend: str | None = None, **backend_kwargs
131129
) -> None: ...
132-
133130
@_dispatchable
134-
def get_edge_attributes(G: Graph[_Node], name: str, default=None) -> dict[tuple[_Node, _Node], Incomplete]: ...
131+
def get_edge_attributes(
132+
G: Graph[_Node, _NodeData, _EdgeData], name: str, default=None
133+
) -> dict[tuple[_Node, _Node], Incomplete]: ...
135134
@_dispatchable
136-
def remove_edge_attributes(G: Graph[_Node], *attr_names, ebunch=None) -> None: ...
137-
def all_neighbors(graph: Graph[_Node], node: _Node) -> Iterator[_Node]: ...
138-
def non_neighbors(graph: Graph[_Node], node: _Node) -> Generator[_Node]: ...
139-
def non_edges(graph: Graph[_Node]) -> Generator[tuple[_Node, _Node]]: ...
140-
def common_neighbors(G: Graph[_Node], u: _Node, v: _Node) -> Generator[_Node]: ...
135+
def remove_edge_attributes(G: Graph[_Node, _NodeData, _EdgeData], *attr_names, ebunch=None) -> None: ...
136+
def all_neighbors(graph: Graph[_Node, _NodeData, _EdgeData], node: _Node) -> Iterator[_Node]: ...
137+
def non_neighbors(graph: Graph[_Node, _NodeData, _EdgeData], node: _Node) -> Generator[_Node]: ...
138+
def non_edges(graph: Graph[_Node, _NodeData, _EdgeData]) -> Generator[tuple[_Node, _Node]]: ...
139+
def common_neighbors(G: Graph[_Node, _NodeData, _EdgeData], u: _Node, v: _Node) -> Generator[_Node]: ...
141140
@_dispatchable
142-
def is_weighted(G: Graph[_Node], edge: tuple[_Node, _Node] | None = None, weight: str = "weight") -> bool: ...
141+
def is_weighted(
142+
G: Graph[_Node, _NodeData, _EdgeData], edge: tuple[_Node, _Node] | None = None, weight: str = "weight"
143+
) -> bool: ...
143144
@_dispatchable
144-
def is_negatively_weighted(G: Graph[_Node], edge: tuple[_Node, _Node] | None = None, weight: str = "weight") -> bool: ...
145+
def is_negatively_weighted(
146+
G: Graph[_Node, _NodeData, _EdgeData], edge: tuple[_Node, _Node] | None = None, weight: str = "weight"
147+
) -> bool: ...
145148
@_dispatchable
146149
def is_empty(G: Graph[Hashable]) -> bool: ...
147-
def nodes_with_selfloops(G: Graph[_Node]) -> Generator[_Node]: ...
148-
150+
def nodes_with_selfloops(G: Graph[_Node, _NodeData, _EdgeData]) -> Generator[_Node]: ...
149151
@overload
150152
def selfloop_edges(
151-
G: Graph[_Node], data: Literal[False] = False, keys: Literal[False] = False, default=None
153+
G: Graph[_Node, _NodeData, _EdgeData], data: Literal[False] = False, keys: Literal[False] = False, default=None
152154
) -> Generator[tuple[_Node, _Node]]: ...
153155
@overload
154156
def selfloop_edges(
155157
G: Graph[_Node, _NodeData, _EdgeData], data: Literal[True], keys: Literal[False] = False, default=None
156158
) -> Generator[tuple[_Node, _Node, _EdgeData]]: ...
157159
@overload
158160
def selfloop_edges(
159-
G: Graph[_Node], data: str, keys: Literal[False] = False, default: _U | None = None
161+
G: Graph[_Node, Any, Any], data: str, keys: Literal[False] = False, default: _U | None = None
160162
) -> Generator[tuple[_Node, _Node, _U]]: ...
161163
@overload
162164
def selfloop_edges(
163-
G: Graph[_Node], data: Literal[False], keys: Literal[True], default=None
165+
G: Graph[_Node, _NodeData, _EdgeData], data: Literal[False], keys: Literal[True], default=None
164166
) -> Generator[tuple[_Node, _Node, int]]: ...
165167
@overload
166168
def selfloop_edges(
167-
G: Graph[_Node], data: Literal[False] = False, *, keys: Literal[True], default=None
169+
G: Graph[_Node, _NodeData, _EdgeData], data: Literal[False] = False, *, keys: Literal[True], default=None
168170
) -> Generator[tuple[_Node, _Node, int]]: ...
169171
@overload
170172
def selfloop_edges(
171173
G: Graph[_Node, _NodeData, _EdgeData], data: Literal[True], keys: Literal[True], default=None
172174
) -> Generator[tuple[_Node, _Node, int, _EdgeData]]: ...
173175
@overload
174176
def selfloop_edges(
175-
G: Graph[_Node], data: str, keys: Literal[True], default: _U | None = None
177+
G: Graph[_Node, Any, Any], data: str, keys: Literal[True], default: _U | None = None
176178
) -> Generator[tuple[_Node, _Node, int, _U]]: ...
177-
178179
@_dispatchable
179180
def number_of_selfloops(G: Graph[Hashable]) -> int: ...
180-
def is_path(G: Graph[_Node], path: Iterable[Incomplete]) -> bool: ...
181-
def path_weight(G: Graph[_Node], path: Collection[Incomplete], weight: str) -> int: ...
182-
def describe(G: Graph[_Node], describe_hook: Callable[[Graph[_Node]], dict[str, Incomplete]] | None = None) -> None: ...
181+
def is_path(G: Graph[_Node, _NodeData, _EdgeData], path: Iterable[Incomplete]) -> bool: ...
182+
def path_weight(G: Graph[_Node, _NodeData, _EdgeData], path: Collection[Incomplete], weight: str) -> int: ...
183+
def describe(
184+
G: Graph[_Node, _NodeData, _EdgeData],
185+
describe_hook: Callable[[Graph[_Node, _NodeData, _EdgeData]], dict[str, Incomplete]] | None = None,
186+
) -> None: ...

stubs/networkx/networkx/convert.pyi

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ def to_networkx_graph(
2020
multigraph_input: bool = False,
2121
) -> Graph[_Node, _NodeData, _EdgeData]: ...
2222
@_dispatchable
23-
def to_dict_of_lists(G: Graph[_Node], nodelist: Collection[_Node] | None = None) -> dict[_Node, list[_Node]]: ...
23+
def to_dict_of_lists(
24+
G: Graph[_Node, _NodeData, _EdgeData], nodelist: Collection[_Node] | None = None
25+
) -> dict[_Node, list[_Node]]: ...
2426
@_dispatchable
2527
def from_dict_of_lists(
2628
d: dict[_Node, Iterable[_Node]], create_using: Graph[Incomplete] | type[Graph[Incomplete]] | None = None
2729
) -> Graph[_Node]: ...
2830
def to_dict_of_dicts(
29-
G: Graph[_Node], nodelist: Collection[_Node] | None = None, edge_data: float | None = None
31+
G: Graph[_Node, _NodeData, _EdgeData], nodelist: Collection[_Node] | None = None, edge_data: float | None = None
3032
) -> dict[Incomplete, Incomplete]: ...
3133
@_dispatchable
3234
def from_dict_of_dicts(
@@ -35,7 +37,7 @@ def from_dict_of_dicts(
3537
multigraph_input: bool = False,
3638
) -> Graph[Incomplete]: ...
3739
@_dispatchable
38-
def to_edgelist(G: Graph[_Node], nodelist: Collection[_Node] | None = None): ...
40+
def to_edgelist(G: Graph[_Node, _NodeData, _EdgeData], nodelist: Collection[_Node] | None = None): ...
3941
@_dispatchable
4042
def from_edgelist(
4143
edgelist: Iterable[Incomplete], create_using: Graph[Incomplete] | type[Graph[Incomplete]] | None = None

stubs/networkx/networkx/convert_matrix.pyi

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ from typing import Literal, TypeAlias, TypeVar, overload
44

55
import numpy
66
import numpy as np
7-
from networkx.classes.graph import Graph, _Node
7+
from networkx.classes.graph import Graph, _EdgeData, _Node, _NodeData
88
from networkx.utils.backends import _dispatchable
99

1010
# stub_uploader won't allow pandas-stubs in the requires field https://github.com/typeshed-internal/stub_uploader/issues/90
@@ -30,30 +30,27 @@ __all__ = [
3030

3131
@_dispatchable
3232
def to_pandas_adjacency(
33-
G: Graph[_Node],
33+
G: Graph[_Node, _NodeData, _EdgeData],
3434
nodelist: _Axes[_Node] | None = None,
3535
dtype: numpy.dtype[Incomplete] | None = None,
3636
order: numpy._OrderCF = None,
3737
multigraph_weight: Callable[[list[float]], float] = ...,
3838
weight: str = "weight",
3939
nonedge: float = 0.0,
4040
) -> _DataFrame: ...
41-
4241
@overload
4342
def from_pandas_adjacency(df: _DataFrame, create_using: type[_G]) -> _G: ...
4443
@overload
4544
def from_pandas_adjacency(df: _DataFrame, create_using: None = None) -> Graph[Incomplete]: ...
46-
4745
@_dispatchable
4846
def to_pandas_edgelist(
49-
G: Graph[_Node],
47+
G: Graph[_Node, _NodeData, _EdgeData],
5048
source: str | int = "source",
5149
target: str | int = "target",
5250
nodelist: Iterable[_Node] | None = None,
5351
dtype: _ExtensionDtype | None = None,
5452
edge_key: str | int | None = None,
5553
) -> _DataFrame: ...
56-
5754
@overload
5855
def from_pandas_edgelist(
5956
df: _DataFrame,
@@ -82,10 +79,9 @@ def from_pandas_edgelist(
8279
create_using: None = None,
8380
edge_key: str | None = None,
8481
) -> Graph[Incomplete]: ...
85-
8682
@_dispatchable
8783
def to_scipy_sparse_array(
88-
G: Graph[_Node],
84+
G: Graph[_Node, _NodeData, _EdgeData],
8985
nodelist: Collection[_Node] | None = None,
9086
dtype: np.dtype[Incomplete] | None = None,
9187
weight: str | None = "weight",
@@ -100,15 +96,14 @@ def from_scipy_sparse_array(
10096
): ...
10197
@_dispatchable
10298
def to_numpy_array(
103-
G: Graph[_Node],
99+
G: Graph[_Node, _NodeData, _EdgeData],
104100
nodelist: Collection[_Node] | None = None,
105101
dtype: numpy.dtype[Incomplete] | None = None,
106102
order: numpy._OrderCF = None,
107103
multigraph_weight: Callable[[list[float]], float] = ...,
108104
weight: str = "weight",
109105
nonedge: float = 0.0,
110106
) -> numpy.ndarray[Incomplete, numpy.dtype[Incomplete]]: ...
111-
112107
@overload
113108
def from_numpy_array(
114109
A: numpy.ndarray[Incomplete, Incomplete], parallel_edges: bool = False, create_using: None = None
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
from networkx.classes.graph import Graph, _Node
1+
from networkx.classes.graph import Graph, _EdgeData, _Node, _NodeData
22
from networkx.utils.backends import _dispatchable
33

44
__all__ = ["ego_graph"]
55

66
@_dispatchable
7-
def ego_graph(G: Graph[_Node], n, radius: float = 1, center: bool = True, undirected: bool = False, distance=None): ...
7+
def ego_graph(
8+
G: Graph[_Node, _NodeData, _EdgeData], n, radius: float = 1, center: bool = True, undirected: bool = False, distance=None
9+
): ...

stubs/networkx/networkx/generators/expanders.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from _typeshed import Incomplete
22
from typing_extensions import deprecated
33

4-
from networkx.classes.graph import Graph, _Node
4+
from networkx.classes.graph import Graph, _EdgeData, _Node, _NodeData
55
from networkx.classes.multigraph import MultiGraph
66
from networkx.utils.backends import _dispatchable
77

@@ -31,6 +31,6 @@ def maybe_regular_expander_graph(n: int, d: int, *, create_using=None, max_tries
3131
)
3232
def maybe_regular_expander(n, d, *, create_using=None, max_tries: int = 100, seed=None): ...
3333
@_dispatchable
34-
def is_regular_expander(G: Graph[_Node], *, epsilon: float = 0) -> bool: ...
34+
def is_regular_expander(G: Graph[_Node, _NodeData, _EdgeData], *, epsilon: float = 0) -> bool: ...
3535
@_dispatchable
3636
def random_regular_expander_graph(n: int, d: int, *, epsilon=0, create_using=None, max_tries=100, seed=None): ...

stubs/networkx/networkx/generators/geometric.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from _typeshed import Incomplete
22
from collections.abc import Callable, Iterable
33

4-
from networkx.classes.graph import Graph, _Node
4+
from networkx.classes.graph import Graph, _EdgeData, _Node, _NodeData
55
from networkx.utils.backends import _dispatchable
66

77
__all__ = [
@@ -16,7 +16,7 @@ __all__ = [
1616
]
1717

1818
@_dispatchable
19-
def geometric_edges(G: Graph[_Node], radius: float, p: float = 2) -> list[Incomplete]: ...
19+
def geometric_edges(G: Graph[_Node, _NodeData, _EdgeData], radius: float, p: float = 2) -> list[Incomplete]: ...
2020
@_dispatchable
2121
def random_geometric_graph(
2222
n: int | Iterable[Incomplete],
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from _typeshed import Incomplete
22

3-
from networkx.classes.graph import Graph, _Node
3+
from networkx.classes.graph import Graph, _EdgeData, _Node, _NodeData
44
from networkx.utils.backends import _dispatchable
55

66
__all__ = ["line_graph", "inverse_line_graph"]
77

88
@_dispatchable
9-
def line_graph(G: Graph[_Node], create_using: Graph[Incomplete] | type[Graph[Incomplete]] | None = None) -> Graph[Incomplete]: ...
9+
def line_graph(
10+
G: Graph[_Node, _NodeData, _EdgeData], create_using: Graph[Incomplete] | type[Graph[Incomplete]] | None = None
11+
) -> Graph[Incomplete]: ...
1012
@_dispatchable
11-
def inverse_line_graph(G: Graph[_Node]) -> Graph[Incomplete]: ...
13+
def inverse_line_graph(G: Graph[_Node, _NodeData, _EdgeData]) -> Graph[Incomplete]: ...
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from _typeshed import Incomplete
22

3-
from networkx.classes.graph import Graph, _Node
3+
from networkx.classes.graph import Graph, _EdgeData, _Node, _NodeData
44
from networkx.utils.backends import _dispatchable
55

66
__all__ = ["mycielskian", "mycielski_graph"]
77

88
@_dispatchable
9-
def mycielskian(G: Graph[_Node], iterations: int = 1) -> Graph[Incomplete]: ...
9+
def mycielskian(G: Graph[_Node, _NodeData, _EdgeData], iterations: int = 1) -> Graph[Incomplete]: ...
1010
@_dispatchable
1111
def mycielski_graph(n: int) -> Graph[Incomplete]: ...

0 commit comments

Comments
 (0)