mirror of
https://github.com/gsi-upm/soil
synced 2025-08-24 03:52:20 +00:00
Fix conditionals
This commit is contained in:
@@ -2,3 +2,13 @@ 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.
|
||||
|
||||
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`:
|
||||
|
||||
```
|
||||
python rabbit_agents.py --set max_time=100 --csv -e summary --set 'seed="CustomSeed"'
|
||||
```
|
||||
|
||||
To learn more about how this functionality works, check out the `soil.easy` function.
|
||||
|
||||
|
@@ -1,6 +1,4 @@
|
||||
from soil import FSM, state, default_state, BaseAgent, NetworkAgent, Environment
|
||||
from soil.time import Delta
|
||||
from enum import Enum
|
||||
from collections import Counter
|
||||
import logging
|
||||
import math
|
||||
@@ -21,7 +19,7 @@ class RabbitEnv(Environment):
|
||||
return self.count_agents(agent_class=Female)
|
||||
|
||||
|
||||
class Rabbit(FSM, NetworkAgent):
|
||||
class Rabbit(NetworkAgent, FSM):
|
||||
|
||||
sexual_maturity = 30
|
||||
life_expectancy = 300
|
||||
@@ -72,7 +70,8 @@ class Male(Rabbit):
|
||||
|
||||
|
||||
class Female(Rabbit):
|
||||
gestation = 30
|
||||
gestation = 10
|
||||
pregnancy = -1
|
||||
|
||||
@state
|
||||
def fertile(self):
|
||||
@@ -80,46 +79,49 @@ class Female(Rabbit):
|
||||
self.age += 1
|
||||
if self.age > self.life_expectancy:
|
||||
return self.dead
|
||||
if self.pregnancy >= 0:
|
||||
return self.pregnant
|
||||
|
||||
def impregnate(self, male):
|
||||
self.info(f'{repr(male)} impregnating female {repr(self)}')
|
||||
self.info(f'impregnated by {repr(male)}')
|
||||
self.mate = male
|
||||
self.pregnancy = -1
|
||||
self.set_state(self.pregnant, when=self.now)
|
||||
self.pregnancy = 0
|
||||
self.number_of_babies = int(8+4*self.random.random())
|
||||
|
||||
@state
|
||||
def pregnant(self):
|
||||
self.debug('I am pregnant')
|
||||
self.info('I am pregnant')
|
||||
self.age += 1
|
||||
self.pregnancy += 1
|
||||
|
||||
if self.prob(self.age / self.life_expectancy):
|
||||
if self.age >= self.life_expectancy:
|
||||
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)
|
||||
try:
|
||||
child.add_edge(self.mate)
|
||||
self.model.agents[self.mate].offspring += 1
|
||||
except ValueError:
|
||||
self.debug('The father has passed away')
|
||||
if self.pregnancy < self.gestation:
|
||||
self.pregnancy += 1
|
||||
return
|
||||
|
||||
self.offspring += 1
|
||||
self.mate = None
|
||||
return self.fertile
|
||||
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)
|
||||
try:
|
||||
child.add_edge(self.mate)
|
||||
self.model.agents[self.mate].offspring += 1
|
||||
except ValueError:
|
||||
self.debug('The father has passed away')
|
||||
|
||||
@state
|
||||
def dead(self):
|
||||
super().dead()
|
||||
self.offspring += 1
|
||||
self.mate = None
|
||||
self.pregnancy = -1
|
||||
return self.fertile
|
||||
|
||||
def die(self):
|
||||
if 'pregnancy' in self and self['pregnancy'] > -1:
|
||||
self.info('A mother has died carrying a baby!!')
|
||||
return super().die()
|
||||
|
||||
|
||||
class RandomAccident(BaseAgent):
|
||||
@@ -138,11 +140,11 @@ class RandomAccident(BaseAgent):
|
||||
if self.prob(prob_death):
|
||||
self.info('I killed a rabbit: {}'.format(i.id))
|
||||
rabbits_alive -= 1
|
||||
i.set_state(i.dead)
|
||||
i.die()
|
||||
self.debug('Rabbits alive: {}'.format(rabbits_alive))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from soil import easy
|
||||
sim = easy('rabbits.yml')
|
||||
sim.run()
|
||||
with easy('rabbits.yml') as sim:
|
||||
sim.run()
|
||||
|
@@ -1,130 +1,157 @@
|
||||
from soil.agents import FSM, state, default_state, BaseAgent, NetworkAgent
|
||||
from soil.time import Delta, When, NEVER
|
||||
from soil import FSM, state, default_state, BaseAgent, NetworkAgent, Environment
|
||||
from soil.time import Delta
|
||||
from enum import Enum
|
||||
from collections import Counter
|
||||
import logging
|
||||
import math
|
||||
|
||||
|
||||
class RabbitModel(FSM, NetworkAgent):
|
||||
class RabbitEnv(Environment):
|
||||
|
||||
mating_prob = 0.005
|
||||
offspring = 0
|
||||
@property
|
||||
def num_rabbits(self):
|
||||
return self.count_agents(agent_class=Rabbit)
|
||||
|
||||
@property
|
||||
def num_males(self):
|
||||
return self.count_agents(agent_class=Male)
|
||||
|
||||
@property
|
||||
def num_females(self):
|
||||
return self.count_agents(agent_class=Female)
|
||||
|
||||
|
||||
class Rabbit(FSM, NetworkAgent):
|
||||
|
||||
sexual_maturity = 30
|
||||
life_expectancy = 300
|
||||
birth = None
|
||||
|
||||
sexual_maturity = 3
|
||||
life_expectancy = 30
|
||||
@property
|
||||
def age(self):
|
||||
if self.birth is None:
|
||||
return None
|
||||
return self.now - self.birth
|
||||
|
||||
@default_state
|
||||
@state
|
||||
def newborn(self):
|
||||
self.info('I am a newborn.')
|
||||
self.birth = self.now
|
||||
self.info(f'I am a newborn.')
|
||||
self.model['rabbits_alive'] = self.model.get('rabbits_alive', 0) + 1
|
||||
self.offspring = 0
|
||||
return self.youngling, Delta(self.sexual_maturity - self.age)
|
||||
|
||||
# Here we can skip the `youngling` state by using a coroutine/generator.
|
||||
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}')
|
||||
return self.fertile
|
||||
|
||||
@property
|
||||
def age(self):
|
||||
return self.now - self.birth
|
||||
@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
|
||||
def fertile(self):
|
||||
raise Exception("Each subclass should define its fertile state")
|
||||
|
||||
def step(self):
|
||||
super().step()
|
||||
if self.prob(self.age / self.life_expectancy):
|
||||
return self.die()
|
||||
@state
|
||||
def dead(self):
|
||||
self.die()
|
||||
|
||||
|
||||
class Male(RabbitModel):
|
||||
|
||||
class Male(Rabbit):
|
||||
max_females = 5
|
||||
mating_prob = 0.001
|
||||
|
||||
@state
|
||||
def fertile(self):
|
||||
if self.age > self.life_expectancy:
|
||||
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):
|
||||
self.debug('Found a female:', repr(f))
|
||||
self.debug('FOUND A FEMALE: ', repr(f), self.mating_prob)
|
||||
if self.prob(self['mating_prob']):
|
||||
f.impregnate(self)
|
||||
break # Take a break, don't try to impregnate the rest
|
||||
break # Do not try to impregnate other females
|
||||
|
||||
|
||||
class Female(RabbitModel):
|
||||
due_date = None
|
||||
age_of_pregnancy = None
|
||||
|
||||
class Female(Rabbit):
|
||||
gestation = 10
|
||||
mate = None
|
||||
conception = None
|
||||
|
||||
@state
|
||||
def fertile(self):
|
||||
return self.fertile, NEVER
|
||||
# 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())
|
||||
|
||||
@state
|
||||
def pregnant(self):
|
||||
self.info('I am pregnant')
|
||||
self.debug('I am pregnant')
|
||||
|
||||
if self.age > self.life_expectancy:
|
||||
return self.dead
|
||||
self.info("Dying before giving birth")
|
||||
return self.die()
|
||||
|
||||
self.due_date = self.now + self.gestation
|
||||
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')
|
||||
|
||||
number_of_babies = int(8+4*self.random.random())
|
||||
self.offspring += 1
|
||||
self.mate = None
|
||||
return self.fertile
|
||||
|
||||
while self.now < self.due_date:
|
||||
yield When(self.due_date)
|
||||
|
||||
self.info('Having {} babies'.format(number_of_babies))
|
||||
for i in range(number_of_babies):
|
||||
agent_class = self.random.choice([Male, Female])
|
||||
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
|
||||
def dead(self):
|
||||
super().dead()
|
||||
if self.due_date is not None:
|
||||
def die(self):
|
||||
if self.pregnancy is not None:
|
||||
self.info('A mother has died carrying a baby!!')
|
||||
|
||||
def impregnate(self, male):
|
||||
self.info(f'{repr(male)} impregnating female {repr(self)}')
|
||||
self.mate = male
|
||||
self.set_state(self.pregnant, when=self.now)
|
||||
return super().die()
|
||||
|
||||
|
||||
class RandomAccident(BaseAgent):
|
||||
|
||||
level = logging.INFO
|
||||
|
||||
def step(self):
|
||||
rabbits_total = self.model.topology.number_of_nodes()
|
||||
if 'rabbits_alive' not in self.model:
|
||||
self.model['rabbits_alive'] = 0
|
||||
rabbits_alive = self.model.get('rabbits_alive', rabbits_total)
|
||||
rabbits_alive = self.model.G.number_of_nodes()
|
||||
|
||||
if not rabbits_alive:
|
||||
return self.die()
|
||||
|
||||
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))
|
||||
for i in self.model.network_agents:
|
||||
if i.state.id == i.dead.id:
|
||||
for i in self.iter_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 = self.model['rabbits_alive'] = rabbits_alive -1
|
||||
i.set_state(i.dead)
|
||||
self.debug('Rabbits alive: {}/{}'.format(rabbits_alive, rabbits_total))
|
||||
if self.model.count_agents(state_id=RabbitModel.dead.id) == self.model.topology.number_of_nodes():
|
||||
self.die()
|
||||
rabbits_alive -= 1
|
||||
i.die()
|
||||
self.debug('Rabbits alive: {}'.format(rabbits_alive))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from soil import easy
|
||||
with easy('rabbits.yml') as sim:
|
||||
sim.run()
|
||||
|
@@ -7,11 +7,10 @@ description: null
|
||||
group: null
|
||||
interval: 1.0
|
||||
max_time: 100
|
||||
model_class: soil.environment.Environment
|
||||
model_class: rabbit_agents.RabbitEnv
|
||||
model_params:
|
||||
agents:
|
||||
topology: true
|
||||
agent_class: rabbit_agents.RabbitModel
|
||||
distribution:
|
||||
- agent_class: rabbit_agents.Male
|
||||
weight: 1
|
||||
@@ -34,5 +33,10 @@ model_params:
|
||||
nodes:
|
||||
- id: 1
|
||||
- id: 0
|
||||
model_reporters:
|
||||
num_males: 'num_males'
|
||||
num_females: 'num_females'
|
||||
num_rabbits: |
|
||||
py:lambda env: env.num_males + env.num_females
|
||||
extra:
|
||||
visualization_params: {}
|
||||
|
Reference in New Issue
Block a user