Repository: networkx/networkx
Issue category: Bug Report

Issue:
`MultiGraph` views return inconsistent `has_edge` results
### Current Behavior

Filtered views of a `MultiGraph`, created with `edge_subgraph`, return inconsistent results from `has_edge`.

### Expected Behavior

Match the same results from a `Graph`: either `True` if the edge exists in the subgraph view, or `False` if not.

### Steps to Reproduce

In case of a `MultiGraph`: checking if the edge `('a', 'c')` exists in the view may return **either `True` or `False`**.

```python
import networkx as nx

G = nx.MultiGraph()

G.add_edges_from([("a", "b"),
                  ("a", "c"),       # <-- to be filtered out
                  ("c", "b")])

H = nx.edge_subgraph(G, [("a", "b", 0), ("c", "b", 0)])

print(f"{H.edges()}\n\n"
      f"{H.has_edge('a', 'c')} \t H.has_edge('a', 'c')\n"
      f"{('a', 'c') in H.edges()} \t ('a', 'c') in H.edges()\n\n"
      f"{'c' in H['a']} \t 'c' in H['a']\n"
      f"{'c' in list(H['a'])} \t 'c' in list(H['a'])")

# [('a', 'b'), ('b', 'c')]
#
# True      H.has_edge('a', 'c')    # <-- inconsistent result
# False     ('a', 'c') in H.edges()
#
# True      'c' in H['a']           # <-- inconsistent result
# False     'c' in list(H['a'])
```

In case of a `Graph`: checking if the edge `('a', 'c')` exists in the view returns **only `False`** (as expected).

```python
import networkx as nx

G = nx.Graph()

G.add_edges_from([("a", "b"),
                  ("a", "c"),
                  ("c", "b")])

H = nx.edge_subgraph(G, [("a", "b"), ("c", "b")])

print(f"{H.edges()}\n\n"
      f"{H.has_edge('a', 'c')} \t H.has_edge('a', 'c')\n"
      f"{('a', 'c') in H.edges()} \t ('a', 'c') in H.edges()\n\n"
      f"{'c' in H['a']} \t 'c' in H['a']\n"
      f"{'c' in list(H['a'])} \t 'c' in list(H['a'])")

# [('a', 'b'), ('b', 'c')]
#
# False     H.has_edge('a', 'c')
# False     ('a', 'c') in H.edges()
#
# False     'c' in H['a']
# False     'c' in list(H['a'])
```

Note that the same results follow in case of a `DiGraph` vs. a `MultiDiGraph` object.

### Environment

Python version: `3.11.10`
NetworkX version: `3.4.2`

### Additional context

I am not sure if this is a known issue or a side-effect of edge keys in multigraphs, but thought I should report it just in case.


Hints:
Thanks for this Report!  It is not a known/reported issue.

I can verify that this behavior is indeed a bug. 
To get this strange behavior, both 'a' and 'c' must be incident to visible edges in the subgraph and there must be a hidden edge between 'a' and 'c'.  The means `'c' in H['a']` passes because the nodes are in the view and `G['a']['c']` exists and we don't check whether any edges between them are visible. Similarly, for `H.has_edge` when the edge key is not included, we don't check whether none of the edges are visible. We just check if the dict of edge-keys exists.  

So, we need to 1) add a hook into those lookups to check if any visible edges are present, and 2) make sure that other strange behavior isn't also happening for other lookups. 

This bug only affects edge_subgraphs of multigraphs. 

A workaround is `nx.edge_subgraph(G, <edges>).copy()`. That workaround is actually faster than the subgraph view when you need to look up most of the edges more than once. Subgraph views save memory, not time. And the memory saved is the size of the subgraph as a graph (which is often pretty small).

Patch:
diff --git a/networkx/classes/coreviews.py b/networkx/classes/coreviews.py
index a6e85213f6c..4769ffa71ab 100644
--- a/networkx/classes/coreviews.py
+++ b/networkx/classes/coreviews.py
@@ -397,7 +397,11 @@ def __iter__(self):
                 yield n
 
     def __getitem__(self, nbr):
-        if nbr in self._atlas and self.NODE_OK(nbr):
+        if (
+            nbr in self._atlas
+            and self.NODE_OK(nbr)
+            and any(self.EDGE_OK(nbr, key) for key in self._atlas[nbr])
+        ):
 
             def new_node_ok(key):
                 return self.EDGE_OK(nbr, key)


Test patch:
diff --git a/networkx/classes/tests/test_subgraphviews.py b/networkx/classes/tests/test_subgraphviews.py
index 73e0fdd2d52..7f70b29ce9d 100644
--- a/networkx/classes/tests/test_subgraphviews.py
+++ b/networkx/classes/tests/test_subgraphviews.py
@@ -360,3 +360,12 @@ def test_readonly(self):
         pytest.raises(nx.NetworkXError, self.H.remove_node, 0)
         pytest.raises(nx.NetworkXError, self.H.add_edge, 5, 6)
         pytest.raises(nx.NetworkXError, self.H.remove_edge, 0, 1)
+
+    def test_consistent_edges(self):
+        # See gh-7724.
+        """Tests that the subgraph returns consistent filtered edges."""
+        G = nx.MultiDiGraph()
+        G.add_edges_from([("a", "b"), ("a", "c"), ("c", "b")])
+        H = nx.edge_subgraph(G, [("a", "b", 0), ("c", "b", 0)])
+        assert "c" not in H["a"]
+        assert not H.has_edge("a", "c")