mirror of
https://github.com/gsi-upm/soil
synced 2024-11-12 22:42:28 +00:00
0.13.8
This commit is contained in:
parent
65f6aa72f3
commit
a3ea434f23
12
CHANGELOG.md
12
CHANGELOG.md
@ -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]
|
||||
@ -16,4 +24,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
* Agent logging uses the agent name.
|
||||
* FSM agents can now return a timeout in addition to a new state. e.g. `return self.idle, self.env.timeout(2)` will execute the *different_state* in 2 *units of time* (`t_step=now+2`).
|
||||
* Example of using timeouts in FSM (custom_timeouts)
|
||||
* `network_agents` entries may include an `ids` entry. If set, it should be a list of node ids that should be assigned that agent type. This complements the previous behavior of setting agent type with `weights`.
|
||||
* `network_agents` entries may include an `ids` entry. If set, it should be a list of node ids that should be assigned that agent type. This complements the previous behavior of setting agent type with `weights`.
|
||||
|
@ -1,4 +1,7 @@
|
||||
include requirements.txt
|
||||
include test-requirements.txt
|
||||
include README.rst
|
||||
graft soil
|
||||
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
|
||||
|
208
examples/terrorism/TerroristNetworkModel.py
Normal file
208
examples/terrorism/TerroristNetworkModel.py
Normal file
@ -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):
|
||||
def count_agents(self, **kwargs):
|
||||
return len(list(self.get_agents(**kwargs)))
|
||||
|
||||
def count_neighboring_agents(self, state_id=None, **kwargs):
|
||||
return len(super().get_neighboring_agents(state_id=state_id, **kwargs))
|
||||
|
||||
def get_neighboring_agents(self, state_id=None, **kwargs):
|
||||
return self.get_agents(limit_neighbors=True, state_id=state_id, **kwargs)
|
||||
|
||||
def get_agents(self, agents=None, limit_neighbors=False, **kwargs):
|
||||
if limit_neighbors:
|
||||
agents = self.global_topology.neighbors(self.id)
|
||||
agents = super().get_agents(limit_neighbors=limit_neighbors)
|
||||
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 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 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
|
||||
|
||||
f = filter(matches_all, agents)
|
||||
if iterator:
|
||||
return f
|
||||
return list(f)
|
||||
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,31 +192,76 @@ 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)
|
||||
|
||||
def state(func):
|
||||
'''
|
||||
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.
|
||||
'''
|
||||
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)
|
||||
|
||||
@wraps(func)
|
||||
def func_wrapper(self):
|
||||
next_state = func(self)
|
||||
when = None
|
||||
if next_state is None:
|
||||
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(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 environment.
|
||||
'''
|
||||
|
||||
@wraps(func)
|
||||
def func_wrapper(self):
|
||||
next_state = func(self)
|
||||
when = None
|
||||
if next_state is None:
|
||||
return when
|
||||
try:
|
||||
next_state, when = next_state
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if next_state:
|
||||
self.set_state(next_state)
|
||||
return when
|
||||
try:
|
||||
next_state, when = next_state
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if next_state:
|
||||
self.set_state(next_state)
|
||||
return when
|
||||
|
||||
func_wrapper.id = func.__name__
|
||||
func_wrapper.is_default = False
|
||||
return func_wrapper
|
||||
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):
|
||||
@ -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):
|
||||
return list(self.agents)
|
||||
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…
Reference in New Issue
Block a user