Repository: networkx/networkx

Issue:
Allow binomial_tree() to create directed graphs (via create_using parameter)
`binomial_tree() ` doesn't seem to allow the `create_using` parameter to get passed and as a result, one can't create a directed binomial graph. 

This seem like a inadvertent omission and easy to fix. I will put in a PR to address this soon.

Comments:

Patch:
diff --git a/doc/reference/algorithms/approximation.rst b/doc/reference/algorithms/approximation.rst
index 5b207d4fb..fe3dc53e0 100644
--- a/doc/reference/algorithms/approximation.rst
+++ b/doc/reference/algorithms/approximation.rst
@@ -107,3 +107,13 @@ Vertex Cover
    :toctree: generated/
 
    min_weighted_vertex_cover
+
+
+Max Cut
+-------
+.. automodule:: networkx.algorithms.approximation.maxcut
+.. autosummary::
+   :toctree: generated/
+
+   randomized_partitioning
+   one_exchange
diff --git a/networkx/algorithms/approximation/__init__.py b/networkx/algorithms/approximation/__init__.py
index 99e8340b4..631a2c353 100644
--- a/networkx/algorithms/approximation/__init__.py
+++ b/networkx/algorithms/approximation/__init__.py
@@ -20,3 +20,4 @@ from networkx.algorithms.approximation.ramsey import *
 from networkx.algorithms.approximation.steinertree import *
 from networkx.algorithms.approximation.vertex_cover import *
 from networkx.algorithms.approximation.treewidth import *
+from networkx.algorithms.approximation.maxcut import *
diff --git a/networkx/algorithms/approximation/maxcut.py b/networkx/algorithms/approximation/maxcut.py
new file mode 100644
index 000000000..47e5c70ca
--- /dev/null
+++ b/networkx/algorithms/approximation/maxcut.py
@@ -0,0 +1,111 @@
+import networkx as nx
+from networkx.utils.decorators import py_random_state
+
+__all__ = ["randomized_partitioning", "one_exchange"]
+
+
+@nx.not_implemented_for("directed", "multigraph")
+@py_random_state(1)
+def randomized_partitioning(G, seed=None, p=0.5, weight=None):
+    """Compute a random partitioning of the graph nodes and its cut value.
+
+    A partitioning is calculated by observing each node
+    and deciding to add it to the partition with probability `p`,
+    returning a random cut and its corresponding value (the
+    sum of weights of edges connecting different partitions).
+
+    Parameters
+    ----------
+    G : NetworkX graph
+
+    seed : integer, random_state, or None (default)
+        Indicator of random number generation state.
+        See :ref:`Randomness<randomness>`.
+
+    p : scalar
+        Probability for each node to be part of the first partition.
+        Should be in [0,1]
+
+    weight : object
+        Edge attribute key to use as weight. If not specified, edges
+        have weight one.
+
+    Returns
+    -------
+    cut_size : scalar
+        Value of the minimum cut.
+
+    partition : pair of node sets
+        A partitioning of the nodes that defines a minimum cut.
+    """
+    cut = {node for node in G.nodes() if seed.random() < p}
+    cut_size = nx.algorithms.cut_size(G, cut, weight=weight)
+    partition = (cut, G.nodes - cut)
+    return cut_size, partition
+
+
+def _swap_node_partition(cut, node):
+    return cut - {node} if node in cut else cut.union({node})
+
+
+@nx.not_implemented_for("directed", "multigraph")
+@py_random_state(2)
+def one_exchange(G, initial_cut=None, seed=None, weight=None):
+    """Compute a partitioning of the graphs nodes and the corresponding cut value.
+
+    Use a greedy one exchange strategy to find a locally maximal cut
+    and its value, it works by finding the best node (one that gives
+    the highest gain to the cut value) to add to the current cut
+    and repeats this process until no improvement can be made.
+
+    Parameters
+    ----------
+    G : networkx Graph
+        Graph to find a maximum cut for.
+
+    initial_cut : set
+        Cut to use as a starting point. If not supplied the algorithm
+        starts with an empty cut.
+
+    seed : integer, random_state, or None (default)
+        Indicator of random number generation state.
+        See :ref:`Randomness<randomness>`.
+
+    weight : object
+        Edge attribute key to use as weight. If not specified, edges
+        have weight one.
+
+    Returns
+    -------
+    cut_value : scalar
+        Value of the maximum cut.
+
+    partition : pair of node sets
+        A partitioning of the nodes that defines a maximum cut.
+    """
+    if initial_cut is None:
+        initial_cut = set()
+    cut = set(initial_cut)
+    current_cut_size = nx.algorithms.cut_size(G, cut, weight=weight)
+    while True:
+        nodes = list(G.nodes())
+        # Shuffling the nodes ensures random tie-breaks in the following call to max
+        seed.shuffle(nodes)
+        best_node_to_swap = max(
+            nodes,
+            key=lambda v: nx.algorithms.cut_size(
+                G, _swap_node_partition(cut, v), weight=weight
+            ),
+            default=None,
+        )
+        potential_cut = _swap_node_partition(cut, best_node_to_swap)
+        potential_cut_size = nx.algorithms.cut_size(G, potential_cut, weight=weight)
+
+        if potential_cut_size > current_cut_size:
+            cut = potential_cut
+            current_cut_size = potential_cut_size
+        else:
+            break
+
+    partition = (cut, G.nodes - cut)
+    return current_cut_size, partition
diff --git a/networkx/generators/classic.py b/networkx/generators/classic.py
index 9968d20f7..b89aca3dc 100644
--- a/networkx/generators/classic.py
+++ b/networkx/generators/classic.py
@@ -188,7 +188,7 @@ def barbell_graph(m1, m2, create_using=None):
     return G
 
 
