Skip to content

Commit 0dbb3fb

Browse files
committed
Merge pull request #272 from marcharper/220-2
Rebase 220 onto master
2 parents e7922ab + 29afa8d commit 0dbb3fb

File tree

12 files changed

+746
-412
lines changed

12 files changed

+746
-412
lines changed

axelrod/eigen.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""
2+
Compute the principal eigenvector of a matrix using power iteration.
3+
4+
See also numpy.linalg.eig which calculates all the eigenvalues and
5+
eigenvectors.
6+
"""
7+
8+
import numpy
9+
10+
11+
def normalise(nvec):
12+
"""Normalises the given numpy array."""
13+
return nvec / numpy.sqrt(numpy.dot(nvec, nvec))
14+
15+
16+
def squared_error(vector_1, vector_2, sqrt=True):
17+
"""Computes the squared error between two numpy arrays."""
18+
diff = vector_1 - vector_2
19+
s = numpy.dot(diff, diff)
20+
if sqrt:
21+
return numpy.sqrt(s)
22+
return s
23+
24+
25+
def power_iteration(mat, initial=None):
26+
"""
27+
Generator of successive approximations.
28+
29+
Params
30+
------
31+
mat: numpy.matrix
32+
The matrix to use for multiplication iteration
33+
initial: numpy.array, None
34+
The initial state. Will be set to numpy.array([1, 1, ...]) if None
35+
36+
Yields
37+
------
38+
Successive powers (mat ^ k) * initial
39+
"""
40+
41+
if initial is None:
42+
size = mat.shape[0]
43+
initial = normalise(numpy.ones(size))
44+
vec = initial
45+
while True:
46+
vec = normalise(numpy.dot(mat, vec))
47+
yield vec
48+
49+
50+
def principal_eigenvector(mat, maximum_iterations=None, max_error=1e-8):
51+
"""
52+
Computes the (normalised) principal eigenvector of the given matrix.
53+
54+
Params
55+
------
56+
mat: numpy.matrix
57+
The matrix to use for multiplication iteration
58+
initial: numpy.array, None
59+
The initial state. Will be set to numpy.array([1, 1, ...]) if None
60+
maximum_iterations: int, None
61+
The maximum number of iterations of the approximation
62+
max_error: float, 1e-8
63+
Exit criterion -- error threshold of the difference of successive steps
64+
"""
65+
66+
mat_ = numpy.matrix(mat)
67+
size = mat_.shape[0]
68+
initial = numpy.ones(size)
69+
70+
# Power iteration
71+
if not maximum_iterations:
72+
maximum_iterations = float('inf')
73+
last = initial
74+
for i, vector in enumerate(power_iteration(mat, initial=initial)):
75+
if i > maximum_iterations:
76+
break
77+
if squared_error(vector, last) < max_error:
78+
break
79+
last = vector
80+
# Compute the eigenvalue (Rayleigh quotient)
81+
eigenvalue = numpy.dot(
82+
numpy.dot(mat_, vector), vector) / numpy.dot(vector, vector)
83+
# Liberate the eigenvalue from numpy
84+
eigenvalue = float(eigenvalue)
85+
return (vector, eigenvalue)

axelrod/plot.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@
1010
class Plot(object):
1111

1212
def __init__(self, result_set):
13-
if result_set._finalised:
14-
self.result_set = result_set
15-
else:
16-
raise AttributeError(
17-
"No payoffs list has been passed to this ResultSet object.")
13+
self.result_set = result_set
1814
# self._nplayers = self.result_set.nplayers
1915
self.matplotlib_installed = matplotlib_installed
2016

axelrod/result_set.py

Lines changed: 61 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -22,80 +22,51 @@ def median(lst):
2222
class ResultSet(object):
2323
"""A class to hold the results of a tournament."""
2424

25-
unfinalised_error_msg = 'payoffs_list has not been set.'
26-
27-
def __init__(self, players, turns, repetitions):
25+
def __init__(self, players, turns, repetitions, outcome):
2826
self.players = players
2927
self.nplayers = len(players)
3028
self.turns = turns
3129
self.repetitions = repetitions
32-
self._init_results()
33-
self._finalised = False
34-
35-
# payoffs_list is the only property with a setter method.
36-
#
37-
# Setting payoffs_list calls methods to set all the other
38-
# properties on the instance (result, scores, ranking, ranked_names,
39-
# payoff_matrix, payoff_stddevs).
40-
#
41-
# The getter methods on those other properties will return an error
42-
# if payoffs_list has not been set.
43-
44-
@property
45-
def payoffs_list(self):
46-
return self._payoffs_list
47-
48-
@payoffs_list.setter
49-
def payoffs_list(self, payoffs_list):
50-
self._payoffs_list = payoffs_list
51-
self._update_results()
52-
self._finalised = True
53-
self._scores = self._generate_scores()
54-
self._normalised_scores = self._generate_normalised_scores()
55-
self._ranking = self._generate_ranking(self.scores)
56-
self._ranked_names = self._generate_ranked_names(self.ranking)
57-
self._payoff_matrix, self._payoff_stddevs = self._generate_payoff_matrix()
58-
59-
@payoffs_list.deleter
60-
def payoffs_list(self):
61-
del(self._payoffs_list)
62-
self._init_results()
63-
self._finalised = False
64-
65-
@property
66-
def results(self):
67-
if self._finalised:
68-
return self._results
69-
else:
70-
raise AttributeError(self.unfinalised_error_msg)
71-
72-
def _init_results(self):
30+
self.outcome = outcome
31+
self.results = self._results(outcome)
32+
if 'payoff' in self.results:
33+
self.scores = self._scores(self.results['payoff'])
34+
self.normalised_scores = self._normalised_scores(self.scores)
35+
self.ranking = self._ranking(self.scores)
36+
self.ranked_names = self._ranked_names(self.ranking)
37+
self.payoff_matrix, self.payoff_stddevs = (
38+
self._payoff_matrix(self.results['payoff']))
39+
if 'cooperation' in self.results:
40+
self.cooperation = self._cooperation(self.results['cooperation'])
41+
self.normalised_cooperation = (
42+
self._normalised_cooperation(self.cooperation))
43+
44+
def _null_matrix(self):
7345
plist = list(range(self.nplayers))
7446
replist = list(range(self.repetitions))
75-
self._results = [[[0 for r in replist] for j in plist] for i in plist]
76-
77-
def _update_results(self):
78-
for index, payoffs in enumerate(self.payoffs_list):
79-
for i in range(len(self.players)):
80-
for j in range(len(self.players)):
81-
self._results[i][j][index] = payoffs[i][j]
82-
83-
@property
84-
def scores(self):
85-
if self._finalised:
86-
return self._scores
87-
else:
88-
raise AttributeError(self.unfinalised_error_msg)
89-
90-
def _generate_scores(self):
91-
"""Return normalized scores based on the results.
92-
93-
Originally there were no self-interactions, so the code here was rewritten
94-
to exclude those from the generated score. To include self-interactions,
95-
remove the condition on ip and ires and fix the normalization factor.
47+
return [[[0 for r in replist] for j in plist] for i in plist]
48+
49+
def _results(self, outcome):
50+
results = {}
51+
for result_type, result_list in outcome.items():
52+
matrix = self._null_matrix()
53+
for index, result_matrix in enumerate(result_list):
54+
for i in range(len(self.players)):
55+
for j in range(len(self.players)):
56+
matrix[i][j][index] = result_matrix[i][j]
57+
results[result_type] = matrix
58+
return results
59+
60+
def _scores(self, payoff):
61+
"""Return scores based on the results.
62+
63+
Originally there were no self-interactions, so the code here was
64+
rewritten to exclude those from the generated score. To include
65+
self-interactions, remove the condition on ip and ires and fix the
66+
normalization factor.
9667
"""
9768
scores = []
98-
for ires, res in enumerate(self.results):
69+
for ires, res in enumerate(payoff):
9970
scores.append([])
10071
for irep in range(self.repetitions):
10172
scores[-1].append(0)
@@ -104,26 +75,12 @@ def _generate_scores(self):
10475
scores[-1][-1] += res[ip][irep]
10576
return scores
10677

