Skip to content

Commit

Permalink
Merge pull request #92 from BlockScience/staging
Browse files Browse the repository at this point in the history
Merge pred-prey demo
  • Loading branch information
markusbkoch authored Mar 15, 2020
2 parents 1888556 + e45e5d9 commit 1a6812b
Show file tree
Hide file tree
Showing 29 changed files with 1,123 additions and 0 deletions.
16 changes: 16 additions & 0 deletions demos/prey-predator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Prey Predator demo

cadCAD P&P modelling in an dynamical system and agent-based modelling approaches.

## File Structure

* lab_notebook.ipynb - The notebook for experimenting and visualizing
* run.py - Script for running all configurated experiments
* prey_predator_abm/ - Folder for the ABM simulation and model
* prey_predator_sd/ - Folder for the SD simulation and model
* {simulation}/config.py - Simulation configuration object
* {simulation}/sim_params.py - Simulation parameters
* {simulation}/model/partial_state_update_block.py - The structure of the logic behind the model
* {simulation}/model/state_variables.py - Model initial state
* {simulation}/model/sys_params.py - Model parameters
* {simulation}/model/parts/ - Model logic
511 changes: 511 additions & 0 deletions demos/prey-predator/lab_notebook.ipynb

Large diffs are not rendered by default.

Empty file.
27 changes: 27 additions & 0 deletions demos/prey-predator/prey_predator_abm/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Simulation configuration object.
"""


MONTE_CARLO_RUNS = 1 # N monte carlo runs

from cadCAD.configuration import append_configs
from cadCAD.configuration.utils import config_sim
from .model.state_variables import genesis_states
from .model.partial_state_update_block import partial_state_update_block
from .model.sys_params import sys_params as sys_params
from .sim_params import SIMULATION_TIME_STEPS


sim_config = config_sim (
{
'N': MONTE_CARLO_RUNS,
'T': range(SIMULATION_TIME_STEPS), # number of timesteps
'M': sys_params,
}
)
append_configs(
sim_configs=sim_config,
initial_state=genesis_states,
partial_state_update_blocks=partial_state_update_block
)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Model logic structure.
"""


from .parts.environment.food_regeneration import *
from .parts.agents.feeding import *
from .parts.agents.reproduction import *
from .parts.agents.movement import *
from .parts.agents.ageing import *
from .parts.agents.natural_death import *


partial_state_update_block = [
{
'policies': {
'grow_food': p_grow_food
},
'variables': {
'sites': s_update_food
}
},
{
'policies': {
'increase_agent_age': p_digest_and_olden
},
'variables': {
'agents': s_agent_food_age

}
},
{
'policies': {
'move_agent': p_move_agents
},
'variables': {
'agents': s_agent_location

}
},
{
'policies': {
'reproduce_agents': p_reproduce_agents

},
'variables': {
'agents': s_agent_create,

}
},
{
'policies': {
'feed_prey': p_feed_prey
},
'variables': {
'agents': s_agent_food,
'sites': s_site_food
}
},
{
'policies': {
'hunt_prey': p_hunt_prey
},
'variables': {
'agents': s_agent_food
}
},
{
'policies': {
'natural_death': p_natural_death
},
'variables': {
'agents': s_agent_remove
}
}
]
Empty file.
Empty file.
18 changes: 18 additions & 0 deletions demos/prey-predator/prey_predator_abm/model/parts/agents/ageing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
def p_digest_and_olden(params, substep, state_history, prev_state):
agents = prev_state['agents']
delta_food = {agent: -1 for agent in agents.keys()}
delta_age = {agent: +1 for agent in agents.keys()}
return {'agent_delta_food': delta_food,
'agent_delta_age': delta_age}


def s_agent_food_age(params, substep, state_history, prev_state, policy_input):
delta_food_by_agent = policy_input['agent_delta_food']
delta_age_by_agent = policy_input['agent_delta_age']
updated_agents = prev_state['agents'].copy()

for agent, delta_food in delta_food_by_agent.items():
updated_agents[agent]['food'] += delta_food
for agent, delta_age in delta_age_by_agent.items():
updated_agents[agent]['age'] += delta_age
return ('agents', updated_agents)
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from ..location import nearby_agents
import random

