exporters
J. Fernando Sánchez 5 years ago
parent 65f6aa72f3
commit a3ea434f23

@ -3,9 +3,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.13.8]
### Changed
* Moved TerroristNetworkModel to examples
### Added
* `get_agents` and `count_agents` methods now accept lists as inputs. They can be used to retrieve agents from node ids
* `subgraph` in BaseAgent
* `agents.select` method, to filter out agents
* `skip_test` property in yaml definitions, to force skipping some examples
* `agents.Geo`, with a search function based on postition
* `BaseAgent.ego_search` to get nodes from the ego network of a node
* `BaseAgent.degree` and `BaseAgent.betweenness`
### Fixed
## [0.13.7]

@ -2,3 +2,6 @@ include requirements.txt
include test-requirements.txt
include README.rst
graft soil
global-exclude __pycache__
global-exclude soil_output
global-exclude *.py[co]

@ -6,7 +6,7 @@ environment_params:
prob_neighbor_spread: 0.0
prob_tv_spread: 0.01
interval: 1
max_time: 30
max_time: 300
name: Sim_all_dumb
network_agents:
- agent_type: DumbViewer
@ -30,7 +30,7 @@ environment_params:
prob_neighbor_spread: 0.0
prob_tv_spread: 0.01
interval: 1
max_time: 30
max_time: 300
name: Sim_half_herd
network_agents:
- agent_type: DumbViewer
@ -62,7 +62,7 @@ environment_params:
prob_neighbor_spread: 0.0
prob_tv_spread: 0.01
interval: 1
max_time: 30
max_time: 300
name: Sim_all_herd
network_agents:
- agent_type: HerdViewer
@ -89,7 +89,7 @@ environment_params:
prob_tv_spread: 0.01
prob_neighbor_cure: 0.1
interval: 1
max_time: 30
max_time: 300
name: Sim_wise_herd
network_agents:
- agent_type: HerdViewer
@ -115,7 +115,7 @@ environment_params:
prob_tv_spread: 0.01
prob_neighbor_cure: 0.1
interval: 1
max_time: 30
max_time: 300
name: Sim_all_wise
network_agents:
- agent_type: WiseViewer

