import networkx as nx
import matplotlib.pyplot as plt
from random import randint
from random import uniform
import numpy as np
import matplotlib
import matplotlib.gridspec as gridspec
from matplotlib.ticker import NullFormatter

nullfmt = NullFormatter()  # Keine Labels im Netzwerkplot

#Von https://de.wikibooks.org/wiki/Algorithmensammlung:_Statistik:_Binomialkoeffizient
def binomialkoeffizient(n, k):
    if k == 0: return 1
    if 2*k > n:
        ergebnis = binomialkoeffizient(n, n-k)
    else:
        ergebnis = n - k + 1
        for i in range(2, k+1):  # i in [2; k]
            ergebnis *= (n-k+i)  # Selbstmultiplikation
            ergebnis /= i  # Achtung: Ergebnis ist eine Kommazahl!
    return int(ergebnis)

#Analytische Verteilungsfunktion eines zufaelligen Netzwerkes
def P(n,kmin,kmax,p):
    ergebnis = []
    for k in range(kmin,kmax):
      ergebnis.append(binomialkoeffizient(n-1,k)*p**k*(1-p)**(n-1-k))
    return np.array(ergebnis)

#plot settings
params = {
    'figure.figsize'    : [5, 7.2],
    'text.usetex'       : True,
}
matplotlib.rcParams.update(params) 

#Grid
plt.figure(0)
gs = gridspec.GridSpec(2, 1, height_ratios=[1,2.2], hspace=0.1)
ax1 = plt.subplot(gs[0])
ax2 = plt.subplot(gs[1])

#########################################################################################
#Beginn des eigentlichen Python Programms "Zufaelliges Netzwerk"
G = nx.Graph()
#Allgemeine Festlegungen des Netzwerks 
NKn = 30 #Anzahl der Knoten (vertices)
anzedges = 100 #Gesamte Anzahl der im Netzwerk bestehenden Kanten (links, connections)

#Hinzufuegen der Knoten zum Netzwerk
G.add_nodes_from(range(NKn))

#Erzeugung der Kanten des Netzwerkes (zufaellig anzedges-Kanten zwischen den NKn-Knoten erzeugen)
links = 0
while links < anzedges:
  KnA = randint(0, NKn-1)
  KnB = randint(0, NKn-1)
  if KnA != KnB and list(G.edges()).count((KnA,KnB)) == 0 and list(G.edges()).count((KnB,KnA)) == 0:
    G.add_edge(KnA,KnB)
    links = links + 1

#Liste der Knotengrade
degree_sequence = []
for n in G.nodes():
  degree_sequence.append(G.degree(n))
degree_sequence = sorted(degree_sequence)
maxk = np.max(degree_sequence)

#Berechnung der Warscheinlichkeit das Knoten KnA mit KnB verbunden ist
p = (2*anzedges/float(NKn*(NKn-1)))

#Erzeugung des Bildes der Verteilungsfunktion der Knotengrade N(k) (analytisch,simulativ) 
ax1.plot(range(int(maxk + 2)), P(NKn, 0, int(maxk + 2), p)*NKn, linewidth = 1, linestyle = '-', c = "black")
ax1.hist(degree_sequence, bins = range(int(maxk + 2)), align = "left" ,histtype = 'bar', color = "blue", alpha = 0.2)

#Achsenbeschriftung
ax1.set_ylabel(r'$\rm N(k)$')
ax2.yaxis.set_major_formatter(nullfmt)
ax2.xaxis.set_major_formatter(nullfmt)

#Erzeugung des Netzwerk-Bildes  
node_size = 150
node_alpha = 0.3
node_color = "red"
edge_thickness = 0.4
edge_alpha = 0.7
edge_color = "blue"
node_text_size = 9
text_font = "sans-serif"
graph_pos = nx.shell_layout(G)
nx.draw_networkx_nodes(G, graph_pos, node_size = node_size, alpha = node_alpha, node_color = node_color)
nx.draw_networkx_edges(G, graph_pos, width = edge_thickness, alpha = edge_alpha, edge_color = edge_color)
nx.draw_networkx_labels(G, graph_pos, font_size = node_text_size, font_family = text_font)
#DifferentVisualisations
#graph_pos=nx.kamada_kawai_layout(G)
#'bipartite_layout',?
#'circular_layout',
#'kamada_kawai_layout', Position nodes using Kamada-Kawai path-length cost-function: Energy-based approach: Kamada and Kawai use a spring approach with the key concept that Euclidean distance in the layout should approximate the graph-theoretic distance, i.e.the shortest path length between two nodes.
#'random_layout',
#'rescale_layout',
#'shell_layout', Knotenkreis mit edges
#'spring_layout', Position nodes using Fruchterman-Reingold force-directed algorithm of the Force-directed graph drawing: position the nodes in two-dimensional space is set so that all the edges are of more or less equal length and there are as few crossing edges as possible.
#'spectral_layout', nur Knoten
#'planar_layout', ?
#'fruchterman_reingold_layout'

# Plotten der Netzwerkeigenschaften in das Bild
roundp = "%.4f"%p
textstr1 = r'$N(k) = N \left( \begin{array}[c]{cc} N -1  \\ \ k \end{array} \right)\, p^k\,(1-p)^{N-1-k} \,\,$'
textstr2 = r'$N='+str(NKn)+', p='+roundp+', m='+str(anzedges)+'$'
props = dict(boxstyle='round', facecolor='white', alpha=0.92)
plt.text(0, -1.44, textstr1, fontsize = 11, verticalalignment = 'top', horizontalalignment = 'center', bbox = props)
plt.text(0, -1.32, textstr2, fontsize = 12, verticalalignment = 'bottom', horizontalalignment = 'center', bbox = props)

#Auflistung einiger das Netzwerk bestimmender Groessen
#print("######### number of nodes: "+str(NKn))
#print("average_clustering: "+str(nx.average_clustering(G)))
#print("clustering: "+str(list(nx.clustering(G).values())))
#print("triangles: "+str(list(nx.triangles(G).values())))
#print("degree: "+str(list(nx.degree(G))))
#print("shortest_path from 0 to NKn-1: "+str(nx.shortest_path(G,0,NKn-1)))
#print("radius: "+str(nx.radius(G)))
#print("diameter: "+str(nx.diameter(G)))
#print("eccentricity: "+str(list(nx.eccentricity(G).values())))
#print("center: "+str(nx.center(G)))
#print("periphery: "+str(nx.periphery(G)))
#print("density: "+str(nx.density(G)))
#print("neighbors node 0: "+str(list(G.neighbors(0))))

#Speicherung des Bildes als .jpg und .pdf Datei
saveFig = "./RandomNetwork.jpg"
plt.savefig(saveFig, dpi=200,bbox_inches="tight",pad_inches=0.05,format="jpg")
saveFig = "./RandomNetwork.pdf"
plt.savefig(saveFig,bbox_inches="tight",pad_inches=0.05,format="pdf")
plt.show()
