1
0
mirror of https://github.com/gsi-upm/soil synced 2025-08-23 19:52:19 +00:00

Added history class

Now the environment does not deal with history directly, it delegates it to a
specific class. The analysis also uses history instances instead of either
using the database directly or creating a proxy environment.

This should make it easier to change the implementation in the future.

In fact, the change was motivated by the large size of the csv files in previous
versions. This new implementation only stores results in deltas, and it fills
any necessary values when needed.
This commit is contained in:
J. Fernando Sánchez
2018-05-04 10:01:49 +02:00
parent 73c90887e8
commit fc48ed7e09
19 changed files with 1469 additions and 2911 deletions

16
tests/test.csv Normal file
View File

@@ -0,0 +1,16 @@
agent_id,t_step,key,value,value_type
a0,0,hello,w,str
a0,1,hello,o,str
a0,2,hello,r,str
a0,3,hello,l,str
a0,4,hello,d,str
a0,5,hello,!,str
env,1,started,,bool
env,2,started,True,bool
env,7,started,,bool
a0,0,hello,w,str
a0,1,hello,o,str
a0,2,hello,r,str
a0,3,hello,l,str
a0,4,hello,d,str
a0,5,hello,!,str
1 agent_id t_step key value value_type
2 a0 0 hello w str
3 a0 1 hello o str
4 a0 2 hello r str
5 a0 3 hello l str
6 a0 4 hello d str
7 a0 5 hello ! str
8 env 1 started bool
9 env 2 started True bool
10 env 7 started bool
11 a0 0 hello w str
12 a0 1 hello o str
13 a0 2 hello r str
14 a0 3 hello l str
15 a0 4 hello d str
16 a0 5 hello ! str

90
tests/test_analysis.py Normal file
View File

@@ -0,0 +1,90 @@
from unittest import TestCase
import os
import pandas as pd
import yaml
from functools import partial
from os.path import join
from soil import simulation, analysis, agents
ROOT = os.path.abspath(os.path.dirname(__file__))
class Ping(agents.FSM):
defaults = {
'count': 0,
}
@agents.default_state
@agents.state
def even(self):
self['count'] += 1
return self.odd
@agents.state
def odd(self):
self['count'] += 1
return self.even
class TestAnalysis(TestCase):
# Code to generate a simple sqlite history
def setUp(self):
"""
The initial states should be applied to the agent and the
agent should be able to update its state."""
config = {
'name': 'analysis',
'dry_run': True,
'seed': 'seed',
'network_params': {
'generator': 'complete_graph',
'n': 2
},
'agent_type': Ping,
'states': [{'interval': 1}, {'interval': 2}],
'max_time': 30,
'num_trials': 1,
'environment_params': {
}
}
s = simulation.from_config(config)
self.env = s.run_simulation()[0]
def test_saved(self):
env = self.env
assert env.get_agent(0)['count', 0] == 1
assert env.get_agent(0)['count', 29] == 30
assert env.get_agent(1)['count', 0] == 1
assert env.get_agent(1)['count', 29] == 15
assert env['env', 29, None]['SEED'] == env['env', 29, 'SEED']
def test_count(self):
env = self.env
df = analysis.read_sql(env._history._db)
res = analysis.get_count(df, 'SEED', 'id')
assert res['SEED']['seedanalysis_trial_0'].iloc[0] == 1
assert res['SEED']['seedanalysis_trial_0'].iloc[-1] == 1
assert res['id']['odd'].iloc[0] == 2
assert res['id']['even'].iloc[0] == 0
assert res['id']['odd'].iloc[-1] == 1
assert res['id']['even'].iloc[-1] == 1
def test_value(self):
env = self.env
df = analysis.read_sql(env._history._db)
res_sum = analysis.get_value(df, 'count')
assert res_sum['count'].iloc[0] == 2
import numpy as np
res_mean = analysis.get_value(df, 'count', aggfunc=np.mean)
assert res_mean['count'].iloc[0] == 1
res_total = analysis.get_value(df)
res_total['SEED'].iloc[0] == 'seedanalysis_trial_0'

90
tests/test_history.py Normal file
View File