def p_feed_prey(params, substep, state_history, prev_state):
"""
Feeds the hungry prey with all food located on its site.
"""
agents = prev_state['agents']
sites = prev_state['sites']
preys = {k: v for k, v in agents.items() if v['type'] == 'prey'}
hungry_preys = {label: properties for label, properties in preys.items()
if properties['food'] < params['hunger_threshold']}

agent_delta_food = {}
site_delta_food = {}
for label, properties in hungry_preys.items():
location = properties['location']
available_food = sites[location]
agent_delta_food[label] = available_food
site_delta_food[location] = -available_food

return {'agent_delta_food': agent_delta_food,
'site_delta_food': site_delta_food}


def s_agent_food(params, substep, state_history, prev_state, policy_input):
updated_agents = prev_state['agents'].copy()
for label, delta_food in policy_input['agent_delta_food'].items():
updated_agents[label]['food'] += delta_food
return ('agents', updated_agents)


def s_site_food(params, substep, state_history, prev_state, policy_input):
updated_sites = prev_state['sites'].copy()
for label, delta_food in policy_input['site_delta_food'].items():
updated_sites[label] += delta_food
return ('sites', updated_sites)

def p_hunt_prey(params, substep, state_history, prev_state):
"""
Feeds the hungry predators with an random nearby prey.
"""
agents = prev_state['agents']
sites = prev_state['sites']
hungry_threshold = params['hunger_threshold']
preys = {k: v for k, v in agents.items()
if v['type'] == 'prey'}
predators = {k: v for k, v in agents.items()
if v['type'] == 'predator'}
hungry_predators = {k: v for k, v in predators.items()
if v['food'] < hungry_threshold}
agent_delta_food = {}
for predator_label, predator_properties in hungry_predators.items():
location = predator_properties['location']
nearby_preys = nearby_agents(location, preys)
if len(nearby_preys) > 0:
eaten_prey_label = random.choice(list(nearby_preys.keys()))
delta_food = preys.pop(eaten_prey_label)['food']
agent_delta_food[predator_label] = delta_food
agent_delta_food[eaten_prey_label] = -1 * delta_food
else:
continue

return {'agent_delta_food': agent_delta_food}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from ..location import get_free_location


def p_move_agents(params, substep, state_history, prev_state):
"""
Move agents.
"""
sites = prev_state['sites']
agents = prev_state['agents']
busy_locations = [agent['location'] for agent in agents.values()]
new_locations = {}
for label, properties in agents.items():
new_location = get_free_location(properties['location'], sites, busy_locations)
if new_location is not False:
new_locations[label] = new_location
busy_locations.append(new_location)
else:
continue
return {'update_agent_location': new_locations}


def s_agent_location(params, substep, state_history, prev_state, policy_input):
updated_agents = prev_state['agents'].copy()
for label, location in policy_input['update_agent_location'].items():
updated_agents[label]['location'] = location
return ('agents', updated_agents)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

def p_natural_death(params, substep, state_history, prev_state):
"""
Remove agents which are old or hungry enough.
"""
agents = prev_state['agents']
maximum_age = params['agent_lifespan']
agents_to_remove = []
for agent_label, agent_properties in agents.items():
to_remove = agent_properties['age'] > maximum_age
to_remove |= (agent_properties['food'] <= 0)
if to_remove:
agents_to_remove.append(agent_label)
return {'remove_agents': agents_to_remove}


def s_agent_remove(params, substep, state_history, prev_state, policy_input):
agents_to_remove = policy_input['remove_agents']
surviving_agents = {k: v for k, v in prev_state['agents'].items()
if k not in agents_to_remove}
return ('agents', surviving_agents)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import random
from uuid import uuid4
from ..location import nearby_agents, get_free_location

