Skip to content

Commit

Permalink
Release 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jofmi committed May 28, 2021
1 parent f6408aa commit 90a1a9a
Show file tree
Hide file tree
Showing 31 changed files with 67,363 additions and 66,465 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![PyPI](https://img.shields.io/pypi/v/agentpy)](https://pypi.org/project/agentpy/)
[![GitHub](https://img.shields.io/github/license/joelforamitti/agentpy)](https://github.com/JoelForamitti/agentpy/blob/master/LICENSE)
[![Build Status](https://travis-ci.com/JoelForamitti/agentpy.svg?branch=master)](https://travis-ci.com/JoelForamitti/agentpy)
[![Documentation Status](https://readthedocs.org/projects/agentpy/badge/?version=latest)](https://agentpy.readthedocs.io/en/latest/?badge=latest)
[![Documentation Status](https://readthedocs.org/projects/agentpy/badge/?version=stable)](https://agentpy.readthedocs.io/en/stable/?badge=stable)
[![codecov](https://codecov.io/gh/JoelForamitti/agentpy/branch/master/graph/badge.svg?token=NTW99HNGB0)](https://codecov.io/gh/JoelForamitti/agentpy)

AgentPy is an open-source library for the development and analysis of agent-based models in Python.
Expand Down
93 changes: 82 additions & 11 deletions agentpy/examples.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# Model design
import agentpy as ap
import numpy as np

# Visualization
import seaborn as sns


def gini(x):

Expand Down Expand Up @@ -37,16 +33,16 @@ def wealth_transfer(self):
class WealthModel(ap.Model):

"""
A simple model of random wealth transfers.
Demonstration model of random wealth transfers.
Parameters:
agents (int): Number of agents.
See Also:
Notebook in the model library: :doc:`agentpy_wealth_transfer`
Recorded variables:
gini: Gini coefficient during each time-step.
Arguments:
parameters (dict):
Reporters:
gini: Gini coefficient at the end of the simulation.
- agents (int): Number of agents.
- steps (int, optional): Number of time-steps.
"""

def setup(self):
Expand All @@ -62,3 +58,78 @@ def update(self):
def end(self):
self.report('gini')


class SegregationAgent(ap.Agent):

def setup(self):
""" Initiate agent attributes. """
self.grid = self.model.grid
self.random = self.model.random
self.group = self.random.choice(range(self.p.n_groups))
self.share_similar = 0
self.happy = False

def update_happiness(self):
""" Be happy if rate of similar neighbors is high enough. """
neighbors = self.grid.neighbors(self)
similar = len([n for n in neighbors if n.group == self.group])
ln = len(neighbors)
self.share_similar = similar / ln if ln > 0 else 0
self.happy = self.share_similar >= self.p.want_similar

def find_new_home(self):
""" Move to random free spot and update free spots. """
new_spot = self.random.choice(self.model.grid.empty)
self.grid.move_to(self, new_spot)


class SegregationModel(ap.Model):
"""
Demonstration model of segregation dynamics.
See Also:
Notebook in the model library: :doc:`agentpy_segregation`
Arguments:
parameters (dict):
- want_similar (float):
Percentage of similar neighbors
for agents to be happy
- n_groups (int): Number of groups
- density (float): Density of population
- size (int): Height and length of the grid
- steps (int, optional): Maximum number of steps
"""

def setup(self):

# Parameters
s = self.p.size
n = self.n = int(self.p.density * (s ** 2))

# Create grid and agents
self.grid = ap.Grid(self, (s, s), track_empty=True)
self.agents = ap.AgentList(self, n, SegregationAgent)
self.grid.add_agents(self.agents, random=True, empty=True)

def update(self):
# Update list of unhappy people
self.agents.update_happiness()
self.unhappy = self.agents.select(self.agents.happy == False)

# Stop simulation if all are happy
if len(self.unhappy) == 0:
self.stop()

def step(self):
# Move unhappy people to new location
self.unhappy.find_new_home()

def get_segregation(self):
# Calculate average percentage of similar neighbors
return round(sum(self.agents.share_similar) / self.n, 2)

def end(self):
# Measure segregation at the end of the simulation
self.report('segregation', self.get_segregation())
99 changes: 18 additions & 81 deletions agentpy/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
"""

import pandas as pd
import ipywidgets
import IPython
import random as rd

from os import sys
Expand All @@ -14,8 +12,7 @@
from datetime import datetime, timedelta
from .tools import make_list
from .datadict import DataDict
from .sample import Sample

from .sample import Sample, Range, IntRange, Values

class Experiment:
""" Experiment that can run an agent-based model
Expand All @@ -36,8 +33,8 @@ class Experiment:
If True, the parameter 'seed' will be used to initialize a random
seed generator for every parameter combination in the sample.
If False, the same seed will be used for every iteration.
If no parameter 'seed' is defined, all seeds will be random.
See :doc:`guide_random` for more information.
If no parameter 'seed' is defined, this option has no effect.
For more information, see :doc:`guide_random` .
**kwargs:
Will be forwarded to all model instances created by the experiment.
Expand All @@ -64,17 +61,18 @@ def __init__(self, model_class, sample=None, iterations=1,
self._sample_log = None

# Prepare runs
combos = len(self.sample)
len_sample = len(self.sample)
iter_range = range(iterations) if iterations > 1 else [None]
sample_range = range(combos) if combos > 1 else [None]
sample_range = range(len_sample) if len_sample > 1 else [None]
self.run_ids = [(sample_id, iteration)
for sample_id in sample_range
for iteration in iter_range]
self.n_runs = len(self.run_ids)

# Prepare seeds
if randomize:
if combos > 1:
if randomize and sample is not None \
and any(['seed' in p for p in self.sample]):
if len_sample > 1:
rngs = [rd.Random(p['seed'])
if 'seed' in p else rd.Random() for p in self.sample]
self._random = {
Expand All @@ -84,10 +82,11 @@ def __init__(self, model_class, sample=None, iterations=1,
}
else:
p = list(self.sample)[0]
if p is not None and 'seed' in p:
rng = rd.Random(p['seed'])
else:
rng = rd.Random()
seed = p['seed']
ranges = (Range, IntRange, Values)
if isinstance(seed, ranges):
seed = seed.vdef
rng = rd.Random(seed)
self._random = {
(None, iteration): rng.getrandbits(128)
for iteration in iter_range
Expand Down Expand Up @@ -248,6 +247,7 @@ def run(self, pool=None, display=True):
single_output, combined_output)

self._combine_dataframes(combined_output)
self.end()
self.output.info['completed'] = True
self.output.info['run_time'] = ct = str(datetime.now() - t0)

Expand All @@ -256,70 +256,7 @@ def run(self, pool=None, display=True):

return self.output

# TODO Depreciate
def interactive(self, plot, *args, **kwargs):
"""
Displays interactive output for Jupyter notebooks,
using :mod:`IPython` and :mod:`ipywidgets`.
A slider will be shown for varied parameters.
Every time a parameter value is changed on the slider,
the experiment will re-run the model and pass it
to the 'plot' function.
Arguments:
plot: Function that takes a model instance as input
and prints or plots the desired output..
*args: Will be forwarded to 'plot'.
**kwargs: Will be forwarded to 'plot'.
Returns:
ipywidgets.HBox: Interactive output widget
Examples:
The following example uses a custom model :class:`MyModel`
and creates a slider for the parameters 'x' and 'y',
both of which can be varied interactively over 10 different values.
Every time a value is changed, the experiment will simulate the
model with the new parameters and pass it to the plot function::
def plot(model):
# Display interactive output here
print(model.output)
param_ranges = {'x': (0, 10), 'y': (0., 1.)}
sample = ap.sample(param_ranges, n=10)
exp = ap.Experiment(MyModel, sample)
exp.interactive(plot)
"""

def var_run(**param_updates):
""" Display plot for updated parameters. """
IPython.display.clear_output()
parameters = dict(self.sample[0])
parameters.update(param_updates)
temp_model = self.model(parameters, **self._model_kwargs)
temp_model.run()
IPython.display.clear_output()
plot(temp_model, *args, **kwargs)

# Get variable parameters
var_pars = self.output._combine_pars(sample=True, constants=False)

# Create widget dict
widget_dict = {}
for par_key in list(var_pars):
par_list = list(var_pars[par_key])

widget_dict[par_key] = ipywidgets.SelectionSlider(
options=par_list,
value=par_list[0],
description=par_key,
continuous_update=False,
style=dict(description_width='initial'),
layout={'width': '300px'}
)

widgets_left = ipywidgets.VBox(list(widget_dict.values()))
output_right = ipywidgets.interactive_output(var_run, widget_dict)

return ipywidgets.HBox([widgets_left, output_right])
def end(self):
""" Defines the experiment's actions after the last simulation.
Can be overwritten for final calculations and reporting."""
pass
10 changes: 9 additions & 1 deletion agentpy/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ class Model(Object):
Template of an agent-based model.
Arguments:
parameters (dict, optional): Dictionary of the model's parameters.
parameters (dict, optional):
Dictionary of the model's parameters.
Default values will be selected from entries of type
:class:`Range`, :class:`IntRange`, and :class:`Values`.
The following parameters will be used automatically:
- steps: Defines the maximum number of time-steps.
- seed: Used to initiate the model's random number generators.
**kwargs: Will be forwarded to :func:`Model.setup`.
Attributes:
Expand Down
19 changes: 6 additions & 13 deletions docs/agentpy_button_network.ipynb

Large diffs are not rendered by default.

Loading

0 comments on commit 90a1a9a

Please sign in to comment.