1
0
mirror of https://github.com/gsi-upm/soil synced 2024-11-13 06:52:28 +00:00

Updated tutorial

Close #3
This commit is contained in:
Tasio Mendez 2017-05-17 11:03:24 +02:00
parent 5a2fcb5564
commit cd9b7e096a
7 changed files with 9217 additions and 9144 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
__pycache__/
.idea/
.ipynb_checkpoints/
*.png
*.gexf

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

17930
data.txt

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -85,7 +85,7 @@ def results(model_name):
cured_line = ax1.plot(x_values, cured_values, label='Cured')
vaccinated_line = ax1.plot(x_values, vaccinated_values, label='Vaccinated')
ax1.legend()
fig1.savefig(model_name+'.png')
fig1.savefig(model_name + '.png')
# plt.show()

View File

@ -32,10 +32,10 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"SOIL is based in 3 main files:\n",
"SOIL is based in 2 main files:\n",
"* __soil.py__: It's the main file of SOIL. The network creation, simulation and visualization are done in this file.\n",
"- __models.py__: All the spread models already implemented are stored in this file.\n",
"+ __settings.py__: This file contains every variable needed in the simulation in order to be modified easily."
"+ __settings.json__: This file contains every variable needed in the simulation in order to be modified easily.\n",
"- __models__: All the spread models already implemented are stored in this directory as modules."
]
},
{
@ -80,24 +80,21 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"from models import *\n",
"from nxsim import NetworkSimulation\n",
"import numpy\n",
"# import numpy\n",
"from matplotlib import pyplot as plt\n",
"import networkx as nx\n",
"import settings\n",
"import models\n",
"from models import *\n",
"import math\n",
"import json\n",
"\n",
"settings.init() # Loads all the data from settings\n",
"models.init() # Loads the models and network variables"
"import json"
]
},
{
@ -116,62 +113,21 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"if settings.network_type == 0:\n",
" G = nx.complete_graph(settings.number_of_nodes)\n",
"if settings.network_type == 1:\n",
" G = nx.barabasi_albert_graph(settings.number_of_nodes,10)\n",
"if settings.network_type == 2:\n",
" G = nx.margulis_gabber_galil_graph(settings.number_of_nodes, None)\n",
"if settings.network_params[\"network_type\"] == 0:\n",
" G = nx.complete_graph(settings.network_params[\"number_of_nodes\"])\n",
"if settings.network_params[\"network_type\"] == 1:\n",
" G = nx.barabasi_albert_graph(settings.network_params[\"number_of_nodes\"], 10)\n",
"if settings.network_params[\"network_type\"] == 2:\n",
" G = nx.margulis_gabber_galil_graph(settings.network_params[\"number_of_nodes\"], None)\n",
"# More types of networks can be added here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Simulation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The simulation starts with the following code. The user can provide the network topology, the maximum time of simulation, the spread model to be used as well as other parameters."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Starting simulations...\n",
"---Trial 0---\n",
"Setting up agents...\n",
"Written 50 items to pickled binary file: sim_01/log.0.state.pickled\n",
"Simulation completed.\n"
]
}
],
"source": [
"sim = NetworkSimulation(topology=G, states=init_states, agent_type=ControlModelM2,\n",
" max_time=settings.max_time, num_trials=settings.num_trials, logging_interval=1.0)\n",
"\n",
"\n",
"sim.run_simulation()"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -213,38 +169,31 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Done!\n"
]
}
],
"outputs": [],
"source": [
"for x in range(0, settings.number_of_nodes):\n",
" for enterprise in models.networkStatus[\"agent_%s\"%x]:\n",
" emotionStatusAux=[]\n",
" for time in models.networkStatus[\"agent_%s\"%x][enterprise]:\n",
" prec = 2\n",
" output = math.floor(models.networkStatus[\"agent_%s\"%x][enterprise][time] * (10 ** prec)) / (10 ** prec) #Para tener 2 decimales solo\n",
" emotionStatusAux.append((output,time,None))\n",
" attributes = {}\n",
" attributes[enterprise] = emotionStatusAux\n",
" G.add_node(x, attributes)\n",
"def visualization(graph_name):\n",
"\n",
" for x in range(0, settings.network_params[\"number_of_nodes\"]):\n",
" for attribute in models.networkStatus[\"agent_%s\" % x]:\n",
" emotionStatusAux = []\n",
" for t_step in models.networkStatus[\"agent_%s\" % x][attribute]:\n",
" prec = 2\n",
" output = math.floor(models.networkStatus[\"agent_%s\" % x][attribute][t_step] * (10 ** prec)) / (10 ** prec) # 2 decimals\n",
" emotionStatusAux.append((output, t_step, t_step + settings.network_params[\"timeout\"]))\n",
" attributes = {}\n",
" attributes[attribute] = emotionStatusAux\n",
" G.add_node(x, attributes)\n",
"\n",
"print(\"Done!\")\n",
" print(\"Done!\")\n",
"\n",
"with open('data.txt', 'w') as outfile:\n",
" json.dump(models.networkStatus, outfile, sort_keys=True, indent=4, separators=(',', ': '))\n",
" with open('data.txt', 'w') as outfile:\n",
" json.dump(models.networkStatus, outfile, sort_keys=True, indent=4, separators=(',', ': '))\n",
"\n",
"nx.write_gexf(G,\"test.gexf\", version=\"1.2draft\")"
" nx.write_gexf(G, graph_name+\".gexf\", version=\"1.2draft\")"
]
},
{
@ -256,70 +205,111 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"x_values = []\n",
"infected_values = []\n",
"neutral_values = []\n",
"cured_values = []\n",
"vaccinated_values = []\n",
"def results(model_name):\n",
" x_values = []\n",
" infected_values = []\n",
" neutral_values = []\n",
" cured_values = []\n",
" vaccinated_values = []\n",
"\n",
"attribute_plot = 'status'\n",
"for time in range(0, settings.max_time):\n",
" value_infected = 0\n",
" value_neutral = 0\n",
" value_cured = 0\n",
" value_vaccinated = 0\n",
" real_time = time * settings.timeout\n",
" activity= False\n",
" for x in range(0, settings.number_of_nodes):\n",
" if attribute_plot in models.networkStatus[\"agent_%s\" % x]:\n",
" if real_time in models.networkStatus[\"agent_%s\" % x][attribute_plot]:\n",
" if models.networkStatus[\"agent_%s\" % x][attribute_plot][real_time] == 1: ##Represent infected\n",
" value_infected += 1\n",
" activity = True\n",
" if models.networkStatus[\"agent_%s\" % x][attribute_plot][real_time] == 0: ##Represent neutrals\n",
" value_neutral += 1\n",
" activity = True\n",
" if models.networkStatus[\"agent_%s\" % x][attribute_plot][real_time] == 2: ##Represent cured\n",
" value_cured += 1\n",
" activity = True\n",
" if models.networkStatus[\"agent_%s\" % x][attribute_plot][real_time] == 3: ##Represent vaccinated\n",
" value_vaccinated += 1\n",
" activity = True\n",
" attribute_plot = 'status'\n",
" for time in range(0, settings.network_params[\"max_time\"]):\n",
" value_infectados = 0\n",
" value_neutral = 0\n",
" value_cured = 0\n",
" value_vaccinated = 0\n",
" real_time = time * settings.network_params[\"timeout\"]\n",
" activity = False\n",
" for x in range(0, settings.network_params[\"number_of_nodes\"]):\n",
" if attribute_plot in models.networkStatus[\"agent_%s\" % x]:\n",
" if real_time in models.networkStatus[\"agent_%s\" % x][attribute_plot]:\n",
" if models.networkStatus[\"agent_%s\" % x][attribute_plot][real_time] == 1: ## Infected\n",
" value_infectados += 1\n",
" activity = True\n",
" if models.networkStatus[\"agent_%s\" % x][attribute_plot][real_time] == 0: ## Neutral\n",
" value_neutral += 1\n",
" activity = True\n",
" if models.networkStatus[\"agent_%s\" % x][attribute_plot][real_time] == 2: ## Cured\n",
" value_cured += 1\n",
" activity = True\n",
" if models.networkStatus[\"agent_%s\" % x][attribute_plot][real_time] == 3: ## Vaccinated\n",
" value_vaccinated += 1\n",
" activity = True\n",
"\n",
" if activity:\n",
" x_values.append(real_time)\n",
" infected_values.append(value_infected)\n",
" neutral_values.append(value_neutral)\n",
" cured_values.append(value_cured)\n",
" vaccinated_values.append(value_vaccinated)\n",
" activity=False\n",
" if activity:\n",
" x_values.append(real_time)\n",
" infected_values.append(value_infectados)\n",
" neutral_values.append(value_neutral)\n",
" cured_values.append(value_cured)\n",
" vaccinated_values.append(value_vaccinated)\n",
" activity = False\n",
"\n",
"infected_line = plt.plot(x_values,infected_values,label='Infected')\n",
"neutral_line = plt.plot(x_values,neutral_values, label='Neutral')\n",
"cured_line = plt.plot(x_values,cured_values, label='Cured')\n",
"vaccinated_line = plt.plot(x_values,vaccinated_values, label='Vaccinated')\n",
"plt.legend()\n",
"plt.savefig('control_model.png')"
" fig1 = plt.figure()\n",
" ax1 = fig1.add_subplot(111)\n",
"\n",
" infected_line = ax1.plot(x_values, infected_values, label='Infected')\n",
" neutral_line = ax1.plot(x_values, neutral_values, label='Neutral')\n",
" cured_line = ax1.plot(x_values, cured_values, label='Cured')\n",
" vaccinated_line = ax1.plot(x_values, vaccinated_values, label='Vaccinated')\n",
" ax1.legend()\n",
" fig1.savefig(model_name + '.png')\n",
" # plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"![alt text](https://raw.githubusercontent.com/gsi-upm/soil/master/control_model.png \"Control model\")"
"### Simulation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Models.py"
"The simulation starts with the following code. The user can provide the network topology, the maximum time of simulation, the spread model to be used as well as other parameters."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"agents = settings.environment_params['agent']\n",
"\n",
"print(\"Using Agent(s): {agents}\".format(agents=agents))\n",
"\n",
"if len(agents) > 1:\n",
" for agent in agents:\n",
" sim = NetworkSimulation(topology=G, states=init_states, agent_type=locals()[agent], max_time=settings.network_params[\"max_time\"],\n",
" num_trials=settings.network_params[\"num_trials\"], logging_interval=1.0, **settings.environment_params)\n",
" sim.run_simulation()\n",
" print(str(agent))\n",
" results(str(agent))\n",
" visualization(str(agent))\n",
"else:\n",
" agent = agents[0]\n",
" sim = NetworkSimulation(topology=G, states=init_states, agent_type=locals()[agent], max_time=settings.network_params[\"max_time\"],\n",
" num_trials=settings.network_params[\"num_trials\"], logging_interval=1.0, **settings.environment_params)\n",
" sim.run_simulation()\n",
" results(str(agent))\n",
" visualization(str(agent))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Models"
]
},
{
@ -331,31 +321,22 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from nxsim import BaseNetworkAgent\n",
"import numpy as np\n",
"import random\n",
"import settings\n",
"\n",
"settings.init()\n",
"networkStatus = {} # Dict that will contain the status of every agent in the network\n",
"\n",
"##############################\n",
"# Variables initializitation #\n",
"##############################\n",
"def init():\n",
" global networkStatus\n",
" networkStatus = {} # Dict that will contain the status of every agent in the network\n",
"\n",
"sentimentCorrelationNodeArray=[]\n",
"for x in range(0, settings.number_of_nodes):\n",
" sentimentCorrelationNodeArray.append({'id':x})\n",
"sentimentCorrelationNodeArray = []\n",
"for x in range(0, settings.network_params[\"number_of_nodes\"]):\n",
" sentimentCorrelationNodeArray.append({'id': x})\n",
"# Initialize agent states. Let's assume everyone is normal.\n",
"init_states = [{'id': 0, } for _ in range(settings.number_of_nodes)] # add keys as as necessary, but \"id\" must always refer to that state category"
"init_states = [{'id': 0, } for _ in range(settings.network_params[\"number_of_nodes\"])]\n",
" # add keys as as necessary, but \"id\" must always refer to that state category"
]
},
{
@ -374,13 +355,18 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": null,
"metadata": {
"collapsed": true
"collapsed": false
},
"outputs": [],
"source": [
"import settings\n",
"from nxsim import BaseNetworkAgent\n",
"\n",
"\n",
"class BaseBehaviour(BaseNetworkAgent):\n",
"\n",
" def __init__(self, environment=None, agent_id=0, state=()):\n",
" super().__init__(environment=environment, agent_id=agent_id, state=state)\n",
" self._attrs = {}\n",
@ -399,19 +385,19 @@
" def run(self):\n",
" while True:\n",
" self.step(self.env.now)\n",
" yield self.env.timeout(settings.timeout)\n",
" yield self.env.timeout(settings.network_params[\"timeout\"])\n",
"\n",
" def step(self, now):\n",
" networkStatus['agent_%s'% self.id] = self.a_json()\n",
" networkStatus['agent_%s'% self.id] = self.to_json()\n",
"\n",
" def a_json(self):\n",
" def to_json(self):\n",
" final = {}\n",
" for stamp, attrs in self._attrs.items():\n",
" for a in attrs:\n",
" if a not in final:\n",
" final[a] = {}\n",
" final[a] = {}\n",
" final[a][stamp] = attrs[a]\n",
" return final\n"
" return final"
]
},
{
@ -437,58 +423,71 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": null,
"metadata": {
"collapsed": true
"collapsed": false
},
"outputs": [],
"source": [
"import settings\n",
"import random\n",
"import numpy as np\n",
"\n",
"\n",
"class ControlModelM2(BaseBehaviour):\n",
" #Init infected\n",
" init_states[random.randint(0,settings.number_of_nodes-1)] = {'id':1}\n",
" init_states[random.randint(0,settings.number_of_nodes-1)] = {'id':1}\n",
"\n",
" # Init infected\n",
" init_states[random.randint(0, settings.network_params[\"number_of_nodes\"]-1)] = {'id': 1}\n",
" init_states[random.randint(0, settings.network_params[\"number_of_nodes\"]-1)] = {'id': 1}\n",
"\n",
" # Init beacons\n",
" init_states[random.randint(0, settings.number_of_nodes-1)] = {'id': 4}\n",
" init_states[random.randint(0, settings.number_of_nodes-1)] = {'id': 4}\n",
" init_states[random.randint(0, settings.network_params[\"number_of_nodes\"]-1)] = {'id': 4}\n",
" init_states[random.randint(0, settings.network_params[\"number_of_nodes\"]-1)] = {'id': 4}\n",
"\n",
" def __init__(self, environment=None, agent_id=0, state=()):\n",
" super().__init__(environment=environment, agent_id=agent_id, state=state)\n",
"\n",
" self.prob_neutral_making_denier = np.random.normal(settings.prob_neutral_making_denier, settings.standard_variance)\n",
" self.prob_neutral_making_denier = np.random.normal(environment.environment_params['prob_neutral_making_denier'],\n",
" environment.environment_params['standard_variance'])\n",
"\n",
" self.prob_infect = np.random.normal(settings.prob_infect, settings.standard_variance)\n",
" self.prob_infect = np.random.normal(environment.environment_params['prob_infect'],\n",
" environment.environment_params['standard_variance'])\n",
"\n",
" self.prob_cured_healing_infected = np.random.normal(settings.prob_cured_healing_infected, settings.standard_variance)\n",
" self.prob_cured_vaccinate_neutral = np.random.normal(settings.prob_cured_vaccinate_neutral, settings.standard_variance)\n",
" self.prob_cured_healing_infected = np.random.normal(environment.environment_params['prob_cured_healing_infected'],\n",
" environment.environment_params['standard_variance'])\n",
" self.prob_cured_vaccinate_neutral = np.random.normal(environment.environment_params['prob_cured_vaccinate_neutral'],\n",
" environment.environment_params['standard_variance'])\n",
"\n",
" self.prob_vaccinated_healing_infected = np.random.normal(settings.prob_vaccinated_healing_infected, settings.standard_variance)\n",
" self.prob_vaccinated_vaccinate_neutral = np.random.normal(settings.prob_vaccinated_vaccinate_neutral, settings.standard_variance)\n",
" self.prob_generate_anti_rumor = np.random.normal(settings.prob_generate_anti_rumor, settings.standard_variance)\n",
" self.prob_vaccinated_healing_infected = np.random.normal(environment.environment_params['prob_vaccinated_healing_infected'],\n",
" environment.environment_params['standard_variance'])\n",
" self.prob_vaccinated_vaccinate_neutral = np.random.normal(environment.environment_params['prob_vaccinated_vaccinate_neutral'],\n",
" environment.environment_params['standard_variance'])\n",
" self.prob_generate_anti_rumor = np.random.normal(environment.environment_params['prob_generate_anti_rumor'],\n",
" environment.environment_params['standard_variance'])\n",
"\n",
" def step(self, now):\n",
"\n",
" if self.state['id'] == 0: #Neutral\n",
" if self.state['id'] == 0: # Neutral\n",
" self.neutral_behaviour()\n",
" elif self.state['id'] == 1: #Infected\n",
" elif self.state['id'] == 1: # Infected\n",
" self.infected_behaviour()\n",
" elif self.state['id'] == 2: #Cured\n",
" elif self.state['id'] == 2: # Cured\n",
" self.cured_behaviour()\n",
" elif self.state['id'] == 3: #Vaccinated\n",
" elif self.state['id'] == 3: # Vaccinated\n",
" self.vaccinated_behaviour()\n",
" elif self.state['id'] == 4: #Beacon-off\n",
" elif self.state['id'] == 4: # Beacon-off\n",
" self.beacon_off_behaviour()\n",
" elif self.state['id'] == 5: #Beacon-on\n",
" elif self.state['id'] == 5: # Beacon-on\n",
" self.beacon_on_behaviour()\n",
"\n",
" self.attrs['status'] = self.state['id']\n",
" super().step(now)\n",
"\n",
"\n",
" def neutral_behaviour(self):\n",
"\n",
" # Infected\n",
" infected_neighbors = self.get_neighboring_agents(state_id=1)\n",
" if len(infected_neighbors)>0:\n",
" if len(infected_neighbors) > 0:\n",
" if random.random() < self.prob_neutral_making_denier:\n",
" self.state['id'] = 3 # Vaccinated making denier\n",
"\n",
@ -514,7 +513,6 @@
" if random.random() < self.prob_cured_healing_infected:\n",
" neighbor.state['id'] = 2 # Cured\n",
"\n",
"\n",
" def vaccinated_behaviour(self):\n",
"\n",
" # Cure\n",
@ -523,7 +521,6 @@
" if random.random() < self.prob_cured_healing_infected:\n",
" neighbor.state['id'] = 2 # Cured\n",
"\n",
"\n",
" # Vaccinate\n",
" neutral_neighbors = self.get_neighboring_agents(state_id=0)\n",
" for neighbor in neutral_neighbors:\n",
@ -539,7 +536,7 @@
" def beacon_off_behaviour(self):\n",
" infected_neighbors = self.get_neighboring_agents(state_id=1)\n",
" if len(infected_neighbors) > 0:\n",
" self.state['id'] == 5 #Beacon on\n",
" self.state['id'] == 5 # Beacon on\n",
"\n",
" def beacon_on_behaviour(self):\n",
"\n",
@ -557,7 +554,6 @@
" if random.random() < self.prob_generate_anti_rumor:\n",
" neighbor.state['id'] = 2 # Cured\n",
"\n",
"\n",
" # Vaccinate\n",
" neutral_neighbors = self.get_neighboring_agents(state_id=0)\n",
" for neighbor in neutral_neighbors:\n",
@ -569,7 +565,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Settings.py"
"## Settings.json"
]
},
{
@ -579,6 +575,78 @@
"This file contains all the variables that can be modified from the simulation. In case of implementing a new spread model, the new variables should be also included in this file."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"[\n",
" {\n",
" \"network_type\": 1,\n",
" \"number_of_nodes\": 1000,\n",
" \"max_time\": 50,\n",
" \"num_trials\": 1,\n",
" \"timeout\": 2\n",
" },\n",
"\n",
" {\n",
" \"agent\": [\"BaseBehaviour\",\"SISaModel\",\"ControlModelM2\"],\n",
"\n",
"\n",
" \"bite_prob\": 0.01,\n",
" \"heal_prob\": 0.01,\n",
"\n",
" \"innovation_prob\": 0.001,\n",
" \"imitation_prob\": 0.005,\n",
"\n",
" \"outside_effects_prob\": 0.2,\n",
" \"anger_prob\": 0.06,\n",
" \"joy_prob\": 0.05,\n",
" \"sadness_prob\": 0.02,\n",
" \"disgust_prob\": 0.02,\n",
"\n",
" \"enterprises\": [\"BBVA\", \"Santander\", \"Bankia\"],\n",
"\n",
" \"tweet_probability_users\": 0.44,\n",
" \"tweet_relevant_probability\": 0.25,\n",
" \"tweet_probability_about\": [0.15, 0.15, 0.15],\n",
" \"sentiment_about\": [0, 0, 0],\n",
"\n",
" \"tweet_probability_enterprises\": [0.3, 0.3, 0.3],\n",
"\n",
" \"neutral_discontent_spon_prob\": 0.04,\n",
" \"neutral_discontent_infected_prob\": 0.04,\n",
" \"neutral_content_spon_prob\": 0.18,\n",
" \"neutral_content_infected_prob\": 0.02,\n",
"\n",
" \"discontent_neutral\": 0.13,\n",
" \"discontent_content\": 0.07,\n",
" \"variance_d_c\": 0.02,\n",
"\n",
" \"content_discontent\": 0.009,\n",
" \"variance_c_d\": 0.003,\n",
" \"content_neutral\": 0.088,\n",
"\n",
" \"standard_variance\": 0.055,\n",
"\n",
"\n",
" \"prob_neutral_making_denier\": 0.035,\n",
"\n",
" \"prob_infect\": 0.075,\n",
"\n",
" \"prob_cured_healing_infected\": 0.035,\n",
" \"prob_cured_vaccinate_neutral\": 0.035,\n",
"\n",
" \"prob_vaccinated_healing_infected\": 0.035,\n",
" \"prob_vaccinated_vaccinate_neutral\": 0.035,\n",
" \"prob_generate_anti_rumor\": 0.035\n",
" }\n",
"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -837,7 +905,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2"
"version": "3.6.0"
}
},
"nbformat": 4,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB