<jupyter_start><jupyter_text>Practice Session 05: Network models In this session we will learn to use [NetworkX](https://networkx.github.io/), a Python package, and we will write code to create random graphs and preferential attachment graphs.**Note:** The graph generators we ask you to delive for this practice are already implemented in the NetworkX library and in other places online. *Do not copy those implementations:* they reproduce the same kinds of graph but follow a design that is different from what we describe here.(Remove this cell when delivering.) Author: Your name hereE-mail: Your e-mail hereDate: The current date here  1. Random (ER) graph generator Write function `generate_random_graph(N, p)`, that:1. Creates an empty graph1. Adds N nodes to this graph, numbered from *0* to *N-1*1. For each pair *(u,v)* of nodes:   1. With probability *p*, adds an edge between *u* and *v*1. Returns the graphSmall graphs can be easily created programmatically in Python with NetworkX.* To create a graph, you use either `networkx.Graph` or `networkx.DiGraph`, which return an undirected an directed graph respectively.* To add a node to a graph *g*, you use `g.add_node(u)`, where *u* is the name of the node.* To add an edge to a graph *g*, you use `g.add_edge(u, v)`, where *u* is the name of the source of the edge, and *v* the name of the destination of the edge.Example:```pythong = nx.Graph()g.add_node(0)g.add_node(1)g.add_edge(0, 1)```(Remove this cell when delivering.)<jupyter_code>import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
from collections import OrderedDict<jupyter_output><empty_output><jupyter_text>The following function, which you can leave as-is, returns `True` with probability *p*, and `False` with probability *1-p*:(Remove this cell when delivering.)<jupyter_code># Leave as-is

def flip_coin(p):
    if np.random.random() < p:
        return True
    else:
        return False<jupyter_output><empty_output><jupyter_text>Now, create an ER graph generator. Your function should be called with `g = generate_random_graph(N, p)`. (Remove this cell when delivering.) Replace this cell with your code for *generate_random_graph(N,p)*, include comments to explain what you are doing at each step. Create a function `is_connected(g)` that given a graph, answers True if the graph is connected, False otherwise. Do not use the built-in `is_connected` function of NetworkX, but feel free to use the function `nx.has_path(g, source, target)`. The number of nodes of the graph *g* is `g.number_of_nodes()`.(Remove this cell when delivering.) Replace this cell with your code for *is_connected(g)*, include comments to explain what you are doing at each step. To draw a graph, you can use:```pythonnx.draw_networkx(g)```You can have more control over the visualization of the graph, such as setting the figure size, removing the axis, using a particular layout algorithm, or changing the node size or color:```pythonplt.figure(figsize=(12,6))plt.axis('off')pos=nx.spring_layout(g)nx.draw_networkx(g, pos, with_labels=True, node_size=500, node_color='yellow')```Tip: In the graph drawings of ER and BA graphs on this report you can use options `with_labels=False, node_size=10` (you can play with different values for `node_size`)(Remove this cell when delivering.) Replace this cell with your code to generate and visualize 3 ER graphs of about 200 nodes each. **Make sure all the graphs you generate are connected, check them with your is_connected(g) function** Create another function `print_er_statistics(g,p)` that given an ER graph and a probability *p* prints:* its observed average degree *&lt;k&gt;* * its expected average degree given *N* and *p*, using the formula seen in class* its observed number of links *L** its expected number of links given *N* and *p*, using the formula seen in classYou can get a list of *(node, degree)* pairs by invoking `g.degree()`, or ask for the degree of node *u* using `g.degree(u)`. (Remove this cell when delivering.) Replace this cell with your code for print_er_statistics You can use the following function (as-is, or modified) to plot the degree distribution in a graph.(Remove this cell when delivering.)<jupyter_code># Leave as-is or modify if you want

def plot_degree_distribution(g):
    degree_dict = dict(g.degree())
    degree_ordered = OrderedDict(sorted(degree_dict.items(), key=lambda x: x[1], reverse=True))
    degree_sequence = list(degree_ordered.values())
    prob, bin_edges = np.histogram(degree_sequence, bins=range(1,np.max(degree_sequence)+2), density=True)
    plt.figure(figsize=(8,4))
    plt.loglog(bin_edges[:-1], prob, 'x-')
    plt.title("Probability density function")
    plt.xlabel("degree")
    plt.ylabel("probability")
    plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09539679233493022, 'fraction_numerical': 0.006873568006665278, 'mean_word_length': 5.148527528809219, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 13, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '419327', 'license': 'CC-BY-4.0', 'n_tokens_mistral': 1383, 'n_tokens_neox': 1345, 'n_words': 626, 'path': 'practicum/ps05_network_models.ipynb', 'repo_name': 'Giuliano-Boscarin/networks-science-course', 'size': '4801'}