mirror of https://github.com/gsi-upm/soil
Fix conditionals
parent
5d759d0072
commit
227fdf050e
@ -1,130 +1,157 @@
|
|||||||
from soil.agents import FSM, state, default_state, BaseAgent, NetworkAgent
|
from soil import FSM, state, default_state, BaseAgent, NetworkAgent, Environment
|
||||||
from soil.time import Delta, When, NEVER
|
from soil.time import Delta
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from collections import Counter
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
|
||||||
class RabbitModel(FSM, NetworkAgent):
|
class RabbitEnv(Environment):
|
||||||
|
|
||||||
mating_prob = 0.005
|
@property
|
||||||
offspring = 0
|
def num_rabbits(self):
|
||||||
birth = None
|
return self.count_agents(agent_class=Rabbit)
|
||||||
|
|
||||||
sexual_maturity = 3
|
@property
|
||||||
life_expectancy = 30
|
def num_males(self):
|
||||||
|
return self.count_agents(agent_class=Male)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def num_females(self):
|
||||||
|
return self.count_agents(agent_class=Female)
|
||||||
|
|
||||||
@default_state
|
|
||||||
@state
|
|
||||||
def newborn(self):
|
|
||||||
self.birth = self.now
|
|
||||||
self.info(f'I am a newborn.')
|
|
||||||
self.model['rabbits_alive'] = self.model.get('rabbits_alive', 0) + 1
|
|
||||||
|
|
||||||
# Here we can skip the `youngling` state by using a coroutine/generator.
|
class Rabbit(FSM, NetworkAgent):
|
||||||
while self.age < self.sexual_maturity:
|
|
||||||
interval = self.sexual_maturity - self.age
|
|
||||||
yield Delta(interval)
|
|
||||||
|
|
||||||
self.info(f'I am fertile! My age is {self.age}')
|
sexual_maturity = 30
|
||||||
return self.fertile
|
life_expectancy = 300
|
||||||
|
birth = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def age(self):
|
def age(self):
|
||||||
|
if self.birth is None:
|
||||||
|
return None
|
||||||
return self.now - self.birth
|
return self.now - self.birth
|
||||||
|
|
||||||
|
@default_state
|
||||||
|
@state
|
||||||
|
def newborn(self):
|
||||||
|
self.info('I am a newborn.')
|
||||||
|
self.birth = self.now
|
||||||
|
self.offspring = 0
|
||||||
|
return self.youngling, Delta(self.sexual_maturity - self.age)
|
||||||
|
|
||||||
|
@state
|
||||||
|
def youngling(self):
|
||||||
|
if self.age >= self.sexual_maturity:
|
||||||
|
self.info(f'I am fertile! My age is {self.age}')
|
||||||
|
return self.fertile
|
||||||
|
|
||||||
@state
|
@state
|
||||||
def fertile(self):
|
def fertile(self):
|
||||||
raise Exception("Each subclass should define its fertile state")
|
raise Exception("Each subclass should define its fertile state")
|
||||||
|
|
||||||
def step(self):
|
@state
|
||||||
super().step()
|
def dead(self):
|
||||||
if self.prob(self.age / self.life_expectancy):
|
self.die()
|
||||||
return self.die()
|
|
||||||
|
|
||||||
|
|
||||||
class Male(RabbitModel):
|
|
||||||
|
|
||||||
|
class Male(Rabbit):
|
||||||
max_females = 5
|
max_females = 5
|
||||||
|
mating_prob = 0.001
|
||||||
|
|
||||||
@state
|
@state
|
||||||
def fertile(self):
|
def fertile(self):
|
||||||
|
if self.age > self.life_expectancy:
|
||||||
|
return self.dead
|
||||||
|
|
||||||
# Males try to mate
|
# Males try to mate
|
||||||
for f in self.model.agents(agent_class=Female,
|
for f in self.model.agents(agent_class=Female,
|
||||||
state_id=Female.fertile.id,
|
state_id=Female.fertile.id,
|
||||||
limit=self.max_females):
|
limit=self.max_females):
|
||||||
self.debug('Found a female:', repr(f))
|
self.debug('FOUND A FEMALE: ', repr(f), self.mating_prob)
|
||||||
if self.prob(self['mating_prob']):
|
if self.prob(self['mating_prob']):
|
||||||
f.impregnate(self)
|
f.impregnate(self)
|
||||||
break # Take a break, don't try to impregnate the rest
|
break # Do not try to impregnate other females
|
||||||
|
|
||||||
|
|
||||||
|
class Female(Rabbit):
|
||||||
class Female(RabbitModel):
|
|
||||||
due_date = None
|
|
||||||
age_of_pregnancy = None
|
|
||||||
gestation = 10
|
gestation = 10
|
||||||
mate = None
|
conception = None
|
||||||
|
|
||||||
@state
|
@state
|
||||||
def fertile(self):
|
def fertile(self):
|
||||||
return self.fertile, NEVER
|
# Just wait for a Male
|
||||||
|
|
||||||
@state
|
|
||||||
def pregnant(self):
|
|
||||||
self.info('I am pregnant')
|
|
||||||
if self.age > self.life_expectancy:
|
if self.age > self.life_expectancy:
|
||||||
return self.dead
|
return self.dead
|
||||||
|
if self.conception is not None:
|
||||||
|
return self.pregnant
|
||||||
|
|
||||||
self.due_date = self.now + self.gestation
|
@property
|
||||||
|
def pregnancy(self):
|
||||||
|
if self.conception is None:
|
||||||
|
return None
|
||||||
|
return self.now - self.conception
|
||||||
|
|
||||||
number_of_babies = int(8+4*self.random.random())
|
def impregnate(self, male):
|
||||||
|
self.info(f'impregnated by {repr(male)}')
|
||||||
|
self.mate = male
|
||||||
|
self.conception = self.now
|
||||||
|
self.number_of_babies = int(8+4*self.random.random())
|
||||||
|
|
||||||
while self.now < self.due_date:
|
@state
|
||||||
yield When(self.due_date)
|
def pregnant(self):
|
||||||
|
self.debug('I am pregnant')
|
||||||
|
|
||||||
self.info('Having {} babies'.format(number_of_babies))
|
if self.age > self.life_expectancy:
|
||||||
for i in range(number_of_babies):
|
self.info("Dying before giving birth")
|
||||||
agent_class = self.random.choice([Male, Female])
|
return self.die()
|
||||||
child = self.model.add_node(agent_class=agent_class,
|
|
||||||
topology=self.topology)
|
|
||||||
self.model.add_edge(self, child)
|
|
||||||
self.model.add_edge(self.mate, child)
|
|
||||||
self.offspring += 1
|
|
||||||
self.model.agents[self.mate].offspring += 1
|
|
||||||
self.mate = None
|
|
||||||
self.due_date = None
|
|
||||||
return self.fertile
|
|
||||||
|
|
||||||
@state
|
if self.pregnancy >= self.gestation:
|
||||||
def dead(self):
|
self.info('Having {} babies'.format(self.number_of_babies))
|
||||||
super().dead()
|
for i in range(self.number_of_babies):
|
||||||
if self.due_date is not None:
|
state = {}
|
||||||
|
agent_class = self.random.choice([Male, Female])
|
||||||
|
child = self.model.add_node(agent_class=agent_class,
|
||||||
|
**state)
|
||||||
|
child.add_edge(self)
|
||||||
|
if self.mate:
|
||||||
|
child.add_edge(self.mate)
|
||||||
|
self.mate.offspring += 1
|
||||||
|
else:
|
||||||
|
self.debug('The father has passed away')
|
||||||
|
|
||||||
|
self.offspring += 1
|
||||||
|
self.mate = None
|
||||||
|
return self.fertile
|
||||||
|
|
||||||
|
def die(self):
|
||||||
|
if self.pregnancy is not None:
|
||||||
self.info('A mother has died carrying a baby!!')
|
self.info('A mother has died carrying a baby!!')
|
||||||
|
return super().die()
|
||||||
def impregnate(self, male):
|
|
||||||
self.info(f'{repr(male)} impregnating female {repr(self)}')
|
|
||||||
self.mate = male
|
|
||||||
self.set_state(self.pregnant, when=self.now)
|
|
||||||
|
|
||||||
|
|
||||||
class RandomAccident(BaseAgent):
|
class RandomAccident(BaseAgent):
|
||||||
|
|
||||||
level = logging.INFO
|
|
||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
rabbits_total = self.model.topology.number_of_nodes()
|
rabbits_alive = self.model.G.number_of_nodes()
|
||||||
if 'rabbits_alive' not in self.model:
|
|
||||||
self.model['rabbits_alive'] = 0
|
if not rabbits_alive:
|
||||||
rabbits_alive = self.model.get('rabbits_alive', rabbits_total)
|
return self.die()
|
||||||
|
|
||||||
prob_death = self.model.get('prob_death', 1e-100)*math.floor(math.log10(max(1, rabbits_alive)))
|
prob_death = self.model.get('prob_death', 1e-100)*math.floor(math.log10(max(1, rabbits_alive)))
|
||||||
self.debug('Killing some rabbits with prob={}!'.format(prob_death))
|
self.debug('Killing some rabbits with prob={}!'.format(prob_death))
|
||||||
for i in self.model.network_agents:
|
for i in self.iter_agents(agent_class=Rabbit):
|
||||||
if i.state.id == i.dead.id:
|
if i.state_id == i.dead.id:
|
||||||
continue
|
continue
|
||||||
if self.prob(prob_death):
|
if self.prob(prob_death):
|
||||||
self.info('I killed a rabbit: {}'.format(i.id))
|
self.info('I killed a rabbit: {}'.format(i.id))
|
||||||
rabbits_alive = self.model['rabbits_alive'] = rabbits_alive -1
|
rabbits_alive -= 1
|
||||||
i.set_state(i.dead)
|
i.die()
|
||||||
self.debug('Rabbits alive: {}/{}'.format(rabbits_alive, rabbits_total))
|
self.debug('Rabbits alive: {}'.format(rabbits_alive))
|
||||||
if self.model.count_agents(state_id=RabbitModel.dead.id) == self.model.topology.number_of_nodes():
|
|
||||||
self.die()
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from soil import easy
|
||||||
|
with easy('rabbits.yml') as sim:
|
||||||
|
sim.run()
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
from . import main as init_main
|
from . import main as init_main
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
init_main(do_run=True)
|
init_main(do_run=True)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
if __name__ == "__main__":
|
|
||||||
init_main(do_run=True)
|
init_main(do_run=True)
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from soil import time, agents, environment
|
||||||
|
|
||||||
|
class TestMain(TestCase):
|
||||||
|
def test_cond(self):
|
||||||
|
'''
|
||||||
|
A condition should match a When if the concition is True
|
||||||
|
'''
|
||||||
|
|
||||||
|
t = time.Cond(lambda t: True)
|
||||||
|
f = time.Cond(lambda t: False)
|
||||||
|
for i in range(10):
|
||||||
|
w = time.When(i)
|
||||||
|
assert w == t
|
||||||
|
assert w is not f
|
||||||
|
|
||||||
|
def test_cond(self):
|
||||||
|
'''
|
||||||
|
Comparing a Cond to a Delta should always return False
|
||||||
|
'''
|
||||||
|
|
||||||
|
c = time.Cond(lambda t: False)
|
||||||
|
d = time.Delta(1)
|
||||||
|
assert c is not d
|
||||||
|
|
||||||
|
def test_cond_env(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
times_started = []
|
||||||
|
times_awakened = []
|
||||||
|
times = []
|
||||||
|
done = 0
|
||||||
|
|
||||||
|
class CondAgent(agents.BaseAgent):
|
||||||
|
|
||||||
|
def step(self):
|
||||||
|
nonlocal done
|
||||||
|
times_started.append(self.now)
|
||||||
|
while True:
|
||||||
|
yield time.Cond(lambda agent: agent.model.schedule.time >= 10)
|
||||||
|
times_awakened.append(self.now)
|
||||||
|
if self.now >= 10:
|
||||||
|
break
|
||||||
|
done += 1
|
||||||
|
|
||||||
|
env = environment.Environment(agents=[{'agent_class': CondAgent}])
|
||||||
|
|
||||||
|
|
||||||
|
while env.schedule.time < 11:
|
||||||
|
env.step()
|
||||||
|
times.append(env.now)
|
||||||
|
assert env.schedule.time == 11
|
||||||
|
assert times_started == [0]
|
||||||
|
assert times_awakened == [10]
|
||||||
|
assert done == 1
|
||||||
|
# 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
|
||||||
|
|
||||||
|
while env.schedule.time < 12:
|
||||||
|
env.step()
|
||||||
|
times.append(env.now)
|
||||||
|
|
||||||
|
assert env.schedule.time == 12
|
||||||
|
assert times_started == [0, 11]
|
||||||
|
assert times_awakened == [10, 11]
|
||||||
|
assert done == 2
|
||||||
|
# Once more to yield the cond, another one to continue
|
||||||
|
assert env.schedule.steps == 14
|
||||||
|
assert len(times) == 14
|
Loading…
Reference in New Issue