# # Copyright (C) 2014 Paul Schultz and the SWIPO Project
#
# Authors (this file):
# Stefan Schinkel <stefan.schinkel@gmail.com>
# Paul Schultz pschultz@pik-potsdam.de>
#
#
# References: [Schultz2014]_, [Schultz2014a]_,
#
# ToDo:
# - included Schultz' Predictor and growth algorithm (among others)
"""
Module contains class ResNetwork.
Provides function for computing resistance based networks. It is subclassed
from GeoNetwork and provides most GeoNetwork's functions/properties.
The class has the following instance variables::
(bool) flagDebug : flag for debugging mode
(bool) flagComplex : flag for complex input
(ndarray) resistances: array of resistances (complex or real)
Overriden inherited methods::
(str) __str__ : extended description
(ndarray) get_adjacency: returns complex adjacency if needed
"""
# Import NumPy for the array object and fast numerics
import numpy as np
# Import handler for sparse matrices
from scipy import sparse
# Import iGraph for high performance graph theory tools written in pure ANSI-C
import igraph
from ._ext.types import to_cy, FIELD, DFIELD
from ._ext.numerics import _vertex_current_flow_betweenness, \
_edge_current_flow_betweenness
# Import things we inherit from
from .geo_network import GeoNetwork
from .geo_grid import GeoGrid
# a network Error (use uncertain)
# from .network import NetworkError
[docs]
class ResNetwork(GeoNetwork):
""" A resistive network class
ResNetwork, provides methods for an extended analysis of
resistive/resistance-based networks.
**Examples:**
>>> print(ResNetwork.SmallTestNetwork())
ResNetwork:
GeoNetwork:
Network: undirected, 5 nodes, 5 links, link density 0.500.
Geographical boundaries:
time lat lon
min 0.0 0.00 -180.00
max 9.0 90.00 180.00
Average resistance: 2.4
"""
###############################################################################
# ## MAGIC FUNCTIONS ## #
###############################################################################
[docs]
def __init__(self, resistances, grid=None, adjacency=None, edge_list=None,
directed=False, node_weight_type=None, silence_level=2):
"""Initialize an instance of ResNetwork.
:type resistances: 2D NumPy array
:arg resistances: A matrix with the resistances
:type grid: GeoGrid object
:arg grid: The GeoGrid object describing the network's spatial
embedding.
:type adjacency: 2D NumPy array (int8) [index, index]
:arg adjacency: The network's adjacency matrix.
:type edge_list: array-like list of lists
:arg edge_list: Edge list of the new network.
Entries [i,0], [i,1] contain the end-nodes of an edge.
:type directed: boolean
:arg directed: Determines, whether the network is treated as directed.
:type node_weight_type: string
:arg node_weight_type: The type of geographical node weight to be used.
:type silence_level: number (int)
:arg silence_level: The inverse level of verbosity of the object.
"""
# 1) prepare params so we can init() the parent
# 1a) an adjacency matrix
# by default, if only resistance are given,
# these define the adjacency
if adjacency is None:
if silence_level < 2:
print("Using adjacency as definded by resistances")
adjacency = np.zeros(resistances.shape)
adjacency[resistances != 0] = 1
# 1b) a Grid object
# an actual grid might not exist, so we fake one
if grid is None:
if silence_level < 2:
print("Using dummy grid")
grid = GeoGrid(
time_seq=np.arange(10), lat_seq=np.absolute(
np.linspace(-90, 90, adjacency.shape[0])),
lon_seq=np.linspace(-180, 180, adjacency.shape[0]),
silence_level=0)
# 2) init parent
GeoNetwork.__init__(self, grid, adjacency=adjacency,
node_weight_type=node_weight_type,
silence_level=silence_level)
# 3) add ResNetwork specific parts
# 3a) set the resitance values
# this sets the property and forces the
# updating of the admittance and R
self.sparse_Adm = None
self.adm_graph = None
self.sparse_R = None
self.update_resistances(resistances)
# 4) cache
self._effective_resistances = None
[docs]
def __str__(self):
"""
Return a short summary of the resistive network.
"""
return (f"ResNetwork:\n{GeoNetwork.__str__(self)}\n"
f"Average resistance: {self.resistances.mean()}")
###############################################################################
# ## PUBLIC FUNCTIONS ## #
###############################################################################
[docs]
@staticmethod
def SmallTestNetwork():
r"""
Create a small test network with unit resistances of the following
topology::
0------1--------3------4
\ /
\ /
\ /
\/
2
:rtype: Resistive Network instance
:return: an ResNetwork instance for testing purposes.
**Examples:**
>>> res = ResNetwork.SmallTestNetwork()
>>> isinstance(res, ResNetwork)
True
"""
adjacency = np.array([[0, 1, 0, 0, 0],
[1, 0, 1, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 1, 0, 1],
[0, 0, 0, 1, 0]], dtype='int8')
# sample symmetric resistances w/ rational reciprocals
# resistances = np.array([2,2,8,2,8,8,2,8,10,10])
# # the resistances should be a full matrix
resistances = np.array([[0, 2, 0, 0, 0],
[2, 0, 8, 2, 0],
[0, 8, 0, 8, 0],
[0, 2, 8, 0, 10],
[0, 0, 0, 10, 0]])
# a grid
grid = GeoGrid(
time_seq=np.arange(10), lat_seq=np.absolute(
np.linspace(-90, 90, adjacency.shape[0])),
lon_seq=np.linspace(-180, 180, adjacency.shape[0]),
silence_level=0)
return ResNetwork(resistances, grid=grid, adjacency=adjacency)
[docs]
@staticmethod
def SmallComplexNetwork():
"""
A test network with complex resistances analogue to
SmallTestNetwork()
:rtype: Resistive Network instance
:return: an ResNetwork instance with complex resistances
**Examples:**
>>> res = ResNetwork.SmallComplexNetwork()
>>> isinstance(res, ResNetwork)
True
>>> res.flagComplex
True
>>> adm = res.get_admittance()
>>> print(adm.real)
[[ 0. 0.1 0. 0. 0. ]
[ 0.1 0. 0.0625 0.25 0. ]
[ 0. 0.0625 0. 0.0625 0. ]
[ 0. 0.25 0.0625 0. 0.05 ]
[ 0. 0. 0. 0.05 0. ]]
>>> print(adm.imag)
[[ 0. -0.2 0. 0. 0. ]
[-0.2 0. -0.0625 -0.25 0. ]
[ 0. -0.0625 0. -0.0625 0. ]
[ 0. -0.25 -0.0625 0. -0.05 ]
[ 0. 0. 0. -0.05 0. ]]
"""
resistances = np.zeros((5, 5), dtype=complex)
resistances.real = [[0, 2, 0, 0, 0],
[2, 0, 8, 2, 0],
[0, 8, 0, 8, 0],
[0, 2, 8, 0, 10],
[0, 0, 0, 10, 0]]
resistances.imag = [[0, 4, 0, 0, 0],
[4, 0, 8, 2, 0],
[0, 8, 0, 8, 0],
[0, 2, 8, 0, 10],
[0, 0, 0, 10, 0]]
return ResNetwork(resistances)
[docs]
def update_resistances(self, resistances):
"""
Update the resistance matrix
This function is called to changed the resistance matrix. It sets the
property and the calls the :meth:`update_admittance` and
:meth:`update_R` functions.
:rtype: None
**Examples:**
>>> # test network with given resistances
>>> res = ResNetwork.SmallTestNetwork()
>>> print(res.resistances)
[[ 0 2 0 0 0]
[ 2 0 8 2 0]
[ 0 8 0 8 0]
[ 0 2 8 0 10]
[ 0 0 0 10 0]]
>>> # print admittance and admittance Laplacian
>>> print(res.get_admittance())
[[ 0. 0.5 0. 0. 0. ]
[ 0.5 0. 0.125 0.5 0. ]
[ 0. 0.125 0. 0.125 0. ]
[ 0. 0.5 0.125 0. 0.1 ]
[ 0. 0. 0. 0.1 0. ]]
>>> print(res.admittance_lapacian())
[[ 0.5 -0.5 0. 0. 0. ]
[-0.5 1.125 -0.125 -0.5 0. ]
[ 0. -0.125 0.25 -0.125 0. ]
[ 0. -0.5 -0.125 0.725 -0.1 ]
[ 0. 0. 0. -0.1 0.1 ]]
>>> # now update to unit resistance
>>> res.update_resistances(res.adjacency)
>>> # and check new admittance/admittance Laplacian
>>> print(res.get_admittance())
[[ 0. 1. 0. 0. 0.]
[ 1. 0. 1. 1. 0.]
[ 0. 1. 0. 1. 0.]
[ 0. 1. 1. 0. 1.]
[ 0. 0. 0. 1. 0.]]
>>> print(res.admittance_lapacian())
[[ 1. -1. 0. 0. 0.]
[-1. 3. -1. -1. 0.]
[ 0. -1. 2. -1. 0.]
[ 0. -1. -1. 3. -1.]
[ 0. 0. 0. -1. 1.]]
"""
# ensure ndarray
if not isinstance(resistances, np.ndarray):
resistances = np.array(resistances)
# check complex/real
self.flagComplex = np.iscomplexobj(resistances)
# set property
self.resistances = resistances
# update the admittance
self.update_admittance()
# and update R
self.update_R()
[docs]
def update_admittance(self):
"""
Updates admittance matrix which is inverse the resistances
:rtype: none
**Examples:**
>>> res = ResNetwork.SmallTestNetwork();print(res.get_admittance())
[[ 0. 0.5 0. 0. 0. ]
[ 0.5 0. 0.125 0.5 0. ]
[ 0. 0.125 0. 0.125 0. ]
[ 0. 0.5 0.125 0. 0.1 ]
[ 0. 0. 0. 0.1 0. ]]
>>> print(type(res.get_admittance()))
<class 'numpy.ndarray'>
"""
# a sparse matrix for the admittance values
# we start w/ a lil_matrix, maybe convert that
# to csr_matrix ( Compressed Sparse Row) or
# to csc_matrix ( Compressed Sparse Column) later
# complex number support
if self.flagComplex:
dtype = complex
else:
dtype = float
self.sparse_Adm = sparse.lil_matrix((self.N, self.N), dtype=dtype)
# get the edges
edgeList = list(self.edge_list())
# populate array
for edge in edgeList:
# print("setting %d %d to %f" % (
# edge[0], edge[1], 1./self.resistances[edge[0], edge[1]]))
self.sparse_Adm[edge[0], edge[1]] = \
1./self.resistances[edge[0], edge[1]]
# Similar to GeoNetwork, we embed an iGraph instance for
# the admittance matrix
self.adm_graph = igraph.Graph(n=self.N, edges=edgeList,
directed=self.directed)
self.graph.simplify()
[docs]
def get_admittance(self):
"""Return the (possibly non-symmetric) dense admittance matrix
:rtype: square NumPy matrix [node,node] of ints
**Examples:**
>>> res = ResNetwork.SmallTestNetwork();print(res.get_admittance())
[[ 0. 0.5 0. 0. 0. ]
[ 0.5 0. 0.125 0.5 0. ]
[ 0. 0.125 0. 0.125 0. ]
[ 0. 0.5 0.125 0. 0.1 ]
[ 0. 0. 0. 0.1 0. ]]
>>> print(type( res.get_admittance() ))
<class 'numpy.ndarray'>
"""
return self.sparse_Adm.toarray()
[docs]
def update_R(self):
"""Updates R, the pseudo inverse of the admittance Laplacian
This function is run, whenever the admittance is changed.
:rtype: none
**Examples:**
>>> res = ResNetwork.SmallTestNetwork();print(res.get_admittance())
[[ 0. 0.5 0. 0. 0. ]
[ 0.5 0. 0.125 0.5 0. ]
[ 0. 0.125 0. 0.125 0. ]
[ 0. 0.5 0.125 0. 0.1 ]
[ 0. 0. 0. 0.1 0. ]]
>>> print(type( res.get_admittance() ))
<class 'numpy.ndarray'>
"""
# a sparse matrix for the admittance values
self.sparse_R = sparse.lil_matrix(
np.linalg.pinv(self.admittance_lapacian()))
[docs]
def get_R(self):
"""Return the pseudo inverse of of the admittance Laplacian
The pseudoinverse is used of the novel betweeness measures such as
:meth:`vertex_current_flow_betweenness` and
:meth:`edge_current_flow_betweenness` It is computed on instantiation
and on change of the resistances/admittance
:returns: the pseudoinverse of the admittange Laplacian
:rtype: ndarray (float)
**Examples:**
>>> res = ResNetwork.SmallTestNetwork();print(res.get_R())
[[ 2.28444444 0.68444444 -0.56 -0.20444444 -2.20444444]
[ 0.68444444 1.08444444 -0.16 0.19555556 -1.80444444]
[-0.56 -0.16 3.04 -0.16 -2.16 ]
[-0.20444444 0.19555556 -0.16 1.08444444 -0.91555556]
[-2.20444444 -1.80444444 -2.16 -0.91555556 7.08444444]]
"""
return self.sparse_R.toarray()
[docs]
def admittance_lapacian(self):
"""
Return the (possibly non-symmetric) dense Laplacian matrix of the
admittance.
:rtype: square NumPy matrix [node,node] of
**Examples:**
>>> print(ResNetwork.SmallTestNetwork().admittance_lapacian())
[[ 0.5 -0.5 0. 0. 0. ]
[-0.5 1.125 -0.125 -0.5 0. ]
[ 0. -0.125 0.25 -0.125 0. ]
[ 0. -0.5 -0.125 0.725 -0.1 ]
[ 0. 0. 0. -0.1 0.1 ]]
>>> print(type( ResNetwork.SmallTestNetwork().admittance_lapacian() ))
<class 'numpy.ndarray'>
"""
return np.diag(sum(self.get_admittance())) - self.get_admittance()
[docs]
def admittive_degree(self):
"""admittive degree of the network
The admittive (or effective) degree of the resistive network,
which is the counterpart to the traditional degree.
:rtype: 1D NumPy array
**Examples:**
>>> print(ResNetwork.SmallTestNetwork().admittive_degree())
[ 0.5 1.125 0.25 0.725 0.1 ]
>>> print(type( ResNetwork.SmallTestNetwork().admittive_degree() ))
<class 'numpy.ndarray'>
"""
return np.sum(self.get_admittance(), axis=0)
[docs]
def average_neighbors_admittive_degree(self):
""" Average neighbour effective degree
:rtype: 1D NumPy array
**Examples:**
>>> print(ResNetwork.SmallTestNetwork().\
average_neighbors_admittive_degree())
[ 2.25 1.31111111 7.4 2.03448276 7.25 ]
>>> print(type(ResNetwork.SmallTestNetwork().admittive_degree()))
<class 'numpy.ndarray'>
"""
# get the admittive degree (row sum)
# and adjacency matrix
ad = self.admittive_degree()
adj = self.adjacency
# in case of complex resistances we also use the dot product,
# but of complex-valued arrays
# This is NOT right, BUT:
# np.dot treats complex numbers wrongly and computes the
# dot product of the real and the imag part seperately
# which in our case is exactly what we want
if self.flagComplex:
adj = np.array(adj, dtype=complex)
adj.imag = adj.real
# dot product of adjacency and degree
# normalised by the row sum (admittive degree)
return np.dot(adj, ad) / ad
# to sweave later on:
# N = self.N
# adjacency = self.adjacency
# ANED = np.zeros(N)
# ED = self.admittive_degree()
# for i in range(N):
# sum = 0
# for j in range(N):
# sum += adjacency[i][j]*ED[j]
# ANED[i] = sum/ED[i]
# # print(ANED)
[docs]
def local_admittive_clustering(self):
r"""
Return node wise admittive clustering coefficient (AC).
The AC is the electrical analogue of the clustering coefficient for
regular network (see :meth:`.get_admittive_ws_clustering` and
:meth:`.get_local_clustering` and sometimes called Effective Clustering
(EC))
The admittive clustering (:math:`ac`) of node :math:`i` is defined as:
.. math::
\text{ac}_i = \frac
{\sum_{j,k}^N\alpha_{i,j},\alpha_{i,k},\alpha_{j,k}}
{\text{ad}_i(\text{d}_i-1)}
where
- :math:`\alpha` is the admittance matrix
- :math:`ad_i` is the admittive degree of the node :math:`i`
- :math:`d_i` is the degree of the node :math:`i`
:rtype: 1d NumPy array (float)
**Examples:**
>>> res = ResNetwork.SmallTestNetwork()
>>> print(res.local_admittive_clustering())
[ 0. 0.00694444 0.0625 0.01077586 0. ]
>>> print(type(res.local_admittive_clustering()))
<class 'numpy.ndarray'>
"""
# needed vals: admittance matrix and admittiv_degree
# are already complex/real as needed
admittance = self.get_admittance()
ad = self.admittive_degree()
# output and the degree have to be switched
if self.flagComplex:
d = np.array(self.degree(), dtype=complex)
ac = np.zeros(self.N, dtype=complex)
else:
d = self.degree()
ac = np.zeros(self.N)
# TODO: Sweave me!
for i in range(self.N):
dummy = 0
for j in range(self.N):
for k in range(self.N):
dummy += admittance[i][j]*admittance[i][k]*admittance[j][k]
if d[i] == 1:
ac[i] = 0
else:
ac[i] = dummy/(ad[i] * (d[i]-1))
# return
return ac
[docs]
def global_admittive_clustering(self):
"""
Return node wise admittive clustering coefficient.
:rtype: NumPy float
**Examples:**
>>> res = ResNetwork.SmallTestNetwork()
>>> print("%.3f" % res.global_admittive_clustering())
0.016
>>> print(type(res.global_admittive_clustering()))
<class 'numpy.float64'>
"""
return self.local_admittive_clustering().mean()
[docs]
def effective_resistance(self, a, b):
"""
Return the effective resistance (ER) between two nodes
a and b. The ER is the electrical analogue to the shortest path
where a is considered as "source" and b as the "sink"
:type a: int
:arg a: index of the "source" node
:type b: int
:arg b: index of the "sink" node
:rtype: NumPy float
**Examples:**
>>> res = ResNetwork.SmallTestNetwork()
>>> print(res.effective_resistance(1,1))
0.0
>>> print(type( res.effective_resistance(1,1) ))
<class 'float'>
>>> print("%.3f" % res.effective_resistance(1,2))
4.444
>>> print(type( res.effective_resistance(1,1) ))
<class 'float'>
"""
# return def for self-loop
if a == b:
if self.flagComplex:
return complex(0.0)
else:
return float(0.0)
# Get pseudoinverse of the Laplacian
R = self.get_R()
# return looked-up values
return R[a, a] - R[a, b] - R[b, a] + R[b, b]
[docs]
def average_effective_resistance(self):
"""
Return the average effective resistance (<ER>) of the resistive
network, the average resistances for all "paths" (connections)
:rtype: float
**Examples:**
>>> res = ResNetwork.SmallTestNetwork()
>>> print("%.5f" % res.average_effective_resistance())
7.28889
>>> print(type( res.average_effective_resistance() ))
<class 'numpy.float64'>
"""
# since the NW is symmetric, we can only
# sum over upper triangle, excluding zeros
# but multiply by 2 later on
# we also store a hidden, quick access var
self._effective_resistances = np.array([])
for i in range(self.N):
for j in range(i):
self._effective_resistances = np.append(
self._effective_resistances,
self.effective_resistance(i, j))
return 2*np.sum(self._effective_resistances) / (self.N*(self.N-1))
[docs]
def diameter_effective_resistance(self):
"""
Return the diameter (the highest resistance path between any nodes).
:rtype: float
**Examples:**
>>> res = ResNetwork.SmallTestNetwork()
>>> print("%.3f" % res.diameter_effective_resistance())
Re-computing all effective resistances
14.444
>>> print(type(res.diameter_effective_resistance()))
<class 'numpy.float64'>
>>> res = ResNetwork.SmallTestNetwork()
>>> x = res.average_effective_resistance()
>>> print("%.3f" % res.diameter_effective_resistance())
14.444
"""
# try to use pre-computed values
if self._effective_resistances is not None:
diameter = np.max(self._effective_resistances)
else:
print("Re-computing all effective resistances")
self.average_effective_resistance()
diameter = np.max(self._effective_resistances)
return diameter
[docs]
def effective_resistance_closeness_centrality(self, a):
"""
The effective resistance closeness centrality (ERCC) of node a
:type a: int
:arg a: index of the "source" node
:rtype: NumPy float
**Examples:**
>>> res = ResNetwork.SmallTestNetwork()
>>> print("%.3f" % res.effective_resistance_closeness_centrality(0))
0.154
>>> print("%.3f" % res.effective_resistance_closeness_centrality(4))
0.080
"""
# alloc
ERCC = DFIELD(0.0)
# compute
for i in range(self.N):
ERCC += self.effective_resistance(a, i)
# ERCC /= np.square( self.N - 1 )
ERCC = (self.N - 1) / ERCC
# return
return ERCC
[docs]
def vertex_current_flow_betweenness(self, i):
r"""
Vertex Current Flow Betweeness (VCFB) of a node i.
The electrial version of Newmann's node betweeness is here
defined as the Vertex Current Flow Betweeness (VCGB) of a node
.. math::
VCFB_i := \frac{ 2 }{ n \left( n-1 \right)} \sum_{s<t} I_i^{st}
where
.. math::
I_i^{st} &= \frac{1}{2}\sum_{j} \Gamma_{i,j} | V_i - V_j |\\
&= \frac{1}{2}\sum_{j} \Gamma_{i,j}
| I_s(R_{i,s}-R_{j,s}) + I_t(R_{j,t}-R_{i,t}) |
and further:
- :math:`I_{s}^{st} := I_{s}`
- :math:`I_{t}^{st} := I_{t}`
- :math:`\Gamma` is the admittance matrix
- :math:`R` is the pseudoinverse of the admittance Laplacian
:arg int a: index of the "source" node
:rtype: NumPy float
**Examples:**
>>> res = ResNetwork.SmallTestNetwork()
>>> print("%.3f" % res.vertex_current_flow_betweenness(1))
0.389
>>> print("%.3f" % res.vertex_current_flow_betweenness(2))
0.044
"""
# set params
Is = It = FIELD(1.0)
return _vertex_current_flow_betweenness(
self.N, Is, It,
to_cy(self.get_admittance(), FIELD), to_cy(self.get_R(), FIELD), i)
[docs]
def edge_current_flow_betweenness(self):
"""The electrial version of Newmann's edge betweeness
:rtype: NumPy float
**Examples:**
>>> res = ResNetwork.SmallTestNetwork()
>>> print(r(res.edge_current_flow_betweenness()))
[[ 0. 0.4 0. 0. 0. ]
[ 0.4 0. 0.2444 0.5333 0. ]
[ 0. 0.2444 0. 0.2444 0. ]
[ 0. 0.5333 0.2444 0. 0.4 ]
[ 0. 0. 0. 0.4 0. ]]
>>> # update to unit resistances
>>> res.update_resistances(res.adjacency)
>>> print(r(res.edge_current_flow_betweenness()))
[[ 0. 0.4 0. 0. 0. ]
[ 0.4 0. 0.3333 0.4 0. ]
[ 0. 0.3333 0. 0.3333 0. ]
[ 0. 0.4 0.3333 0. 0.4 ]
[ 0. 0. 0. 0.4 0. ]]
"""
# set currents
Is = It = FIELD(1)
return _edge_current_flow_betweenness(
self.N, Is, It,
to_cy(self.get_admittance(), FIELD), to_cy(self.get_R(), FIELD))
###############################################################################
# ## FUNCTIONS ATTIC ## #
###############################################################################
# These functions are no longer needed as the computation can be broken
# down to some indexing but they are kept as implementatin references
# since all git logs will be lost when adding resnw to pyunicorn.
#
#
# def _effective_resistance_python(self,a,b):
# """Python version of the effective resistance
# """
# # Get pseudoinverse of the Laplacian
# R = self.get_R()
# t = R[a,a] - R[a,b] - R[b,a] + R[b,b]
# print("the t is %f + %fi " % (t.real,t.imag))
# # construct a vector that is all zero except for
# # the source (a) and the sink (b)
# # if self.flagComplex:
# # base = np.zeros(self.N,dtype=complex)
# # else:
# base = np.zeros(self.N)
# base[a] = 1;
# base[b] = -1;
# # multiply every row of R with the above vector
# # and sum across rows => np.sum(R * base, axis=1) and then
# # build the scalar product of the result and the base vector
# ER = np.dot( np.sum(R * base, axis=1), base )
# return ER
# def _effective_resistance_weave(self,a,b):
# """ C version of effective resistance
# """
# # Get pseudoinverse of the Laplacian
# R = self.get_R()
# N = np.float( self.N)
# code = \
# """
# int i=0;
# int j=0;
# double ER = 0.0;
# // vector for temp values
# double tmp[int(N)];
# // setup the base vector
# double base[int(N)];
# base[a] = 1;
# base[b] = -1;
# for(i=0;i<N; i++){
# for (j=0; j<N; j++){
# tmp[i] += R2(i,j) * base[j];
# } //for j
# ER += base[i] * tmp[i];
# } // for i
# return_val = ER;
# """
# variables = ['a','b','N','R'];
# ER = weave.inline(code,variables,compiler = "gcc")
# return ER
# def _effective_resistance_weave_complex(self,a,b):
# """ C version of effective resistance
# """
# # Get pseudoinverse of the Laplacian
# R = self.get_R()
# N = np.float( self.N)
# code = \
# """
# int i=0;
# int j=0;
# std::complex<double> ER = 0.0;
# // vector for base and values
# std::complex<double> base[int(N)];
# base[a].real() = 1;
# base[b].real() = -1;
# // vector for temp values
# std::complex<double> tmp[int(N)];
# /*
# tmp = (double*) calloc(N,sizeof(double));
# /// handling of complex numbers via struct definition
# // struct describing complex no.
# typedef struct {
# double real;
# double imag;
# } complex_def;
# complex_def *base;
# base = calloc(N * sizeof(*array));
# */
# for(i=0;i<N; i++){
# for (j=0; j<N; j++){
# tmp[i] += R2(i,j) * base[j];
# } //for j
# ER += base[i] * tmp[i];
# } // for i
# return_val = ER;
# """
# variables = ['a','b','N','R'];
# ER = weave.inline(code,variables,compiler = "gcc",
# headers=["<complex.h>"])
# return ER