mirror of
https://github.com/gsi-upm/soil
synced 2025-08-24 12:02:20 +00:00
WIP
This commit is contained in:
@@ -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`:
|
||||
|
@@ -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)
|
@@ -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)
|
Reference in New Issue
Block a user