mirror of
https://github.com/gsi-upm/soil
synced 2025-08-24 03:52:20 +00:00
Large set of changes for v0.30
The examples weren't being properly tested in the last commit. When we fixed that a lot of bugs in the new implementation of environment and agent were found, which accounts for most of these changes. The main difference is the mechanism to load simulations from a configuration file. For that to work, we had to rework our module loading code in `serialization` and add a `source_file` attribute to configurations (and simulations, for that matter).
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
---
|
||||
version: '2'
|
||||
name: simple
|
||||
group: tests
|
||||
dir_path: "/tmp/"
|
||||
num_trials: 3
|
||||
max_time: 100
|
||||
interval: 1
|
||||
seed: "CompleteSeed!"
|
||||
model_class: Environment
|
||||
model_params:
|
||||
topology:
|
||||
params:
|
||||
generator: complete_graph
|
||||
n: 4
|
||||
agents:
|
||||
agent_class: CounterModel
|
||||
state:
|
||||
group: network
|
||||
times: 1
|
||||
topology: true
|
||||
distribution:
|
||||
- agent_class: CounterModel
|
||||
weight: 0.25
|
||||
state:
|
||||
state_id: 0
|
||||
times: 1
|
||||
- agent_class: AggregatedCounter
|
||||
weight: 0.5
|
||||
state:
|
||||
times: 2
|
||||
override:
|
||||
- filter:
|
||||
node_id: 1
|
||||
state:
|
||||
name: 'Node 1'
|
||||
- filter:
|
||||
node_id: 2
|
||||
state:
|
||||
name: 'Node 2'
|
||||
fixed:
|
||||
- agent_class: BaseAgent
|
||||
hidden: true
|
||||
topology: false
|
||||
state:
|
||||
name: 'Environment Agent 1'
|
||||
times: 10
|
||||
group: environment
|
||||
am_i_complete: true
|
@@ -1,37 +0,0 @@
|
||||
---
|
||||
name: simple
|
||||
group: tests
|
||||
dir_path: "/tmp/"
|
||||
num_trials: 3
|
||||
max_time: 100
|
||||
interval: 1
|
||||
seed: "CompleteSeed!"
|
||||
network_params:
|
||||
generator: complete_graph
|
||||
n: 4
|
||||
network_agents:
|
||||
- agent_class: CounterModel
|
||||
weight: 0.25
|
||||
state:
|
||||
state_id: 0
|
||||
times: 1
|
||||
- agent_class: AggregatedCounter
|
||||
weight: 0.5
|
||||
state:
|
||||
times: 2
|
||||
environment_agents:
|
||||
- agent_id: 'Environment Agent 1'
|
||||
agent_class: BaseAgent
|
||||
state:
|
||||
times: 10
|
||||
environment_class: Environment
|
||||
environment_params:
|
||||
am_i_complete: true
|
||||
agent_class: CounterModel
|
||||
default_state:
|
||||
times: 1
|
||||
states:
|
||||
1:
|
||||
name: 'Node 1'
|
||||
2:
|
||||
name: 'Node 2'
|
@@ -22,7 +22,9 @@ class TestAgents(TestCase):
|
||||
def test_die_raises_exception(self):
|
||||
"""A dead agent should raise an exception if it is stepped after death"""
|
||||
d = Dead(unique_id=0, model=environment.Environment())
|
||||
assert d.alive
|
||||
d.step()
|
||||
assert not d.alive
|
||||
with pytest.raises(stime.DeadAgent):
|
||||
d.step()
|
||||
|
||||
@@ -161,3 +163,15 @@ class TestAgents(TestCase):
|
||||
assert sum(pings) == sum(range(time)) * 2
|
||||
# It is the same as pings, without the leading 0
|
||||
assert sum(pongs) == sum(range(time)) * 2
|
||||
|
||||
def test_agent_filter(self):
|
||||
e = environment.Environment()
|
||||
e.add_agent(agent_class=agents.BaseAgent)
|
||||
e.add_agent(agent_class=agents.Evented)
|
||||
base = list(e.agents(agent_class=agents.BaseAgent))
|
||||
assert len(base) == 2
|
||||
ev = list(e.agents(agent_class=agents.Evented))
|
||||
assert len(ev) == 1
|
||||
assert ev[0].unique_id == 1
|
||||
null = list(e.agents(unique_ids=[0, 1], agent_class=agents.NetworkAgent))
|
||||
assert not null
|
@@ -23,86 +23,18 @@ def isequal(a, b):
|
||||
assert a == b
|
||||
|
||||
|
||||
@skip("new versions of soil do not rely on configuration files")
|
||||
# @skip("new versions of soil do not rely on configuration files")
|
||||
class TestConfig(TestCase):
|
||||
def test_conversion(self):
|
||||
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(exclude_unset=True)
|
||||
|
||||
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.to_dict())
|
||||
|
||||
s.run_simulation(dry_run=True)
|
||||
nconfig = s.to_dict()
|
||||
# del nconfig['to
|
||||
isequal(init_config, nconfig)
|
||||
|
||||
def test_topology_config(self):
|
||||
netconfig = config.NetConfig(**{"path": join(ROOT, "test.gexf")})
|
||||
net = network.from_config(netconfig, dir_path=ROOT)
|
||||
assert len(net.nodes) == 2
|
||||
assert len(net.edges) == 1
|
||||
|
||||
def test_env_from_config(self):
|
||||
"""
|
||||
Simple configuration that tests that the graph is loaded, and that
|
||||
network agents are initialized properly.
|
||||
"""
|
||||
cfg = {
|
||||
"name": "CounterAgent",
|
||||
"model_params": {
|
||||
"topology": join(ROOT, "test.gexf"),
|
||||
"agent_class": "CounterModel",
|
||||
},
|
||||
# 'states': [{'times': 10}, {'times': 20}],
|
||||
"max_time": 2,
|
||||
"dry_run": True,
|
||||
"num_trials": 1,
|
||||
}
|
||||
s = simulation.from_config(cfg)
|
||||
|
||||
env = s.get_env()
|
||||
assert len(env.G.nodes) == 2
|
||||
assert len(env.G.edges) == 1
|
||||
assert len(env.agents) == 2
|
||||
assert env.agents[0].G == env.G
|
||||
|
||||
def test_agents_from_config(self):
|
||||
"""We test that the known complete configuration produces
|
||||
the right agents in the right groups"""
|
||||
cfg = serialization.load_file(join(ROOT, "complete_converted.yml"))[0]
|
||||
s = simulation.from_config(cfg)
|
||||
env = s.get_env()
|
||||
assert len(env.G.nodes) == 4
|
||||
assert len(env.agents(group="network")) == 4
|
||||
assert len(env.agents(group="environment")) == 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_config(config)
|
||||
with utils.timer("serializing"):
|
||||
serial = s.to_yaml()
|
||||
with utils.timer("recovering"):
|
||||
recovered = yaml.load(serial, Loader=yaml.FullLoader)
|
||||
for (k, v) in config.items():
|
||||
assert recovered[k] == v
|
||||
def test_torvalds_config(self):
|
||||
sim = simulation.from_config(os.path.join(ROOT, "test_config.yml"))
|
||||
assert sim.interval == 2
|
||||
envs = sim.run()
|
||||
assert len(envs) == 1
|
||||
env = envs[0]
|
||||
assert env.interval == 2
|
||||
assert env.count_agents() == 3
|
||||
assert env.now == 20
|
||||
|
||||
|
||||
def make_example_test(path, cfg):
|
||||
@@ -116,7 +48,7 @@ def make_example_test(path, cfg):
|
||||
s.num_trials = 1
|
||||
if cfg.skip_test and not FORCE_TESTS:
|
||||
self.skipTest('Example ignored.')
|
||||
envs = s.run_simulation(dry_run=True)
|
||||
envs = s.run_simulation(dump=False)
|
||||
assert envs
|
||||
for env in envs:
|
||||
assert env
|
||||
|
5
tests/test_config.yml
Normal file
5
tests/test_config.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source_file: "../examples/torvalds_sim.py"
|
||||
model: "TorvaldsEnv"
|
||||
max_steps: 10
|
||||
interval: 2
|
@@ -1,9 +1,12 @@
|
||||
from unittest import TestCase
|
||||
from unittest.case import SkipTest
|
||||
|
||||
import os
|
||||
from os.path import join
|
||||
from glob import glob
|
||||
|
||||
from soil import simulation, config, do_not_run
|
||||
|
||||
from soil import simulation
|
||||
|
||||
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||
EXAMPLES = join(ROOT, "..", "examples")
|
||||
@@ -16,44 +19,54 @@ class TestExamples(TestCase):
|
||||
pass
|
||||
|
||||
|
||||
def get_test_for_sim(sim, path):
|
||||
def get_test_for_sims(sims, path):
|
||||
root = os.getcwd()
|
||||
iterations = sim.max_steps * sim.num_trials
|
||||
if iterations < 0 or iterations > 1000:
|
||||
sim.max_steps = 100
|
||||
sim.num_trials = 1
|
||||
|
||||
def wrapped(self):
|
||||
envs = sim.run_simulation(dry_run=True)
|
||||
assert envs
|
||||
for env in envs:
|
||||
assert env
|
||||
try:
|
||||
n = sim.model_params["network_params"]["n"]
|
||||
assert len(list(env.network_agents)) == n
|
||||
except KeyError:
|
||||
pass
|
||||
assert env.schedule.steps > 0 # It has run
|
||||
assert env.schedule.steps <= sim.max_steps # But not further than allowed
|
||||
run = False
|
||||
for sim in sims:
|
||||
if sim.skip_test and not FORCE_TESTS:
|
||||
continue
|
||||
run = True
|
||||
iterations = sim.max_steps * sim.num_trials
|
||||
if iterations < 0 or iterations > 1000:
|
||||
sim.max_steps = 100
|
||||
sim.num_trials = 1
|
||||
envs = sim.run_simulation(dump=False)
|
||||
assert envs
|
||||
for env in envs:
|
||||
assert env
|
||||
assert env.now > 0
|
||||
try:
|
||||
n = sim.model_params["network_params"]["n"]
|
||||
assert len(list(env.network_agents)) == n
|
||||
except KeyError:
|
||||
pass
|
||||
assert env.schedule.steps > 0 # It has run
|
||||
assert env.schedule.steps <= sim.max_steps # But not further than allowed
|
||||
if not run:
|
||||
raise SkipTest("Example ignored because all simulations are set up to be skipped.")
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def add_example_tests():
|
||||
sim_paths = []
|
||||
sim_paths = {}
|
||||
for path in glob(join(EXAMPLES, '**', '*.yml')):
|
||||
if "soil_output" in path:
|
||||
continue
|
||||
if path not in sim_paths:
|
||||
sim_paths[path] = []
|
||||
for sim in simulation.iter_from_config(path):
|
||||
sim_paths.append((sim, path))
|
||||
sim_paths[path].append(sim)
|
||||
for path in glob(join(EXAMPLES, '**', '*_sim.py')):
|
||||
if path not in sim_paths:
|
||||
sim_paths[path] = []
|
||||
for sim in simulation.iter_from_py(path):
|
||||
sim_paths.append((sim, path))
|
||||
sim_paths[path].append(sim)
|
||||
|
||||
for (sim, path) in sim_paths:
|
||||
if sim.skip_test and not FORCE_TESTS:
|
||||
continue
|
||||
test_case = get_test_for_sim(sim, path)
|
||||
for (path, sims) in sim_paths.items():
|
||||
test_case = get_test_for_sims(sims, path)
|
||||
fname = os.path.basename(path)
|
||||
test_case.__name__ = "test_example_file_%s" % fname
|
||||
test_case.__doc__ = "%s should be a valid configuration" % fname
|
||||
|
@@ -10,6 +10,8 @@ from soil import environment
|
||||
from soil import simulation
|
||||
from soil import agents
|
||||
|
||||
from mesa import Agent as MesaAgent
|
||||
|
||||
|
||||
class Dummy(exporters.Exporter):
|
||||
started = False
|
||||
@@ -41,14 +43,15 @@ class Exporters(TestCase):
|
||||
# ticks every step
|
||||
class SimpleEnv(environment.Environment):
|
||||
def init(self):
|
||||
self.add_agent(agent_class=agents.BaseAgent)
|
||||
self.add_agent(agent_class=MesaAgent)
|
||||
|
||||
|
||||
num_trials = 5
|
||||
max_time = 2
|
||||
s = simulation.Simulation(num_trials=num_trials, max_time=max_time, name="exporter_sim", dry_run=True, model=SimpleEnv)
|
||||
s = simulation.Simulation(num_trials=num_trials, max_time=max_time, name="exporter_sim",
|
||||
dump=False, model=SimpleEnv)
|
||||
|
||||
for env in s.run_simulation(exporters=[Dummy], dry_run=True):
|
||||
for env in s.run_simulation(exporters=[Dummy], dump=False):
|
||||
assert len(env.agents) == 1
|
||||
|
||||
assert Dummy.started
|
||||
@@ -60,18 +63,20 @@ class Exporters(TestCase):
|
||||
assert Dummy.total_time == max_time * num_trials
|
||||
|
||||
def test_writing(self):
|
||||
"""Try to write CSV, sqlite and YAML (without dry_run)"""
|
||||
"""Try to write CSV, sqlite and YAML (without no_dump)"""
|
||||
n_trials = 5
|
||||
n_nodes = 4
|
||||
max_time = 2
|
||||
config = {
|
||||
"name": "exporter_sim",
|
||||
"model_params": {
|
||||
"network_generator": "complete_graph",
|
||||
"network_params": {"n": 4},
|
||||
"network_params": {"n": n_nodes},
|
||||
"agent_class": "CounterModel",
|
||||
},
|
||||
"max_time": 2,
|
||||
"max_time": max_time,
|
||||
"num_trials": n_trials,
|
||||
"dry_run": False,
|
||||
"dump": True,
|
||||
}
|
||||
output = io.StringIO()
|
||||
s = simulation.from_config(config)
|
||||
@@ -87,7 +92,7 @@ class Exporters(TestCase):
|
||||
"constant": lambda x: 1,
|
||||
},
|
||||
},
|
||||
dry_run=False,
|
||||
dump=True,
|
||||
outdir=tmpdir,
|
||||
exporter_params={"copy_to": output},
|
||||
)
|
||||
@@ -100,12 +105,13 @@ class Exporters(TestCase):
|
||||
|
||||
try:
|
||||
for e in envs:
|
||||
db = sqlite3.connect(os.path.join(simdir, f"{s.name}.sqlite"))
|
||||
dbpath = os.path.join(simdir, f"{s.name}.sqlite")
|
||||
db = sqlite3.connect(dbpath)
|
||||
cur = db.cursor()
|
||||
agent_entries = cur.execute("SELECT * from agents").fetchall()
|
||||
env_entries = cur.execute("SELECT * from env").fetchall()
|
||||
assert len(agent_entries) > 0
|
||||
assert len(env_entries) > 0
|
||||
agent_entries = cur.execute("SELECT times FROM agents WHERE times > 0").fetchall()
|
||||
env_entries = cur.execute("SELECT constant from env WHERE constant == 1").fetchall()
|
||||
assert len(agent_entries) == n_nodes * n_trials * max_time
|
||||
assert len(env_entries) == n_trials * max_time
|
||||
|
||||
with open(os.path.join(simdir, "{}.env.csv".format(e.id))) as f:
|
||||
result = f.read()
|
||||
|
@@ -6,9 +6,11 @@ import networkx as nx
|
||||
from functools import partial
|
||||
|
||||
from os.path import join
|
||||
from soil import simulation, Environment, agents, network, serialization, utils, config
|
||||
from soil import simulation, Environment, agents, network, serialization, utils, config, from_file
|
||||
from soil.time import Delta
|
||||
|
||||
from mesa import Agent as MesaAgent
|
||||
|
||||
ROOT = os.path.abspath(os.path.dirname(__file__))
|
||||
EXAMPLES = join(ROOT, "..", "examples")
|
||||
|
||||
@@ -30,11 +32,12 @@ class TestMain(TestCase):
|
||||
config = {
|
||||
"model_params": {
|
||||
"topology": join(ROOT, "test.gexf"),
|
||||
"agent_class": "NetworkAgent",
|
||||
}
|
||||
"agent_class": MesaAgent,
|
||||
},
|
||||
"max_time": 1
|
||||
}
|
||||
s = simulation.from_config(config)
|
||||
s.run_simulation(dry_run=True)
|
||||
s.run_simulation(dump=False)
|
||||
|
||||
def test_network_agent(self):
|
||||
"""
|
||||
@@ -75,7 +78,6 @@ class TestMain(TestCase):
|
||||
|
||||
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
|
||||
env = Environment(topology=join(ROOT, "test.gexf"))
|
||||
env.populate_network([CustomAgent.w(weight=1), CustomAgent.w(weight=3)])
|
||||
assert env.agents[0].weight == 1
|
||||
@@ -91,7 +93,7 @@ class TestMain(TestCase):
|
||||
try:
|
||||
os.chdir(os.path.dirname(pyfile))
|
||||
s = simulation.from_py(pyfile)
|
||||
env = s.run_simulation(dry_run=True)[0]
|
||||
env = s.run_simulation(dump=False)[0]
|
||||
for a in env.network_agents:
|
||||
skill_level = a["skill_level"]
|
||||
if a.node_id == "Torvalds":
|
||||
@@ -151,7 +153,6 @@ class TestMain(TestCase):
|
||||
def step(self):
|
||||
nonlocal n_runs
|
||||
n_runs += 1
|
||||
return super().step()
|
||||
|
||||
n_trials = 50
|
||||
max_time = 2
|
||||
@@ -160,7 +161,7 @@ class TestMain(TestCase):
|
||||
num_trials=n_trials,
|
||||
max_time=max_time,
|
||||
)
|
||||
runs = list(s.run_simulation(dry_run=True))
|
||||
runs = list(s.run_simulation(dump=False))
|
||||
over = list(x.now for x in runs if x.now > 2)
|
||||
assert len(runs) == n_trials
|
||||
assert len(over) == 0
|
||||
@@ -203,3 +204,21 @@ class TestMain(TestCase):
|
||||
assert when == 2
|
||||
when = a.step()
|
||||
assert when == Delta(a.interval)
|
||||
|
||||
def test_load_sim(self):
|
||||
"""Make sure at least one of the examples can be loaded"""
|
||||
sims = from_file(os.path.join(EXAMPLES, "newsspread", "newsspread_sim.py"))
|
||||
assert len(sims) == 3*3*2
|
||||
for sim in sims:
|
||||
assert sim
|
||||
assert sim.name == "newspread_sim"
|
||||
assert sim.num_trials == 5
|
||||
assert sim.max_steps == 300
|
||||
assert not sim.dump
|
||||
assert sim.model_params
|
||||
assert "ratio_dumb" in sim.model_params
|
||||
assert "ratio_herd" in sim.model_params
|
||||
assert "ratio_wise" in sim.model_params
|
||||
assert "network_generator" in sim.model_params
|
||||
assert "network_params" in sim.model_params
|
||||
assert "prob_neighbor_spread" in sim.model_params
|
@@ -79,8 +79,8 @@ class TestNetwork(TestCase):
|
||||
env = environment.Environment(name="Test", topology=G)
|
||||
env.populate_network(agents.NetworkAgent)
|
||||
|
||||
a2 = env.find_one(node_id=2)
|
||||
a3 = env.find_one(node_id=3)
|
||||
a2 = env.agent(node_id=2)
|
||||
a3 = env.agent(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
|
||||
|
Reference in New Issue
Block a user