-def binomial_tree(n):
+def binomial_tree(n, create_using=None):
     """Returns the Binomial Tree of order n.
 
     The binomial tree of order 0 consists of a single node. A binomial tree of order k
@@ -200,16 +200,21 @@ def binomial_tree(n):
     n : int
         Order of the binomial tree.
 
+    create_using : NetworkX graph constructor, optional (default=nx.Graph)
+       Graph type to create. If graph instance, then cleared before populated.
+
     Returns
     -------
     G : NetworkX graph
         A binomial tree of $2^n$ nodes and $2^n - 1$ edges.
 
     """
-    G = nx.empty_graph(1)
+    G = nx.empty_graph(1, create_using)
+
     N = 1
     for i in range(n):
-        edges = [(u + N, v + N) for (u, v) in G.edges]
+        # Use G.edges() to ensure 2-tuples. G.edges is 3-tuple for MultiGraph
+        edges = [(u + N, v + N) for (u, v) in G.edges()]
         G.add_edges_from(edges)
         G.add_edge(0, N)
         N *= 2


Test patch:
diff --git a/networkx/algorithms/approximation/tests/test_maxcut.py b/networkx/algorithms/approximation/tests/test_maxcut.py
new file mode 100644
index 000000000..897d25594
--- /dev/null
+++ b/networkx/algorithms/approximation/tests/test_maxcut.py
@@ -0,0 +1,82 @@
+import random
+
+import networkx as nx
+from networkx.algorithms.approximation import maxcut
+
+
+def _is_valid_cut(G, set1, set2):
+    union = set1.union(set2)
+    assert union == set(G.nodes)
+    assert len(set1) + len(set2) == G.number_of_nodes()
+
+
+def _cut_is_locally_optimal(G, cut_size, set1):
+    # test if cut can be locally improved
+    for i, node in enumerate(set1):
+        cut_size_without_node = nx.algorithms.cut_size(
+            G, set1 - {node}, weight="weight"
+        )
+        assert cut_size_without_node <= cut_size
+
+
+def test_random_partitioning():
+    G = nx.complete_graph(5)
+    _, (set1, set2) = maxcut.randomized_partitioning(G, seed=5)
+    _is_valid_cut(G, set1, set2)
+
+
+def test_random_partitioning_all_to_one():
+    G = nx.complete_graph(5)
+    _, (set1, set2) = maxcut.randomized_partitioning(G, p=1)
+    _is_valid_cut(G, set1, set2)
+    assert len(set1) == G.number_of_nodes()
+    assert len(set2) == 0
+
+
+def test_one_exchange_basic():
+    G = nx.complete_graph(5)
+    random.seed(5)
+    for (u, v, w) in G.edges(data=True):
+        w["weight"] = random.randrange(-100, 100, 1) / 10
+
+    initial_cut = set(random.sample(G.nodes(), k=5))
+    cut_size, (set1, set2) = maxcut.one_exchange(
+        G, initial_cut, weight="weight", seed=5
+    )
+
+    _is_valid_cut(G, set1, set2)
+    _cut_is_locally_optimal(G, cut_size, set1)
+
+
+def test_one_exchange_optimal():
+    # Greedy one exchange should find the optimal solution for this graph (14)
+    G = nx.Graph()
+    G.add_edge(1, 2, weight=3)
+    G.add_edge(1, 3, weight=3)
+    G.add_edge(1, 4, weight=3)
+    G.add_edge(1, 5, weight=3)
+    G.add_edge(2, 3, weight=5)
+
+    cut_size, (set1, set2) = maxcut.one_exchange(G, weight="weight", seed=5)
+
+    _is_valid_cut(G, set1, set2)
+    _cut_is_locally_optimal(G, cut_size, set1)
+    # check global optimality
+    assert cut_size == 14
+
+
+def test_negative_weights():
+    G = nx.complete_graph(5)
+    random.seed(5)
+    for (u, v, w) in G.edges(data=True):
+        w["weight"] = -1 * random.random()
+
+    initial_cut = set(random.sample(G.nodes(), k=5))
+    cut_size, (set1, set2) = maxcut.one_exchange(G, initial_cut, weight="weight")
+
+    # make sure it is a valid cut
+    _is_valid_cut(G, set1, set2)
+    # check local optimality
+    _cut_is_locally_optimal(G, cut_size, set1)
+    # test that all nodes are in the same partition
+    assert len(set1) == len(G.nodes) or len(set2) == len(G.nodes)
diff --git a/networkx/generators/tests/test_classic.py b/networkx/generators/tests/test_classic.py
index c98eb8f5a..3ec36f0f9 100644
--- a/networkx/generators/tests/test_classic.py
+++ b/networkx/generators/tests/test_classic.py
@@ -138,10 +138,12 @@ class TestGeneratorClassic:
         assert_edges_equal(mb.edges(), b.edges())
 
     def test_binomial_tree(self):
-        for n in range(0, 4):
-            b = nx.binomial_tree(n)
-            assert nx.number_of_nodes(b) == 2 ** n
-            assert nx.number_of_edges(b) == (2 ** n - 1)
+        graphs = (None, nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
+        for create_using in graphs:
+            for n in range(0, 4):
+                b = nx.binomial_tree(n, create_using)
+                assert nx.number_of_nodes(b) == 2 ** n
+                assert nx.number_of_edges(b) == (2 ** n - 1)
 
     def test_complete_graph(self):
         # complete_graph(m) is a connected graph with