Repository: networkx/networkx

Issue:
Unexpected Crash in "nx.percolation_centrality" caused by KeyError: 0
Hello! Sorry for bothering you again.
I noticed that "nx.percolation_centrality" will sometimes crash by a `KeyError: 0` exception.
Since this exception does not belong to NetworkX.Exceptions, I believe that it may related to an unexpected exception issue.
Would it be possible for you to confirm and investigate this?

Best regards,
Joye 

### Step to Reproduce

Please run the following Python code:
```Python
import networkx as nx

L = [(0, 1101, 0), (0, 597, 0), (2, 986, 0), (4, 829, 0), (7, 885, 0), (9, 587, 0), (10, 218, 0), (11, 5, 0), (19, 498, 0), (24, 591, 0), (26, 131, 0), (45, 344, 0), (53, 152, 0), (55, 736, 0), (57, 9, 0), (59, 79, 0), (64, 1095, 0), (78, 167, 0), (78, 578, 0), (85, 365, 0), (93, 451, 0), (96, 1088, 0), (97, 107, 0), (105, 568, 0), (107, 801, 0), (109, 1109, 0), (110, 411, 0), (112, 532, 0), (113, 815, 0), (114, 329, 0), (121, 114, 0), (123, 1057, 0), (124, 333, 0), (124, 471, 0), (124, 690, 0), (125, 932, 0), (141, 916, 0), (149, 589, 0), (152, 1011, 0), (152, 753, 0), (153, 850, 0), (156, 65, 0), (159, 469, 0), (173, 272, 0), (177, 557, 0), (182, 629, 0), (186, 594, 0), (188, 675, 0), (193, 664, 0), (194, 215, 0), (200, 966, 0), (206, 366, 0), (220, 176, 0), (223, 414, 0), (224, 657, 0), (224, 378, 0), (227, 1192, 0), (232, 809, 0), (233, 322, 0), (239, 964, 0), (244, 515, 0), (246, 457, 0), (252, 1085, 0), (260, 674, 0), (263, 231, 0), (265, 86, 0), (275, 661, 0), (276, 161, 0), (279, 1012, 0), (282, 995, 0), (295, 366, 0), (306, 582, 0), (307, 341, 0), (308, 501, 0), (308, 1189, 0), (314, 1085, 0), (315, 236, 0), (317, 1165, 0), (332, 436, 0), (333, 668, 0), (337, 143, 0), (339, 409, 0), (342, 1206, 0), (345, 1101, 0), (348, 1192, 0), (353, 533, 0), (358, 1015, 0), (375, 38, 0), (378, 712, 0), (378, 387, 0), (378, 279, 0), (381, 422, 0), (390, 760, 0), (399, 73, 0), (399, 859, 0), (399, 724, 0), (401, 311, 0), (402, 906, 0), (408, 568, 0), (409, 909, 0), (413, 39, 0), (419, 61, 0), (420, 454, 0), (424, 1158, 0), (426, 167, 0), (430, 839, 0), (430, 120, 0), (432, 336, 0), (432, 67, 0), (435, 88, 0), (436, 303, 0), (437, 317, 0), (445, 707, 0), (447, 915, 0), (450, 313, 0), (450, 407, 0), (456, 586, 0), (457, 833, 0), (461, 1130, 0), (462, 128, 0), (464, 283, 0), (464, 1183, 0), (469, 477, 0), (469, 597, 0), (472, 356, 0), (477, 88, 0), (480, 437, 0), (481, 953, 0), (483, 1024, 0), (483, 1065, 0), (485, 449, 0), (488, 791, 0), (492, 79, 0), (494, 646, 0), (498, 1002, 0), (499, 197, 0), (504, 1120, 0), (505, 13, 0), (512, 361, 0), (513, 1189, 0), (513, 534, 0), (517, 59, 0), (522, 706, 0), (529, 392, 0), (531, 247, 0), (531, 749, 0), (535, 833, 0), (537, 3, 0), (537, 943, 0), (540, 308, 0), (546, 369, 0), (553, 191, 0), (554, 426, 0), (561, 662, 0), (564, 602, 0), (568, 1003, 0), (570, 605, 0), (576, 1125, 0), (580, 81, 0), (591, 1228, 0), (607, 270, 0), (608, 120, 0), (609, 854, 0), (615, 1125, 0), (623, 891, 0), (625, 1217, 0), (628, 278, 0), (628, 1160, 0), (633, 981, 0), (638, 912, 0), (640, 315, 0), (645, 422, 0), (647, 467, 0), (650, 786, 0), (653, 323, 0), (658, 333, 0), (659, 1024, 0), (661, 1163, 0), (662, 810, 0), (666, 761, 0), (666, 41, 0), (669, 1167, 0), (670, 1206, 0), (673, 722, 0), (676, 766, 0), (692, 968, 0), (694, 1157, 0), (695, 561, 0), (704, 428, 0), (704, 815, 0), (706, 360, 0), (719, 1030, 0), (720, 1086, 0), (723, 368, 0), (724, 704, 0), (737, 928, 0), (751, 99, 0), (751, 651, 0), (757, 938, 0), (765, 617, 0), (767, 1236, 0), (768, 264, 0), (774, 24, 0), (777, 34, 0), (777, 99, 0), (779, 1018, 0), (790, 550, 0), (790, 244, 0), (793, 1035, 0), (801, 253, 0), (807, 843, 0), (809, 393, 0), (810, 563, 0), (811, 902, 0), (813, 486, 0), (816, 954, 0), (817, 29, 0), (822, 56, 0), (824, 94, 0), (834, 1040, 0), (835, 834, 0), (845, 50, 0), (863, 1136, 0), (871, 993, 0), (877, 212, 0), (880, 864, 0), (885, 1019, 0), (890, 228, 0), (899, 963, 0), (907, 949, 0), (907, 212, 0), (910, 144, 0), (916, 992, 0), (920, 398, 0), (922, 671, 0), (922, 131, 0), (924, 178, 0), (925, 562, 0), (925, 302, 0), (935, 609, 0), (935, 150, 0), (936, 60, 0), (939, 672, 0), (949, 140, 0), (950, 957, 0), (960, 761, 0), (963, 184, 0), (967, 1092, 0), (982, 1176, 0), (986, 891, 0), (989, 431, 0), (992, 858, 0), (996, 475, 0), (996, 32, 0), (1002, 234, 0), (1006, 278, 0), (1009, 968, 0), (1021, 1117, 0), (1021, 1006, 0), (1026, 996, 0), (1034, 699, 0), (1035, 387, 0), (1036, 1120, 0), (1042, 1235, 0), (1049, 413, 0), (1053, 881, 0), (1056, 1018, 0), (1078, 1131, 0), (1080, 596, 0), (1087, 711, 0), (1090, 294, 0), (1096, 939, 0), (1097, 1087, 0), (1099, 1127, 0), (1114, 858, 0), (1119, 59, 0), (1126, 702, 0), (1128, 494, 0), (1134, 318, 0), (1135, 666, 0), (1135, 301, 0), (1142, 1007, 0), (1145, 877, 0), (1148, 37, 0), (1152, 606, 0), (1153, 182, 0), (1153, 546, 0), (1157, 488, 0), (1160, 1120, 0), (1163, 1170, 0), (1166, 1077, 0), (1168, 905, 0), (1169, 1014, 0), (1173, 957, 0), (1174, 849, 0), (1179, 194, 0), (1180, 180, 0), (1183, 206, 0), (1185, 1072, 0), (1186, 639, 0), (1194, 230, 0), (1195, 654, 0), (1195, 714, 0), (1202, 517, 0), (1204, 1072, 0), (1206, 529, 0), (1212, 946, 0), (1214, 621, 0), (1225, 221, 0), (1227, 338, 0), (1229, 1092, 0), (1232, 547, 0), (1233, 896, 0), (1236, 793, 0)]
edge_list = [(x[0], x[1]) for x in L]
#import the Grpah data

G = nx.from_edgelist(edge_list)
nx.percolation_centrality(G)
```
#### Results:
```
File "/home/qiuyang/.local/lib/python3.10/site-packages/networkx/algorithms/centrality/percolation.py", line 122, in _accumulate_percolation
    pw_s_w = states[s] / (p_sigma_x_t - states[w])
KeyError: 0
```

