146 lines
4.9 KiB
Python
146 lines
4.9 KiB
Python
# -*- coding: utf-8 -*-
|
||
'''Copyright (c) 2015 – Thomson Licensing, SAS
|
||
|
||
Redistribution and use in source and binary forms, with or without
|
||
modification, are permitted (subject to the limitations in the
|
||
disclaimer below) provided that the following conditions are met:
|
||
|
||
* Redistributions of source code must retain the above copyright
|
||
notice, this list of conditions and the following disclaimer.
|
||
|
||
* Redistributions in binary form must reproduce the above copyright
|
||
notice, this list of conditions and the following disclaimer in the
|
||
documentation and/or other materials provided with the distribution.
|
||
|
||
* Neither the name of Thomson Licensing, or Technicolor, nor the names
|
||
of its contributors may be used to endorse or promote products derived
|
||
from this software without specific prior written permission.
|
||
|
||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
|
||
GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
|
||
HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
'''
|
||
|
||
import networkx as nx
|
||
from networkx.utils import not_implemented_for
|
||
|
||
# Authors: Erwan Le Merrer (erwan.lemerrer@technicolor.com)
|
||
''' Second order centrality measure.'''
|
||
|
||
__all__ = ['second_order_centrality']
|
||
|
||
|
||
@not_implemented_for('directed')
|
||
def second_order_centrality(G):
|
||
"""Compute the second order centrality for nodes of G.
|
||
|
||
The second order centrality of a given node is the standard deviation of
|
||
the return times to that node of a perpetual random walk on G:
|
||
|
||
Parameters
|
||
----------
|
||
G : graph
|
||
A NetworkX connected and undirected graph.
|
||
|
||
Returns
|
||
-------
|
||
nodes : dictionary
|
||
Dictionary keyed by node with second order centrality as the value.
|
||
|
||
Examples
|
||
--------
|
||
>>> G = nx.star_graph(10)
|
||
>>> soc = nx.second_order_centrality(G)
|
||
>>> print(sorted(soc.items(), key=lambda x:x[1])[0][0]) # pick first id
|
||
0
|
||
|
||
Raises
|
||
------
|
||
NetworkXException
|
||
If the graph G is empty, non connected or has negative weights.
|
||
|
||
See Also
|
||
--------
|
||
betweenness_centrality
|
||
|
||
Notes
|
||
-----
|
||
Lower values of second order centrality indicate higher centrality.
|
||
|
||
The algorithm is from Kermarrec, Le Merrer, Sericola and Trédan [1]_.
|
||
|
||
This code implements the analytical version of the algorithm, i.e.,
|
||
there is no simulation of a random walk process involved. The random walk
|
||
is here unbiased (corresponding to eq 6 of the paper [1]_), thus the
|
||
centrality values are the standard deviations for random walk return times
|
||
on the transformed input graph G (equal in-degree at each nodes by adding
|
||
self-loops).
|
||
|
||
Complexity of this implementation, made to run locally on a single machine,
|
||
is O(n^3), with n the size of G, which makes it viable only for small
|
||
graphs.
|
||
|
||
References
|
||
----------
|
||
.. [1] Anne-Marie Kermarrec, Erwan Le Merrer, Bruno Sericola, Gilles Trédan
|
||
"Second order centrality: Distributed assessment of nodes criticity in
|
||
complex networks", Elsevier Computer Communications 34(5):619-628, 2011.
|
||
"""
|
||
|
||
try:
|
||
import numpy as np
|
||
except ImportError:
|
||
raise ImportError('Requires NumPy: http://scipy.org/')
|
||
|
||
n = len(G)
|
||
|
||
if n == 0:
|
||
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)):
|
||
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'))
|
||
d_max = max(in_deg.values())
|
||
for i, deg in in_deg.items():
|
||
if deg < d_max:
|
||
G.add_edge(i, i, weight=d_max-deg)
|
||
|
||
P = nx.to_numpy_matrix(G)
|
||
P = P / P.sum(axis=1) # to transition probability matrix
|
||
|
||
def _Qj(P, j):
|
||
P = P.copy()
|
||
P[:, j] = 0
|
||
return P
|
||
|
||
M = np.empty([n, n])
|
||
|
||
for i in range(n):
|
||
M[:, i] = np.linalg.solve(np.identity(n) - _Qj(P, i),
|
||
np.ones([n, 1])[:, 0]) # eq 3
|
||
|
||
return dict(zip(G.nodes,
|
||
[np.sqrt((2*np.sum(M[:, i])-n*(n+1))) for i in range(n)]
|
||
)) # eq 6
|
||
|
||
|
||
# fixture for pytest
|
||
def setup_module(module):
|
||
import pytest
|
||
numpy = pytest.importorskip('numpy')
|
||
scipy = pytest.importorskip('scipy')
|