@@ -40,7 +40,11 @@ def __init__(self, players, turns=100, noise=0, deterministic_cache=None, mutati
40
40
41
41
If the mutation_rate is 0, the population will eventually fixate on exactly one player type. In this
42
42
case a StopIteration exception is raised and the play stops. If mutation_rate is not zero, then
43
- the process will iterate indefinitely.
43
+ the process will iterate indefinitely, so mp.play() will never exit, and you should use the class as an
44
+ iterator instead.
45
+
46
+ When a player mutates, it chooses a random player type from the initial population. This is not the only
47
+ method yet emulates the common method in the literature.
44
48
45
49
Parameters
46
50
----------
@@ -65,11 +69,21 @@ def __init__(self, players, turns=100, noise=0, deterministic_cache=None, mutati
65
69
self .mutation_rate = mutation_rate
66
70
assert (mutation_rate >= 0 ) and (mutation_rate <= 1 )
67
71
assert (noise >= 0 ) and (noise <= 1 )
68
-
69
72
if deterministic_cache is not None :
70
73
self .deterministic_cache = deterministic_cache
71
74
else :
72
75
self .deterministic_cache = DeterministicCache ()
76
+ # Build the set of mutation targets
77
+ # Determine the number of unique types (players)
78
+ keys = set ([str (p ) for p in players ])
79
+ # Create a dictionary mapping each type to a set of representatives of the other types
80
+ d = dict ()
81
+ for p in players :
82
+ d [str (p )] = p
83
+ mt = dict ()
84
+ for key in keys :
85
+ mt [key ] = [v for (k , v ) in d .items () if k != key ]
86
+ self .mutation_targets = mt
73
87
74
88
def set_players (self ):
75
89
"""Copy the initial players into the first population."""
@@ -88,10 +102,23 @@ def _stochastic(self):
88
102
"""
89
103
return is_stochastic (self .players , self .noise ) or (self .mutation_rate > 0 )
90
104
105
+ def mutate (self , index ):
106
+ # If mutate, choose another strategy at random from the initial population
107
+ r = random .random ()
108
+ if r < self .mutation_rate :
109
+ s = str (self .players [index ])
110
+ p = random .choice (self .mutation_targets [s ])
111
+ new_player = p .clone ()
112
+ else :
113
+ # Just clone the player
114
+ new_player = self .players [index ].clone ()
115
+ return new_player
116
+
91
117
def __next__ (self ):
92
118
"""Iterate the population:
93
119
- play the round's matches
94
120
- chooses a player proportionally to fitness (total score) to reproduce
121
+ - mutate, if appropriate
95
122
- choose a player at random to be replaced
96
123
- update the population
97
124
"""
@@ -107,15 +134,15 @@ def __next__(self):
107
134
j = fitness_proportionate_selection (scores )
108
135
# Mutate?
109
136
if self .mutation_rate :
110
- r = random .random ()
111
- # If mutate, choose another strategy at random
112
- if r < self .mutation_rate :
113
- j = randrange (0 , len (self .players ))
137
+ new_player = self .mutate (j )
138
+ else :
139
+ new_player = self .players [j ].clone ()
114
140
# Randomly remove a strategy
115
141
i = randrange (0 , len (self .players ))
116
142
# Replace player i with clone of player j
117
- self .players [i ] = self . players [ j ]. clone ()
143
+ self .players [i ] = new_player
118
144
self .populations .append (self .population_distribution ())
145
+ return self
119
146
120
147
def _play_next_round (self ):
121
148
"""Plays the next round of the process. Every player is paired up
0 commit comments