from soil import FSM, state, default_state, BaseAgent, NetworkAgent, Environment, Simulation, report, parameters as params from collections import Counter import logging import math class RabbitEnv(Environment): prob_death: params.probability = 1e-100 def init(self): 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) @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(NetworkAgent, FSM): sexual_maturity = 30 life_expectancy = 300 @default_state @state def newborn(self): self.info("I am a newborn.") self.age = 0 self.offspring = 0 return self.youngling @state def youngling(self): self.age += 1 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") @state def dead(self): self.die() class Male(Rabbit): max_females = 5 mating_prob = 0.001 @state def fertile(self): self.age += 1 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.mating_prob) if self.prob(self["mating_prob"]): f.impregnate(self) break # Take a break class Female(Rabbit): gestation = 10 pregnancy = -1 @state def fertile(self): # Just wait for a Male 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"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.age += 1 if self.age >= self.life_expectancy: return self.die() if self.pregnancy < self.gestation: self.pregnancy += 1 return 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") 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): def step(self): rabbits_alive = self.model.G.number_of_nodes() if not rabbits_alive: return self.die() prob_death = self.model.prob_death * math.floor( math.log10(max(1, rabbits_alive)) ) 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 i.die() self.debug("Rabbits alive: {}".format(rabbits_alive)) sim = Simulation(model=RabbitEnv, max_time=100, seed="MySeed", num_trials=1) if __name__ == "__main__": sim.run()