@@ -0,0 +1,90 @@
from unittest import TestCase
import os
import pandas as pd
from soil import history, analysis
ROOT = os.path.abspath(os.path.dirname(__file__))
class TestHistory(TestCase):
def test_history(self):
"""
"""
tuples = (
('a_0', 0, 'id', 'h', ),
('a_0', 1, 'id', 'e', ),
('a_0', 2, 'id', 'l', ),
('a_0', 3, 'id', 'l', ),
('a_0', 4, 'id', 'o', ),
('a_1', 0, 'id', 'v', ),
('a_1', 1, 'id', 'a', ),
('a_1', 2, 'id', 'l', ),
('a_1', 3, 'id', 'u', ),
('a_1', 4, 'id', 'e', ),
('env', 1, 'prob', 1),
('env', 3, 'prob', 2),
('env', 5, 'prob', 3),
('a_2', 7, 'finished', True),
)
h = history.History()
h.save_tuples(tuples)
# assert h['env', 0, 'prob'] == 0
for i in range(1, 7):
assert h['env', i, 'prob'] == ((i-1)//2)+1
for i, k in zip(range(5), 'hello'):
assert h['a_0', i, 'id'] == k
for record, value in zip(h['a_0', None, 'id'], 'hello'):
t_step, val = record
assert val == value
for i, k in zip(range(5), 'value'):
assert h['a_1', i, 'id'] == k
for i in range(5, 8):
assert h['a_1', i, 'id'] == 'e'
for i in range(7):
assert h['a_2', i, 'finished'] == False
assert h['a_2', 7, 'finished']
def test_history_gen(self):
"""
"""
tuples = (
('a_1', 0, 'id', 'v', ),
('a_1', 1, 'id', 'a', ),
('a_1', 2, 'id', 'l', ),
('a_1', 3, 'id', 'u', ),
('a_1', 4, 'id', 'e', ),
('env', 1, 'prob', 1),
('env', 2, 'prob', 2),
('env', 3, 'prob', 3),
('a_2', 7, 'finished', True),
)
h = history.History()
h.save_tuples(tuples)
for t_step, key, value in h['env', None, None]:
assert t_step == value
assert key == 'prob'
records = list(h[None, 7, None])
assert len(records) == 3
for i in records:
agent_id, key, value = i
if agent_id == 'a_1':
assert key == 'id'
assert value == 'e'
elif agent_id == 'a_2':
assert key == 'finished'
assert value == True
else:
assert key == 'prob'
assert value == 3
records = h['a_1', 7, None]
assert records['id'] == 'e'

View File

@@ -22,6 +22,7 @@ class TestMain(TestCase):
Raise an exception otherwise.
"""
config = {
'dry_run': True,
'network_params': {
'path': join(ROOT, 'test.gexf')
}
@@ -31,6 +32,7 @@ class TestMain(TestCase):
assert len(G) == 2
with self.assertRaises(AttributeError):
config = {
'dry_run': True,
'network_params': {
'path': join(ROOT, 'unknown.extension')
}
@@ -44,6 +46,7 @@ class TestMain(TestCase):
should be used to generate a network
"""
config = {
'dry_run': True,
'network_params': {
'generator': 'barabasi_albert_graph'
}
@@ -58,6 +61,7 @@ class TestMain(TestCase):
def test_empty_simulation(self):
"""A simulation with a base behaviour should do nothing"""
config = {
'dry_run': True,
'network_params': {
'path': join(ROOT, 'test.gexf')
},
@@ -74,11 +78,12 @@ class TestMain(TestCase):
agent should be able to update its state."""
config = {
'name': 'CounterAgent',
'dry_run': True,
'network_params': {
'path': join(ROOT, 'test.gexf')
},
'agent_type': 'CounterModel',
'states': [{'neighbors': 10}, {'total': 12}],
'states': [{'times': 10}, {'times': 20}],
'max_time': 2,
'num_trials': 1,
'environment_params': {
@@ -86,10 +91,10 @@ class TestMain(TestCase):
}
s = simulation.from_config(config)
env = s.run_simulation(dry_run=True)[0]
assert env.get_agent(0)['neighbors', 0] == 10
assert env.get_agent(0)['neighbors', 1] == 1
assert env.get_agent(1)['total', 0] == 12
assert env.get_agent(1)['neighbors', 1] == 1
assert env.get_agent(0)['times', 0] == 11
assert env.get_agent(0)['times', 1] == 12
assert env.get_agent(1)['times', 0] == 21
assert env.get_agent(1)['times', 1] == 22
def test_counter_agent_history(self):
"""
@@ -97,6 +102,7 @@ class TestMain(TestCase):
"""
config = {
'name': 'CounterAgent',
'dry_run': True,
'network_params': {
'path': join(ROOT, 'test.gexf')
},
@@ -114,11 +120,10 @@ class TestMain(TestCase):
env = s.run_simulation(dry_run=True)[0]
for agent in env.network_agents:
last = 0
assert len(agent[None, None]) == 11
for step, total in agent['total', None].items():
if step > 0:
assert total == last + 2
last = total
assert len(agent[None, None]) == 10
for step, total in sorted(agent['total', None]):
assert total == last + 2
last = total
def test_custom_agent(self):
"""Allow for search of neighbors with a certain state_id"""
@@ -127,6 +132,7 @@ class TestMain(TestCase):
self.state['neighbors'] = self.count_agents(state_id=0,
limit_neighbors=True)
config = {
'dry_run': True,
'network_params': {
'path': join(ROOT, 'test.gexf')
},
@@ -150,7 +156,8 @@ class TestMain(TestCase):
config['network_params']['path'] = join(EXAMPLES,
config['network_params']['path'])
s = simulation.from_config(config)
env = s.run_simulation(dry_run=True)[0]
s.dry_run = True
env = s.run_simulation()[0]
for a in env.network_agents:
skill_level = a.state['skill_level']
if a.id == 'Torvalds':
@@ -174,14 +181,15 @@ class TestMain(TestCase):
with utils.timer('loading'):
config = utils.load_file(join(EXAMPLES, 'complete.yml'))[0]
s = simulation.from_config(config)
s.dry_run = True
with utils.timer('serializing'):
serial = s.to_yaml()
with utils.timer('recovering'):
recovered = yaml.load(serial)
with utils.timer('deleting'):
del recovered['topology']
del recovered['dry_run']
del recovered['load_module']
del recovered['dry_run']
assert config == recovered
def test_configuration_changes(self):
@@ -191,6 +199,7 @@ class TestMain(TestCase):
"""
config = utils.load_file('examples/complete.yml')[0]
s = simulation.from_config(config)
s.dry_run = True
for i in range(5):
s.run_simulation(dry_run=True)
nconfig = s.to_dict()
@@ -206,17 +215,14 @@ class TestMain(TestCase):
pass
def test_row_conversion(self):
sim = simulation.SoilSimulation()
env = environment.SoilEnvironment(dry_run=True)
env['test'] = 'test_value'
env._save_state(now=0)
res = list(env.history_to_tuples())
assert len(res) == len(env.environment_params)
assert ('env', 0, 'test', 'test_value', 'str') in res
env._now = 1
env['test'] = 'second_value'
env._save_state(now=1)
res = list(env.history_to_tuples())
assert env['env', 0, 'test' ] == 'test_value'