@ -0,0 +1,208 @@
import random
import networkx as nx
from soil.agents import Geo, NetworkAgent, FSM, state, default_state
from soil import Environment
class TerroristSpreadModel(FSM, Geo):
"""
Settings:
information_spread_intensity
terrorist_additional_influence
min_vulnerability (optional else zero)
max_vulnerability
prob_interaction
"""
def __init__(self, environment=None, agent_id=0, state=()):
super().__init__(environment=environment, agent_id=agent_id, state=state)
self.information_spread_intensity = environment.environment_params['information_spread_intensity']
self.terrorist_additional_influence = environment.environment_params['terrorist_additional_influence']
self.prob_interaction = environment.environment_params['prob_interaction']
if self['id'] == self.civilian.id: # Civilian
self.mean_belief = random.uniform(0.00, 0.5)
elif self['id'] == self.terrorist.id: # Terrorist
self.mean_belief = random.uniform(0.8, 1.00)
elif self['id'] == self.leader.id: # Leader
self.mean_belief = 1.00
else:
raise Exception('Invalid state id: {}'.format(self['id']))
if 'min_vulnerability' in environment.environment_params:
self.vulnerability = random.uniform( environment.environment_params['min_vulnerability'], environment.environment_params['max_vulnerability'] )
else :
self.vulnerability = random.uniform( 0, environment.environment_params['max_vulnerability'] )
@state
def civilian(self):
neighbours = list(self.get_neighboring_agents(agent_type=TerroristSpreadModel))
if len(neighbours) > 0:
# Only interact with some of the neighbors
interactions = list(n for n in neighbours if random.random() <= self.prob_interaction)
influence = sum( self.degree(i) for i in interactions )
mean_belief = sum( i.mean_belief * self.degree(i) / influence for i in interactions )
mean_belief = mean_belief * self.information_spread_intensity + self.mean_belief * ( 1 - self.information_spread_intensity )
self.mean_belief = mean_belief * self.vulnerability + self.mean_belief * ( 1 - self.vulnerability )
if self.mean_belief >= 0.8:
return self.terrorist
@state
def leader(self):
self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence )
for neighbour in self.get_neighboring_agents(state_id=[self.terrorist.id, self.leader.id]):
if self.betweenness(neighbour) > self.betweenness(self):
return self.terrorist
@state
def terrorist(self):
neighbours = self.get_agents(state_id=[self.terrorist.id, self.leader.id],
agent_type=TerroristSpreadModel,
limit_neighbors=True)
if len(neighbours) > 0:
influence = sum( self.degree(n) for n in neighbours )
mean_belief = sum( n.mean_belief * self.degree(n) / influence for n in neighbours )
mean_belief = mean_belief * self.vulnerability + self.mean_belief * ( 1 - self.vulnerability )
self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence )
# Check if there are any leaders in the group
leaders = list(filter(lambda x: x.state.id == self.leader.id, neighbours))
if not leaders:
# Check if this is the potential leader
# Stop once it's found. Otherwise, set self as leader
for neighbour in neighbours:
if self.betweenness(self) < self.betweenness(neighbour):
return
return self.leader
class TrainingAreaModel(FSM, Geo):
"""
Settings:
training_influence
min_vulnerability
Requires TerroristSpreadModel.
"""
def __init__(self, environment=None, agent_id=0, state=()):
super().__init__(environment=environment, agent_id=agent_id, state=state)
self.training_influence = environment.environment_params['training_influence']
if 'min_vulnerability' in environment.environment_params:
self.min_vulnerability = environment.environment_params['min_vulnerability']
else: self.min_vulnerability = 0
@default_state
@state
def terrorist(self):
for neighbour in self.get_neighboring_agents(agent_type=TerroristSpreadModel):
if neighbour.vulnerability > self.min_vulnerability:
neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.training_influence )
class HavenModel(FSM, Geo):
"""
Settings:
haven_influence
min_vulnerability
max_vulnerability
Requires TerroristSpreadModel.
"""
def __init__(self, environment=None, agent_id=0, state=()):
super().__init__(environment=environment, agent_id=agent_id, state=state)
self.haven_influence = environment.environment_params['haven_influence']
if 'min_vulnerability' in environment.environment_params:
self.min_vulnerability = environment.environment_params['min_vulnerability']
else: self.min_vulnerability = 0
self.max_vulnerability = environment.environment_params['max_vulnerability']
def get_occupants(self, **kwargs):
return self.get_neighboring_agents(agent_type=TerroristSpreadModel, **kwargs)
@state
def civilian(self):
civilians = self.get_occupants(state_id=self.civilian.id)
if not civilians:
return self.terrorist
for neighbour in self.get_occupants():
if neighbour.vulnerability > self.min_vulnerability:
neighbour.vulnerability = neighbour.vulnerability * ( 1 - self.haven_influence )
return self.civilian
@state
def terrorist(self):
for neighbour in self.get_occupants():
if neighbour.vulnerability < self.max_vulnerability:
neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.haven_influence )
return self.terrorist
class TerroristNetworkModel(TerroristSpreadModel):
"""
Settings:
sphere_influence
vision_range
weight_social_distance
weight_link_distance
"""
def __init__(self, environment=None, agent_id=0, state=()):
super().__init__(environment=environment, agent_id=agent_id, state=state)
self.vision_range = environment.environment_params['vision_range']
self.sphere_influence = environment.environment_params['sphere_influence']
self.weight_social_distance = environment.environment_params['weight_social_distance']
self.weight_link_distance = environment.environment_params['weight_link_distance']
@state
def terrorist(self):
self.update_relationships()
return super().terrorist()
@state
def leader(self):
self.update_relationships()
return super().leader()
def update_relationships(self):
if self.count_neighboring_agents(state_id=self.civilian.id) == 0:
close_ups = set(self.geo_search(radius=self.vision_range, agent_type=TerroristNetworkModel))
step_neighbours = set(self.ego_search(self.sphere_influence, agent_type=TerroristNetworkModel, center=False))
neighbours = set(agent.id for agent in self.get_neighboring_agents(agent_type=TerroristNetworkModel))
search = (close_ups | step_neighbours) - neighbours
for agent in self.get_agents(search):
social_distance = 1 / self.shortest_path_length(agent.id)
spatial_proximity = ( 1 - self.get_distance(agent.id) )
prob_new_interaction = self.weight_social_distance * social_distance + self.weight_link_distance * spatial_proximity
if agent['id'] == agent.civilian.id and random.random() < prob_new_interaction:
self.add_edge(agent)
break
def get_distance(self, target):
source_x, source_y = nx.get_node_attributes(self.global_topology, 'pos')[self.id]
target_x, target_y = nx.get_node_attributes(self.global_topology, 'pos')[target]
dx = abs( source_x - target_x )
dy = abs( source_y - target_y )
return ( dx ** 2 + dy ** 2 ) ** ( 1 / 2 )
def shortest_path_length(self, target):
try:
return nx.shortest_path_length(self.global_topology, self.id, target)
except nx.NetworkXNoPath:
return float('inf')