### Environments:
NetworkX: 3.1
Python: 3.10



Discussion:
Thanks for this @joyemang33!

`percolation_centrality` indeed only works if all the nodes have the right node attribute (default is `percolation`). We should catch this and have a better default behavior. Maybe we can just set the same percolation state for all the nodes by default (in which case I think this just becomes betweenness centrality).

In your example if you set the node attribute `percolation` to `1.0` before calling `percolation_centrality`
```python
nx.set_node_attributes(G, 1.0, 'percolation')
```
it should work (but this is just betweenness centrality)

Or we should raise an error if the user doesn't provide either a starting `state` or an explicit node attribute?


Hi @MridulS ! Thanks for your so quick response. It looks that setting a default value will be good😉

Resolving patch:
diff --git a/networkx/algorithms/centrality/percolation.py b/networkx/algorithms/centrality/percolation.py
--- a/networkx/algorithms/centrality/percolation.py
+++ b/networkx/algorithms/centrality/percolation.py
@@ -38,7 +38,10 @@ def percolation_centrality(G, attribute="percolation", states=None, weight=None)
 
     attribute : None or string, optional (default='percolation')
       Name of the node attribute to use for percolation state, used
-      if `states` is None.
+      if `states` is None. If a node does not set the attribute the
+      state of that node will be set to the default value of 1.
+      If all nodes do not have the attribute all nodes will be set to
+      1 and the centrality measure will be equivalent to betweenness centrality.
 
     states : None or dict, optional (default=None)
       Specify percolation states for the nodes, nodes as keys states
