Skip to content

Commit 8a231d1

Browse files
committed
Add @GDKO's Particle Swarm optimization code
1 parent 1806289 commit 8a231d1

File tree

2 files changed

+159
-1
lines changed

2 files changed

+159
-1
lines changed

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015 Martin Jones
3+
Copyright (c) 2015 Martin Jones, Georgios Koutsovoulos, Marc Harper
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

pso_evolve.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
"""
2+
Particle Swarm strategy training code.
3+
4+
Original version by Georgios Koutsovoulos @GDKO :
5+
https://gist.github.com/GDKO/60c3d0fd423598f3c4e4
6+
Based on Martin Jones @mojones original LookerUp code
7+
"""
8+
9+
import axelrod
10+
11+
from pyswarm import pso
12+
13+
14+
class Gambler(Player):
15+
16+
name = 'Gambler'
17+
classifier = {
18+
'memory_depth': float('inf'),
19+
'stochastic': True,
20+
'makes_use_of': set(),
21+
'inspects_source': False,
22+
'manipulates_source': False,
23+
'manipulates_state': False
24+
}
25+
26+
@init_args
27+
def __init__(self, lookup_table=None):
28+
"""
29+
If no lookup table is provided to the constructor, then use the TFT one.
30+
"""
31+
Player.__init__(self)
32+
33+
if not lookup_table:
34+
lookup_table = {
35+
('', 'C', 'D') : 0,
36+
('', 'D', 'D') : 0,
37+
('', 'C', 'C') : 1,
38+
('', 'D', 'C') : 1,
39+
}
40+
41+
self.lookup_table = lookup_table
42+
# Rather than pass the number of previous turns (m) to consider in as a
43+
# separate variable, figure it out. The number of turns is the length
44+
# of the second element of any given key in the dict.
45+
self.plays = len(list(self.lookup_table.keys())[0][1])
46+
# The number of opponent starting actions is the length of the first
47+
# element of any given key in the dict.
48+
self.opponent_start_plays = len(list(self.lookup_table.keys())[0][0])
49+
# If the table dictates to ignore the opening actions of the opponent
50+
# then the memory classification is adjusted
51+
if self.opponent_start_plays == 0:
52+
self.classifier['memory_depth'] = self.plays
53+
54+
# Ensure that table is well-formed
55+
for k, v in lookup_table.items():
56+
if (len(k[1]) != self.plays) or (len(k[0]) != self.opponent_start_plays):
57+
raise ValueError("All table elements must have the same size")
58+
59+
60+
def strategy(self, opponent):
61+
# If there isn't enough history to lookup an action, cooperate.
62+
if len(self.history) < max(self.plays, self.opponent_start_plays):
63+
return C
64+
# Count backward m turns to get my own recent history.
65+
history_start = -1 * self.plays
66+
my_history = ''.join(self.history[history_start:])
67+
# Do the same for the opponent.
68+
opponent_history = ''.join(opponent.history[history_start:])
69+
# Get the opponents first n actions.
70+
opponent_start = ''.join(opponent.history[:self.opponent_start_plays])
71+
# Put these three strings together in a tuple.
72+
key = (opponent_start, my_history, opponent_history)
73+
# Look up the action associated with that tuple in the lookup table.
74+
action = float(self.lookup_table[key])
75+
return random_choice(action)
76+
77+
78+
79+
class TestGambler(Gambler):
80+
"""
81+
A LookerUp strategy that uses pattern supplied when initialised.
82+
"""
83+
84+
name = "TestGambler"
85+
86+
def __init__(self,pattern):
87+
plays = 2
88+
opponent_start_plays = 2
89+
90+
# Generate the list of possible tuples, i.e. all possible combinations
91+
# of m actions for me, m actions for opponent, and n starting actions
92+
# for opponent.
93+
self_histories = [''.join(x) for x in product('CD', repeat=plays)]
94+
other_histories = [''.join(x) for x in product('CD', repeat=plays)]
95+
opponent_starts = [''.join(x) for x in
96+
product('CD', repeat=opponent_start_plays)]
97+
lookup_table_keys = list(product(opponent_starts, self_histories,
98+
other_histories))
99+
100+
# Zip together the keys and the action pattern to get the lookup table.
101+
lookup_table = dict(zip(lookup_table_keys, pattern))
102+
Gambler.__init__(self, lookup_table=lookup_table)
103+
104+
105+
def score_for_pattern(my_strategy_factory,pattern, iterations=200):
106+
"""
107+
Given a function that will return a strategy,
108+
calculate the average score per turn
109+
against all ordinary strategies. If the
110+
opponent is classified as stochastic, then
111+
run 100 repetitions and take the average to get
112+
a good estimate.
113+
"""
114+
scores_for_all_opponents = []
115+
for opponent in axelrod.ordinary_strategies:
116+
117+
# decide whether we need to sample or not
118+
if opponent.classifier['stochastic']:
119+
repetitions = 100
120+
else:
121+
repetitions = 1
122+
scores_for_this_opponent = []
123+
124+
# calculate an average for this opponent
125+
for _ in range(repetitions):
126+
me = my_strategy_factory(pattern)
127+
other = opponent()
128+
# make sure that both players know what length the match will be
129+
me.set_tournament_attributes(length=iterations)
130+
other.set_tournament_attributes(length=iterations)
131+
132+
scores_for_this_opponent.append(score_single(me, other, iterations))
133+
134+
mean_vs_opponent = sum(scores_for_this_opponent) / len(scores_for_this_opponent)
135+
scores_for_all_opponents.append(mean_vs_opponent)
136+
137+
# calculate the average for all opponents
138+
overall_average_score = sum(scores_for_all_opponents) / len(scores_for_all_opponents)
139+
return overall_average_score
140+
141+
142+
def optimizepso(x):
143+
return -score_for_pattern(TestGambler, x)
144+
145+
if __name__ == "__main__":
146+
lb = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
147+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
148+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
149+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
150+
ub = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
151+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
152+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
153+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
154+
155+
# The parameters of phip, phig and omega will lead to slower conversion
156+
xopt, fopt = pso(optimizepso, lb, ub, swarmsize=100, maxiter=20, processes=60,
157+
debug=True,
158+
phip=0.8, phig=0.8, omega=0.8)

0 commit comments

Comments
 (0)