@ -60,3 +60,4 @@ visualization_params:
background_image: 'map_4800x2860.jpg'
background_opacity: '0.9'
background_filter_color: 'blue'
skip_test: true # This simulation takes too long for automated tests.

@ -5,3 +5,4 @@ numpy
matplotlib
pyyaml
pandas
scipy

@ -1 +1 @@
0.13.7
0.13.8

@ -22,11 +22,17 @@ class AggregatedCounter(BaseAgent):
in each step and adds it to its state.
"""
defaults = {
'times': 0,
'neighbors': 0,
'total': 0
}
def step(self):
# Outside effects
total = len(list(self.get_all_agents()))
self['times'] += 1
neighbors = len(list(self.get_neighboring_agents()))
self['times'] = self.get('times', 0) + 1
self['neighbors'] = self.get('neighbors', 0) + neighbors
self['total'] = total = self.get('total', 0) + total
self['neighbors'] += neighbors
total = len(list(self.get_all_agents()))
self['total'] += total
self.debug('Running for step: {}. Total: {}'.format(self.now, total))

@ -10,6 +10,7 @@ import logging
from collections import OrderedDict
from copy import deepcopy
from functools import partial
from scipy.spatial import cKDTree as KDTree
import json
from functools import wraps
@ -17,6 +18,12 @@ from functools import wraps
from .. import utils, history
def as_node(agent):
if isinstance(agent, BaseAgent):
return agent.id
return agent
class BaseAgent(nxsim.BaseAgent):
"""
A special simpy BaseAgent that keeps track of its state history.
@ -46,8 +53,7 @@ class BaseAgent(nxsim.BaseAgent):
if not hasattr(self, 'level'):
self.level = logging.DEBUG
self.logger = logging.getLogger('{}.{}'.format(self.env.name,
self.id))
self.logger = logging.getLogger(self.env.name)
self.logger.setLevel(self.level)
# initialize every time an instance of the agent is created
@ -134,43 +140,21 @@ class BaseAgent(nxsim.BaseAgent):
def step(self):
pass
def count_agents(self, state_id=None, limit_neighbors=False):
if limit_neighbors:
agents = self.global_topology.neighbors(self.id)
else:
agents = self.global_topology.nodes()
count = 0
for agent in agents:
if state_id and state_id != self.global_topology.node[agent]['agent']['id']:
continue
count += 1
return count
def count_neighboring_agents(self, state_id=None):
return len(super().get_agents(state_id, limit_neighbors=True))
def count_agents(self, **kwargs):
return len(list(self.get_agents(**kwargs)))
def get_agents(self, state_id=None, agent_type=None, limit_neighbors=False, iterator=False, **kwargs):
agents = self.env.agents
if limit_neighbors:
agents = super().get_agents(state_id, limit_neighbors)
def count_neighboring_agents(self, state_id=None, **kwargs):
return len(super().get_neighboring_agents(state_id=state_id, **kwargs))
def matches_all(agent):
if state_id is not None:
if agent.state.get('id', None) != state_id:
return False
if agent_type is not None:
if type(agent) != agent_type:
return False
state = agent.state
for k, v in kwargs.items():
if state.get(k, None) != v:
return False
return True
def get_neighboring_agents(self, state_id=None, **kwargs):
return self.get_agents(limit_neighbors=True, state_id=state_id, **kwargs)
f = filter(matches_all, agents)
if iterator:
return f
return list(f)
def get_agents(self, agents=None, limit_neighbors=False, **kwargs):
if limit_neighbors:
agents = super().get_agents(limit_neighbors=limit_neighbors)
else:
agents = self.env.get_agents(agents)
return select(agents, **kwargs)
def log(self, message, *args, level=logging.INFO, **kwargs):
message = message + " ".join(str(i) for i in args)
@ -208,12 +192,52 @@ class BaseAgent(nxsim.BaseAgent):
self._state = state['_state']
self.env = state['environment']
def add_edge(self, node1, node2, **attrs):
node1 = as_node(node1)
node2 = as_node(node2)
for n in [node1, node2]:
if n not in self.global_topology.nodes(data=False):
raise ValueError('"{}" not in the graph'.format(n))
return self.global_topology.add_edge(node1, node2, **attrs)
def subgraph(self, center=True, **kwargs):
include = [self] if center else []
return self.global_topology.subgraph(n.id for n in self.get_agents(**kwargs)+include)
class NetworkAgent(BaseAgent):
def add_edge(self, other, **kwargs):
return super(NetworkAgent, self).add_edge(node1=self.id, node2=other, **kwargs)
def ego_search(self, steps=1, center=False, node=None, **kwargs):
'''Get a list of nodes in the ego network of *node* of radius *steps*'''
node = as_node(node if node is not None else self)
G = self.subgraph(**kwargs)
return nx.ego_graph(G, node, center=center, radius=steps).nodes()
def degree(self, node, force=False):
node = as_node(node)
if force or (not hasattr(self.env, '_degree')) or getattr(self.env, '_last_step', 0) < self.now:
self.env._degree = nx.degree_centrality(self.global_topology)
self.env._last_step = self.now
return self.env._degree[node]
def betweenness(self, node, force=False):
node = as_node(node)
if force or (not hasattr(self.env, '_betweenness')) or getattr(self.env, '_last_step', 0) < self.now:
self.env._betweenness = nx.betweenness_centrality(self.global_topology)
self.env._last_step = self.now
return self.env._betweenness[node]
def state(func):
def state(name=None):
def decorator(func, name=None):
'''
A state function should return either a state id, or a tuple (state_id, when)
The default value for state_id is the current state id.
The default value for when is the interval defined in the nevironment.
The default value for when is the interval defined in the environment.
'''
@wraps(func)
@ -230,10 +254,15 @@ def state(func):
self.set_state(next_state)
return when
func_wrapper.id = func.__name__
func_wrapper.id = name or func.__name__
func_wrapper.is_default = False
return func_wrapper
if callable(name):
return decorator(name)
else:
return partial(decorator, name=name)
def default_state(func):
func.is_default = True
@ -340,7 +369,7 @@ def calculate_distribution(network_agents=None,
elif agent_type:
network_agents = [{'agent_type': agent_type}]
else:
return []
raise ValueError('Specify a distribution or a default agent type')
# Calculate the thresholds
total = sum(x.get('weight', 1) for x in network_agents)
@ -427,6 +456,58 @@ def _agent_from_distribution(distribution, value=-1, agent_id=None):
raise Exception('Distribution for value {} not found in: {}'.format(value, distribution))
class Geo(NetworkAgent):
'''In this type of network, nodes have a "pos" attribute.'''
def geo_search(self, radius, node=None, center=False, **kwargs):
'''Get a list of nodes whose coordinates are closer than *radius* to *node*.'''
node = as_node(node if node is not None else self)
G = self.subgraph(**kwargs)
pos = nx.get_node_attributes(G, 'pos')
if not pos:
return []
nodes, coords = list(zip(*pos.items()))
kdtree = KDTree(coords) # Cannot provide generator.
indices = kdtree.query_ball_point(pos[node], radius)
return [nodes[i] for i in indices if center or (nodes[i] != node)]
def select(agents, state_id=None, agent_type=None, ignore=None, iterator=False, **kwargs):
if state_id is not None:
try:
state_id = tuple(state_id)
except TypeError:
state_id = tuple([state_id])
if agent_type is not None:
try:
agent_type = tuple(agent_type)
except TypeError:
agent_type = tuple([agent_type])
def matches_all(agent):
if state_id is not None:
if agent.state.get('id', None) not in state_id:
return False
if agent_type is not None:
if not isinstance(agent, agent_type):
return False
state = agent.state
for k, v in kwargs.items():
if state.get(k, None) != v:
return False
return True
f = filter(matches_all, agents)
if ignore:
f = filter(lambda x: x not in ignore, f)
if iterator:
return f
return list(f)
from .BassModel import *
from .BigMarketModel import *
from .IndependentCascadeModel import *

@ -102,8 +102,7 @@ class Environment(nxsim.NetworkEnvironment):
@network_agents.setter
def network_agents(self, network_agents):
if not network_agents:
return
self._network_agents = network_agents
for ix in self.G.nodes():
self.init_agent(ix, agent_distribution=network_agents)
@ -124,6 +123,9 @@ class Environment(nxsim.NetworkEnvironment):
agent_type = agents.deserialize_type(agent_type)
elif agent_distribution:
agent_type, state = agents._agent_from_distribution(agent_distribution, agent_id=agent_id)
else:
utils.logger.debug('Skipping node {}'.format(agent_id))
return
return self.set_agent(agent_id, agent_type, state)
def set_agent(self, agent_id, agent_type, state=None):
@ -149,12 +151,13 @@ class Environment(nxsim.NetworkEnvironment):
a['visible'] = True
return a
def add_edge(self, agent1, agent2, attrs=None):
def add_edge(self, agent1, agent2, start=None, **attrs):
if hasattr(agent1, 'id'):
agent1 = agent1.id
if hasattr(agent2, 'id'):
agent2 = agent2.id
return self.G.add_edge(agent1, agent2)
start = start or self.now
return self.G.add_edge(agent1, agent2, **attrs)
def run(self, *args, **kwargs):
self._save_state()
@ -231,8 +234,10 @@ class Environment(nxsim.NetworkEnvironment):
def get_agent(self, agent_id):
return self.G.node[agent_id]['agent']
def get_agents(self):
def get_agents(self, nodes=None):
if nodes is None:
return list(self.agents)
return [self.G.node[i]['agent'] for i in nodes]
def dump_csv(self, dir_path=None):
csv_name = os.path.join(self.get_path(dir_path),

@ -147,7 +147,7 @@ def deserializer(type_, known_modules=[]):
module = importlib.import_module(modname)
cls = getattr(module, tname)
return getattr(cls, 'deserialize', cls)
except (ImportError, AttributeError) as ex:
except (ModuleNotFoundError, AttributeError) as ex:
errors.append((modname, tname, ex))
raise Exception('Could not find type {}. Tried: {}'.format(type_, errors))

@ -1,255 +0,0 @@
import random
import networkx as nx
from soil.agents import BaseAgent, FSM, state, default_state
from scipy.spatial import cKDTree as KDTree
global betweenness_centrality_global
global degree_centrality_global
betweenness_centrality_global = None
degree_centrality_global = None
class TerroristSpreadModel(FSM):
"""
Settings:
information_spread_intensity
terrorist_additional_influence
min_vulnerability (optional else zero)
max_vulnerability
prob_interaction
"""
def __init__(self, environment=None, agent_id=0, state=()):
super().__init__(environment=environment, agent_id=agent_id, state=state)
global betweenness_centrality_global
global degree_centrality_global
if betweenness_centrality_global == None:
betweenness_centrality_global = nx.betweenness_centrality(self.global_topology)
if degree_centrality_global == None:
degree_centrality_global = nx.degree_centrality(self.global_topology)
self.information_spread_intensity = environment.environment_params['information_spread_intensity']
self.terrorist_additional_influence = environment.environment_params['terrorist_additional_influence']
self.prob_interaction = environment.environment_params['prob_interaction']
if self['id'] == self.civilian.id: # Civilian
self.initial_belief = random.uniform(0.00, 0.5)
elif self['id'] == self.terrorist.id: # Terrorist
self.initial_belief = random.uniform(0.8, 1.00)
elif self['id'] == self.leader.id: # Leader
self.initial_belief = 1.00
else:
raise Exception('Invalid state id: {}'.format(self['id']))
if 'min_vulnerability' in environment.environment_params:
self.vulnerability = random.uniform( environment.environment_params['min_vulnerability'], environment.environment_params['max_vulnerability'] )
else :
self.vulnerability = random.uniform( 0, environment.environment_params['max_vulnerability'] )
self.mean_belief = self.initial_belief
self.betweenness_centrality = betweenness_centrality_global[self.id]
self.degree_centrality = degree_centrality_global[self.id]
# self.state['radicalism'] = self.mean_belief
def count_neighboring_agents(self, state_id=None):
if isinstance(state_id, list):
return len(self.get_neighboring_agents(state_id))
else:
return len(super().get_agents(state_id, limit_neighbors=True))
def get_neighboring_agents(self, state_id=None):
if isinstance(state_id, list):
_list = []
for i in state_id:
_list += super().get_agents(i, limit_neighbors=True)
return [ neighbour for neighbour in _list if isinstance(neighbour, TerroristSpreadModel) ]
else:
_list = super().get_agents(state_id, limit_neighbors=True)
return [ neighbour for neighbour in _list if isinstance(neighbour, TerroristSpreadModel) ]
@state
def civilian(self):
if self.count_neighboring_agents() > 0:
neighbours = []
for neighbour in self.get_neighboring_agents():
if random.random() < self.prob_interaction:
neighbours.append(neighbour)
influence = sum( neighbour.degree_centrality for neighbour in neighbours )
mean_belief = sum( neighbour.mean_belief * neighbour.degree_centrality / influence for neighbour in neighbours )
self.initial_belief = self.mean_belief
mean_belief = mean_belief * self.information_spread_intensity + self.initial_belief * ( 1 - self.information_spread_intensity )
self.mean_belief = mean_belief * self.vulnerability + self.initial_belief * ( 1 - self.vulnerability )
if self.mean_belief >= 0.8:
return self.terrorist
@state
def leader(self):
self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence )
if self.count_neighboring_agents(state_id=[self.terrorist.id, self.leader.id]) > 0:
for neighbour in self.get_neighboring_agents(state_id=[self.terrorist.id, self.leader.id]):
if neighbour.betweenness_centrality > self.betweenness_centrality:
return self.terrorist
@state
def terrorist(self):
if self.count_neighboring_agents(state_id=[self.terrorist.id, self.leader.id]) > 0:
neighbours = self.get_neighboring_agents(state_id=[self.terrorist.id, self.leader.id])
influence = sum( neighbour.degree_centrality for neighbour in neighbours )
mean_belief = sum( neighbour.mean_belief * neighbour.degree_centrality / influence for neighbour in neighbours )
self.initial_belief = self.mean_belief
self.mean_belief = mean_belief * self.vulnerability + self.initial_belief * ( 1 - self.vulnerability )
self.mean_belief = self.mean_belief ** ( 1 - self.terrorist_additional_influence )
if self.count_neighboring_agents(state_id=self.leader.id) == 0 and self.count_neighboring_agents(state_id=self.terrorist.id) > 0:
max_betweenness_centrality = self
for neighbour in self.get_neighboring_agents(state_id=self.terrorist.id):
if neighbour.betweenness_centrality > max_betweenness_centrality.betweenness_centrality:
max_betweenness_centrality = neighbour
if max_betweenness_centrality == self:
return self.leader
def add_edge(self, G, source, target):
G.add_edge(source.id, target.id, start=self.env._now)
def link_search(self, G, node, radius):
pos = nx.get_node_attributes(G, 'pos')
nodes, coords = list(zip(*pos.items()))
kdtree = KDTree(coords) # Cannot provide generator.
edge_indexes = kdtree.query_pairs(radius, 2)
_list = [ edge[int(not edge.index(node))] for edge in edge_indexes if node in edge ]
return [ G.nodes()[index]['agent'] for index in _list ]
def social_search(self, G, node, steps):
nodes = list(nx.ego_graph(G, node, radius=steps).nodes())
nodes.remove(node)
return [ G.nodes()[index]['agent'] for index in nodes ]
class TrainingAreaModel(FSM):
"""
Settings:
training_influence
min_vulnerability
Requires TerroristSpreadModel.
"""
def __init__(self, environment=None, agent_id=0, state=()):
super().__init__(environment=environment, agent_id=agent_id, state=state)
self.training_influence = environment.environment_params['training_influence']
if 'min_vulnerability' in environment.environment_params:
self.min_vulnerability = environment.environment_params['min_vulnerability']
else: self.min_vulnerability = 0
@default_state
@state
def terrorist(self):
for neighbour in self.get_neighboring_agents():
if isinstance(neighbour, TerroristSpreadModel) and neighbour.vulnerability > self.min_vulnerability:
neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.training_influence )
class HavenModel(FSM):
"""
Settings:
haven_influence
min_vulnerability
max_vulnerability
Requires TerroristSpreadModel.
"""
def __init__(self, environment=None, agent_id=0, state=()):
super().__init__(environment=environment, agent_id=agent_id, state=state)
self.haven_influence = environment.environment_params['haven_influence']
if 'min_vulnerability' in environment.environment_params:
self.min_vulnerability = environment.environment_params['min_vulnerability']
else: self.min_vulnerability = 0
self.max_vulnerability = environment.environment_params['max_vulnerability']
@state
def civilian(self):
for neighbour_agent in self.get_neighboring_agents():
if isinstance(neighbour_agent, TerroristSpreadModel) and neighbour_agent['id'] == neighbour_agent.civilian.id:
for neighbour in self.get_neighboring_agents():
if isinstance(neighbour, TerroristSpreadModel) and neighbour.vulnerability > self.min_vulnerability:
neighbour.vulnerability = neighbour.vulnerability * ( 1 - self.haven_influence )
return self.civilian
return self.terrorist
@state
def terrorist(self):
for neighbour in self.get_neighboring_agents():
if isinstance(neighbour, TerroristSpreadModel) and neighbour.vulnerability < self.max_vulnerability:
neighbour.vulnerability = neighbour.vulnerability ** ( 1 - self.haven_influence )
return self.terrorist
class TerroristNetworkModel(TerroristSpreadModel):
"""
Settings:
sphere_influence
vision_range
weight_social_distance
weight_link_distance
"""
def __init__(self, environment=None, agent_id=0, state=()):
super().__init__(environment=environment, agent_id=agent_id, state=state)
self.vision_range = environment.environment_params['vision_range']
self.sphere_influence = environment.environment_params['sphere_influence']
self.weight_social_distance = environment.environment_params['weight_social_distance']
self.weight_link_distance = environment.environment_params['weight_link_distance']
@state
def terrorist(self):
self.update_relationships()
return super().terrorist()
@state
def leader(self):
self.update_relationships()
return super().leader()
def update_relationships(self):
if self.count_neighboring_agents(state_id=self.civilian.id) == 0:
close_ups = self.link_search(self.global_topology, self.id, self.vision_range)
step_neighbours = self.social_search(self.global_topology, self.id, self.sphere_influence)
search = list(set(close_ups).union(step_neighbours))
neighbours = self.get_neighboring_agents()
search = [item for item in search if not item in neighbours and isinstance(item, TerroristNetworkModel)]
for agent in search:
social_distance = 1 / self.shortest_path_length(self.global_topology, self.id, agent.id)
spatial_proximity = ( 1 - self.get_distance(self.global_topology, self.id, agent.id) )
prob_new_interaction = self.weight_social_distance * social_distance + self.weight_link_distance * spatial_proximity
if agent['id'] == agent.civilian.id and random.random() < prob_new_interaction:
self.add_edge(self.global_topology, self, agent)
break
def get_distance(self, G, source, target):
source_x, source_y = nx.get_node_attributes(G, 'pos')[source]
target_x, target_y = nx.get_node_attributes(G, 'pos')[target]
dx = abs( source_x - target_x )
dy = abs( source_y - target_y )
return ( dx ** 2 + dy ** 2 ) ** ( 1 / 2 )
def shortest_path_length(self, G, source, target):
try:
return nx.shortest_path_length(G, source, target)
except nx.NetworkXNoPath:
return float('inf')