@@ -85,7 +88,7 @@ def percolation_centrality(G, attribute="percolation", states=None, weight=None)
     nodes = G
 
     if states is None:
-        states = nx.get_node_attributes(nodes, attribute)
+        states = nx.get_node_attributes(nodes, attribute, default=1)
 
     # sum of all percolation states
     p_sigma_x_t = 0.0


Test patch:
diff --git a/networkx/algorithms/centrality/tests/test_percolation_centrality.py b/networkx/algorithms/centrality/tests/test_percolation_centrality.py
--- a/networkx/algorithms/centrality/tests/test_percolation_centrality.py
+++ b/networkx/algorithms/centrality/tests/test_percolation_centrality.py
@@ -31,52 +31,57 @@ def example1b_G():
     return G
 
 
-class TestPercolationCentrality:
-    def test_percolation_example1a(self):
-        """percolation centrality: example 1a"""
-        G = example1a_G()
-        p = nx.percolation_centrality(G)
-        p_answer = {4: 0.625, 6: 0.667}
-        for n, k in p_answer.items():
-            assert p[n] == pytest.approx(k, abs=1e-3)
+def test_percolation_example1a():
+    """percolation centrality: example 1a"""
+    G = example1a_G()
+    p = nx.percolation_centrality(G)
+    p_answer = {4: 0.625, 6: 0.667}
+    for n, k in p_answer.items():
+        assert p[n] == pytest.approx(k, abs=1e-3)
 
-    def test_percolation_example1b(self):
-        """percolation centrality: example 1a"""
-        G = example1b_G()
-        p = nx.percolation_centrality(G)
-        p_answer = {4: 0.825, 6: 0.4}
-        for n, k in p_answer.items():
-            assert p[n] == pytest.approx(k, abs=1e-3)
 
-    def test_converge_to_betweenness(self):
-        """percolation centrality: should converge to betweenness
-        centrality when all nodes are percolated the same"""
-        # taken from betweenness test test_florentine_families_graph
-        G = nx.florentine_families_graph()
-        b_answer = {
-            "Acciaiuoli": 0.000,
-            "Albizzi": 0.212,
-            "Barbadori": 0.093,
-            "Bischeri": 0.104,
-            "Castellani": 0.055,
-            "Ginori": 0.000,
-            "Guadagni": 0.255,
-            "Lamberteschi": 0.000,
-            "Medici": 0.522,
-            "Pazzi": 0.000,
-            "Peruzzi": 0.022,
-            "Ridolfi": 0.114,
-            "Salviati": 0.143,
-            "Strozzi": 0.103,
-            "Tornabuoni": 0.092,
-        }
+def test_percolation_example1b():
+    """percolation centrality: example 1a"""
+    G = example1b_G()
+    p = nx.percolation_centrality(G)
+    p_answer = {4: 0.825, 6: 0.4}
+    for n, k in p_answer.items():
+        assert p[n] == pytest.approx(k, abs=1e-3)
 
-        p_states = {k: 1.0 for k, v in b_answer.items()}
-        p_answer = nx.percolation_centrality(G, states=p_states)
-        for n in sorted(G):
-            assert p_answer[n] == pytest.approx(b_answer[n], abs=1e-3)
 
-        p_states = {k: 0.3 for k, v in b_answer.items()}
-        p_answer = nx.percolation_centrality(G, states=p_states)
-        for n in sorted(G):
-            assert p_answer[n] == pytest.approx(b_answer[n], abs=1e-3)
+def test_converge_to_betweenness():
+    """percolation centrality: should converge to betweenness
+    centrality when all nodes are percolated the same"""
+    # taken from betweenness test test_florentine_families_graph
+    G = nx.florentine_families_graph()
+    b_answer = {
+        "Acciaiuoli": 0.000,
+        "Albizzi": 0.212,
+        "Barbadori": 0.093,
+        "Bischeri": 0.104,
+        "Castellani": 0.055,
+        "Ginori": 0.000,
+        "Guadagni": 0.255,
+        "Lamberteschi": 0.000,
+        "Medici": 0.522,
+        "Pazzi": 0.000,
+        "Peruzzi": 0.022,
+        "Ridolfi": 0.114,
+        "Salviati": 0.143,
+        "Strozzi": 0.103,
+        "Tornabuoni": 0.092,
+    }
+
+    # If no initial state is provided, state for
+    # every node defaults to 1
+    p_answer = nx.percolation_centrality(G)
+    assert p_answer == pytest.approx(b_answer, abs=1e-3)
+
+    p_states = {k: 0.3 for k, v in b_answer.items()}
+    p_answer = nx.percolation_centrality(G, states=p_states)
+    assert p_answer == pytest.approx(b_answer, abs=1e-3)
+
+
+def test_default_percolation():
+    G = nx.erdos_renyi_graph(42, 0.42, seed=42)
+    assert nx.percolation_centrality(G) == pytest.approx(nx.betweenness_centrality(G))