Skip to content

Commit

Permalink
Avoid creating results with numpy scalars (re: NEP 51) (networkx#7282)
Browse files Browse the repository at this point in the history
* Avoid creating results with numpy scalars (re: NEP 51)

* Revert changes to `convert_matrix.py` to see what happens

* Unrevert reversion

* Better

* respond to feedback: be more clear and use `x.item()`

* Use `a.item(i)` where appropriate
  • Loading branch information
eriknw authored and cvanelteren committed Apr 22, 2024
1 parent d11c116 commit 0db9a8b
Show file tree
Hide file tree
Showing 26 changed files with 181 additions and 133 deletions.
6 changes: 3 additions & 3 deletions networkx/algorithms/assortativity/correlation.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def degree_pearson_correlation_coefficient(G, x="out", y="in", weight=None, node

xy = node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight)
x, y = zip(*xy)
return sp.stats.pearsonr(x, y)[0]
return float(sp.stats.pearsonr(x, y)[0])


@nx._dispatchable(node_attrs="attribute")
Expand Down Expand Up @@ -280,7 +280,7 @@ def attribute_ac(M):
s = (M @ M).sum()
t = M.trace()
r = (t - s) / (1 - s)
return r
return float(r)


def _numeric_ac(M, mapping):
Expand All @@ -299,4 +299,4 @@ def _numeric_ac(M, mapping):
varb = (b[idx] * y**2).sum() - ((b[idx] * y).sum()) ** 2
xy = np.outer(x, y)
ab = np.outer(a[idx], b[idx])
return (xy * (M - ab)).sum() / np.sqrt(vara * varb)
return float((xy * (M - ab)).sum() / np.sqrt(vara * varb))
3 changes: 2 additions & 1 deletion networkx/algorithms/bipartite/extendability.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

@not_implemented_for("directed")
@not_implemented_for("multigraph")
@nx._dispatchable
def maximal_extendability(G):
"""Computes the extendability of a graph.
Expand Down Expand Up @@ -97,7 +98,7 @@ def maximal_extendability(G):

# For node-pairs between V & U, keep min of max number of node-disjoint paths
# Variable $k$ stands for the extendability of graph G
k = float("Inf")
k = float("inf")
for u in U:
for v in V:
num_paths = sum(1 for _ in nx.node_disjoint_paths(residual_G, u, v))
Expand Down
4 changes: 2 additions & 2 deletions networkx/algorithms/bipartite/spectral.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ def spectral_bipartivity(G, nodes=None, weight="weight"):
coshA = 0.5 * (expA + expmA)
if nodes is None:
# return single number for entire graph
return coshA.diagonal().sum() / expA.diagonal().sum()
return float(coshA.diagonal().sum() / expA.diagonal().sum())
else:
# contribution for individual nodes
index = dict(zip(nodelist, range(len(nodelist))))
sb = {}
for n in nodes:
i = index[n]
sb[n] = coshA[i, i] / expA[i, i]
sb[n] = coshA.item(i, i) / expA.item(i, i)
return sb
38 changes: 18 additions & 20 deletions networkx/algorithms/centrality/current_flow_betweenness.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def approximate_current_flow_betweenness_centrality(
continue
for nbr in H[v]:
w = H[v][nbr].get(weight, 1.0)
betweenness[v] += w * np.abs(p[v] - p[nbr]) * cstar2k
betweenness[v] += float(w * np.abs(p[v] - p[nbr]) * cstar2k)
if normalized:
factor = 1.0
else:
Expand Down Expand Up @@ -220,24 +220,22 @@ def current_flow_betweenness_centrality(
"""
if not nx.is_connected(G):
raise nx.NetworkXError("Graph not connected.")
n = G.number_of_nodes()
N = G.number_of_nodes()
ordering = list(reverse_cuthill_mckee_ordering(G))
# make a copy with integer labels according to rcm ordering
# this could be done without a copy if we really wanted to
H = nx.relabel_nodes(G, dict(zip(ordering, range(n))))
betweenness = dict.fromkeys(H, 0.0) # b[v]=0 for v in H
H = nx.relabel_nodes(G, dict(zip(ordering, range(N))))
betweenness = dict.fromkeys(H, 0.0) # b[n]=0 for n in H
for row, (s, t) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver):
pos = dict(zip(row.argsort()[::-1], range(n)))
for i in range(n):
betweenness[s] += (i - pos[i]) * row[i]
betweenness[t] += (n - i - 1 - pos[i]) * row[i]
pos = dict(zip(row.argsort()[::-1], range(N)))
for i in range(N):
betweenness[s] += (i - pos[i]) * row.item(i)
betweenness[t] += (N - i - 1 - pos[i]) * row.item(i)
if normalized:
nb = (n - 1.0) * (n - 2.0) # normalization factor
nb = (N - 1.0) * (N - 2.0) # normalization factor
else:
nb = 2.0
for v in H:
betweenness[v] = float((betweenness[v] - v) * 2.0 / nb)
return {ordering[k]: v for k, v in betweenness.items()}
return {ordering[n]: (b - n) * 2.0 / nb for n, b in betweenness.items()}