107-
@property
108-
def normalised_scores(self):
109-
if self._finalised:
110-
return self._normalised_scores
111-
else:
112-
raise AttributeError(self.unfinalised_error_msg)
113-
114-
def _generate_normalised_scores(self):
78+
def _normalised_scores(self, scores):
11579
normalisation = self.turns * (self.nplayers - 1)
11680
return [
117-
[1.0 * s / normalisation for s in r] for r in self.scores]
81+
[1.0 * s / normalisation for s in r] for r in scores]
11882

119-
@property
120-
def ranking(self):
121-
if self._finalised:
122-
return self._ranking
123-
else:
124-
raise AttributeError(self.unfinalised_error_msg)
125-
126-
def _generate_ranking(self, scores):
83+
def _ranking(self, scores):
12784
"""
12885
Returns a list of players (their index within the
12986
players list rather than a player instance)
@@ -134,37 +91,16 @@ def _generate_ranking(self, scores):
13491
key=lambda i: -median(scores[i]))
13592
return ranking
13693

137-
@property
138-
def ranked_names(self):
139-
if self._finalised:
140-
return self._ranked_names
141-
else:
142-
raise AttributeError(self.unfinalised_error_msg)
143-
144-
def _generate_ranked_names(self, ranking):
94+
def _ranked_names(self, ranking):
14595
"""Returns a list of players names sorted by their ranked order."""
14696
ranked_names = [str(self.players[i]) for i in ranking]
14797
return ranked_names
14898

149-
@property
150-
def payoff_matrix(self):
151-
if self._finalised:
152-
return self._payoff_matrix
153-
else:
154-
raise AttributeError(self.unfinalised_error_msg)
155-
156-
@property
157-
def payoff_stddevs(self):
158-
if self._finalised:
159-
return self._payoff_stddevs
160-
else:
161-
raise AttributeError(self.unfinalised_error_msg)
162-
163-
def _generate_payoff_matrix(self):
99+
def _payoff_matrix(self, payoff):
164100
"""Returns a per-turn averaged payoff matrix and its stddevs."""
165101
averages = []
166102
stddevs = []
167-
for res in self.results:
103+
for res in payoff:
168104
averages.append([])
169105
stddevs.append([])
170106
for s in res:
@@ -176,15 +112,22 @@ def _generate_payoff_matrix(self):
176112
stddevs[-1].append(dev)
177113
return averages, stddevs
178114

115+
def _cooperation(self, results):
116+
return[[sum(element) for element in row] for row in results]
117+
118+
def _normalised_cooperation(self, cooperation):
119+
normalisation = self.turns * self.repetitions
120+
return[
121+
[1.0 * element / normalisation for element in row]
122+
for row in cooperation]
123+
179124
def csv(self):
180-
if self._finalised:
181-
csv_string = StringIO()
182-
header = ",".join(self.ranked_names) + "\n"
183-
csv_string.write(header)
184-
writer = csv.writer(csv_string, lineterminator="\n")
185-
for irep in range(self.repetitions):
186-
data = [self.normalised_scores[rank][irep] for rank in self.ranking]
187-
writer.writerow(list(map(str, data)))
188-
return csv_string.getvalue()
189-
else:
190-
raise AttributeError(self.unfinalised_error_msg)
125+
csv_string = StringIO()
126+
header = ",".join(self.ranked_names) + "\n"
127+
csv_string.write(header)
128+
writer = csv.writer(csv_string, lineterminator="\n")
129+
for irep in range(self.repetitions):
130+
data = [self.normalised_scores[rank][irep]
131+
for rank in self.ranking]
132+
writer.writerow(list(map(str, data)))
133+
return csv_string.getvalue()

0 commit comments

Comments
 (0)