-
Notifications
You must be signed in to change notification settings - Fork 263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Moran Process with Mutation #754
Merged
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
4bec215
Add mutation to the Moran process; improve docstrings
marcharper 4932c98
Modications and Tests for Moran process
marcharper 9f725a4
Sort mutation targets for test consistency
marcharper 9227e1d
Addition Moran test and a test fix
marcharper ba97670
Remove comment
marcharper 9648ea4
Docs for Moran process with mutation and an exception (with test) for…
marcharper 2413893
Python 2/3 random compatibility in Moran Process
marcharper File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,7 +31,33 @@ def fitness_proportionate_selection(scores): | |
|
||
|
||
class MoranProcess(object): | ||
def __init__(self, players, turns=100, noise=0, deterministic_cache=None): | ||
def __init__(self, players, turns=100, noise=0, deterministic_cache=None, mutation_rate=0.): | ||
""" | ||
An agent based Moran process class. In each round, each player plays a Match with each other | ||
player. Players are assigned a fitness score by their total score from all matches in the round. | ||
A player is chosen to reproduce proportionally to fitness, possibly mutated, and is cloned. The | ||
clone replaces a randomly chosen player. | ||
|
||
If the mutation_rate is 0, the population will eventually fixate on exactly one player type. In this | ||
case a StopIteration exception is raised and the play stops. If mutation_rate is not zero, then | ||
the process will iterate indefinitely, so mp.play() will never exit, and you should use the class as an | ||
iterator instead. | ||
|
||
When a player mutates it chooses a random player type from the initial population. This is not the only | ||
method yet emulates the common method in the literature. | ||
|
||
Parameters | ||
---------- | ||
players, iterable of axelrod.Player subclasses | ||
turns: int, 100 | ||
The number of turns in each pairwise interaction | ||
noise: float, 0 | ||
The background noise, if any. Randomly flips plays with probability `noise`. | ||
deterministic_cache: axelrod.DeterministicCache, None | ||
A optional prebuilt deterministic cache | ||
mutation_rate: float, 0 | ||
The rate of mutation. Replicating players are mutated with probability `mutation_rate` | ||
""" | ||
self.turns = turns | ||
self.noise = noise | ||
self.initial_players = players # save initial population | ||
|
@@ -40,10 +66,24 @@ def __init__(self, players, turns=100, noise=0, deterministic_cache=None): | |
self.set_players() | ||
self.score_history = [] | ||
self.winning_strategy_name = None | ||
self.mutation_rate = mutation_rate | ||
assert (mutation_rate >= 0) and (mutation_rate <= 1) | ||
assert (noise >= 0) and (noise <= 1) | ||
if deterministic_cache is not None: | ||
self.deterministic_cache = deterministic_cache | ||
else: | ||
self.deterministic_cache = DeterministicCache() | ||
# Build the set of mutation targets | ||
# Determine the number of unique types (players) | ||
keys = set([str(p) for p in players]) | ||
# Create a dictionary mapping each type to a set of representatives of the other types | ||
d = dict() | ||
for p in players: | ||
d[str(p)] = p | ||
mt = dict() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure |
||
for key in sorted(keys): | ||
mt[key] = [v for (k, v) in sorted(d.items()) if k != key] | ||
self.mutation_targets = mt | ||
|
||
def set_players(self): | ||
"""Copy the initial players into the first population.""" | ||
|
@@ -60,29 +100,48 @@ def _stochastic(self): | |
A boolean to show whether a match between two players would be | ||
stochastic | ||
""" | ||
return is_stochastic(self.players, self.noise) | ||
return is_stochastic(self.players, self.noise) or (self.mutation_rate > 0) | ||
|
||
def mutate(self, index): | ||
# If mutate, choose another strategy at random from the initial population | ||
r = random.random() | ||
if r < self.mutation_rate: | ||
s = str(self.players[index]) | ||
p = random.choice(self.mutation_targets[s]) | ||
new_player = p.clone() | ||
else: | ||
# Just clone the player | ||
new_player = self.players[index].clone() | ||
return new_player | ||
|
||
def __next__(self): | ||
"""Iterate the population: | ||
- play the round's matches | ||
- chooses a player proportionally to fitness (total score) to reproduce | ||
- mutate, if appropriate | ||
- choose a player at random to be replaced | ||
- update the population | ||
""" | ||
# Check the exit condition, that all players are of the same type. | ||
classes = set(p.__class__ for p in self.players) | ||
if len(classes) == 1: | ||
classes = set(str(p) for p in self.players) | ||
if (self.mutation_rate == 0) and (len(classes) == 1): | ||
self.winning_strategy_name = str(self.players[0]) | ||
raise StopIteration | ||
scores = self._play_next_round() | ||
# Update the population | ||
# Fitness proportionate selection | ||
j = fitness_proportionate_selection(scores) | ||
# Mutate? | ||
if self.mutation_rate: | ||
new_player = self.mutate(j) | ||
else: | ||
new_player = self.players[j].clone() | ||
# Randomly remove a strategy | ||
i = randrange(0, len(self.players)) | ||
# Replace player i with clone of player j | ||
self.players[i] = self.players[j].clone() | ||
self.players[i] = new_player | ||
self.populations.append(self.population_distribution()) | ||
return self | ||
|
||
def _play_next_round(self): | ||
"""Plays the next round of the process. Every player is paired up | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of the literature I've seen write this in a generic way with a matrix$M$ for mutation where $M_ij$ is the probability if mutating from species $i$ to $j$ . Is that worth doing (perhaps at a later date)?
More of a question than a request. We could simply have that the mutation rate could either be a float or a 2 d array... (Or whatever: not suggest this needs to be done here).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we could do that. This implementation uses the "standard matrix" which is 1 - mu on the diagonal and mu / (n-1) elsewhere with n being the number of types and mu the mutation rate.
In my experience this is far more common than an arbitrary mutation matrix but we can certainly generalize later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah no doubt I'm reading theoretic stuff but in general the numeric computations would be using this form. :)