Repository: networkx/networkx

Issue:
min_weight_matching gives incorrect results
<!-- If you have a general question about NetworkX, please use the discussions tab to create a new discussion -->

<!--- Provide a general summary of the issue in the Title above -->

Consider the following graph:
```python
G = nx.Graph()
G.add_edge(1, 4, weight=2)
G.add_edge(2, 3, weight=2)
G.add_edge(1, 2, weight=1)
G.add_edge(3, 4, weight=4)
nx.min_weight_matching(G)
```

### Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
The program returns `{(2, 1), (4, 3)}`
### Expected Behavior
<!--- Tell us what should happen -->
There are two maximal matchings in the graph, (1,2)(3,4) with weight 1+4=5, and (1,4)(2,3) with weight 2+2=4. The minimum weight maximal matching should be (1,4)(2,3) which has weight 4.
### Steps to Reproduce
<!--- Provide a minimal example that reproduces the bug -->
Run the code provided
### Environment
<!--- Please provide details about your local environment -->
Python version: 3.9
NetworkX version: 2.7.1


### Additional context
<!--- Add any other context about the problem here, screenshots, etc. -->
From the documentation and the code, `min_weight_matching` is simply taking the reciprocal of the weights and uses `max_weight_matching`. This logic seems to be faulty in this example: 1/1+1/4>1/2+1/2. 


Discussion:
Thanks!   You are correct.  

That is a faulty way of thinking about turning a min problem into a max problem. Maximizing over sums of reciprocal weights does not minimizing correctly.  Other options include negating (if the problem doesn't have trouble with negative weights -- which min_weight_matching does not... but shortest_path does). Or perhaps a more robust approach would be to subtract the weights from a little bigger than the maximum of all weights. 
Looking into this bug, I'm pretty sure the keyword argument `maxcardinality` should be deprecated and set to True inside the function. What would `min_weight_matching` mean for `maxcardinality=False`?  It would mean the empty matching or maybe the matching that is just the lowest weight edge. 

I propose that we deprecate `maxcardinality`, and inside the function where we call `max_weight_matching` on the newly weighted edges, we set `maxcardinality=True`.  Luckily this code was only merged Summer 2021 so not too many people will be inconvenienced by this deprecation.

Resolving patch:
diff --git a/networkx/algorithms/matching.py b/networkx/algorithms/matching.py
--- a/networkx/algorithms/matching.py
+++ b/networkx/algorithms/matching.py
@@ -227,28 +227,49 @@ def is_perfect_matching(G, matching):
 
 @not_implemented_for("multigraph")
 @not_implemented_for("directed")
-def min_weight_matching(G, maxcardinality=False, weight="weight"):
+def min_weight_matching(G, maxcardinality=None, weight="weight"):
     """Computing a minimum-weight maximal matching of G.
 
-    Use reciprocal edge weights with the maximum-weight algorithm.
+    Use the maximum-weight algorithm with edge weights subtracted
+    from the maximum weight of all edges.
 
     A matching is a subset of edges in which no node occurs more than once.
     The weight of a matching is the sum of the weights of its edges.
     A maximal matching cannot add more edges and still be a matching.
     The cardinality of a matching is the number of matched edges.
 
-    This method replaces the weights with their reciprocal and
-    then runs :func:`max_weight_matching`.
-    Read the documentation of max_weight_matching for more information.
+    This method replaces the edge weights with 1 plus the maximum edge weight
+    minus the original edge weight.
+
+    new_weight = (max_weight + 1) - edge_weight
+
+    then runs :func:`max_weight_matching` with the new weights.
+    The max weight matching with these new weights corresponds
+    to the min weight matching using the original weights.
+    Adding 1 to the max edge weight keeps all edge weights positive
+    and as integers if they started as integers.
+
+    You might worry that adding 1 to each weight would make the algorithm
+    favor matchings with more edges. But we use the parameter
+    `maxcardinality=True` in `max_weight_matching` to ensure that the
+    number of edges in the competing matchings are the same and thus
+    the optimum does not change due to changes in the number of edges.
+
+    Read the documentation of `max_weight_matching` for more information.
 
     Parameters
     ----------
     G : NetworkX graph
       Undirected graph
 
-    maxcardinality: bool, optional (default=False)
-       If maxcardinality is True, compute the maximum-cardinality matching
-       with minimum weight among all maximum-cardinality matchings.
+    maxcardinality: bool
+        .. deprecated:: 2.8
+            The `maxcardinality` parameter will be removed in v3.0.
+            It doesn't make sense to set it to False when looking for
+            a min weight matching because then we just return no edges.
+
+        If maxcardinality is True, compute the maximum-cardinality matching
+        with minimum weight among all maximum-cardinality matchings.
 
     weight: string, optional (default='weight')
        Edge data key corresponding to the edge weight.
@@ -258,15 +279,25 @@ def min_weight_matching(G, maxcardinality=False, weight="weight"):
     -------
     matching : set
         A minimal weight matching of the graph.
+
+    See Also
+    --------
+    max_weight_matching
     """
+    if maxcardinality not in (True, None):
+        raise nx.NetworkXError(
+            "The argument maxcardinality does not make sense "
+            "in the context of minimum weight matchings."
+            "It is deprecated and will be removed in v3.0."
+        )
     if len(G.edges) == 0:
-        return max_weight_matching(G, maxcardinality, weight)
+        return max_weight_matching(G, maxcardinality=True, weight=weight)
     G_edges = G.edges(data=weight, default=1)
-    min_weight = min(w for _, _, w in G_edges)
+    max_weight = 1 + max(w for _, _, w in G_edges)
     InvG = nx.Graph()
-    edges = ((u, v, 1 / (1 + w - min_weight)) for u, v, w in G_edges)
+    edges = ((u, v, max_weight - w) for u, v, w in G_edges)
     InvG.add_weighted_edges_from(edges, weight=weight)
-    return max_weight_matching(InvG, maxcardinality, weight)
+    return max_weight_matching(InvG, maxcardinality=True, weight=weight)
 
 
 @not_implemented_for("multigraph")


Test patch:
diff --git a/networkx/algorithms/tests/test_matching.py b/networkx/algorithms/tests/test_matching.py
--- a/networkx/algorithms/tests/test_matching.py
+++ b/networkx/algorithms/tests/test_matching.py
@@ -19,15 +19,13 @@ def test_trivial1(self):
         assert nx.max_weight_matching(G) == set()
         assert nx.min_weight_matching(G) == set()
 
-    def test_trivial2(self):
-        """Self loop"""
+    def test_selfloop(self):
         G = nx.Graph()
         G.add_edge(0, 0, weight=100)
         assert nx.max_weight_matching(G) == set()
         assert nx.min_weight_matching(G) == set()
 
-    def test_trivial3(self):
-        """Single edge"""
+    def test_single_edge(self):
         G = nx.Graph()
         G.add_edge(0, 1)
         assert edges_equal(
@@ -37,8 +35,7 @@ def test_trivial3(self):
             nx.min_weight_matching(G), matching_dict_to_set({0: 1, 1: 0})
         )
 
-    def test_trivial4(self):
-        """Small graph"""
+    def test_two_path(self):
         G = nx.Graph()
         G.add_edge("one", "two", weight=10)
         G.add_edge("two", "three", weight=11)
@@ -51,8 +48,7 @@ def test_trivial4(self):
             matching_dict_to_set({"one": "two", "two": "one"}),
         )
 
