Skip to content

Commit

Permalink
Make weight part of the API for functions which had default assumptio…
Browse files Browse the repository at this point in the history
…ns (networkx#6814)

* make weight part of the func signature - second_order_centrality

* make weight part of the func signature - quotient_graph

* change the dispatch decorator

* make weight part of the func signature - panther_similarity, generate_random_paths

* remove preserve_edge_attrs from number_of_walks
  • Loading branch information
MridulS authored Jul 28, 2023
1 parent 12b258c commit b2583e1
Show file tree
Hide file tree
Showing 7 changed files with 562 additions and 497 deletions.
12 changes: 8 additions & 4 deletions networkx/algorithms/centrality/second_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@


@not_implemented_for("directed")
@nx._dispatch(preserve_edge_attrs={"G": {"weight": 1}})
def second_order_centrality(G):
@nx._dispatch(edge_attrs="weight")
def second_order_centrality(G, weight="weight"):
"""Compute the second order centrality for nodes of G.
The second order centrality of a given node is the standard deviation of
Expand All @@ -51,6 +51,10 @@ def second_order_centrality(G):
G : graph
A NetworkX connected and undirected graph.
weight : string or None, optional (default="weight")
The name of an edge attribute that holds the numerical value
used as a weight. If None then each edge has weight 1.
Returns
-------
nodes : dictionary
Expand Down Expand Up @@ -103,12 +107,12 @@ def second_order_centrality(G):
raise nx.NetworkXException("Empty graph.")
if not nx.is_connected(G):
raise nx.NetworkXException("Non connected graph.")
if any(d.get("weight", 0) < 0 for u, v, d in G.edges(data=True)):
if any(d.get(weight, 0) < 0 for u, v, d in G.edges(data=True)):
raise nx.NetworkXException("Graph has negative edge weights.")

# balancing G for Metropolis-Hastings random walks
G = nx.DiGraph(G)
in_deg = dict(G.in_degree(weight="weight"))
in_deg = dict(G.in_degree(weight=weight))
d_max = max(in_deg.values())
for i, deg in in_deg.items():
if deg < d_max:
Expand Down
103 changes: 59 additions & 44 deletions networkx/algorithms/centrality/tests/test_second_order_centrality.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,58 +10,73 @@
import networkx as nx


class TestSecondOrderCentrality:
def test_empty(self):
with pytest.raises(nx.NetworkXException):
G = nx.empty_graph()
nx.second_order_centrality(G)

def test_non_connected(self):
with pytest.raises(nx.NetworkXException):
G = nx.Graph()
G.add_node(0)
G.add_node(1)
nx.second_order_centrality(G)

def test_non_negative_edge_weights(self):
with pytest.raises(nx.NetworkXException):
G = nx.path_graph(2)
G.add_edge(0, 1, weight=-1)
nx.second_order_centrality(G)

def test_one_node_graph(self):
"""Second order centrality: single node"""
def test_empty():
with pytest.raises(nx.NetworkXException):
G = nx.empty_graph()
nx.second_order_centrality(G)


def test_non_connected():
with pytest.raises(nx.NetworkXException):
G = nx.Graph()
G.add_node(0)
G.add_edge(0, 0)
assert nx.second_order_centrality(G)[0] == 0
G.add_node(1)
nx.second_order_centrality(G)


def test_non_negative_edge_weights():
with pytest.raises(nx.NetworkXException):
G = nx.path_graph(2)
G.add_edge(0, 1, weight=-1)
nx.second_order_centrality(G)


def test_weight_attribute():
G = nx.Graph()
G.add_weighted_edges_from([(0, 1, 1.0), (1, 2, 3.5)], weight="w")
expected = {0: 3.431, 1: 3.082, 2: 5.612}
b = nx.second_order_centrality(G, weight="w")

for n in sorted(G):
assert b[n] == pytest.approx(expected[n], abs=1e-2)


def test_one_node_graph():
"""Second order centrality: single node"""
G = nx.Graph()
G.add_node(0)
G.add_edge(0, 0)
assert nx.second_order_centrality(G)[0] == 0


def test_P3():
"""Second order centrality: line graph, as defined in paper"""
G = nx.path_graph(3)
b_answer = {0: 3.741, 1: 1.414, 2: 3.741}

b = nx.second_order_centrality(G)

def test_P3(self):
"""Second order centrality: line graph, as defined in paper"""
G = nx.path_graph(3)
b_answer = {0: 3.741, 1: 1.414, 2: 3.741}
for n in sorted(G):
assert b[n] == pytest.approx(b_answer[n], abs=1e-2)

b = nx.second_order_centrality(G)

for n in sorted(G):
assert b[n] == pytest.approx(b_answer[n], abs=1e-2)
def test_K3():
"""Second order centrality: complete graph, as defined in paper"""
G = nx.complete_graph(3)
b_answer = {0: 1.414, 1: 1.414, 2: 1.414}

def test_K3(self):
"""Second order centrality: complete graph, as defined in paper"""
G = nx.complete_graph(3)
b_answer = {0: 1.414, 1: 1.414, 2: 1.414}
b = nx.second_order_centrality(G)

b = nx.second_order_centrality(G)
for n in sorted(G):
assert b[n] == pytest.approx(b_answer[n], abs=1e-2)

for n in sorted(G):
assert b[n] == pytest.approx(b_answer[n], abs=1e-2)

def test_ring_graph(self):
"""Second order centrality: ring graph, as defined in paper"""
G = nx.cycle_graph(5)
b_answer = {0: 4.472, 1: 4.472, 2: 4.472, 3: 4.472, 4: 4.472}
def test_ring_graph():
"""Second order centrality: ring graph, as defined in paper"""
G = nx.cycle_graph(5)
b_answer = {0: 4.472, 1: 4.472, 2: 4.472, 3: 4.472, 4: 4.472}

b = nx.second_order_centrality(G)
b = nx.second_order_centrality(G)

for n in sorted(G):
assert b[n] == pytest.approx(b_answer[n], abs=1e-2)
for n in sorted(G):
assert b[n] == pytest.approx(b_answer[n], abs=1e-2)
59 changes: 36 additions & 23 deletions networkx/algorithms/minors/contraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,14 @@ def equivalence_classes(iterable, relation):
return {frozenset(block) for block in blocks}


@nx._dispatch(preserve_edge_attrs={"G": {"weight": 1}})
@nx._dispatch(edge_attrs="weight")
def quotient_graph(
G,
partition,
edge_relation=None,
node_data=None,
edge_data=None,
weight="weight",
relabel=False,
create_using=None,
):
Expand Down Expand Up @@ -141,18 +142,6 @@ def quotient_graph(
only if some node in *B* is adjacent to some node in *C*,
according to the edge set of `G`.
edge_data : function
This function takes two arguments, *B* and *C*, each one a set
of nodes, and must return a dictionary representing the edge
data attributes to set on the edge joining *B* and *C*, should
there be an edge joining *B* and *C* in the quotient graph (if
no such edge occurs in the quotient graph as determined by
`edge_relation`, then the output of this function is ignored).
If the quotient graph would be a multigraph, this function is
not applied, since the edge data from each edge in the graph
`G` appears in the edges of the quotient graph.
node_data : function
This function takes one argument, *B*, a set of nodes in `G`,
and must return a dictionary representing the node data
Expand All @@ -166,6 +155,22 @@ def quotient_graph(
* 'density', the density of the subgraph of `G` that this
block represents.
edge_data : function
This function takes two arguments, *B* and *C*, each one a set
of nodes, and must return a dictionary representing the edge
data attributes to set on the edge joining *B* and *C*, should
there be an edge joining *B* and *C* in the quotient graph (if
no such edge occurs in the quotient graph as determined by
`edge_relation`, then the output of this function is ignored).
If the quotient graph would be a multigraph, this function is
not applied, since the edge data from each edge in the graph
`G` appears in the edges of the quotient graph.
weight : string or None, optional (default="weight")
The name of an edge attribute that holds the numerical value
used as a weight. If None then each edge has weight 1.
relabel : bool
If True, relabel the nodes of the quotient graph to be
nonnegative integers. Otherwise, the nodes are identified with
Expand Down Expand Up @@ -303,7 +308,14 @@ def quotient_graph(
"Input `partition` is not an equivalence relation for nodes of G"
)
return _quotient_graph(
G, partition, edge_relation, node_data, edge_data, relabel, create_using
G,
partition,
edge_relation,
node_data,
edge_data,
weight,
relabel,
create_using,
)

# If the partition is a dict, it is assumed to be one where the keys are
Expand All @@ -321,18 +333,19 @@ def quotient_graph(
if not nx.community.is_partition(G, partition):
raise NetworkXException("each node must be in exactly one part of `partition`")
return _quotient_graph(
G, partition, edge_relation, node_data, edge_data, relabel, create_using
G,
partition,
edge_relation,
node_data,
edge_data,
weight,
relabel,
create_using,
)


def _quotient_graph(
G,
partition,
edge_relation=None,
node_data=None,
edge_data=None,
relabel=False,
create_using=None,
G, partition, edge_relation, node_data, edge_data, weight, relabel, create_using
):
"""Construct the quotient graph assuming input has been checked"""
if create_using is None:
Expand Down Expand Up @@ -377,7 +390,7 @@ def edge_data(b, c):
for u, v, d in G.edges(b | c, data=True)
if (u in b and v in c) or (u in c and v in b)
)
return {"weight": sum(d.get("weight", 1) for d in edgedata)}
return {"weight": sum(d.get(weight, 1) for d in edgedata)}

block_pairs = permutations(H, 2) if H.is_directed() else combinations(H, 2)
# In a multigraph, add one edge in the quotient graph for each edge
Expand Down
Loading

0 comments on commit b2583e1

Please sign in to comment.