@not_implemented_for("directed")
Expand Down Expand Up @@ -323,21 +321,21 @@ def edge_current_flow_betweenness_centrality(
"""
if not nx.is_connected(G):
raise nx.NetworkXError("Graph not connected.")
n = G.number_of_nodes()
N = G.number_of_nodes()
ordering = list(reverse_cuthill_mckee_ordering(G))
# make a copy with integer labels according to rcm ordering
# this could be done without a copy if we really wanted to
H = nx.relabel_nodes(G, dict(zip(ordering, range(n))))
H = nx.relabel_nodes(G, dict(zip(ordering, range(N))))
edges = (tuple(sorted((u, v))) for u, v in H.edges())
betweenness = dict.fromkeys(edges, 0.0)
if normalized:
nb = (n - 1.0) * (n - 2.0) # normalization factor
nb = (N - 1.0) * (N - 2.0) # normalization factor
else:
nb = 2.0
for row, (e) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver):
pos = dict(zip(row.argsort()[::-1], range(1, n + 1)))
for i in range(n):
betweenness[e] += (i + 1 - pos[i]) * row[i]
betweenness[e] += (n - i - pos[i]) * row[i]
pos = dict(zip(row.argsort()[::-1], range(1, N + 1)))
for i in range(N):
betweenness[e] += (i + 1 - pos[i]) * row.item(i)
betweenness[e] += (N - i - pos[i]) * row.item(i)
betweenness[e] /= nb
return {(ordering[s], ordering[t]): v for (s, t), v in betweenness.items()}
return {(ordering[s], ordering[t]): b for (s, t), b in betweenness.items()}
28 changes: 14 additions & 14 deletions networkx/algorithms/centrality/current_flow_betweenness_subset.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,27 +96,27 @@ def current_flow_betweenness_centrality_subset(

if not nx.is_connected(G):
raise nx.NetworkXError("Graph not connected.")
n = G.number_of_nodes()
N = G.number_of_nodes()
ordering = list(reverse_cuthill_mckee_ordering(G))
# make a copy with integer labels according to rcm ordering
# this could be done without a copy if we really wanted to
mapping = dict(zip(ordering, range(n)))
mapping = dict(zip(ordering, range(N)))
H = nx.relabel_nodes(G, mapping)
betweenness = dict.fromkeys(H, 0.0) # b[v]=0 for v in H
betweenness = dict.fromkeys(H, 0.0) # b[n]=0 for n in H
for row, (s, t) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver):
for ss in sources:
i = mapping[ss]
for tt in targets:
j = mapping[tt]
betweenness[s] += 0.5 * np.abs(row[i] - row[j])
betweenness[t] += 0.5 * np.abs(row[i] - row[j])
betweenness[s] += 0.5 * abs(row.item(i) - row.item(j))
betweenness[t] += 0.5 * abs(row.item(i) - row.item(j))
if normalized:
nb = (n - 1.0) * (n - 2.0) # normalization factor
nb = (N - 1.0) * (N - 2.0) # normalization factor
else:
nb = 2.0
for v in H:
betweenness[v] = betweenness[v] / nb + 1.0 / (2 - n)
return {ordering[k]: v for k, v in betweenness.items()}
for node in H:
betweenness[node] = betweenness[node] / nb + 1.0 / (2 - N)
return {ordering[node]: value for node, value in betweenness.items()}


@not_implemented_for("directed")
Expand Down Expand Up @@ -204,23 +204,23 @@ def edge_current_flow_betweenness_centrality_subset(

if not nx.is_connected(G):
raise nx.NetworkXError("Graph not connected.")
n = G.number_of_nodes()
N = G.number_of_nodes()
ordering = list(reverse_cuthill_mckee_ordering(G))
# make a copy with integer labels according to rcm ordering
# this could be done without a copy if we really wanted to
mapping = dict(zip(ordering, range(n)))
mapping = dict(zip(ordering, range(N)))
H = nx.relabel_nodes(G, mapping)
edges = (tuple(sorted((u, v))) for u, v in H.edges())
betweenness = dict.fromkeys(edges, 0.0)
if normalized:
nb = (n - 1.0) * (n - 2.0) # normalization factor
nb = (N - 1.0) * (N - 2.0) # normalization factor
else:
nb = 2.0
for row, (e) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver):
for ss in sources:
i = mapping[ss]
for tt in targets:
j = mapping[tt]
betweenness[e] += 0.5 * np.abs(row[i] - row[j])
betweenness[e] += 0.5 * abs(row.item(i) - row.item(j))
betweenness[e] /= nb
return {(ordering[s], ordering[t]): v for (s, t), v in betweenness.items()}
return {(ordering[s], ordering[t]): value for (s, t), value in betweenness.items()}
18 changes: 8 additions & 10 deletions networkx/algorithms/centrality/current_flow_closeness.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,24 +74,22 @@ def current_flow_closeness_centrality(G, weight=None, dtype=float, solver="lu"):
"lu": SuperLUInverseLaplacian,
"cg": CGInverseLaplacian,
}
n = G.number_of_nodes()
N = G.number_of_nodes()
ordering = list(reverse_cuthill_mckee_ordering(G))
# make a copy with integer labels according to rcm ordering
# this could be done without a copy if we really wanted to
H = nx.relabel_nodes(G, dict(zip(ordering, range(n))))
betweenness = dict.fromkeys(H, 0.0) # b[v]=0 for v in H
n = H.number_of_nodes()
L = nx.laplacian_matrix(H, nodelist=range(n), weight=weight).asformat("csc")
H = nx.relabel_nodes(G, dict(zip(ordering, range(N))))
betweenness = dict.fromkeys(H, 0.0) # b[n]=0 for n in H
N = H.number_of_nodes()
L = nx.laplacian_matrix(H, nodelist=range(N), weight=weight).asformat("csc")
L = L.astype(dtype)
C2 = solvername[solver](L, width=1, dtype=dtype) # initialize solver
for v in H:
col = C2.get_row(v)
for w in H:
betweenness[v] += col[v] - 2 * col[w]
betweenness[w] += col[v]
for v in H:
betweenness[v] = 1 / (betweenness[v])
return {ordering[k]: v for k, v in betweenness.items()}
betweenness[v] += col.item(v) - 2 * col.item(w)
betweenness[w] += col.item(v)
return {ordering[node]: 1 / value for node, value in betweenness.items()}


information_centrality = current_flow_closeness_centrality
2 changes: 1 addition & 1 deletion networkx/algorithms/centrality/eigenvector.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,4 +338,4 @@ def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0):
)
largest = eigenvector.flatten().real
norm = np.sign(largest.sum()) * sp.linalg.norm(largest)
return dict(zip(G, largest / norm))
return dict(zip(G, (largest / norm).tolist()))
4 changes: 2 additions & 2 deletions networkx/algorithms/centrality/katz.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,6 @@ def katz_centrality_numpy(G, alpha=0.1, beta=1.0, normalized=True, weight=None):
n = A.shape[0]
centrality = np.linalg.solve(np.eye(n, n) - (alpha * A), b).squeeze()

# Normalize: rely on truediv to cast to float
# Normalize: rely on truediv to cast to float, then tolist to make Python numbers
norm = np.sign(sum(centrality)) * np.linalg.norm(centrality) if normalized else 1
return dict(zip(nodelist, centrality / norm))
return dict(zip(nodelist, (centrality / norm).tolist()))
2 changes: 1 addition & 1 deletion networkx/algorithms/centrality/laplacian.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,6 @@ def laplacian_centrality(
if normalized:
lapl_cent = lapl_cent / full_energy

laplace_centralities_dict[node] = lapl_cent
laplace_centralities_dict[node] = float(lapl_cent)

return laplace_centralities_dict
5 changes: 4 additions & 1 deletion networkx/algorithms/centrality/second_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,8 @@ def _Qj(P, j):
) # eq 3

return dict(
zip(G.nodes, [np.sqrt(2 * np.sum(M[:, i]) - n * (n + 1)) for i in range(n)])
zip(
G.nodes,
(float(np.sqrt(2 * np.sum(M[:, i]) - n * (n + 1))) for i in range(n)),
)
) # eq 6
5 changes: 2 additions & 3 deletions networkx/algorithms/centrality/subgraph_alg.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,16 +278,15 @@ def communicability_betweenness_centrality(G):
B[i, :] = 0
B[:, i] = 0
B -= np.diag(np.diag(B))
cbc[v] = B.sum()
cbc[v] = float(B.sum())
# put row and col back
A[i, :] = row
A[:, i] = col
# rescale when more than two nodes
order = len(cbc)
if order > 2:
scale = 1.0 / ((order - 1.0) ** 2 - (order - 1.0))
for v in cbc:
cbc[v] *= scale
cbc = {node: value * scale for node, value in cbc.items()}
return cbc


Expand Down
4 changes: 2 additions & 2 deletions networkx/algorithms/centrality/trophic.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def trophic_levels(G, weight="weight"):
# all other nodes have levels as calculated
nonzero_node_ids = (node_id for node_id, degree in G.in_degree if degree != 0)
for i, node_id in enumerate(nonzero_node_ids):
levels[node_id] = y[i]
levels[node_id] = y.item(i)

return levels

Expand Down Expand Up @@ -159,4 +159,4 @@ def trophic_incoherence_parameter(G, weight="weight", cannibalism=False):
# Avoid copy otherwise
G_2 = G
diffs = trophic_differences(G_2, weight=weight)
return np.std(list(diffs.values()))
return float(np.std(list(diffs.values())))
58 changes: 29 additions & 29 deletions networkx/algorithms/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,10 @@ def wt(u, v):
# Only compute the edge weight once, before the inner inner
# loop.
wij = wt(i, j)
weighted_triangles += sum(
np.cbrt([(wij * wt(j, k) * wt(k, i)) for k in inbrs & jnbrs])
)
yield (i, len(inbrs), 2 * weighted_triangles)
weighted_triangles += np.cbrt(
[(wij * wt(j, k) * wt(k, i)) for k in inbrs & jnbrs]
).sum()
yield (i, len(inbrs), 2 * float(weighted_triangles))


@not_implemented_for("multigraph")
Expand Down Expand Up @@ -213,38 +213,38 @@ def wt(u, v):
for j in ipreds:
jpreds = set(G._pred[j]) - {j}
jsuccs = set(G._succ[j]) - {j}
directed_triangles += sum(
np.cbrt([(wt(j, i) * wt(k, i) * wt(k, j)) for k in ipreds & jpreds])
)
directed_triangles += sum(
np.cbrt([(wt(j, i) * wt(k, i) * wt(j, k)) for k in ipreds & jsuccs])
)
directed_triangles += sum(
np.cbrt([(wt(j, i) * wt(i, k) * wt(k, j)) for k in isuccs & jpreds])
)
directed_triangles += sum(
np.cbrt([(wt(j, i) * wt(i, k) * wt(j, k)) for k in isuccs & jsuccs])
)
directed_triangles += np.cbrt(
[(wt(j, i) * wt(k, i) * wt(k, j)) for k in ipreds & jpreds]
).sum()
directed_triangles += np.cbrt(
[(wt(j, i) * wt(k, i) * wt(j, k)) for k in ipreds & jsuccs]
).sum()
directed_triangles += np.cbrt(
[(wt(j, i) * wt(i, k) * wt(k, j)) for k in isuccs & jpreds]
).sum()
directed_triangles += np.cbrt(
[(wt(j, i) * wt(i, k) * wt(j, k)) for k in isuccs & jsuccs]
).sum()

for j in isuccs:
jpreds = set(G._pred[j]) - {j}
jsuccs = set(G._succ[j]) - {j}
directed_triangles += sum(
np.cbrt([(wt(i, j) * wt(k, i) * wt(k, j)) for k in ipreds & jpreds])
)
directed_triangles += sum(
np.cbrt([(wt(i, j) * wt(k, i) * wt(j, k)) for k in ipreds & jsuccs])
)
directed_triangles += sum(
np.cbrt([(wt(i, j) * wt(i, k) * wt(k, j)) for k in isuccs & jpreds])
)
directed_triangles += sum(
np.cbrt([(wt(i, j) * wt(i, k) * wt(j, k)) for k in isuccs & jsuccs])
)
directed_triangles += np.cbrt(
[(wt(i, j) * wt(k, i) * wt(k, j)) for k in ipreds & jpreds]
).sum()
directed_triangles += np.cbrt(
[(wt(i, j) * wt(k, i) * wt(j, k)) for k in ipreds & jsuccs]
).sum()
directed_triangles += np.cbrt(
[(wt(i, j) * wt(i, k) * wt(k, j)) for k in isuccs & jpreds]
).sum()
directed_triangles += np.cbrt(
[(wt(i, j) * wt(i, k) * wt(j, k)) for k in isuccs & jsuccs]
).sum()

dtotal = len(ipreds) + len(isuccs)
dbidirectional = len(ipreds & isuccs)
yield (i, dtotal, dbidirectional, directed_triangles)
yield (i, dtotal, dbidirectional, float(directed_triangles))


@nx._dispatchable(edge_attrs="weight")
Expand Down

0 comments on commit 0db9a8b

Please sign in to comment.