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

WIP: all tests pass

This commit is contained in:
J. Fernando Sánchez
2022-10-13 22:43:16 +02:00
parent f811ee18c5
commit cd62c23cb9
46 changed files with 1720 additions and 1434 deletions

View File

@@ -1,49 +1,50 @@
---
version: '2'
general:
id: simple
group: tests
dir_path: "/tmp/"
num_trials: 3
max_time: 100
interval: 1
seed: "CompleteSeed!"
topologies:
default:
params:
generator: complete_graph
n: 10
agents:
default:
name: simple
group: tests
dir_path: "/tmp/"
num_trials: 3
max_time: 100
interval: 1
seed: "CompleteSeed!"
model_class: Environment
model_params:
topologies:
default:
params:
generator: complete_graph
n: 4
agents:
agent_class: CounterModel
state:
group: network
times: 1
network:
topology: 'default'
distribution:
- agent_class: CounterModel
weight: 0.4
weight: 0.25
state:
state_id: 0
times: 1
- agent_class: AggregatedCounter
weight: 0.6
override:
- filter:
node_id: 0
weight: 0.5
state:
name: 'The first node'
times: 2
override:
- filter:
node_id: 1
state:
name: 'The second node'
environment:
fixed:
- name: 'Environment Agent 1'
agent_class: CounterModel
name: 'Node 1'
- filter:
node_id: 2
state:
name: 'Node 2'
fixed:
- agent_class: BaseAgent
hidden: true
topology: null
state:
name: 'Environment Agent 1'
times: 10
environment:
environment_class: Environment
params:
am_i_complete: true
group: environment
am_i_complete: true

View File

@@ -8,17 +8,20 @@ interval: 1
seed: "CompleteSeed!"
network_params:
generator: complete_graph
n: 10
n: 4
network_agents:
- agent_class: CounterModel
weight: 0.4
weight: 0.25
state:
state_id: 0
times: 1
- agent_class: AggregatedCounter
weight: 0.6
weight: 0.5
state:
times: 2
environment_agents:
- agent_id: 'Environment Agent 1'
agent_class: CounterModel
agent_class: BaseAgent
state:
times: 10
environment_class: Environment
@@ -28,5 +31,7 @@ agent_class: CounterModel
default_state:
times: 1
states:
- name: 'The first node'
- name: 'The second node'
1:
name: 'Node 1'
2:
name: 'Node 2'

View File

@@ -8,7 +8,7 @@ class Dead(agents.FSM):
@agents.default_state
@agents.state
def only(self):
self.die()
return self.die()
class TestMain(TestCase):
def test_die_raises_exception(self):
@@ -19,4 +19,6 @@ class TestMain(TestCase):
def test_die_returns_infinity(self):
d = Dead(unique_id=0, model=environment.Environment())
assert d.step().abs(0) == stime.INFINITY
ret = d.step().abs(0)
print(ret, 'next')
assert ret == stime.INFINITY

View File

@@ -1,91 +0,0 @@
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.debug(f'Even {self["count"]}')
self['count'] += 1
return self.odd
@agents.state
def odd(self):
self.debug(f'Odd {self["count"]}')
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',
'seed': 'seed',
'network_params': {
'generator': 'complete_graph',
'n': 2
},
'agent_class': Ping,
'states': [{'interval': 1}, {'interval': 2}],
'max_time': 30,
'num_trials': 1,
'history': True,
'environment_params': {
}
}
s = simulation.from_config(config)
self.env = s.run_simulation(dry_run=True)[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_path)
res = analysis.get_count(df, 'SEED', 'state_id')
assert res['SEED'][self.env['SEED']].iloc[0] == 1
assert res['SEED'][self.env['SEED']].iloc[-1] == 1
assert res['state_id']['odd'].iloc[0] == 2
assert res['state_id']['even'].iloc[0] == 0
assert res['state_id']['odd'].iloc[-1] == 1
assert res['state_id']['even'].iloc[-1] == 1
def test_value(self):
env = self.env
df = analysis.read_sql(env._history.db_path)
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[15] == (16+8)/2
res_total = analysis.get_majority(df)
res_total['SEED'].iloc[0] == self.env['SEED']

