diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..623062f8eae2fc82e9c013daf6e389e7492f5725 --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +*.pyc +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/Copy_of_social_network_abm.ipynb b/Copy_of_social_network_abm.ipynb index dbff98eba4a9377fca71c777c0a0d3bca5abcd1b..0afa2d8f39d92872f73494e1deb3865d3c62b585 100644 --- a/Copy_of_social_network_abm.ipynb +++ b/Copy_of_social_network_abm.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -29,19 +29,14 @@ "id": "NELTz5Ax_wh-", "outputId": "0180289a-ccae-4bf2-c783-41bf71291b80" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Mesa version: 3.0.3\n" - ] - } - ], + "outputs": [], "source": [ "import mesa\n", + "print(f\"Mesa version: {mesa.__version__}\")\n", + "\n", "\n", - "print(f\"Mesa version: {mesa.__version__}\")" + "import random\n", + "random.random()" ] }, { @@ -53,7 +48,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "id": "JkBtDB_tdrIO" }, @@ -64,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -107,25 +102,7 @@ "id": "60nsPHQ8_wh_", "outputId": "e5083585-0810-4c08-9d3d-5755dfb71718" }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "1144bcabdd8b4bd6aa2d4ca9a538febe", - "version_major": 2, - "version_minor": 0 - }, - "text/html": [ - "Cannot show widget. You probably want to rerun the code cell above (<i>Click in the code cell, and press Shift+Enter <kbd>⇧</kbd>+<kbd>↩</kbd></i>)." - ], - "text/plain": [ - "Cannot show ipywidgets in text" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from models.grid import GridModel\n", "\n", @@ -201,27 +178,9 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "97d3fb4ea5e04e38832820fe16d90ac5", - "version_major": 2, - "version_minor": 0 - }, - "text/html": [ - "Cannot show widget. You probably want to rerun the code cell above (<i>Click in the code cell, and press Shift+Enter <kbd>⇧</kbd>+<kbd>↩</kbd></i>)." - ], - "text/plain": [ - "Cannot show ipywidgets in text" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from mesa.visualization import SolaraViz, make_space_component\n", "from models.network import NetworkModel\n", @@ -229,10 +188,17 @@ "def agent_portrayal(agent):\n", " return {\n", " \"color\": agent.model.narrative_info[agent.opinion]['color'],\n", - " \"marker\": \"s\",\n", - " \"size\": 5,\n", + " \"size\": 12,\n", " }\n", "\n", + "algorithm_values = {\n", + " \"type\": \"SliderFloat\",\n", + " 'initial': 0,\n", + " 'min': 0,\n", + " 'max': 0.1,\n", + " 'step': 0.001\n", + "}\n", + "\n", "model_params = {\n", " \"initial_prob_a\":{\n", " \"type\": \"SliderFloat\",\n", @@ -252,11 +218,11 @@ " },\n", " \"n\": {\n", " \"type\": \"SliderInt\",\n", - " \"value\": 900,\n", + " \"value\": 100,\n", " \"label\": \"n\",\n", - " \"min\": 10,\n", - " \"max\": 5000,\n", - " \"step\": 100,\n", + " \"min\": 1,\n", + " \"max\": 1000,\n", + " \"step\": 10,\n", " },\n", " \"k\": {\n", " \"type\": \"SliderInt\",\n", @@ -266,17 +232,29 @@ " \"max\": 50,\n", " \"step\": 1,\n", " },\n", - " \"p\": {\n", - " \"type\": \"SliderInt\",\n", - " \"value\": 3,\n", - " \"label\": \"p\",\n", - " \"min\": 1,\n", - " \"max\": 10,\n", - " \"step\": 1,\n", + " \"same_connect_prob\": {\n", + " \"label\": \"same_connect_prob\",\n", + " \"type\": \"SliderFloat\",\n", + " **algorithm_values\n", + " },\n", + " \"opp_connect_prob\": {\n", + " \"label\": \"opp_connect_prob\",\n", + " \"type\": \"SliderFloat\",\n", + " **algorithm_values\n", + " },\n", + " \"same_disconnect_prob\": {\n", + " \"label\": \"same_disconnect_prob\",\n", + " \"type\": \"SliderFloat\",\n", + " **algorithm_values\n", + " },\n", + " \"opp_disconnect_prob\": {\n", + " \"label\": \"opp_disconnect_prob\",\n", + " \"type\": \"SliderFloat\",\n", + " **algorithm_values\n", " },\n", "}\n", "\n", - "model = NetworkModel()\n", + "model = NetworkModel(n=300)\n", "SpaceGraph = make_space_component(agent_portrayal)\n", "\n", "page = SolaraViz(\n", @@ -288,133 +266,6 @@ "\n", "page" ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "# import math\n", - "\n", - "# import solara\n", - "\n", - "# from mesa.examples.basic.virus_on_network.model import (\n", - "# State,\n", - "# VirusOnNetwork,\n", - "# number_infected,\n", - "# )\n", - "# from mesa.visualization import (\n", - "# Slider,\n", - "# SolaraViz,\n", - "# make_plot_component,\n", - "# make_space_component,\n", - "# )\n", - "\n", - "\n", - "# def agent_portrayal(agent):\n", - "# node_color_dict = {\n", - "# State.INFECTED: \"tab:red\",\n", - "# State.SUSCEPTIBLE: \"tab:green\",\n", - "# State.RESISTANT: \"tab:gray\",\n", - "# }\n", - "# return {\"color\": node_color_dict[agent.state], \"size\": 10}\n", - "\n", - "\n", - "# def get_resistant_susceptible_ratio(model):\n", - "# ratio = model.resistant_susceptible_ratio()\n", - "# ratio_text = r\"$\\infty$\" if ratio is math.inf else f\"{ratio:.2f}\"\n", - "# infected_text = str(number_infected(model))\n", - "\n", - "# return solara.Markdown(\n", - "# f\"Resistant/Susceptible Ratio: {ratio_text}<br>Infected Remaining: {infected_text}\"\n", - "# )\n", - "\n", - "\n", - "# model_params = {\n", - "# \"seed\": {\n", - "# \"type\": \"InputText\",\n", - "# \"value\": 42,\n", - "# \"label\": \"Random Seed\",\n", - "# },\n", - "# \"num_nodes\": Slider(\n", - "# label=\"Number of agents\",\n", - "# value=10,\n", - "# min=10,\n", - "# max=100,\n", - "# step=1,\n", - "# ),\n", - "# \"avg_node_degree\": Slider(\n", - "# label=\"Avg Node Degree\",\n", - "# value=3,\n", - "# min=3,\n", - "# max=8,\n", - "# step=1,\n", - "# ),\n", - "# \"initial_outbreak_size\": Slider(\n", - "# label=\"Initial Outbreak Size\",\n", - "# value=1,\n", - "# min=1,\n", - "# max=10,\n", - "# step=1,\n", - "# ),\n", - "# \"virus_spread_chance\": Slider(\n", - "# label=\"Virus Spread Chance\",\n", - "# value=0.4,\n", - "# min=0.0,\n", - "# max=1.0,\n", - "# step=0.1,\n", - "# ),\n", - "# \"virus_check_frequency\": Slider(\n", - "# label=\"Virus Check Frequency\",\n", - "# value=0.4,\n", - "# min=0.0,\n", - "# max=1.0,\n", - "# step=0.1,\n", - "# ),\n", - "# \"recovery_chance\": Slider(\n", - "# label=\"Recovery Chance\",\n", - "# value=0.3,\n", - "# min=0.0,\n", - "# max=1.0,\n", - "# step=0.1,\n", - "# ),\n", - "# \"gain_resistance_chance\": Slider(\n", - "# label=\"Gain Resistance Chance\",\n", - "# value=0.5,\n", - "# min=0.0,\n", - "# max=1.0,\n", - "# step=0.1,\n", - "# ),\n", - "# }\n", - "\n", - "\n", - "# def post_process_lineplot(ax):\n", - "# ax.set_ylim(ymin=0)\n", - "# ax.set_ylabel(\"# people\")\n", - "# ax.legend(bbox_to_anchor=(1.05, 1.0), loc=\"upper left\")\n", - "\n", - "\n", - "# SpacePlot = make_space_component(agent_portrayal)\n", - "# StatePlot = make_plot_component(\n", - "# {\"Infected\": \"tab:red\", \"Susceptible\": \"tab:green\", \"Resistant\": \"tab:gray\"},\n", - "# post_process=post_process_lineplot,\n", - "# )\n", - "\n", - "# model1 = VirusOnNetwork()\n", - "\n", - "# page = SolaraViz(\n", - "# model1,\n", - "# components=[\n", - "# SpacePlot,\n", - "# StatePlot,\n", - "# get_resistant_susceptible_ratio,\n", - "# ],\n", - "# model_params=model_params,\n", - "# name=\"Virus Model\",\n", - "# )\n", - "# page # noqa" - ] } ], "metadata": { @@ -422,7 +273,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "base", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -436,7 +287,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.4" + "version": "3.11.1" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/models/__pycache__/__init__.cpython-311.pyc b/models/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 7e1cd863e65a84ef4e129f7cdd8806f4f60ec039..0000000000000000000000000000000000000000 Binary files a/models/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/models/__pycache__/base.cpython-311.pyc b/models/__pycache__/base.cpython-311.pyc deleted file mode 100644 index 3e641e3f4cdbc8ec6fb0961b288948ef045b5492..0000000000000000000000000000000000000000 Binary files a/models/__pycache__/base.cpython-311.pyc and /dev/null differ diff --git a/models/__pycache__/grid.cpython-311.pyc b/models/__pycache__/grid.cpython-311.pyc deleted file mode 100644 index 4e7c8f8286b5ecff89829f272f87ca4c5ec3215b..0000000000000000000000000000000000000000 Binary files a/models/__pycache__/grid.cpython-311.pyc and /dev/null differ diff --git a/models/__pycache__/network.cpython-311.pyc b/models/__pycache__/network.cpython-311.pyc deleted file mode 100644 index b8f0fbe20585e75cf6c2f41dee60ccc6556d51da..0000000000000000000000000000000000000000 Binary files a/models/__pycache__/network.cpython-311.pyc and /dev/null differ diff --git a/models/base.py b/models/base.py index a16b76ba63c46cce118008f2e35125a50c18665e..661b9f7926c0005254833e9efe649d727124c018 100644 --- a/models/base.py +++ b/models/base.py @@ -7,7 +7,7 @@ class Narrative(Enum): A = 0 B = 1 C = 2 - + class BaseUser(mesa.Agent): """ A node in the social network. """ diff --git a/models/network.py b/models/network.py index 17d84735aea3380618438a5946d06f4f32d5bd90..65fa5146dd1993aa4ad5189822cd666a548510ca 100644 --- a/models/network.py +++ b/models/network.py @@ -1,4 +1,5 @@ -from models.base import BaseUser, BaseModel +import random +from models.base import BaseUser, BaseModel, Narrative import mesa import numpy as np import networkx as nx @@ -13,7 +14,7 @@ class NetworkUser(BaseUser): def _get_neighbors(self): return self.model.space.get_neighbors(self.unique_id) - + class NetworkModel(BaseModel): """ A model of a social network. """ @@ -23,7 +24,12 @@ class NetworkModel(BaseModel): initial_prob_b = 0.4, n = 900, k = 8, - p = 3 + p = 3, + range = 'inf', + same_connect_prob = 0, + opp_connect_prob = 0, + same_disconnect_prob = 0, + opp_disconnect_prob = 0, ): """ Initialize the model. """ payoff_matrix = np.array([[1, 0, 1], [0, 2, -4], [0, 0, 0]]) #from paper to calculate fitness @@ -32,6 +38,11 @@ class NetworkModel(BaseModel): space = mesa.space.NetworkGrid(small_world) super().__init__(space, payoff_matrix, beta, initial_prob_a, initial_prob_b) + + self.same_connect_prob = same_connect_prob + self.opp_connect_prob = opp_connect_prob + self.same_disconnect_prob = same_disconnect_prob + self.opp_disconnect_prob = opp_disconnect_prob def _init_agents(self): @@ -40,11 +51,49 @@ class NetworkModel(BaseModel): self.schedule.add(new_agent) self.space.place_agent(new_agent, node) - for agent in self.space.get_all_cell_contents(): + for agent in self.agents: agent.calculate_fitness() def step(self): """ Run one step of the model. """ - # do we want to explore with more activation strategies? - self.schedule.step() # this updates all the nodes, in random order \ No newline at end of file + super().step() + self._reshuffle_network() + + def _reshuffle_network(self): + agents_a = set(self.agents.select(lambda a: a.opinion == Narrative.A)) + agents_b = set(self.agents.select(lambda a: a.opinion == Narrative.B)) + + for agent in self.agents: + neighbors = set(agent.neighbors) + same_group = agents_a if agent.opinion == Narrative.A else agents_b + opp_group = agents_b if agent.opinion == Narrative.A else agents_a + + # Connect similar + if random.random() < self.same_connect_prob: + candidates = list(same_group - neighbors) + if len(candidates): + same_partner = random.choice(candidates) + self.space.G.add_edge(agent.unique_id, same_partner.unique_id) + + # Connect opposite + if random.random() < self.opp_connect_prob: + candidates = list(opp_group - neighbors) + if len(candidates): + opp_partner = random.choice(candidates) + self.space.G.add_edge(agent.unique_id, opp_partner.unique_id) + + # Disconnect similar + if random.random() < self.same_disconnect_prob: + candidates = list(same_group.intersection(neighbors)) + if len(candidates): + same_partner = random.choice(candidates) + self.space.G.remove_edge(agent.unique_id, same_partner.unique_id) + + # Disconnect opposite + if random.random() < self.opp_disconnect_prob: + candidates = list(opp_group.intersection(neighbors)) + if len(candidates): + opp_partner = random.choice(candidates) + self.space.G.remove_edge(agent.unique_id, opp_partner.unique_id) + \ No newline at end of file