Repository: Pyomo/pyomo

Issue:
Tests failures in pyomo.contrib.community_detection with NetworkX 2.8.1
## Summary

Looks like the latest release of NetworkX on May 18, 2022 has caused some test failures in `pyomo.contrib.community_detection`. Currently seeing test failures across all operating systems and all Python versions 3.8 and above. 

### Error Message
```
=================================== FAILURES ===================================
_____________________ TestDecomposition.test_communities_3 _____________________

self = <pyomo.contrib.community_detection.tests.test_detection.TestDecomposition testMethod=test_communities_3>

    def test_communities_3(self):
        m_class = LP_unbounded()
        m_class._generate_model()
        model = m = m_class.model
    
>       test_community_maps, test_partitions = _collect_community_maps(model)

pyomo/contrib/community_detection/tests/test_detection.py:177: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
pyomo/contrib/community_detection/tests/test_detection.py:820: in _collect_community_maps
    latest_community_map = detect_communities(model, type_of_community_map=community_map_type,
pyomo/contrib/community_detection/detection.py:107: in detect_communities
    model_graph, number_component_map, constraint_variable_map = generate_model_graph(
pyomo/contrib/community_detection/community_graph.py:144: in generate_model_graph
    projected_model_graph = nx.bipartite.weighted_projected_graph(bipartite_model_graph, graph_nodes)
<class 'networkx.utils.decorators.argmap'> compilation 8:4: in argmap_weighted_projected_graph_5
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

B = <networkx.classes.graph.Graph object at 0x12f49cd90>, nodes = {0, 1}
ratio = False

    @not_implemented_for("multigraph")
    def weighted_projected_graph(B, nodes, ratio=False):
        r"""Returns a weighted projection of B onto one of its node sets.
    
        The weighted projected graph is the projection of the bipartite
        network B onto the specified nodes with weights representing the
        number of shared neighbors or the ratio between actual shared
        neighbors and possible shared neighbors if ``ratio is True`` [1]_.
        The nodes retain their attributes and are connected in the resulting
        graph if they have an edge to a common node in the original graph.
    
        Parameters
        ----------
        B : NetworkX graph
            The input graph should be bipartite.
    
        nodes : list or iterable
            Distinct nodes to project onto (the "bottom" nodes).
    
        ratio: Bool (default=False)
            If True, edge weight is the ratio between actual shared neighbors
            and maximum possible shared neighbors (i.e., the size of the other
            node set). If False, edges weight is the number of shared neighbors.
    
        Returns
        -------
        Graph : NetworkX graph
           A graph that is the projection onto the given nodes.
    
        Examples
        --------
        >>> from networkx.algorithms import bipartite
        >>> B = nx.path_graph(4)
        >>> G = bipartite.weighted_projected_graph(B, [1, 3])
        >>> list(G)
        [1, 3]
        >>> list(G.edges(data=True))
        [(1, 3, {'weight': 1})]
        >>> G = bipartite.weighted_projected_graph(B, [1, 3], ratio=True)
        >>> list(G.edges(data=True))
        [(1, 3, {'weight': 0.5})]
    
        Notes
        -----
        No attempt is made to verify that the input graph B is bipartite, or that
        the input nodes are distinct. However, if the length of the input nodes is
        greater than or equal to the nodes in the graph B, an exception is raised.
        If the nodes are not distinct but don't raise this error, the output weights
        will be incorrect.
        The graph and node properties are (shallow) copied to the projected graph.
    
        See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
        for further details on how bipartite graphs are handled in NetworkX.
    
        See Also
        --------
        is_bipartite,
        is_bipartite_node_set,
        sets,
        collaboration_weighted_projected_graph,
        overlap_weighted_projected_graph,
        generic_weighted_projected_graph
        projected_graph
    
        References
        ----------
        .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
            Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
            of Social Network Analysis. Sage Publications.
        """
        if B.is_directed():
            pred = B.pred
            G = nx.DiGraph()
        else:
            pred = B.adj
            G = nx.Graph()
        G.graph.update(B.graph)
        G.add_nodes_from((n, B.nodes[n]) for n in nodes)
        n_top = float(len(B) - len(nodes))
    
        if n_top < 1:
>           raise NetworkXAlgorithmError(
                f"the size of the nodes to project onto ({len(nodes)}) is >= the graph size ({len(B)}).\n"
                "They are either not a valid bipartite partition or contain duplicates"
            )
E           networkx.exception.NetworkXAlgorithmError: the size of the nodes to project onto (2) is >= the graph size (2).
E           They are either not a valid bipartite partition or contain duplicates

../../../hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/networkx/algorithms/bipartite/projection.py:200: NetworkXAlgorithmError
------------------------------ Captured log call -------------------------------
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:event_log.py:134 No nodes were created for the graph (based on the model and the given parameters)
WARNING  pyomo.contrib.community_detection:event_log.py:136 No edges were created for the graph (based on the model and the given parameters)
ERROR    pyomo.contrib.community_detection:detection.py:186 in detect_communities: Empty community map was returned
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:event_log.py:136 No edges were created for the graph (based on the model and the given parameters)
WARNING  pyomo.contrib.community_detection:detection.py:188 Community detection found that with the given parameters, the model could not be decomposed - only one community was found
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:event_log.py:134 No nodes were created for the graph (based on the model and the given parameters)
WARNING  pyomo.contrib.community_detection:event_log.py:136 No edges were created for the graph (based on the model and the given parameters)
ERROR    pyomo.contrib.community_detection:detection.py:186 in detect_communities: Empty community map was returned
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:event_log.py:136 No edges were created for the graph (based on the model and the given parameters)
WARNING  pyomo.contrib.community_detection:detection.py:188 Community detection found that with the given parameters, the model could not be decomposed - only one community was found
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:event_log.py:134 No nodes were created for the graph (based on the model and the given parameters)
WARNING  pyomo.contrib.community_detection:event_log.py:136 No edges were created for the graph (based on the model and the given parameters)
ERROR    pyomo.contrib.community_detection:detection.py:186 in detect_communities: Empty community map was returned
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:event_log.py:136 No edges were created for the graph (based on the model and the given parameters)
WARNING  pyomo.contrib.community_detection:detection.py:188 Community detection found that with the given parameters, the model could not be decomposed - only one community was found
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:event_log.py:134 No nodes were created for the graph (based on the model and the given parameters)
WARNING  pyomo.contrib.community_detection:event_log.py:136 No edges were created for the graph (based on the model and the given parameters)
ERROR    pyomo.contrib.community_detection:detection.py:186 in detect_communities: Empty community map was returned
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:event_log.py:136 No edges were created for the graph (based on the model and the given parameters)
WARNING  pyomo.contrib.community_detection:detection.py:188 Community detection found that with the given parameters, the model could not be decomposed - only one community was found
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:event_log.py:136 No edges were created for the graph (based on the model and the given parameters)
WARNING  pyomo.contrib.community_detection:event_log.py:118 No constraints found in the model
WARNING  pyomo.contrib.community_detection:detection.py:188 Community detection found that with the given parameters, the model could not be decomposed - only one community was found
_____________________ TestDecomposition.test_communities_8 _____________________

self = <pyomo.contrib.community_detection.tests.test_detection.TestDecomposition testMethod=test_communities_8>

    def test_communities_8(self):
        output = StringIO()
    
        with LoggingIntercept(output, 'pyomo.contrib.community_detection', logging.ERROR):
>           detect_communities(ConcreteModel())

pyomo/contrib/community_detection/tests/test_detection.py:576: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
pyomo/contrib/community_detection/detection.py:107: in detect_communities
    model_graph, number_component_map, constraint_variable_map = generate_model_graph(
pyomo/contrib/community_detection/community_graph.py:144: in generate_model_graph
    projected_model_graph = nx.bipartite.weighted_projected_graph(bipartite_model_graph, graph_nodes)
<class 'networkx.utils.decorators.argmap'> compilation 8:4: in argmap_weighted_projected_graph_5
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

B = <networkx.classes.graph.Graph object at 0x12fee3820>, nodes = set()
ratio = False

    @not_implemented_for("multigraph")
    def weighted_projected_graph(B, nodes, ratio=False):
        r"""Returns a weighted projection of B onto one of its node sets.
    
        The weighted projected graph is the projection of the bipartite
        network B onto the specified nodes with weights representing the
        number of shared neighbors or the ratio between actual shared
        neighbors and possible shared neighbors if ``ratio is True`` [1]_.
        The nodes retain their attributes and are connected in the resulting
        graph if they have an edge to a common node in the original graph.
    
        Parameters
        ----------
        B : NetworkX graph
            The input graph should be bipartite.
    
        nodes : list or iterable
            Distinct nodes to project onto (the "bottom" nodes).
    
        ratio: Bool (default=False)
            If True, edge weight is the ratio between actual shared neighbors
            and maximum possible shared neighbors (i.e., the size of the other
            node set). If False, edges weight is the number of shared neighbors.
    
        Returns
        -------
        Graph : NetworkX graph
           A graph that is the projection onto the given nodes.
    
        Examples
        --------
        >>> from networkx.algorithms import bipartite
        >>> B = nx.path_graph(4)
        >>> G = bipartite.weighted_projected_graph(B, [1, 3])
        >>> list(G)
        [1, 3]
        >>> list(G.edges(data=True))
        [(1, 3, {'weight': 1})]
        >>> G = bipartite.weighted_projected_graph(B, [1, 3], ratio=True)
        >>> list(G.edges(data=True))
        [(1, 3, {'weight': 0.5})]
    
        Notes
        -----
        No attempt is made to verify that the input graph B is bipartite, or that
        the input nodes are distinct. However, if the length of the input nodes is
        greater than or equal to the nodes in the graph B, an exception is raised.
        If the nodes are not distinct but don't raise this error, the output weights
        will be incorrect.
        The graph and node properties are (shallow) copied to the projected graph.
    
        See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
        for further details on how bipartite graphs are handled in NetworkX.
    
        See Also
        --------
        is_bipartite,
        is_bipartite_node_set,
        sets,
        collaboration_weighted_projected_graph,
        overlap_weighted_projected_graph,
        generic_weighted_projected_graph
        projected_graph
    
        References
        ----------
        .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
            Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
            of Social Network Analysis. Sage Publications.
        """
        if B.is_directed():
            pred = B.pred
            G = nx.DiGraph()
        else:
            pred = B.adj
            G = nx.Graph()
        G.graph.update(B.graph)
        G.add_nodes_from((n, B.nodes[n]) for n in nodes)
        n_top = float(len(B) - len(nodes))
    
        if n_top < 1:
>           raise NetworkXAlgorithmError(
                f"the size of the nodes to project onto ({len(nodes)}) is >= the graph size ({len(B)}).\n"
                "They are either not a valid bipartite partition or contain duplicates"
            )
E           networkx.exception.NetworkXAlgorithmError: the size of the nodes to project onto (0) is >= the graph size (0).
E           They are either not a valid bipartite partition or contain duplicates

../../../hostedtoolcache/Python/3.9.12/x64/lib/python3.9/site-packages/networkx/algorithms/bipartite/projection.py:200: NetworkXAlgorithmError
------ generated xml file: /Users/runner/work/pyomo/pyomo/TEST-pyomo.xml -------
=========================== short test summary info ============================
FAILED pyomo/contrib/community_detection/tests/test_detection.py::TestDecomposition::test_communities_3
FAILED pyomo/contrib/community_detection/tests/test_detection.py::TestDecomposition::test_communities_8

```





