Skip to content

Commit

Permalink
Merge pull request #1042 from Axelrod-Python/reconcile-tournaments
Browse files Browse the repository at this point in the history
Reconcile tournaments
  • Loading branch information
marcharper authored Jun 10, 2017
2 parents 99d0e2e + 5ae0f9d commit 3b1a4d6
Show file tree
Hide file tree
Showing 23 changed files with 545 additions and 917 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,6 @@ ENV/
# Docker files
Dockerfile
docker-compose.yml

# Mypy files
.mypy_cache/
5 changes: 3 additions & 2 deletions axelrod/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os

on_windows = os.name == 'nt'
DEFAULT_TURNS = 200

# The order of imports matters!
from .version import __version__
Expand All @@ -19,8 +20,8 @@
from .strategies import *
from .deterministic_cache import DeterministicCache
from .match_generator import *
from .tournament import (
Tournament, ProbEndTournament, SpatialTournament, ProbEndSpatialTournament)
from .tournament import Tournament
from .result_set import ResultSet, ResultSetFromFile
from .ecosystem import Ecosystem
from .fingerprint import AshlockFingerprint

8 changes: 3 additions & 5 deletions axelrod/fingerprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,9 @@ def fingerprint(
step, progress_bar=progress_bar)

self.step = step
self.spatial_tournament = axl.SpatialTournament(
tourn_players,
turns=turns,
repetitions=repetitions,
edges=edges)
self.spatial_tournament = axl.Tournament(tourn_players, turns=turns,
repetitions=repetitions,
edges=edges)
self.spatial_tournament.play(build_results=False,
filename=filename,
processes=processes,
Expand Down
70 changes: 60 additions & 10 deletions axelrod/match.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from axelrod.actions import Actions
from axelrod.game import Game
from axelrod import DEFAULT_TURNS
import axelrod.interaction_utils as iu
from .deterministic_cache import DeterministicCache
import random
from math import ceil, log


C, D = Actions.C, Actions.D
Expand All @@ -15,7 +18,8 @@ def is_stochastic(players, noise):

class Match(object):

def __init__(self, players, turns, game=None, deterministic_cache=None,
def __init__(self, players, turns=None, prob_end=None,
game=None, deterministic_cache=None,
noise=0, match_attributes=None):
"""
Parameters
Expand All @@ -24,6 +28,8 @@ def __init__(self, players, turns, game=None, deterministic_cache=None,
A pair of axelrod.Player objects
turns : integer
The number of turns per match
prob_end : float
The probability of a given turn ending a match
game : axelrod.Game
The game object used to score the match
deterministic_cache : axelrod.DeterministicCache
Expand All @@ -35,9 +41,14 @@ def __init__(self, players, turns, game=None, deterministic_cache=None,
The default is to use the correct values for turns, game and noise
but these can be overridden if desired.
"""

defaults = {(True, True): (DEFAULT_TURNS, 0),
(True, False): (float('inf'), prob_end),
(False, True): (turns, 0),
(False, False): (turns, prob_end)}
self.turns, self.prob_end = defaults[(turns is None, prob_end is None)]

self.result = []
self.turns = turns
self._cache_key = (players[0], players[1], turns)
self.noise = noise

if game is None:
Expand All @@ -51,8 +62,9 @@ def __init__(self, players, turns, game=None, deterministic_cache=None,
self._cache = deterministic_cache

if match_attributes is None:
known_turns = self.turns if prob_end is None else float('inf')
self.match_attributes = {
'length': self.turns,
'length': known_turns,
'game': self.game,
'noise': self.noise
}
Expand Down Expand Up @@ -112,20 +124,21 @@ def play(self):
i.e. One entry per turn containing a pair of actions.
"""
if self._stochastic or (self._cache_key not in self._cache):
turn = 0
turns = min(sample_length(self.prob_end), self.turns)
cache_key = (self.players[0], self.players[1], turns)

if self._stochastic or (cache_key not in self._cache):
for p in self.players:
p.reset()
while turn < self.turns:
turn += 1
for _ in range(turns):
self.players[0].play(self.players[1], self.noise)
result = list(
zip(self.players[0].history, self.players[1].history))

if self._cache_update_required:
self._cache[self._cache_key] = result
self._cache[cache_key] = result
else:
result = self._cache[self._cache_key]
result = self._cache[cache_key]

self.result = result
return result
Expand Down Expand Up @@ -176,3 +189,40 @@ def sparklines(self, c_symbol='█', d_symbol=' '):

def __len__(self):
return self.turns


def sample_length(prob_end):
"""
Sample length of a game.
This is using inverse random sample on a probability density function
<https://en.wikipedia.org/wiki/Probability_density_function> given by:
f(n) = p_end * (1 - p_end) ^ (n - 1)
(So the probability of length n is given by f(n))
Which gives cumulative distribution function
<https://en.wikipedia.org/wiki/Cumulative_distribution_function>:
F(n) = 1 - (1 - p_end) ^ n
(So the probability of length less than or equal to n is given by F(n))
Which gives for given x = F(n) (ie the random sample) gives n:
n = ceil((ln(1-x)/ln(1-p_end)))
This approach of sampling from a distribution is called inverse
transform sampling
<https://en.wikipedia.org/wiki/Inverse_transform_sampling>.
Note that this corresponds to sampling at the end of every turn whether
or not the Match ends.
"""
if prob_end == 0:
return float("inf")
if prob_end == 1:
return 1
x = random.random()
return int(ceil(log(1 - x) / log(1 - prob_end)))
Loading

0 comments on commit 3b1a4d6

Please sign in to comment.