1
0
mirror of https://github.com/gsi-upm/soil synced 2025-08-24 12:02:20 +00:00
This commit is contained in:
J. Fernando Sánchez
2023-05-12 14:09:00 +02:00
parent f49be3af68
commit 3041156f19
27 changed files with 887 additions and 953 deletions

View File

@@ -1,7 +1,7 @@
There are two similar implementations of this simulation.
- `basic`. Using simple primites
- `improved`. Using more advanced features such as the `time` module to avoid unnecessary computations (i.e., skip steps), and generator functions.
- `improved`. Using more advanced features such as the delays to avoid unnecessary computations (i.e., skip steps).
The examples can be run directly in the terminal, and they accept command like arguments.
For example, to enable the CSV exporter and the Summary exporter, while setting `max_time` to `100` and `seed` to `CustomSeed`:

View File

@@ -1,22 +1,36 @@
from soil import FSM, state, default_state, BaseAgent, NetworkAgent, Environment, Simulation
from enum import Enum
from collections import Counter
import logging
from soil import Evented, FSM, state, default_state, BaseAgent, NetworkAgent, Environment, parameters, report, TimedOut
import math
from rabbits_basic_sim import RabbitEnv
from soilent import Scheduler
class RabbitsImprovedEnv(RabbitEnv):
class RabbitsImprovedEnv(Environment):
prob_death: parameters.probability = 1e-3
schedule_class = Scheduler
def init(self):
"""Initialize the environment with the new versions of the agents"""
a1 = self.add_node(Male)
a2 = self.add_node(Female)
a1.add_edge(a2)
self.add_agent(RandomAccident)
@report
@property
def num_rabbits(self):
return self.count_agents(agent_class=Rabbit)
class Rabbit(FSM, NetworkAgent):
@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)
class Rabbit(Evented, FSM, NetworkAgent):
sexual_maturity = 30
life_expectancy = 300
@@ -31,42 +45,40 @@ class Rabbit(FSM, NetworkAgent):
@default_state
@state
def newborn(self):
self.info("I am a newborn.")
self.debug("I am a newborn.")
self.birth = self.now
self.offspring = 0
return self.youngling.delay(self.sexual_maturity - self.age)
return self.youngling
@state
def youngling(self):
if self.age >= self.sexual_maturity:
self.info(f"I am fertile! My age is {self.age}")
return self.fertile
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
@state
def fertile(self):
raise Exception("Each subclass should define its fertile state")
@state
def dead(self):
self.die()
class Male(Rabbit):
max_females = 5
mating_prob = 0.001
mating_prob = 0.005
@state
def fertile(self):
if self.age > self.life_expectancy:
return self.dead
return self.die()
# Males try to mate
for f in self.model.agents(
agent_class=Female, state_id=Female.fertile.id, limit=self.max_females
):
self.debug("FOUND A FEMALE: ", repr(f), self.mating_prob)
self.debug(f"FOUND A FEMALE: {repr(f)}. Mating with prob {self.mating_prob}")
if self.prob(self["mating_prob"]):
f.impregnate(self)
f.tell(self.unique_id, sender=self, timeout=1)
break # Do not try to impregnate other females
@@ -75,78 +87,91 @@ class Female(Rabbit):
conception = None
@state
def fertile(self):
async def fertile(self):
# Just wait for a Male
if self.age > self.life_expectancy:
return self.dead
if self.conception is not None:
return self.pregnant
@property
def pregnancy(self):
if self.conception is None:
return None
return self.now - self.conception
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())
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()
@state
def pregnant(self):
async def pregnant(self):
self.debug("I am pregnant")
# 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)
if self.age > self.life_expectancy:
self.info("Dying before giving birth")
self.debug("Dying before giving birth")
return self.die()
if self.pregnancy >= self.gestation:
self.info("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)
if self.mate:
child.add_edge(self.mate)
self.mate.offspring += 1
else:
self.debug("The father has passed away")
# assert self.now - self.conception >= self.gestation
if not self.alive:
return self.die()
self.offspring += 1
self.mate = None
return self.fertile
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
def die(self):
if self.pregnancy is not None:
self.info("A mother has died carrying a baby!!")
if self.conception is not None:
self.debug("A mother has died carrying a baby!!")
return super().die()
class RandomAccident(BaseAgent):
# Default value, but the value from the environment takes precedence
prob_death = 1e-3
def step(self):
rabbits_alive = self.model.G.number_of_nodes()
if not rabbits_alive:
return self.die()
alive = self.get_agents(agent_class=Rabbit, alive=True)
prob_death = self.model.get("prob_death", 1e-100) * math.floor(
math.log10(max(1, rabbits_alive))
)
if not alive:
return self.die("No more rabbits to kill")
num_alive = len(alive)
prob_death = min(1, self.prob_death * num_alive/10)
self.debug("Killing some rabbits with prob={}!".format(prob_death))
for i in self.iter_agents(agent_class=Rabbit):
for i in alive:
if i.state_id == i.dead.id:
continue
if self.prob(prob_death):
self.info("I killed a rabbit: {}".format(i.id))
rabbits_alive -= 1
self.debug("I killed a rabbit: {}".format(i.unique_id))
num_alive -= 1
i.die()
self.debug("Rabbits alive: {}".format(rabbits_alive))
self.debug("Rabbits alive: {}".format(num_alive))
sim = Simulation(model=RabbitsImprovedEnv, max_time=100, seed="MySeed", iterations=1)
if __name__ == "__main__":
sim.run()
RabbitsImprovedEnv.run(max_time=1000, seed="MySeed", iterations=1)

View File

@@ -1,11 +1,9 @@
from soil import FSM, state, default_state, BaseAgent, NetworkAgent, Environment, Simulation, report, parameters as params
from collections import Counter
import logging
from soil import FSM, state, default_state, BaseAgent, NetworkAgent, Environment, report, parameters as params
import math
class RabbitEnv(Environment):
prob_death: params.probability = 1e-100
prob_death: params.probability = 1e-3
def init(self):
a1 = self.add_node(Male)
@@ -37,7 +35,7 @@ class Rabbit(NetworkAgent, FSM):
@default_state
@state
def newborn(self):
self.info("I am a newborn.")
self.debug("I am a newborn.")
self.age = 0
self.offspring = 0
return self.youngling
@@ -46,7 +44,7 @@ class Rabbit(NetworkAgent, FSM):
def youngling(self):
self.age += 1
if self.age >= self.sexual_maturity:
self.info(f"I am fertile! My age is {self.age}")
self.debug(f"I am fertile! My age is {self.age}")
return self.fertile
@state
@@ -60,7 +58,7 @@ class Rabbit(NetworkAgent, FSM):
class Male(Rabbit):
max_females = 5
mating_prob = 0.001
mating_prob = 0.005
@state
def fertile(self):
@@ -70,9 +68,8 @@ class Male(Rabbit):
return self.dead
# Males try to mate
for f in self.model.agents(
agent_class=Female, state_id=Female.fertile.id, limit=self.max_females
):
for f in self.model.agents.filter(
agent_class=Female, state_id=Female.fertile.id).limit(self.max_females):
self.debug("FOUND A FEMALE: ", repr(f), self.mating_prob)
if self.prob(self["mating_prob"]):
f.impregnate(self)
@@ -93,14 +90,14 @@ class Female(Rabbit):
return self.pregnant
def impregnate(self, male):
self.info(f"impregnated by {repr(male)}")
self.debug(f"impregnated by {repr(male)}")
self.mate = male
self.pregnancy = 0
self.number_of_babies = int(8 + 4 * self.random.random())
@state
def pregnant(self):
self.info("I am pregnant")
self.debug("I am pregnant")
self.age += 1
if self.age >= self.life_expectancy:
@@ -110,7 +107,7 @@ class Female(Rabbit):
self.pregnancy += 1
return
self.info("Having {} babies".format(self.number_of_babies))
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])
@@ -129,33 +126,30 @@ class Female(Rabbit):
def die(self):
if "pregnancy" in self and self["pregnancy"] > -1:
self.info("A mother has died carrying a baby!!")
self.debug("A mother has died carrying a baby!!")
return super().die()
class RandomAccident(BaseAgent):
prob_death = None
def step(self):
rabbits_alive = self.model.G.number_of_nodes()
alive = self.get_agents(agent_class=Rabbit, alive=True)
if not rabbits_alive:
return self.die()
if not alive:
return self.die("No more rabbits to kill")
prob_death = self.model.prob_death * math.floor(
math.log10(max(1, rabbits_alive))
)
num_alive = len(alive)
prob_death = min(1, self.prob_death * num_alive/10)
self.debug("Killing some rabbits with prob={}!".format(prob_death))
for i in self.get_agents(agent_class=Rabbit):
if i.state_id == i.dead.id:
continue
if self.prob(prob_death):
self.info("I killed a rabbit: {}".format(i.id))
rabbits_alive -= 1
self.debug("I killed a rabbit: {}".format(i.unique_id))
num_alive -= 1
i.die()
self.debug("Rabbits alive: {}".format(rabbits_alive))
self.debug("Rabbits alive: {}".format(num_alive))
sim = Simulation(model=RabbitEnv, max_time=100, seed="MySeed", iterations=1)
if __name__ == "__main__":
sim.run()
RabbitEnv.run(max_time=1000, seed="MySeed", iterations=1)