def p_reproduce_agents(params, substep, state_history, prev_state):
"""
Generates an new agent through an nearby agent pair, subject to rules.
Not done.
"""
agents = prev_state['agents']
sites = prev_state['sites']
food_threshold = params['reproduction_food_threshold']
reproduction_food = params['reproduction_food']
reproduction_probability = params['reproduction_probability']
busy_locations = [agent['location'] for agent in agents.values()]
already_reproduced = []
new_agents = {}
agent_delta_food = {}
for agent_type in set(agent['type'] for agent in agents.values()):
# Only reproduce agents of the same type
specific_agents = {label: agent for label, agent in agents.items()
if agent['type'] == agent_type}
for agent_label, agent_properties in specific_agents.items():
location = agent_properties['location']
if (agent_properties['food'] < food_threshold or agent_label in already_reproduced):
continue
kind_neighbors = nearby_agents(location, specific_agents)
available_partners = [label for label, agent in kind_neighbors.items()
if agent['food'] >= food_threshold
and label not in already_reproduced]

reproduction_location = get_free_location(location, sites, busy_locations)

if reproduction_location is not False and len(available_partners) > 0:
reproduction_partner_label = random.choice(available_partners)
reproduction_partner = agents[reproduction_partner_label]
already_reproduced += [agent_label, reproduction_partner_label]

agent_delta_food[agent_label] = -1.0 * reproduction_food
agent_delta_food[reproduction_partner_label] = -1.0 * reproduction_food
new_agent_properties = {'type': agent_type,
'location': reproduction_location,
'food': 2.0 * reproduction_food,
'age': 0}
new_agents[uuid4()] = new_agent_properties
busy_locations.append(reproduction_location)

return {'agent_delta_food': agent_delta_food,
'agent_create': new_agents}

def s_agent_create(params, substep, state_history, prev_state, policy_input):
updated_agents = prev_state['agents'].copy()
for label, food in policy_input['agent_delta_food'].items():
updated_agents[label]['food'] += food
for label, properties in policy_input['agent_create'].items():
updated_agents[label] = properties
return ('agents', updated_agents)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import numpy as np

@np.vectorize
def calculate_increment(value, growth_rate, max_value):
new_value = (value + growth_rate
if value + growth_rate < max_value
else max_value)
return new_value


def p_grow_food(params, substep, state_history, prev_state):
"""
Increases the food supply in all sites, subject to an maximum.
"""
regenerated_sites = calculate_increment(prev_state['sites'],
params['food_growth_rate'],
params['maximum_food_per_site'])
return {'update_food': regenerated_sites}


def s_update_food(params, substep, state_history, prev_state, policy_input):
key = 'sites'
value = policy_input['update_food']
return (key, value)
58 changes: 58 additions & 0 deletions demos/prey-predator/prey_predator_abm/model/parts/location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
Helper functions associated with location
"""


import numpy as np
import random
from typing import *


def check_location(position: tuple,
all_sites: np.matrix,
busy_locations: List[tuple]) -> List[tuple]:
"""
Returns an list of available location tuples neighboring an given
position location.
"""
N, M = all_sites.shape
potential_sites = [(position[0], position[1] + 1),
(position[0], position[1] - 1),
(position[0] + 1, position[1]),
(position[0] - 1, position[1])]
potential_sites = [(site[0] % N, site[1] % M) for site in potential_sites]
valid_sites = [site for site in potential_sites if site not in busy_locations]
return valid_sites


def get_free_location(position: tuple,
all_sites: np.matrix,
used_sites: List[tuple]) -> tuple:
"""
Gets an random free location neighboring an position. Returns False if
there aren't any location available.
"""
available_locations = check_location(position, all_sites, used_sites)
if len(available_locations) > 0:
return random.choice(available_locations)
else:
return False


def nearby_agents(location: tuple, agents: Dict[str, dict]) -> Dict[str, dict]:
"""
Filter the non-nearby agents.
"""
neighbors = {label: agent for label, agent in agents.items()
if is_neighbor(agent['location'], location)}
return neighbors


def is_neighbor(location_1: tuple, location_2: tuple) -> bool:
dx = np.abs(location_1[0] - location_2[0])
dy = (location_1[1] - location_2[0])
distance = dx + dy
if distance == 1:
return True
else:
return False
Loading

0 comments on commit 1a6812b

Please sign in to comment.