@ -7,6 +7,8 @@ from soil import utils, simulation
ROOT = os.path.abspath(os.path.dirname(__file__))
EXAMPLES = join(ROOT, '..', 'examples')
FORCE_TESTS = os.environ.get('FORCE_TESTS', '')
class TestExamples(TestCase):
pass
@ -19,7 +21,10 @@ def make_example_test(path, config):
s = simulation.from_config(config)
iterations = s.max_time * s.num_trials
if iterations > 1000:
self.skipTest('This example would probably take too long')
s.max_time = 100
s.num_trials = 1
if config.get('skip_test', False) and not FORCE_TESTS:
self.skipTest('Example ignored.')
envs = s.run_simulation(dry_run=True)
assert envs
for env in envs:

@ -320,3 +320,19 @@ class TestMain(TestCase):
h = history.History()
h.save_record(agent_id=0, t_step=0, key="test", value="hello")
assert h[0, 0, "test"] == "hello"
def test_subgraph(self):
'''An agent should be able to subgraph the global topology'''
G = nx.Graph()
G.add_node(3)
G.add_edge(1, 2)
distro = agents.calculate_distribution(agent_type=agents.NetworkAgent)
env = Environment(name='Test', topology=G, network_agents=distro)
lst = list(env.network_agents)
a2 = env.get_agent(2)
a3 = env.get_agent(3)
assert len(a2.subgraph(limit_neighbors=True)) == 2
assert len(a3.subgraph(limit_neighbors=True)) == 1
assert len(a3.subgraph(limit_neighbors=True, center=False)) == 0
assert len(a3.subgraph(agent_type=agents.NetworkAgent)) == 3

Loading…
Cancel
Save