Discussion:
This is relevant NetworkX PR: https://github.com/networkx/networkx/pull/5442 

Resolving patch:
diff --git a/pyomo/contrib/community_detection/community_graph.py b/pyomo/contrib/community_detection/community_graph.py
--- a/pyomo/contrib/community_detection/community_graph.py
+++ b/pyomo/contrib/community_detection/community_graph.py
@@ -140,10 +140,16 @@ def generate_model_graph(model, type_of_graph, with_objective=True, weighted_gra
         variable_nodes = set(number_component_map) - constraint_nodes
         graph_nodes = variable_nodes
 
-    if weighted_graph:
-        projected_model_graph = nx.bipartite.weighted_projected_graph(bipartite_model_graph, graph_nodes)
-    else:
-        projected_model_graph = nx.bipartite.projected_graph(bipartite_model_graph, graph_nodes)
+    try:
+        if weighted_graph:
+            projected_model_graph = nx.bipartite.weighted_projected_graph(bipartite_model_graph, graph_nodes)
+        else:
+            projected_model_graph = nx.bipartite.projected_graph(bipartite_model_graph, graph_nodes)
+    except nx.exception.NetworkXAlgorithmError:
+        # See Pyomo #2413: networkx now raises exceptions for invalid
+        # projections.  This restores the (probably invalid) previous
+        # behavior in community_detection
+        projected_model_graph = nx.Graph()
 
     # Log important information with the following logger function
     _event_log(model, projected_model_graph, set(constraint_variable_map), type_of_graph, with_objective)


Test patch: