mirror of
https://github.com/gsi-upm/soil
synced 2025-08-23 19:52:19 +00:00
Refactored time
Treating time and conditions as the same entity was getting confusing, and it added a lot of unnecessary abstraction in a critical part (the scheduler). The scheduling queue now has the time as a floating number (faster), the agent id (for ties) and the condition, as well as the agent. The first three elements (time, id, condition) can be considered as the "key" for the event. To allow for agent execution to be "randomized" within every step, a new parameter has been added to the scheduler, which makes it add a random number to the key in order to change the ordering. `EventedAgent.received` now checks the messages before returning control to the user by default.
This commit is contained in:
@@ -12,12 +12,11 @@ class Dead(agents.FSM):
|
||||
return self.die()
|
||||
|
||||
|
||||
class TestMain(TestCase):
|
||||
class TestAgents(TestCase):
|
||||
def test_die_returns_infinity(self):
|
||||
'''The last step of a dead agent should return time.INFINITY'''
|
||||
d = Dead(unique_id=0, model=environment.Environment())
|
||||
ret = d.step().abs(0)
|
||||
print(ret, "next")
|
||||
ret = d.step()
|
||||
assert ret == stime.NEVER
|
||||
|
||||
def test_die_raises_exception(self):
|
||||
@@ -66,4 +65,95 @@ class TestMain(TestCase):
|
||||
a.step()
|
||||
assert a.run == 1
|
||||
a.step()
|
||||
assert a.run == 2
|
||||
|
||||
|
||||
def test_broadcast(self):
|
||||
'''
|
||||
An agent should be able to broadcast messages to every other agent, AND each receiver should be able
|
||||
to process it
|
||||
'''
|
||||
class BCast(agents.Evented):
|
||||
pings_received = 0
|
||||
|
||||
def step(self):
|
||||
print(self.model.broadcast)
|
||||
try:
|
||||
self.model.broadcast('PING')
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
while True:
|
||||
self.check_messages()
|
||||
yield
|
||||
|
||||
def on_receive(self, msg, sender=None):
|
||||
self.pings_received += 1
|
||||
|
||||
e = environment.EventedEnvironment()
|
||||
|
||||
for i in range(10):
|
||||
e.add_agent(agent_class=BCast)
|
||||
e.step()
|
||||
pings_received = lambda: [a.pings_received for a in e.agents]
|
||||
assert sorted(pings_received()) == list(range(1, 11))
|
||||
e.step()
|
||||
assert all(x==10 for x in pings_received())
|
||||
|
||||
def test_ask_messages(self):
|
||||
'''
|
||||
An agent should be able to ask another agent, and wait for a response.
|
||||
'''
|
||||
|
||||
# #Results depend on ordering (agents are shuffled), so force the first agent
|
||||
pings = []
|
||||
pongs = []
|
||||
responses = []
|
||||
|
||||
class Ping(agents.EventedAgent):
|
||||
def step(self):
|
||||
target_id = (self.unique_id + 1) % self.count_agents()
|
||||
target = self.model.agents[target_id]
|
||||
print('starting')
|
||||
while True:
|
||||
print('Pings: ', pings, responses or not pings, self.model.schedule._queue)
|
||||
if pongs or not pings:
|
||||
pings.append(self.now)
|
||||
response = yield target.ask('PING')
|
||||
responses.append(response)
|
||||
else:
|
||||
print('NOT sending ping')
|
||||
print('Checking msgs')
|
||||
# Do not advance until we have received a message.
|
||||
# warning: it will wait at least until the next time in the simulation
|
||||
yield self.received(check=True)
|
||||
print('done')
|
||||
|
||||
def on_receive(self, msg, sender=None):
|
||||
if msg == 'PING':
|
||||
pongs.append(self.now)
|
||||
return 'PONG'
|
||||
|
||||
e = environment.EventedEnvironment()
|
||||
for i in range(2):
|
||||
e.add_agent(agent_class=Ping)
|
||||
assert e.now == 0
|
||||
|
||||
# There should be a delay of one step between agent 0 and 1
|
||||
# On the first step:
|
||||
# Agent 0 sends a PING, but blocks before a PONG
|
||||
# Agent 1 sends a PONG, and blocks after its PING
|
||||
# After that step, every agent can both receive (there are pending messages) and then send.
|
||||
|
||||
e.step()
|
||||
assert e.now == 1
|
||||
assert pings == [0]
|
||||
assert pongs == []
|
||||
|
||||
e.step()
|
||||
assert e.now == 2
|
||||
assert pings == [0, 1]
|
||||
assert pongs == [1]
|
||||
|
||||
e.step()
|
||||
assert e.now == 3
|
||||
assert pings == [0, 1, 2]
|
||||
assert pongs == [1, 2]
|
||||
|
@@ -44,6 +44,8 @@ def add_example_tests():
|
||||
for cfg, path in serialization.load_files(
|
||||
join(EXAMPLES, "**", "*.yml"),
|
||||
):
|
||||
if 'soil_output' in path:
|
||||
continue
|
||||
p = make_example_test(path=path, cfg=config.Config.from_raw(cfg))
|
||||
fname = os.path.basename(path)
|
||||
p.__name__ = "test_example_file_%s" % fname
|
||||
|
@@ -172,25 +172,21 @@ class TestMain(TestCase):
|
||||
assert len(configs) > 0
|
||||
|
||||
def test_until(self):
|
||||
config = {
|
||||
"name": "until_sim",
|
||||
"model_params": {
|
||||
"network_params": {},
|
||||
"agents": {
|
||||
"fixed": [
|
||||
{
|
||||
"agent_class": agents.BaseAgent,
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
"max_time": 2,
|
||||
"num_trials": 50,
|
||||
}
|
||||
s = simulation.from_config(config)
|
||||
n_runs = 0
|
||||
|
||||
class CheckRun(agents.BaseAgent):
|
||||
def step(self):
|
||||
nonlocal n_runs
|
||||
n_runs += 1
|
||||
return super().step()
|
||||
|
||||
n_trials = 50
|
||||
max_time = 2
|
||||
s = simulation.Simulation(model_params={'agents': [{'agent_class': CheckRun}]},
|
||||
num_trials=n_trials, max_time=max_time)
|
||||
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(runs) == n_trials
|
||||
assert len(over) == 0
|
||||
|
||||
def test_fsm(self):
|
||||
|
@@ -72,7 +72,7 @@ class TestNetwork(TestCase):
|
||||
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
|
||||
assert env.agents[0].count_neighbors() == 1
|
||||
|
||||
def test_custom_agent_neighbors(self):
|
||||
"""Allow for search of neighbors with a certain state_id"""
|
||||
@@ -90,7 +90,7 @@ class TestNetwork(TestCase):
|
||||
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].count_neighbors() == 1
|
||||
|
||||
def test_subgraph(self):
|
||||
"""An agent should be able to subgraph the global topology"""
|
||||
|
@@ -30,8 +30,9 @@ class TestMain(TestCase):
|
||||
|
||||
times_started = []
|
||||
times_awakened = []
|
||||
times_asleep = []
|
||||
times = []
|
||||
done = 0
|
||||
done = []
|
||||
|
||||
class CondAgent(agents.BaseAgent):
|
||||
|
||||
@@ -39,36 +40,38 @@ class TestMain(TestCase):
|
||||
nonlocal done
|
||||
times_started.append(self.now)
|
||||
while True:
|
||||
yield time.Cond(lambda agent: agent.model.schedule.time >= 10)
|
||||
times_asleep.append(self.now)
|
||||
yield time.Cond(lambda agent: agent.now >= 10,
|
||||
delta=2)
|
||||
times_awakened.append(self.now)
|
||||
if self.now >= 10:
|
||||
break
|
||||
done += 1
|
||||
done.append(self.now)
|
||||
|
||||
env = environment.Environment(agents=[{'agent_class': CondAgent}])
|
||||
|
||||
|
||||
while env.schedule.time < 11:
|
||||
env.step()
|
||||
times.append(env.now)
|
||||
env.step()
|
||||
|
||||
assert env.schedule.time == 11
|
||||
assert times_started == [0]
|
||||
assert times_awakened == [10]
|
||||
assert done == 1
|
||||
assert done == [10]
|
||||
# The first time will produce the Cond.
|
||||
# Since there are no other agents, time will not advance, but the number
|
||||
# of steps will.
|
||||
assert env.schedule.steps == 12
|
||||
assert len(times) == 12
|
||||
assert env.schedule.steps == 6
|
||||
assert len(times) == 6
|
||||
|
||||
while env.schedule.time < 12:
|
||||
env.step()
|
||||
while env.schedule.time < 13:
|
||||
times.append(env.now)
|
||||
env.step()
|
||||
|
||||
assert env.schedule.time == 12
|
||||
assert times == [0, 2, 4, 6, 8, 10, 11]
|
||||
assert env.schedule.time == 13
|
||||
assert times_started == [0, 11]
|
||||
assert times_awakened == [10, 11]
|
||||
assert done == 2
|
||||
assert times_awakened == [10]
|
||||
assert done == [10]
|
||||
# Once more to yield the cond, another one to continue
|
||||
assert env.schedule.steps == 14
|
||||
assert len(times) == 14
|
||||
assert env.schedule.steps == 7
|
||||
assert len(times) == 7
|
||||
|
Reference in New Issue
Block a user