-    def test_trivial5(self):
-        """Path"""
+    def test_path(self):
         G = nx.Graph()
         G.add_edge(1, 2, weight=5)
         G.add_edge(2, 3, weight=11)
@@ -70,8 +66,20 @@ def test_trivial5(self):
             nx.min_weight_matching(G, 1), matching_dict_to_set({1: 2, 3: 4})
         )
 
-    def test_trivial6(self):
-        """Small graph with arbitrary weight attribute"""
+    def test_square(self):
+        G = nx.Graph()
+        G.add_edge(1, 4, weight=2)
+        G.add_edge(2, 3, weight=2)
+        G.add_edge(1, 2, weight=1)
+        G.add_edge(3, 4, weight=4)
+        assert edges_equal(
+            nx.max_weight_matching(G), matching_dict_to_set({1: 2, 3: 4})
+        )
+        assert edges_equal(
+            nx.min_weight_matching(G), matching_dict_to_set({1: 4, 2: 3})
+        )
+
+    def test_edge_attribute_name(self):
         G = nx.Graph()
         G.add_edge("one", "two", weight=10, abcd=11)
         G.add_edge("two", "three", weight=11, abcd=10)
@@ -85,7 +93,6 @@ def test_trivial6(self):
         )
 
     def test_floating_point_weights(self):
-        """Floating point weights"""
         G = nx.Graph()
         G.add_edge(1, 2, weight=math.pi)
         G.add_edge(2, 3, weight=math.exp(1))
@@ -99,7 +106,6 @@ def test_floating_point_weights(self):
         )
 
     def test_negative_weights(self):
-        """Negative weights"""
         G = nx.Graph()
         G.add_edge(1, 2, weight=2)
         G.add_edge(1, 3, weight=-2)