Compare commits
No commits in common. "eca4cae2987916450377454d0e4bb7851e27cd7e" and "4e296e0cf193b80e662db526fdc17321fb3f604c" have entirely different histories.
eca4cae298
...
4e296e0cf1
@ -4,7 +4,7 @@
|
|||||||
Soil is an extensible and user-friendly Agent-based Social Simulator for Social Networks.
|
Soil is an extensible and user-friendly Agent-based Social Simulator for Social Networks.
|
||||||
Learn how to run your own simulations with our [documentation](http://soilsim.readthedocs.io).
|
Learn how to run your own simulations with our [documentation](http://soilsim.readthedocs.io).
|
||||||
|
|
||||||
Follow our [tutorial](docs/tutorial/soil_tutorial.ipynb) to develop your own agent models.
|
Follow our [tutorial](examples/tutorial/soil_tutorial.ipynb) to develop your own agent models.
|
||||||
|
|
||||||
> **Warning**
|
> **Warning**
|
||||||
> Soil 1.0 introduced many fundamental changes. Check the [documention on how to update your simulations to work with newer versions](docs/notes_v1.0.rst)
|
> Soil 1.0 introduced many fundamental changes. Check the [documention on how to update your simulations to work with newer versions](docs/notes_v1.0.rst)
|
||||||
|
10
docs/conf.py
@ -31,10 +31,7 @@
|
|||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = ['IPython.sphinxext.ipython_console_highlighting']
|
||||||
"IPython.sphinxext.ipython_console_highlighting",
|
|
||||||
"nbsphinx",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
@ -67,7 +64,7 @@ release = '0.1'
|
|||||||
#
|
#
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
# Usually you set "language" from the command line for these cases.
|
# Usually you set "language" from the command line for these cases.
|
||||||
language = "en"
|
language = None
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
@ -155,3 +152,6 @@ texinfo_documents = [
|
|||||||
author, 'Soil', 'One line description of project.',
|
author, 'Soil', 'One line description of project.',
|
||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,20 +2,19 @@ Welcome to Soil's documentation!
|
|||||||
================================
|
================================
|
||||||
|
|
||||||
Soil is an opinionated Agent-based Social Simulator in Python focused on Social Networks.
|
Soil is an opinionated Agent-based Social Simulator in Python focused on Social Networks.
|
||||||
To get started developing your own simulations and agent behaviors, check out our :doc:`Tutorial <soil_tutorial>` and the `examples on GitHub <https://github.com/gsi-upm/soil/tree/master/examples>`.
|
|
||||||
|
|
||||||
Soil can be installed through pip (see more details in the :doc:`installation` page):.
|
|
||||||
|
|
||||||
.. image:: soil.png
|
.. image:: soil.png
|
||||||
:width: 80%
|
:width: 80%
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
|
Soil can be installed through pip (see more details in the :doc:`installation` page):
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
pip install soil
|
pip install soil
|
||||||
|
|
||||||
|
|
||||||
|
To get started developing your own simulations and agent behaviors, check out our :doc:`Tutorial <soil_tutorial>` and the `examples on GitHub <https://github.com/gsi-upm/soil/tree/master/examples>.
|
||||||
|
|
||||||
If you use Soil in your research, do not forget to cite this paper:
|
If you use Soil in your research, do not forget to cite this paper:
|
||||||
|
|
||||||
@ -47,8 +46,7 @@ If you use Soil in your research, do not forget to cite this paper:
|
|||||||
:caption: Learn more about soil:
|
:caption: Learn more about soil:
|
||||||
|
|
||||||
installation
|
installation
|
||||||
Tutorial <tutorial/soil_tutorial>
|
Tutorial <soil_tutorial>
|
||||||
notes_v1.0
|
|
||||||
|
|
||||||
..
|
..
|
||||||
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
Upgrading to Soil 1.0
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
What are the main changes in version 1.0?
|
What are the main changes in version 1.0?
|
||||||
#########################################
|
#########################################
|
||||||
|
|
||||||
|
BIN
docs/output_30_0.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
docs/output_34_0.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/output_49_0.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
docs/output_50_0.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
docs/output_58_0.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
docs/output_58_1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
docs/output_58_2.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
docs/output_58_3.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
docs/output_58_4.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
docs/output_60_0.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/output_60_1.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
docs/output_60_2.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
docs/output_60_3.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
docs/output_60_4.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
docs/output_62_0.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/output_62_1.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/output_62_2.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/output_62_3.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/output_62_4.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/output_68_1.png
Normal file
After Width: | Height: | Size: 952 KiB |
BIN
docs/output_70_1.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
docs/output_77_0.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
docs/output_81_0.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
docs/output_82_1.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
docs/output_83_0.png
Normal file
After Width: | Height: | Size: 53 KiB |
@ -1,2 +1 @@
|
|||||||
ipython>=7.31.1
|
ipython>=7.31.1
|
||||||
nbsphinx==0.9.1
|
|
||||||
|
1405
docs/soil_tutorial.rst
Normal file
4
setup.py
@ -17,9 +17,9 @@ def parse_requirements(filename):
|
|||||||
install_reqs = parse_requirements("requirements.txt")
|
install_reqs = parse_requirements("requirements.txt")
|
||||||
test_reqs = parse_requirements("test-requirements.txt")
|
test_reqs = parse_requirements("test-requirements.txt")
|
||||||
extras_require={
|
extras_require={
|
||||||
|
'mesa': ['mesa>=0.8.9'],
|
||||||
'geo': ['scipy>=1.3'],
|
'geo': ['scipy>=1.3'],
|
||||||
'web': ['tornado'],
|
'web': ['tornado']
|
||||||
'ipython': ['ipython==8.12', 'nbformat==5.8'],
|
|
||||||
}
|
}
|
||||||
extras_require['all'] = [dep for package in extras_require.values() for dep in package]
|
extras_require['all'] = [dep for package in extras_require.values() for dep in package]
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
def plot(env, agent_df=None, model_df=None, steps=False, ignore=["agent_count", ]):
|
def plot(env, agent_df=None, model_df=None, steps=False, ignore=["agent_count", ]):
|
||||||
"""Plot the model dataframe and agent dataframe together."""
|
"""Plot the model dataframe and agent dataframe together."""
|
||||||
|
if agent_df is None:
|
||||||
|
agent_df = env.agent_df()
|
||||||
if model_df is None:
|
if model_df is None:
|
||||||
model_df = env.model_df()
|
model_df = env.model_df()
|
||||||
ignore = list(ignore)
|
ignore = list(ignore)
|
||||||
@ -15,17 +16,9 @@ def plot(env, agent_df=None, model_df=None, steps=False, ignore=["agent_count",
|
|||||||
ignore.append("time")
|
ignore.append("time")
|
||||||
|
|
||||||
ax = model_df.drop(ignore, axis='columns').plot();
|
ax = model_df.drop(ignore, axis='columns').plot();
|
||||||
if agent_df is None:
|
|
||||||
try:
|
|
||||||
agent_df = env.agent_df()
|
|
||||||
except UserWarning:
|
|
||||||
print("No agent dataframe provided and no agent reporters found. Skipping agent plot.", file=sys.stderr)
|
|
||||||
return
|
|
||||||
if not agent_df.empty:
|
if not agent_df.empty:
|
||||||
agent_df.unstack().apply(lambda x: x.value_counts(),
|
agent_df.unstack().apply(lambda x: x.value_counts(),
|
||||||
axis=1).fillna(0).plot(ax=ax,
|
axis=1).fillna(0).plot(ax=ax, secondary_y=True);
|
||||||
secondary_y=True)
|
|
||||||
|
|
||||||
|
|
||||||
Results = namedtuple("Results", ["config", "parameters", "env", "agents"])
|
Results = namedtuple("Results", ["config", "parameters", "env", "agents"])
|
||||||
#TODO implement reading from CSV and SQLITE
|
#TODO implement reading from CSV and SQLITE
|
||||||
|
@ -216,11 +216,10 @@ class BaseEnvironment(Model):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run(cls, *,
|
def run(cls, *,
|
||||||
name=None,
|
|
||||||
iterations=1,
|
iterations=1,
|
||||||
num_processes=1, **kwargs):
|
num_processes=1, **kwargs):
|
||||||
from .simulation import Simulation
|
from .simulation import Simulation
|
||||||
return Simulation(name=name or cls.__name__,
|
return Simulation(name=cls.__name__,
|
||||||
model=cls, iterations=iterations,
|
model=cls, iterations=iterations,
|
||||||
num_processes=num_processes, **kwargs).run()
|
num_processes=num_processes, **kwargs).run()
|
||||||
|
|
||||||
|
@ -8,8 +8,6 @@ import importlib.machinery, importlib.util
|
|||||||
from glob import glob
|
from glob import glob
|
||||||
from itertools import product, chain
|
from itertools import product, chain
|
||||||
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
|
||||||
@ -112,12 +110,12 @@ KNOWN_MODULES = {
|
|||||||
|
|
||||||
MODULE_FILES = {}
|
MODULE_FILES = {}
|
||||||
|
|
||||||
def _add_source_file(file):
|
def add_source_file(file):
|
||||||
"""Add a file to the list of known modules"""
|
"""Add a file to the list of known modules"""
|
||||||
file = os.path.abspath(file)
|
file = os.path.abspath(file)
|
||||||
if file in MODULE_FILES:
|
if file in MODULE_FILES:
|
||||||
logger.warning(f"File {file} already added as module {MODULE_FILES[file]}. Reloading")
|
logger.warning(f"File {file} already added as module {MODULE_FILES[file]}. Reloading")
|
||||||
_remove_source_file(file)
|
remove_source_file(file)
|
||||||
modname = f"imported_module_{len(MODULE_FILES)}"
|
modname = f"imported_module_{len(MODULE_FILES)}"
|
||||||
loader = importlib.machinery.SourceFileLoader(modname, file)
|
loader = importlib.machinery.SourceFileLoader(modname, file)
|
||||||
spec = importlib.util.spec_from_loader(loader.name, loader)
|
spec = importlib.util.spec_from_loader(loader.name, loader)
|
||||||
@ -126,7 +124,7 @@ def _add_source_file(file):
|
|||||||
MODULE_FILES[file] = modname
|
MODULE_FILES[file] = modname
|
||||||
KNOWN_MODULES[modname] = my_module
|
KNOWN_MODULES[modname] = my_module
|
||||||
|
|
||||||
def _remove_source_file(file):
|
def remove_source_file(file):
|
||||||
"""Remove a file from the list of known modules"""
|
"""Remove a file from the list of known modules"""
|
||||||
file = os.path.abspath(file)
|
file = os.path.abspath(file)
|
||||||
modname = None
|
modname = None
|
||||||
@ -136,18 +134,6 @@ def _remove_source_file(file):
|
|||||||
except KeyError as ex:
|
except KeyError as ex:
|
||||||
raise ValueError(f"File {file} had not been added as a module: {ex}")
|
raise ValueError(f"File {file} had not been added as a module: {ex}")
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def with_source(file=None):
|
|
||||||
"""Add a file to the list of known modules, and remove it afterwards"""
|
|
||||||
if file:
|
|
||||||
_add_source_file(file)
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
if file:
|
|
||||||
_remove_source_file(file)
|
|
||||||
|
|
||||||
def get_module(modname):
|
def get_module(modname):
|
||||||
"""Get a module from the list of known modules"""
|
"""Get a module from the list of known modules"""
|
||||||
if modname not in KNOWN_MODULES or KNOWN_MODULES[modname] is None:
|
if modname not in KNOWN_MODULES or KNOWN_MODULES[modname] is None:
|
||||||
|
@ -119,9 +119,12 @@ class Simulation:
|
|||||||
self.logger = logger.getChild(self.name)
|
self.logger = logger.getChild(self.name)
|
||||||
self.logger.setLevel(self.level)
|
self.logger.setLevel(self.level)
|
||||||
|
|
||||||
if self.source_file and (not os.path.isabs(self.source_file)):
|
if self.source_file:
|
||||||
self.source_file = os.path.abspath(os.path.join(self.dir_path, self.source_file))
|
source_file = self.source_file
|
||||||
with serialization.with_source(self.source_file):
|
if not os.path.isabs(source_file):
|
||||||
|
source_file = os.path.abspath(os.path.join(self.dir_path, source_file))
|
||||||
|
serialization.add_source_file(source_file)
|
||||||
|
self.source_file = source_file
|
||||||
|
|
||||||
if isinstance(self.model, str):
|
if isinstance(self.model, str):
|
||||||
self.model = serialization.deserialize(self.model)
|
self.model = serialization.deserialize(self.model)
|
||||||
@ -135,6 +138,8 @@ class Simulation:
|
|||||||
self.agent_reporters = deserialize_reporters(self.agent_reporters)
|
self.agent_reporters = deserialize_reporters(self.agent_reporters)
|
||||||
self.model_reporters = deserialize_reporters(self.model_reporters)
|
self.model_reporters = deserialize_reporters(self.model_reporters)
|
||||||
self.tables = deserialize_reporters(self.tables)
|
self.tables = deserialize_reporters(self.tables)
|
||||||
|
if self.source_file:
|
||||||
|
serialization.remove_source_file(self.source_file)
|
||||||
self.id = f"{self.name}_{current_time()}"
|
self.id = f"{self.name}_{current_time()}"
|
||||||
|
|
||||||
def run(self, **kwargs):
|
def run(self, **kwargs):
|
||||||
@ -212,7 +217,10 @@ class Simulation:
|
|||||||
):
|
):
|
||||||
"""Run the simulation and yield the resulting environments."""
|
"""Run the simulation and yield the resulting environments."""
|
||||||
|
|
||||||
with serialization.with_source(self.source_file):
|
try:
|
||||||
|
if self.source_file:
|
||||||
|
serialization.add_source_file(self.source_file)
|
||||||
|
|
||||||
with utils.timer(f"running for config {params}"):
|
with utils.timer(f"running for config {params}"):
|
||||||
if self.dry_run:
|
if self.dry_run:
|
||||||
def func(*args, **kwargs):
|
def func(*args, **kwargs):
|
||||||
@ -229,6 +237,9 @@ class Simulation:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
yield env
|
yield env
|
||||||
|
finally:
|
||||||
|
if self.source_file:
|
||||||
|
serialization.remove_source_file(self.source_file)
|
||||||
|
|
||||||
def _get_env(self, iteration_id, params):
|
def _get_env(self, iteration_id, params):
|
||||||
"""Create an environment for a iteration of the simulation"""
|
"""Create an environment for a iteration of the simulation"""
|
||||||
|
@ -2,6 +2,3 @@ pytest
|
|||||||
pytest-profiling
|
pytest-profiling
|
||||||
scipy>=1.3
|
scipy>=1.3
|
||||||
tornado
|
tornado
|
||||||
nbconvert==7.3.1
|
|
||||||
nbformat==5.8.0
|
|
||||||
jupyter==1.0.0
|
|
@ -1,18 +0,0 @@
|
|||||||
from unittest import TestCase
|
|
||||||
import os
|
|
||||||
import nbformat
|
|
||||||
from nbconvert.preprocessors import ExecutePreprocessor
|
|
||||||
|
|
||||||
ROOT = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
|
|
||||||
class TestNotebooks(TestCase):
|
|
||||||
def test_tutorial(self):
|
|
||||||
notebook = os.path.join(ROOT, "../docs/tutorial/soil_tutorial.ipynb")
|
|
||||||
with open(notebook) as f:
|
|
||||||
nb = nbformat.read(f, as_version=4)
|
|
||||||
ep = ExecutePreprocessor(timeout=60000)
|
|
||||||
try:
|
|
||||||
assert ep.preprocess(nb) is not None, f"Got empty notebook for {notebook}"
|
|
||||||
except Exception:
|
|
||||||
assert False, f"Failed executing {notebook}"
|
|
||||||
|
|