©2019 Raazesh Sainudiin. Attribution 4.0 International (CC BY 4.0)
The first part of the notebook resulted in discussions between Lucas Gerin, Raazesh Sainudiin and Amandine Veber in 2016 at CMAP, Ecole Polytechnique, Palaiseau, France.
This is the code for the project on coevolution of retweet transmission trees and ideological networks in twitter.
This notebook further goes through the digraphs documentation and jots down the commands one may need (and a lot more commands that may be useful for insights and visualizations).
A minimal tour of the digraphs documentation in SageMath is in the following:
Other considerations
We will next make a much simpler model for the emergence of echo-chambers in a dynamically evolving social network as follows:
With this model we hope to create a highly structured or segregated network at stationarity if the homophily parameter is large. The simulations confirm this. Let's try to make this more rigorous and perhaps find a critical threshold value for homophily, etc...
import numpy as np
def thinEdges(G,p):
'''remove each edge with prob 1-p and return it as a list'''
removedEdges=[];
presentEdges=[];
for E in G.edge_iterator():
edgeE=(E[0],E[1])
if random() > p:
removedEdges.append(edgeE)
G.delete_edge(edgeE)
#else:
# presentEdges.append(edgeE)
return removedEdges#,presentEdges
def makeNodeColors(G,p):
'''color node v in G with color 1 with prob p and color 0 with prob 1-p'''
N=g.order();# number of nodes
Gc = np.zeros(N); # List to store colors
for v in G.vertex_iterator():
assert(v >= 0 and v < N)
#if random() < p:
if v < floor(p*N):
Gc[v] = 1
return Gc
def RandomVertexPair(G,NumTrials):
'''return a pair of vertices as an edge not in digraph G by at most NumTrials'''
it = G.random_vertex_iterator()
for i in range(NumTrials):
u,v=(next(it),next(it))
if not (G.has_edge(u,v)):
break
return (u,v)
def RandomOutEdgeOfGivenVertex(G,u,Vset):
'''return a random out edge of vertex u that is not in digraph G and not a self-loop with Vertex set Vset'''
#Vset = set(range(G.order()))
outgoingESet = set([E[1] for E in G.outgoing_edge_iterator([u])])
outgoingESet.add(u) # to avoid self-loops
diffVs=list(Vset.difference(outgoingESet))
NumOfNewOutNeighbors=len(diffVs)
assert(NumOfNewOutNeighbors>0)
return (u,diffVs[floor(random()*NumOfNewOutNeighbors)])
def EvolveHomophilousNetwork(G,Gc,homophilProb, surfingProb, k):
'''k steps of the network evolution where Prob(keeping homophylous edge) = P(edge 0->0) = P(edge 1->1) = homophilProb'''
Vset = set(range(G.order())) # vertex set is fixed
assert(Vset==set(G.vertices())) # checking vertex labels are from 0 to g.order()-1
for i in range(k):
#print i,G.order(),G.size()
if random() < surfingProb: # try to add an edge that is not there or delete one that is there with small prob to ensure irreducibility
if random() < 0.5: # add new edge at random
u,v = RandomVertexPair(G,100)
G.add_edge(u,v)
else: # delete edge at random
if G.size()>0:
u,v = G.random_edge(labels=False)
G.delete_edge((u,v))
else: # choose an edge for potential deletion (perhaps rewiring?)
if G.size()>0:
u,v = G.random_edge(labels=False)
assert(G.has_edge(u,v))
edgeDiversity=abs(Gc[u]-Gc[v])
if edgeDiversity == 0:
edgeRetentionProb = homophilProb
else:
edgeRetentionProb = 1-homophilProb
if random() > edgeRetentionProb:
G.delete_edge((u,v))
# rewire (u,w) for w randomly chosen from all nodes not connected to u
u,w = RandomOutEdgeOfGivenVertex(G,u,Vset)
G.add_edge(u,w)
def EvolveHomophilousNetworkWithConstantSize(G,Gc,homophilProb, surfingProb, k):
'''k steps of the network evolution where Prob(keeping homophylous edge) = P(edge 0->0) = P(edge 1->1) = homophilProb'''
Vset = set(range(G.order())) # vertex set is fixed
assert(Vset==set(G.vertices())) # checking vertex labels are from 0 to g.order()-1
for i in range(k):
#print "before ",i,G.order(),G.size()
currentSize=G.size() # current number of edges
if random() < surfingProb:
# try to add an edge that is not there AND delete one that is there
# with small prob to ensure irreducibility within state space constrained by same number of edges
# add new edge at random
u,v = RandomVertexPair(G,100)
G.add_edge(u,v)
updatedSize=G.size() # updated number of edges
#print "edge added"
# delete edge at random
if ( (updatedSize>0) and ((updatedSize-currentSize)==1)):
u,v = G.random_edge(labels=False)
G.delete_edge((u,v))
#print "edge deleted"
else: # choose an edge for potential deletion (perhaps rewiring?)
if currentSize > 0:
u,v = G.random_edge(labels=False)
assert(G.has_edge(u,v))
edgeDiversity=abs(Gc[u]-Gc[v])
if edgeDiversity == 0:
edgeRetentionProb = homophilProb
else:
edgeRetentionProb = 1-homophilProb
if random() > edgeRetentionProb:
G.delete_edge((u,v))
# rewire (u,w) for w randomly chosen from all nodes not connected to u
u,w = RandomOutEdgeOfGivenVertex(G,u,Vset)
G.add_edge(u,w)
#print "edge rewired homophilously ",u,v," -> ",u,w
updatedSize=G.size() # updated number of edges
#print "after ",i,G.order(),G.size()
assert(currentSize==updatedSize) # making sure we are keeping same number of edges
# later we can make node-specific tolerance prob = prob of retaining an edge between 0 and 1 or 1 and 0; this can give rise to highly tolerant nodes having connections with both '0' and '1' vertices...
g=graphs.CompleteGraph(10).to_directed()
print g.order(),g.size()
rEdges=thinEdges(g,0.25)
print g.order(),g.size()
gc = makeNodeColors(g,0.5)
gc
g.allow_loops(True)
g.allows_loops()
g.allow_multiple_edges(True)
g.allows_multiple_edges()
g.plot(layout='spring', iterations=10)
g1=copy(g)
EvolveHomophilousNetwork(g1,gc,0.99, 0.01, 500) # homophily is very high
print g1.order(),g1.size()
g1.plot(layout='spring', iterations=20)
g2=copy(g)
EvolveHomophilousNetwork(g2,gc,0.1, 0.01, 100) # homophily is very low
print g2.order(),g2.size()
g2.plot(layout='spring', iterations=20)
g1=copy(g)
print g1.order(),g1.size()
EvolveHomophilousNetworkWithConstantSize(g1,gc,0.99, 0.01, 1000) # homophily is very high
print g1.order(),g1.size()
g1.plot(layout='spring', iterations=20)
g2=copy(g)
print g2.order(),g2.size()
EvolveHomophilousNetworkWithConstantSize(g2,gc,0.1, 0.01, 1000) # homophily is very low
print g2.order(),g2.size()
g2.plot(layout='spring', iterations=20)
g=graphs.CompleteGraph(40).to_directed()
g.allow_loops(True)
g.multiple_edges(True)
print g.order(),g.size()
rEdges=thinEdges(g,0.10)
print g.order(),g.size()
gc = makeNodeColors(g,0.5)
gc
g1=copy(g)
print g1.order(),g1.size()
EvolveHomophilousNetwork(g1,gc,0.99, 0.1, 5000) # homophily is very high
print g1.order(),g1.size()
g1.plot(layout='spring', iterations=20)
# with number of edgs preserved
g1=copy(g)
print g1.order(),g1.size()
EvolveHomophilousNetworkWithConstantSize(g1,gc,0.99, 0.1, 5000) # homophily is very high
print g1.order(),g1.size()
g1.plot(layout='spring', iterations=50, figsize=(5,5))
See the following for more detials.
g.
g.edges?
g.edges()
g.add_edges?
g=graphs.CompleteGraph(4).to_directed()
print g.edges()
g.add_edges([(0,1)]) # Note that adding existing edges to a digraph does nothing to the digraph!
print g.edges()
g.delete_edges?
g=graphs.CompleteGraph(4).to_directed()
print g.edges()
g.delete_edges([(0,10)]) # Note that deleting non-existing edges to a digraph does nothing to the digraph!
print g.edges()
g=graphs.CompleteGraph(4).to_directed()
print g.edges()
g.delete_edges([(0,1),(1,3)])
print g.edges()
print g.edges()
g.add_edges([(0,1)])
print g.edges()
g.allows_multiple_edges() # we could get multigraphs with `G = Graph(multiedges=True,sparse=True); G` but let's work with digraphs without multiple edges for now...
g.allows_loops() # also let's not allow loops for now
g.am? # Returns the adjacency matrix of the (di)graph.
g.connected_component_containing_vertex?
g.connected_components?
g.connected_components_number?
g.connected_components_sizes?
g.connected_components_subgraphs?
g.copy?
#Warning: Please use this method only if you need to copy but
# change the underlying implementation or weightedness. Otherwise
# simply do "copy(g)" instead of "g.copy()".
g.degree?
g.degree()
print g.in_degree()
print g.out_degree()
g.degree_histogram()
g.degree_sequence()
g.distance? # Returns the (directed) distance from u to v in the (di)graph, i.e. the length of the shortest path from u to v.
g.distance_all_pairs? # Returns the distances between all pairs of vertices.
g.distance_graph?
g.distance_matrix?
g.edges()
g.distance_matrix()
g.distances_distribution?
g.edges_incident?
print g.edges()
print g.edges_incident([0])
print g.edges_incident([0,3])
g.edge_boundary?
g.edge_disjoint_spanning_trees?
g.edge_labels?
g.edge_connectivity()
g.eigenspaces?
g.eigenvectors?
g.flow?
g.flow_polytope?
print g.has_edge((0,1))
print g.has_vertex(0)
g.get_vertices?
g.in_degree?
g.in_degree_iterator?
g.in_degree_sequence?
g.incoming_edge_iterator?
g.incoming_edges?
g.is_strongly_connected?
g.laplacian_matrix?
g.layout?
g = digraphs.ButterflyGraph(1)
g.layout()
g.level_sets?
g.latex_options?
g.lex_BFS?
g.min_spanning_tree?
g.multicommodity_flow?
g.neighbor_in_iterator?
g.neighbors_in?
g.edges()
g.neighbors_in(0)
g.neighbors_out(0)
g.neighbor_out_iterator?
g.num_edges()
g.num_verts()
g.order() # Returns the number of vertices.
g.out_degree?
g.out_degree(0)
g.out_degree_iterator?
g.outgoing_edges(0)
g.outgoing_edge_iterator?
g.parent()
g.plot?
g.plot3d?
g.random_edge?
g.random_edge_iterator?
g.random_vertex?
g.random_vertex_iterator?
g.relabel?
g.radius?
g.reverse_edge?
g.reverse_edges?
g.set_edge_label?
g.set_edge_label(0,1,'hi')
print g.edges()
g.set_edge_label(0,1,'shut the f*(-#| up!')
print g.edges()
g.set_planar_positions?
g.set_pos?
g.shortest_path?
g.show?
g.sinks?
g.sinks()
g.sources?
g.sources()
g.size?
g.size()
g.sinks()
g.sources()
g.strongly_connected_component_containing_vertex?
g.strongly_connected_components_digraph?
g.szeged_index?
g.tensor_product?
g.to_dictionary?
g.topological_sort?
g.traveling_salesman_problem?
g.triangles_count?
g.union?
g.vertex_boundary?
g.vertex_connectivity?
g.vertex_iterator?
g.vertices()
g.weighted?
g.copy?
gw = g.copy(weighted=True)
gw.edges()
gw.weighted_adjacency_matrix?
g.wiener_index?