1
0
mirror of https://github.com/gsi-upm/soil synced 2025-08-23 19:52:19 +00:00
This commit is contained in:
J. Fernando Sánchez
2022-10-06 15:49:10 +02:00
parent 0a9c6d8b19
commit f811ee18c5
53 changed files with 856 additions and 774 deletions

View File

@@ -10,21 +10,21 @@ network_params:
generator: complete_graph
n: 10
network_agents:
- agent_type: CounterModel
- agent_class: CounterModel
weight: 0.4
state:
state_id: 0
- agent_type: AggregatedCounter
- agent_class: AggregatedCounter
weight: 0.6
environment_agents:
- agent_id: 'Environment Agent 1'
agent_type: CounterModel
agent_class: CounterModel
state:
times: 10
environment_class: Environment
environment_params:
am_i_complete: true
agent_type: CounterModel
agent_class: CounterModel
default_state:
times: 1
states:

View File

@@ -46,7 +46,7 @@ class TestAnalysis(TestCase):
'generator': 'complete_graph',
'n': 2
},
'agent_type': Ping,
'agent_class': Ping,
'states': [{'interval': 1}, {'interval': 2}],
'max_time': 30,
'num_trials': 1,

View File

@@ -1,8 +1,10 @@
from unittest import TestCase
import os
import yaml
import copy
from os.path import join
from soil import simulation, serialization, config, network, agents
from soil import simulation, serialization, config, network, agents, utils
ROOT = os.path.abspath(os.path.dirname(__file__))
EXAMPLES = join(ROOT, '..', 'examples')
@@ -10,6 +12,17 @@ EXAMPLES = join(ROOT, '..', 'examples')
FORCE_TESTS = os.environ.get('FORCE_TESTS', '')
def isequal(a, b):
if isinstance(a, dict):
for (k, v) in a.items():
if v:
isequal(a[k], b[k])
else:
assert not b.get(k, None)
return
assert a == b
class TestConfig(TestCase):
def test_conversion(self):
@@ -18,18 +31,23 @@ class TestConfig(TestCase):
converted_defaults = config.convert_old(old, strict=False)
converted = converted_defaults.dict(skip_defaults=True)
def isequal(a, b):
if isinstance(a, dict):
for (k, v) in a.items():
if v:
isequal(a[k], b[k])
else:
assert not b.get(k, None)
return
assert a == b
isequal(converted, expected)
def test_configuration_changes(self):
"""
The configuration should not change after running
the simulation.
"""
config = serialization.load_file(join(EXAMPLES, 'complete.yml'))[0]
s = simulation.from_config(config)
init_config = copy.copy(s.config)
s.run_simulation(dry_run=True)
nconfig = s.config
# del nconfig['to
isequal(init_config, nconfig)
def test_topology_config(self):
netconfig = config.NetConfig(**{
'path': join(ROOT, 'test.gexf')
@@ -48,7 +66,7 @@ class TestConfig(TestCase):
'network_params': {
'path': join(ROOT, 'test.gexf')
},
'agent_type': 'CounterModel',
'agent_class': 'CounterModel',
# 'states': [{'times': 10}, {'times': 20}],
'max_time': 2,
'dry_run': True,
@@ -63,7 +81,6 @@ class TestConfig(TestCase):
assert len(env.agents) == 2
assert env.agents[0].topology == env.topologies['default']
def test_agents_from_config(self):
'''We test that the known complete configuration produces
the right agents in the right groups'''
@@ -74,8 +91,25 @@ class TestConfig(TestCase):
assert len(env.agents(group='network')) == 10
assert len(env.agents(group='environment')) == 1
assert sum(1 for a in env.agents(group='network', agent_type=agents.CounterModel)) == 4
assert sum(1 for a in env.agents(group='network', agent_type=agents.AggregatedCounter)) == 6
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):
"""
The YAML version of a newly created configuration should be equivalent
to the configuration file used.
Values not present in the original config file should have reasonable
defaults.
"""
with utils.timer('loading'):
config = serialization.load_file(join(EXAMPLES, 'complete.yml'))[0]
s = simulation.from_config(config)
with utils.timer('serializing'):
serial = s.to_yaml()
with utils.timer('recovering'):
recovered = yaml.load(serial, Loader=yaml.SafeLoader)
for (k, v) in config.items():
assert recovered[k] == v
def make_example_test(path, cfg):
def wrapped(self):

View File

@@ -36,7 +36,7 @@ class Exporters(TestCase):
config = {
'name': 'exporter_sim',
'network_params': {},
'agent_type': 'CounterModel',
'agent_class': 'CounterModel',
'max_time': 2,
'num_trials': 5,
'environment_params': {}
@@ -62,7 +62,7 @@ class Exporters(TestCase):
'generator': 'complete_graph',
'n': 4
},
'agent_type': 'CounterModel',
'agent_class': 'CounterModel',
'max_time': 2,
'num_trials': n_trials,
'dry_run': False,

View File

@@ -41,7 +41,7 @@ class TestHistory(TestCase):
'path': join(ROOT, 'test.gexf')
},
'network_agents': [{
'agent_type': 'AggregatedCounter',
'agent_class': 'AggregatedCounter',
'weight': 1,
'state': {'state_id': 0}

View File

@@ -1,9 +1,6 @@
from unittest import TestCase
import os
import io
import yaml
import copy
import pickle
import networkx as nx
from functools import partial
@@ -29,56 +26,17 @@ class CustomAgent(agents.FSM, agents.NetworkAgent):
class TestMain(TestCase):
def test_load_graph(self):
"""
Load a graph from file if the extension is known.
Raise an exception otherwise.
"""
config = {
'network_params': {
'path': join(ROOT, 'test.gexf')
}
}
G = network.from_config(config['network_params'])
assert G
assert len(G) == 2
with self.assertRaises(AttributeError):
config = {
'network_params': {
'path': join(ROOT, 'unknown.extension')
}
}
G = network.from_config(config['network_params'])
print(G)
def test_generate_barabasi(self):
"""
If no path is given, a generator and network parameters
should be used to generate a network
"""
cfg = {
'params': {
'generator': 'barabasi_albert_graph'
}
}
with self.assertRaises(Exception):
G = network.from_config(cfg)
cfg['params']['n'] = 100
cfg['params']['m'] = 10
G = network.from_config(cfg)
assert len(G) == 100
def test_empty_simulation(self):
"""A simulation with a base behaviour should do nothing"""
config = {
'network_params': {
'path': join(ROOT, 'test.gexf')
},
'agent_type': 'BaseAgent',
'environment_params': {
'model_params': {
'network_params': {
'path': join(ROOT, 'test.gexf')
},
'agent_class': 'BaseAgent',
}
}
s = simulation.from_old_config(config)
s = simulation.from_config(config)
s.run_simulation(dry_run=True)
@@ -88,21 +46,21 @@ class TestMain(TestCase):
agent should be able to update its state."""
config = {
'name': 'CounterAgent',
'network_params': {
'generator': nx.complete_graph,
'n': 2,
},
'agent_type': 'CounterModel',
'states': {
0: {'times': 10},
1: {'times': 20},
},
'max_time': 2,
'num_trials': 1,
'environment_params': {
'max_time': 2,
'model_params': {
'network_params': {
'generator': nx.complete_graph,
'n': 2,
},
'agent_class': 'CounterModel',
'states': {
0: {'times': 10},
1: {'times': 20},
},
}
}
s = simulation.from_old_config(config)
s = simulation.from_config(config)
def test_counter_agent(self):
"""
@@ -110,24 +68,24 @@ class TestMain(TestCase):
agent should be able to update its state."""
config = {
'version': '2',
'general': {
'name': 'CounterAgent',
'max_time': 2,
'dry_run': True,
'num_trials': 1,
},
'topologies': {
'default': {
'path': join(ROOT, 'test.gexf')
}
},
'agents': {
'default': {
'agent_class': 'CounterModel',
'name': 'CounterAgent',
'dry_run': True,
'num_trials': 1,
'max_time': 2,
'model_params': {
'topologies': {
'default': {
'path': join(ROOT, 'test.gexf')
}
},
'counters': {
'topology': 'default',
'fixed': [{'state': {'times': 10}}, {'state': {'times': 20}}],
'agents': {
'default': {
'agent_class': 'CounterModel',
},
'counters': {
'topology': 'default',
'fixed': [{'state': {'times': 10}}, {'state': {'times': 20}}],
}
}
}
}
@@ -141,33 +99,37 @@ class TestMain(TestCase):
assert env.agents[0]['times'] == 11
assert env.agents[1]['times'] == 21
def test_custom_agent(self):
"""Allow for search of neighbors with a certain state_id"""
def test_init_and_count_agents(self):
"""Agents should be properly initialized and counting should filter them properly"""
#TODO: separate this test into two or more test cases
config = {
'network_params': {
'path': join(ROOT, 'test.gexf')
},
'network_agents': [{
'agent_type': CustomAgent,
'weight': 1
}],
'max_time': 10,
'environment_params': {
}
'model_params': {
'agents': [(CustomAgent, {'weight': 1}),
(CustomAgent, {'weight': 3}),
],
'topologies': {
'default': {
'path': join(ROOT, 'test.gexf')
}
},
},
}
s = simulation.from_old_config(config)
s = simulation.from_config(config)
env = s.run_simulation(dry_run=True)[0]
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
assert env.agents[0].weight == 1
assert env.count_agents() == 2
assert env.count_agents(weight=1) == 1
assert env.count_agents(weight=3) == 1
assert env.count_agents(agent_class=CustomAgent) == 2
def test_torvalds_example(self):
"""A complete example from a documentation should work."""
config = serialization.load_file(join(EXAMPLES, 'torvalds.yml'))[0]
config['network_params']['path'] = join(EXAMPLES,
config['model_params']['network_params']['path'] = join(EXAMPLES,
config['network_params']['path'])
s = simulation.from_old_config(config)
s = simulation.from_config(config)
env = s.run_simulation(dry_run=True)[0]
for a in env.network_agents:
skill_level = a.state['skill_level']
@@ -184,47 +146,6 @@ class TestMain(TestCase):
assert a.state['total'] == 3
assert a.state['neighbors'] == 1
def test_yaml(self):
"""
The YAML version of a newly created configuration should be equivalent
to the configuration file used.
Values not present in the original config file should have reasonable
defaults.
"""
with utils.timer('loading'):
config = serialization.load_file(join(EXAMPLES, 'complete.yml'))[0]
s = simulation.from_old_config(config)
with utils.timer('serializing'):
serial = s.to_yaml()
with utils.timer('recovering'):
recovered = yaml.load(serial, Loader=yaml.SafeLoader)
for (k, v) in config.items():
assert recovered[k] == v
def test_configuration_changes(self):
"""
The configuration should not change after running
the simulation.
"""
config = serialization.load_file(join(EXAMPLES, 'complete.yml'))[0]
s = simulation.from_old_config(config)
init_config = copy.copy(s.config)
s.run_simulation(dry_run=True)
nconfig = s.config
# del nconfig['to
assert init_config == nconfig
def test_save_geometric(self):
"""
There is a bug in networkx that prevents it from creating a GEXF file
from geometric models. We should work around it.
"""
G = nx.random_geometric_graph(20, 0.1)
env = Environment(topology=G)
f = io.BytesIO()
env.dump_gexf(f)
def test_serialize_class(self):
ser, name = serialization.serialize(agents.BaseAgent, known_modules=[])
assert name == 'soil.agents.BaseAgent'
@@ -247,7 +168,7 @@ class TestMain(TestCase):
des = serialization.deserialize(name, ser)
assert i == des
def test_serialize_agent_type(self):
def test_serialize_agent_class(self):
'''A class from soil.agents should be serialized without the module part'''
ser = agents.serialize_type(CustomAgent)
assert ser == 'test_main.CustomAgent'
@@ -258,33 +179,33 @@ class TestMain(TestCase):
def test_deserialize_agent_distribution(self):
agent_distro = [
{
'agent_type': 'CounterModel',
'agent_class': 'CounterModel',
'weight': 1
},
{
'agent_type': 'test_main.CustomAgent',
'agent_class': 'test_main.CustomAgent',
'weight': 2
},
]
converted = agents.deserialize_definition(agent_distro)
assert converted[0]['agent_type'] == agents.CounterModel
assert converted[1]['agent_type'] == CustomAgent
assert converted[0]['agent_class'] == agents.CounterModel
assert converted[1]['agent_class'] == CustomAgent
pickle.dumps(converted)
def test_serialize_agent_distribution(self):
agent_distro = [
{
'agent_type': agents.CounterModel,
'agent_class': agents.CounterModel,
'weight': 1
},
{
'agent_type': CustomAgent,
'agent_class': CustomAgent,
'weight': 2
},
]
converted = agents.serialize_definition(agent_distro)
assert converted[0]['agent_type'] == 'CounterModel'
assert converted[1]['agent_type'] == 'test_main.CustomAgent'
assert converted[0]['agent_class'] == 'CounterModel'
assert converted[1]['agent_class'] == 'test_main.CustomAgent'
pickle.dumps(converted)
def test_subgraph(self):
@@ -292,7 +213,7 @@ class TestMain(TestCase):
G = nx.Graph()
G.add_node(3)
G.add_edge(1, 2)
distro = agents.calculate_distribution(agent_type=agents.NetworkAgent)
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})
@@ -303,7 +224,7 @@ class TestMain(TestCase):
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
assert len(a3.subgraph(agent_class=agents.NetworkAgent)) == 3
def test_templates(self):
'''Loading a template should result in several configs'''
@@ -313,19 +234,19 @@ class TestMain(TestCase):
def test_until(self):
config = {
'name': 'until_sim',
'network_params': {},
'agent_type': 'CounterModel',
'model_params': {
'network_params': {},
'agent_class': 'CounterModel',
},
'max_time': 2,
'num_trials': 50,
'environment_params': {}
}
s = simulation.from_old_config(config)
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)
assert len(runs) == config['num_trials']
assert len(over) == 0
def test_fsm(self):
'''Basic state change'''
class ToggleAgent(agents.FSM):

85
tests/test_network.py Normal file
View File

@@ -0,0 +1,85 @@
from unittest import TestCase
import io
import os
import networkx as nx
from os.path import join
from soil import network, environment
ROOT = os.path.abspath(os.path.dirname(__file__))
EXAMPLES = join(ROOT, '..', 'examples')
class TestNetwork(TestCase):
def test_load_graph(self):
"""
Load a graph from file if the extension is known.
Raise an exception otherwise.
"""
config = {
'network_params': {
'path': join(ROOT, 'test.gexf')
}
}
G = network.from_config(config['network_params'])
assert G
assert len(G) == 2
with self.assertRaises(AttributeError):
config = {
'network_params': {
'path': join(ROOT, 'unknown.extension')
}
}
G = network.from_config(config['network_params'])
print(G)
def test_generate_barabasi(self):
"""
If no path is given, a generator and network parameters
should be used to generate a network
"""
cfg = {
'params': {
'generator': 'barabasi_albert_graph'
}
}
with self.assertRaises(Exception):
G = network.from_config(cfg)
cfg['params']['n'] = 100
cfg['params']['m'] = 10
G = network.from_config(cfg)
assert len(G) == 100
def test_save_geometric(self):
"""
There is a bug in networkx that prevents it from creating a GEXF file
from geometric models. We should work around it.
"""
G = nx.random_geometric_graph(20, 0.1)
env = environment.NetworkEnvironment(topology=G)
f = io.BytesIO()
env.dump_gexf(f)
def test_custom_agent_neighbors(self):
"""Allow for search of neighbors with a certain state_id"""
config = {
'network_params': {
'path': join(ROOT, 'test.gexf')
},
'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]
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