1
0
mirror of https://github.com/gsi-upm/soil synced 2024-11-14 23:42:29 +00:00
soil/examples/rabbits/rabbit_improved_sim.py

177 lines
5.1 KiB
Python
Raw Normal View History

2023-05-12 12:09:00 +00:00
from soil import Evented, FSM, state, default_state, BaseAgent, NetworkAgent, Environment, parameters, report, TimedOut
2022-10-13 20:43:16 +00:00
import math
2023-05-12 12:09:00 +00:00
from soilent import Scheduler
2022-10-13 20:43:16 +00:00
2022-10-17 17:29:39 +00:00
2023-05-12 12:09:00 +00:00
class RabbitsImprovedEnv(Environment):
prob_death: parameters.probability = 1e-3
schedule_class = Scheduler
def init(self):
a1 = self.add_node(Male)
a2 = self.add_node(Female)
a1.add_edge(a2)
self.add_agent(RandomAccident)
2022-10-13 20:43:16 +00:00
2023-05-12 12:09:00 +00:00
@report
@property
def num_rabbits(self):
return self.count_agents(agent_class=Rabbit)
@report
@property
def num_males(self):
return self.count_agents(agent_class=Male)
@report
@property
def num_females(self):
return self.count_agents(agent_class=Female)
2022-10-13 20:43:16 +00:00
2023-05-12 12:09:00 +00:00
class Rabbit(Evented, FSM, NetworkAgent):
2022-10-13 20:43:16 +00:00
2022-10-17 17:29:39 +00:00
sexual_maturity = 30
life_expectancy = 300
birth = None
2022-10-13 20:43:16 +00:00
@property
def age(self):
2022-10-17 17:29:39 +00:00
if self.birth is None:
return None
2022-10-13 20:43:16 +00:00
return self.now - self.birth
2022-10-17 17:29:39 +00:00
@default_state
@state
def newborn(self):
2023-05-12 12:09:00 +00:00
self.debug("I am a newborn.")
2022-10-17 17:29:39 +00:00
self.birth = self.now
self.offspring = 0
2023-05-12 12:09:00 +00:00
return self.youngling
2022-10-17 17:29:39 +00:00
@state
2023-05-12 12:09:00 +00:00
async def youngling(self):
self.debug("I am a youngling.")
await self.delay(self.sexual_maturity - self.age)
assert self.age >= self.sexual_maturity
self.debug(f"I am fertile! My age is {self.age}")
return self.fertile
2022-10-17 17:29:39 +00:00
2022-10-13 20:43:16 +00:00
@state
def fertile(self):
raise Exception("Each subclass should define its fertile state")
2022-10-17 17:29:39 +00:00
class Male(Rabbit):
2022-10-13 20:43:16 +00:00
max_females = 5
2023-05-12 12:09:00 +00:00
mating_prob = 0.005
2022-10-13 20:43:16 +00:00
@state
def fertile(self):
2022-10-17 17:29:39 +00:00
if self.age > self.life_expectancy:
2023-05-12 12:09:00 +00:00
return self.die()
2022-10-17 17:29:39 +00:00
2022-10-13 20:43:16 +00:00
# Males try to mate
2022-10-17 18:23:57 +00:00
for f in self.model.agents(
agent_class=Female, state_id=Female.fertile.id, limit=self.max_females
):
2023-05-12 12:09:00 +00:00
self.debug(f"FOUND A FEMALE: {repr(f)}. Mating with prob {self.mating_prob}")
2022-10-17 18:23:57 +00:00
if self.prob(self["mating_prob"]):
2023-05-12 12:09:00 +00:00
f.tell(self.unique_id, sender=self, timeout=1)
2022-10-17 17:29:39 +00:00
break # Do not try to impregnate other females
2022-10-13 20:43:16 +00:00
2022-10-17 17:29:39 +00:00
class Female(Rabbit):
2022-10-13 20:43:16 +00:00
gestation = 10
2022-10-17 17:29:39 +00:00
conception = None
2022-10-13 20:43:16 +00:00
@state
2023-05-12 12:09:00 +00:00
async def fertile(self):
2022-10-17 17:29:39 +00:00
# Just wait for a Male
2023-05-12 12:09:00 +00:00
try:
timeout = self.life_expectancy - self.age
while timeout > 0:
mates = await self.received(timeout=timeout)
# assert self.age <= self.life_expectancy
for mate in mates:
try:
male = self.model.agents[mate.payload]
except ValueError:
continue
self.debug(f"impregnated by {repr(male)}")
self.mate = male
self.number_of_babies = int(8 + 4 * self.random.random())
self.conception = self.now
return self.pregnant
except TimedOut:
pass
return self.die()
2022-10-13 20:43:16 +00:00
2022-10-17 17:29:39 +00:00
@state
2023-05-12 12:09:00 +00:00
async def pregnant(self):
2022-10-17 18:23:57 +00:00
self.debug("I am pregnant")
2023-05-12 12:09:00 +00:00
# assert self.mate is not None
when = min(self.gestation, self.life_expectancy - self.age)
if when < 0:
return self.die()
await self.delay(when)
2022-10-13 20:43:16 +00:00
2022-10-17 17:29:39 +00:00
if self.age > self.life_expectancy:
2023-05-12 12:09:00 +00:00
self.debug("Dying before giving birth")
2022-10-17 17:29:39 +00:00
return self.die()
2022-10-13 20:43:16 +00:00
2023-05-12 12:09:00 +00:00
# assert self.now - self.conception >= self.gestation
if not self.alive:
return self.die()
self.debug("Having {} babies".format(self.number_of_babies))
for i in range(self.number_of_babies):
state = {}
agent_class = self.random.choice([Male, Female])
child = self.model.add_node(agent_class=agent_class, **state)
child.add_edge(self)
try:
child.add_edge(self.mate)
self.model.agents[self.mate].offspring += 1
except ValueError:
self.debug("The father has passed away")
self.offspring += 1
self.mate = None
self.conception = None
return self.fertile
2022-10-17 17:29:39 +00:00
def die(self):
2023-05-12 12:09:00 +00:00
if self.conception is not None:
self.debug("A mother has died carrying a baby!!")
2022-10-17 17:29:39 +00:00
return super().die()
2022-10-13 20:43:16 +00:00
class RandomAccident(BaseAgent):
2023-05-12 12:09:00 +00:00
# Default value, but the value from the environment takes precedence
prob_death = 1e-3
2022-10-13 20:43:16 +00:00
def step(self):
2022-10-17 17:29:39 +00:00
2023-05-12 12:09:00 +00:00
alive = self.get_agents(agent_class=Rabbit, alive=True)
if not alive:
return self.die("No more rabbits to kill")
2022-10-17 17:29:39 +00:00
2023-05-12 12:09:00 +00:00
num_alive = len(alive)
prob_death = min(1, self.prob_death * num_alive/10)
2022-10-17 18:23:57 +00:00
self.debug("Killing some rabbits with prob={}!".format(prob_death))
2023-05-12 12:09:00 +00:00
for i in alive:
2022-10-17 17:29:39 +00:00
if i.state_id == i.dead.id:
2022-10-13 20:43:16 +00:00
continue
if self.prob(prob_death):
2023-05-12 12:09:00 +00:00
self.debug("I killed a rabbit: {}".format(i.unique_id))
num_alive -= 1
2022-10-17 17:29:39 +00:00
i.die()
2023-05-12 12:09:00 +00:00
self.debug("Rabbits alive: {}".format(num_alive))
2022-10-17 17:29:39 +00:00
2022-10-17 18:23:57 +00:00
2023-05-12 12:09:00 +00:00
RabbitsImprovedEnv.run(max_time=1000, seed="MySeed", iterations=1)