66
77import matplotlib .pyplot as plt
88import numpy as np
9- from axelrod import DEFAULT_TURNS , Game , Player
9+ from axelrod import EvolvablePlayer , DEFAULT_TURNS , Game , Player
1010
1111from .deterministic_cache import DeterministicCache
1212from .graph import Graph , complete_graph
@@ -56,6 +56,8 @@ def __init__(
5656 interaction_graph : Graph = None ,
5757 reproduction_graph : Graph = None ,
5858 fitness_transformation : Callable = None ,
59+ mutation_method = "transition" ,
60+ stop_on_fixation = True
5961 ) -> None :
6062 """
6163 An agent based Moran process class. In each round, each player plays a
@@ -108,6 +110,11 @@ def __init__(
108110 given
109111 fitness_transformation:
110112 A function mapping a score to a (non-negative) float
113+ mutation_method:
114+ A string indicating if the mutation method should be between original types ("transition")
115+ or based on the player's mutation method, if present ("atomic").
116+ stop_on_fixation:
117+ A bool indicating if the process should stop on fixation
111118 """
112119 self .turns = turns
113120 self .prob_end = prob_end
@@ -120,6 +127,12 @@ def __init__(
120127 self .score_history = [] # type: List
121128 self .winning_strategy_name = None # type: Optional[str]
122129 self .mutation_rate = mutation_rate
130+ self .stop_on_fixation = stop_on_fixation
131+ m = mutation_method .lower ()
132+ if m in ["atomic" , "transition" ]:
133+ self .mutation_method = m
134+ else :
135+ raise ValueError ("Invalid mutation method {}" .format (mutation_method ))
123136 assert (mutation_rate >= 0 ) and (mutation_rate <= 1 )
124137 assert (noise >= 0 ) and (noise <= 1 )
125138 mode = mode .lower ()
@@ -159,6 +172,7 @@ def __init__(
159172 # Map players to graph vertices
160173 self .locations = sorted (interaction_graph .vertices )
161174 self .index = dict (zip (sorted (interaction_graph .vertices ), range (len (players ))))
175+ self .fixated = self .fixation_check ()
162176
163177 def set_players (self ) -> None :
164178 """Copy the initial players into the first population."""
@@ -176,17 +190,23 @@ def mutate(self, index: int) -> Player:
176190 index:
177191 The index of the player to be mutated
178192 """
179- # Choose another strategy at random from the initial population
180- r = random .random ()
181- if r < self .mutation_rate :
182- s = str (self .players [index ])
183- j = randrange (0 , len (self .mutation_targets [s ]))
184- p = self .mutation_targets [s ][j ]
185- new_player = p .clone ()
186- else :
187- # Just clone the player
188- new_player = self .players [index ].clone ()
189- return new_player
193+
194+ if self .mutation_method == "atomic" :
195+ if not issubclass (self .players [index ].__class__ , EvolvablePlayer ):
196+ raise TypeError ("Player is not evolvable. Use a subclass of EvolvablePlayer." )
197+ return self .players [index ].mutate ()
198+
199+ # Assuming mutation_method == "transition"
200+ if self .mutation_rate > 0 :
201+ # Choose another strategy at random from the initial population
202+ r = random .random ()
203+ if r < self .mutation_rate :
204+ s = str (self .players [index ])
205+ j = randrange (0 , len (self .mutation_targets [s ]))
206+ p = self .mutation_targets [s ][j ]
207+ return p .clone ()
208+ # Just clone the player
209+ return self .players [index ].clone ()
190210
191211 def death (self , index : int = None ) -> int :
192212 """
@@ -250,14 +270,13 @@ def fixation_check(self) -> bool:
250270 Boolean:
251271 True if fixation has occurred (population all of a single type)
252272 """
253- if self .mutation_rate > 0 :
254- return False
255273 classes = set (str (p ) for p in self .players )
274+ self .fixated = False
256275 if len (classes ) == 1 :
257276 # Set the winning strategy name variable
258277 self .winning_strategy_name = str (self .players [0 ])
259- return True
260- return False
278+ self . fixated = True
279+ return self . fixated
261280
262281 def __next__ (self ) -> object :
263282 """
@@ -275,7 +294,7 @@ def __next__(self) -> object:
275294 Returns itself with a new population
276295 """
277296 # Check the exit condition, that all players are of the same type.
278- if self .fixation_check ():
297+ if self .stop_on_fixation and self . fixation_check ():
279298 raise StopIteration
280299 if self .mode == "bd" :
281300 # Birth then death
@@ -286,16 +305,10 @@ def __next__(self) -> object:
286305 i = self .death ()
287306 self .players [i ] = None
288307 j = self .birth (i )
289- # Mutate
290- if self .mutation_rate :
291- new_player = self .mutate (j )
292- else :
293- new_player = self .players [j ].clone ()
294- # Replace player i with clone of player j
295- self .players [i ] = new_player
308+ # Mutate and/or replace player i with clone of player j
309+ self .players [i ] = self .mutate (j )
310+ # Record population.
296311 self .populations .append (self .population_distribution ())
297- # Check again for fixation
298- self .fixation_check ()
299312 return self
300313
301314 def _matchup_indices (self ) -> Set [Tuple [int , int ]]:
@@ -395,10 +408,10 @@ def play(self) -> List[Counter]:
395408 populations:
396409 Returns a list of all the populations
397410 """
398- if self .mutation_rate != 0 :
411+ if not self . stop_on_fixation or self .mutation_rate != 0 :
399412 raise ValueError (
400413 "MoranProcess.play() will never exit if mutation_rate is"
401- "nonzero. Use iteration instead."
414+ "nonzero or stop_on_fixation is False . Use iteration instead."
402415 )
403416 while True :
404417 try :
0 commit comments