View File

@@ -29,7 +29,7 @@ class TestConfig(TestCase):
expected = serialization.load_file(join(ROOT, "complete_converted.yml"))[0]
old = serialization.load_file(join(ROOT, "old_complete.yml"))[0]
converted_defaults = config.convert_old(old, strict=False)
converted = converted_defaults.dict(skip_defaults=True)
converted = converted_defaults.dict(exclude_unset=True)
isequal(converted, expected)
@@ -40,10 +40,10 @@ class TestConfig(TestCase):
"""
config = serialization.load_file(join(EXAMPLES, 'complete.yml'))[0]
s = simulation.from_config(config)
init_config = copy.copy(s.config)
init_config = copy.copy(s.to_dict())
s.run_simulation(dry_run=True)
nconfig = s.config
nconfig = s.to_dict()
# del nconfig['to
isequal(init_config, nconfig)
@@ -61,7 +61,7 @@ class TestConfig(TestCase):
Simple configuration that tests that the graph is loaded, and that
network agents are initialized properly.
"""
config = {
cfg = {
'name': 'CounterAgent',
'network_params': {
'path': join(ROOT, 'test.gexf')
@@ -74,12 +74,14 @@ class TestConfig(TestCase):
'environment_params': {
}
}
s = simulation.from_old_config(config)
conf = config.convert_old(cfg)
s = simulation.from_config(conf)
env = s.get_env()
assert len(env.topologies['default'].nodes) == 2
assert len(env.topologies['default'].edges) == 1
assert len(env.agents) == 2
assert env.agents[0].topology == env.topologies['default']
assert env.agents[0].G == env.topologies['default']
def test_agents_from_config(self):
'''We test that the known complete configuration produces
@@ -87,12 +89,9 @@ class TestConfig(TestCase):
cfg = serialization.load_file(join(ROOT, "complete_converted.yml"))[0]
s = simulation.from_config(cfg)
env = s.get_env()
assert len(env.topologies['default'].nodes) == 10
assert len(env.agents(group='network')) == 10
assert len(env.topologies['default'].nodes) == 4
assert len(env.agents(group='network')) == 4
assert len(env.agents(group='environment')) == 1
assert sum(1 for a in env.agents(group='network', agent_class=agents.CounterModel)) == 4
assert sum(1 for a in env.agents(group='network', agent_class=agents.AggregatedCounter)) == 6
def test_yaml(self):
"""

View File

@@ -2,7 +2,7 @@ from unittest import TestCase
import os
from os.path import join
from soil import serialization, simulation
from soil import serialization, simulation, config
ROOT = os.path.abspath(os.path.dirname(__file__))
EXAMPLES = join(ROOT, '..', 'examples')
@@ -14,36 +14,37 @@ class TestExamples(TestCase):
pass
def make_example_test(path, config):
def make_example_test(path, cfg):
def wrapped(self):
root = os.getcwd()
for s in simulation.all_from_config(path):
iterations = s.config.general.max_time * s.config.general.num_trials
if iterations > 1000:
s.config.general.max_time = 100
s.config.general.num_trials = 1
if config.get('skip_test', False) and not FORCE_TESTS:
for s in simulation.iter_from_config(cfg):
iterations = s.max_steps * s.num_trials
if iterations < 0 or iterations > 1000:
s.max_steps = 100
s.num_trials = 1
assert isinstance(cfg, config.Config)
if getattr(cfg, 'skip_test', False) and not FORCE_TESTS:
self.skipTest('Example ignored.')
envs = s.run_simulation(dry_run=True)
assert envs
for env in envs:
assert env
try:
n = config['network_params']['n']
n = cfg.model_params['network_params']['n']
assert len(list(env.network_agents)) == n
assert env.now > 0 # It has run
assert env.now <= config['max_time'] # But not further than allowed
except KeyError:
pass
assert env.schedule.steps > 0 # It has run
assert env.schedule.steps <= s.max_steps # But not further than allowed
return wrapped
def add_example_tests():
for config, path in serialization.load_files(
for cfg, path in serialization.load_files(
join(EXAMPLES, '*', '*.yml'),
join(EXAMPLES, '*.yml'),
):
p = make_example_test(path=path, config=config)
p = make_example_test(path=path, cfg=config.Config.from_raw(cfg))
fname = os.path.basename(path)
p.__name__ = 'test_example_file_%s' % fname
p.__doc__ = '%s should be a valid configuration' % fname

View File

@@ -6,6 +6,8 @@ import shutil
from unittest import TestCase
from soil import exporters
from soil import simulation
from soil import agents
class Dummy(exporters.Exporter):
started = False
@@ -33,28 +35,36 @@ class Dummy(exporters.Exporter):
class Exporters(TestCase):
def test_basic(self):
# We need to add at least one agent to make sure the scheduler
# ticks every step
num_trials = 5
max_time = 2
config = {
'name': 'exporter_sim',
'network_params': {},
'agent_class': 'CounterModel',
'max_time': 2,
'num_trials': 5,
'environment_params': {}
'model_params': {
'agents': [{
'agent_class': agents.BaseAgent
}]
},
'max_time': max_time,
'num_trials': num_trials,
}
s = simulation.from_config(config)
for env in s.run_simulation(exporters=[Dummy], dry_run=True):
assert env.now <= 2
assert len(env.agents) == 1
assert env.now == max_time
assert Dummy.started
assert Dummy.ended
assert Dummy.called_start == 1
assert Dummy.called_end == 1
assert Dummy.called_trial == 5
assert Dummy.trials == 5
assert Dummy.total_time == 2*5
assert Dummy.called_trial == num_trials
assert Dummy.trials == num_trials
assert Dummy.total_time == max_time * num_trials
def test_writing(self):
'''Try to write CSV, GEXF, sqlite and YAML (without dry_run)'''
'''Try to write CSV, sqlite and YAML (without dry_run)'''
n_trials = 5
config = {
'name': 'exporter_sim',
@@ -74,7 +84,6 @@ class Exporters(TestCase):
envs = s.run_simulation(exporters=[
exporters.default,
exporters.csv,
exporters.gexf,
],
dry_run=False,
outdir=tmpdir,
@@ -88,11 +97,7 @@ class Exporters(TestCase):
try:
for e in envs:
with open(os.path.join(simdir, '{}.gexf'.format(e.name))) as f:
result = f.read()
assert result
with open(os.path.join(simdir, '{}.csv'.format(e.name))) as f:
with open(os.path.join(simdir, '{}.env.csv'.format(e.id))) as f:
result = f.read()
assert result
finally:

View File

@@ -1,128 +0,0 @@
from unittest import TestCase
import os
import io
import yaml
import copy
import pickle
import networkx as nx
from functools import partial
from os.path import join
from soil import (simulation, Environment, agents, serialization,
utils)
from soil.time import Delta
from tsih import NoHistory, History
ROOT = os.path.abspath(os.path.dirname(__file__))
EXAMPLES = join(ROOT, '..', 'examples')
class CustomAgent(agents.FSM):
@agents.default_state
@agents.state
def normal(self):
self.neighbors = self.count_agents(state_id='normal',
limit_neighbors=True)
@agents.state
def unreachable(self):
return
class TestHistory(TestCase):
def test_counter_agent_history(self):
"""
The evolution of the state should be recorded in the logging agent
"""
config = {
'name': 'CounterAgent',
'network_params': {
'path': join(ROOT, 'test.gexf')
},
'network_agents': [{
'agent_class': 'AggregatedCounter',
'weight': 1,
'state': {'state_id': 0}
}],
'max_time': 10,
'environment_params': {
}
}
s = simulation.from_config(config)
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 sorted(agent['total', None]):
assert total == last + 2
last = total
def test_row_conversion(self):
env = Environment(history=True)
env['test'] = 'test_value'
res = list(env.history_to_tuples())
assert len(res) == len(env.environment_params)
env.schedule.time = 1
env['test'] = 'second_value'
res = list(env.history_to_tuples())
assert env['env', 0, 'test' ] == 'test_value'
assert env['env', 1, 'test' ] == 'second_value'
def test_nohistory(self):
'''
Make sure that no history(/sqlite) is used by default
'''
env = Environment(topology=nx.Graph(), network_agents=[])
assert isinstance(env._history, NoHistory)
def test_save_graph_history(self):
'''
The history_to_graph method should return a valid networkx graph.
The state of the agent should be encoded as intervals in the nx graph.
'''
G = nx.cycle_graph(5)
distribution = agents.calculate_distribution(None, agents.BaseAgent)
env = Environment(topology=G, network_agents=distribution, history=True)
env[0, 0, 'testvalue'] = 'start'
env[0, 10, 'testvalue'] = 'finish'
nG = env.history_to_graph()
values = nG.nodes[0]['attr_testvalue']
assert ('start', 0, 10) in values
assert ('finish', 10, None) in values
def test_save_graph_nohistory(self):
'''
The history_to_graph method should return a valid networkx graph.
When NoHistory is used, only the last known value is known
'''
G = nx.cycle_graph(5)
distribution = agents.calculate_distribution(None, agents.BaseAgent)
env = Environment(topology=G, network_agents=distribution, history=False)
env.get_agent(0)['testvalue'] = 'start'
env.schedule.time = 10
env.get_agent(0)['testvalue'] = 'finish'
nG = env.history_to_graph()
values = nG.nodes[0]['attr_testvalue']
assert ('start', 0, None) not in values
assert ('finish', 10, None) in values
def test_pickle_agent_environment(self):
env = Environment(name='Test', history=True)
a = agents.BaseAgent(model=env, unique_id=25)
a['key'] = 'test'
pickled = pickle.dumps(a)
recovered = pickle.loads(pickled)
assert recovered.env.name == 'Test'
assert list(recovered.env._history.to_tuples())
assert recovered['key', 0] == 'test'
assert recovered['key'] == 'test'

View File

@@ -24,6 +24,7 @@ class CustomAgent(agents.FSM, agents.NetworkAgent):
def unreachable(self):
return
class TestMain(TestCase):
def test_empty_simulation(self):
@@ -79,20 +80,16 @@ class TestMain(TestCase):
}
},
'agents': {
'default': {
'agent_class': 'CounterModel',
},
'counters': {
'topology': 'default',
'fixed': [{'state': {'times': 10}}, {'state': {'times': 20}}],
}
'agent_class': 'CounterModel',
'topology': 'default',
'fixed': [{'state': {'times': 10}}, {'state': {'times': 20}}],
}
}
}
s = simulation.from_config(config)
env = s.get_env()
assert isinstance(env.agents[0], agents.CounterModel)
assert env.agents[0].topology == env.topologies['default']
assert env.agents[0].G == env.topologies['default']
assert env.agents[0]['times'] == 10
assert env.agents[0]['times'] == 10
env.step()
@@ -105,8 +102,8 @@ class TestMain(TestCase):
config = {
'max_time': 10,
'model_params': {
'agents': [(CustomAgent, {'weight': 1}),
(CustomAgent, {'weight': 3}),
'agents': [{'agent_class': CustomAgent, 'weight': 1, 'topology': 'default'},
{'agent_class': CustomAgent, 'weight': 3, 'topology': 'default'},
],
'topologies': {
'default': {
@@ -128,7 +125,7 @@ class TestMain(TestCase):
"""A complete example from a documentation should work."""
config = serialization.load_file(join(EXAMPLES, 'torvalds.yml'))[0]
config['model_params']['network_params']['path'] = join(EXAMPLES,
config['network_params']['path'])
config['model_params']['network_params']['path'])
s = simulation.from_config(config)
env = s.run_simulation(dry_run=True)[0]
for a in env.network_agents:
@@ -208,24 +205,6 @@ class TestMain(TestCase):
assert converted[1]['agent_class'] == 'test_main.CustomAgent'
pickle.dumps(converted)
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_class=agents.NetworkAgent)
distro[0]['topology'] = 'default'
aconfig = config.AgentConfig(distribution=distro, topology='default')
env = Environment(name='Test', topologies={'default': G}, agents={'network': aconfig})
lst = list(env.network_agents)
a2 = env.find_one(node_id=2)
a3 = env.find_one(node_id=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_class=agents.NetworkAgent)) == 3
def test_templates(self):
'''Loading a template should result in several configs'''
configs = serialization.load_file(join(EXAMPLES, 'template.yml'))
@@ -236,14 +215,18 @@ class TestMain(TestCase):
'name': 'until_sim',
'model_params': {
'network_params': {},
'agent_class': 'CounterModel',
'agents': {
'fixed': [{
'agent_class': agents.BaseAgent,
}]
},
},
'max_time': 2,
'num_trials': 50,
}
s = simulation.from_config(config)
runs = list(s.run_simulation(dry_run=True))
over = list(x.now for x in runs if x.now>2)
over = list(x.now for x in runs if x.now > 2)
assert len(runs) == config['num_trials']
assert len(over) == 0

View File

@@ -6,7 +6,8 @@ import networkx as nx
from os.path import join
from soil import network, environment
from soil import config, network, environment, agents, simulation
from test_main import CustomAgent
ROOT = os.path.abspath(os.path.dirname(__file__))
EXAMPLES = join(ROOT, '..', 'examples')
@@ -60,22 +61,53 @@ class TestNetwork(TestCase):
G = nx.random_geometric_graph(20, 0.1)
env = environment.NetworkEnvironment(topology=G)
f = io.BytesIO()
env.dump_gexf(f)
assert env.topologies['default']
network.dump_gexf(env.topologies['default'], f)
def test_networkenvironment_creation(self):
"""Networkenvironment should accept netconfig as parameters"""
model_params = {
'topologies': {
'default': {
'path': join(ROOT, 'test.gexf')
}
},
'agents': {
'topology': 'default',
'distribution': [{
'agent_class': CustomAgent,
}]
}
}
env = environment.Environment(**model_params)
assert env.topologies
env.step()
assert len(env.topologies['default']) == 2
assert len(env.agents) == 2
assert env.agents[1].count_agents(state_id='normal') == 2
assert env.agents[1].count_agents(state_id='normal', limit_neighbors=True) == 1
assert env.agents[0].neighbors == 1
def test_custom_agent_neighbors(self):
"""Allow for search of neighbors with a certain state_id"""
config = {
'network_params': {
'path': join(ROOT, 'test.gexf')
'model_params': {
'topologies': {
'default': {
'path': join(ROOT, 'test.gexf')
}
},
'agents': {
'topology': 'default',
'distribution': [
{
'weight': 1,
'agent_class': CustomAgent
}
]
}
},
'network_agents': [{
'agent_class': CustomAgent,
'weight': 1
}],
'max_time': 10,
'environment_params': {
}
}
s = simulation.from_config(config)
env = s.run_simulation(dry_run=True)[0]
@@ -83,3 +115,19 @@ class TestNetwork(TestCase):
assert env.agents[1].count_agents(state_id='normal', limit_neighbors=True) == 1
assert env.agents[0].neighbors == 1
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_class=agents.NetworkAgent)
aconfig = config.AgentConfig(distribution=distro, topology='default')
env = environment.Environment(name='Test', topologies={'default': G}, agents=aconfig)
lst = list(env.network_agents)
a2 = env.find_one(node_id=2)
a3 = env.find_one(node_id=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_class=agents.NetworkAgent)) == 3