Skip to content
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

Re factor/implement first tournament strategies #1275

Merged
merged 31 commits into from
Dec 11, 2019

Conversation

drvinceknight
Copy link
Member

#1273 noted a number of potential implementation errors in the first tournament strategies.

This is a first draft of addressing these so we can discuss particular implementations.

I believe that a number of errors were a result of confusion between first tournament and second tournament code so I've renamed all the strategies: FirstBy<author> and SecondBy<author>. (I'm open to other suggestions).

In some places I've added a number of things to the docstrings to make explicit the assumptions made when descriptions are not clear.

Closes #1273

Happy to change the prefix etc...
Also make minor docstring amendments.

Also made some notes regarding Downing.
I believe the logic was slightly faulty for Tullock: the first 11 moves
was correct however once past there we should only be considering the 10
previous moves.
As noted by @id428 we were not giving a "true" fresh start so I've
implemented that. I also added the two final defections (when the game
length is known).
This is a complete rewrite of the Downing strategy.

To be able to do this I've used the description in Downing's 1975 paper.
This description itself is not sufficiently clear and so I've had to
make some further assumptions which I've clearly documented.

Note: there was documentation claiming that there was a bug in the
implementation in the original tournament. I believe this was a mistake
due to a misinterpretation of one online set of slides where they
commented that there was a mistake in the implementation. This however
was not a bug and was actually described quite a lot in Axelrod's
original tournament: the strategy was implemented to act a particular
way in the first two rounds and this had the result of making the
strategy a king maker. This however was not a bug, just a particular
interpretation of the overall decision rule described in Downing's 1975
paper.
Note that this was not the specific error that @id428 pointed out but
having reviewed the papers and source code I found this one minor
inaccuracy.

@id428 made a point that the strategy should cooperate twice after it's
round of retaliations but I do
not see this in any of the descriptions of the strategy. Once the
strategy has finished retaliating, all the texts indicate that it
cooperates again but ready to retaliate.
@drvinceknight
Copy link
Member Author

I've hopefully kept my commits quite modular and in some cases added text describing my approach to the commit message. For example, Downing required me to sift through Downing's 1975 paper: eccd7e2.

Copy link
Member

@marcharper marcharper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about having a classifier entry for "in Axelrod's first tournament" and "in Axelrod's second tournament"? Then we can also easily add lists of first and second tournament strategies along side short_run_term_strategies for ease of use (and possibly a nice tutorial example, maybe even an advanced tutorial comparing to the Fortran implementations with fingerprints). We'd also need to add these classifiers to TFT.

axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Show resolved Hide resolved
axelrod/strategies/calculator.py Show resolved Hide resolved
axelrod/tests/strategies/test_axelrod_first.py Outdated Show resolved Hide resolved
return D
return C

class FirstByDowning(Player):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since RevisedDowning is in the second tournament, can we also preserve that implementation and/or compare to the fortran implementation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I meant to write that in my PR: we need to consider what we do with RevisedDowning. As it's in the second tournament is it worth waiting until we translate https://github.com/Axelrod-Python/TourExec/blob/v0.2.0/src/strategies/K59R.f or implement RevisedDowning as a modification of the strategy in this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current RevisedDowning looks really similar to the Fortran code. If they are basically the same I'm in favor of leaving RevisedDowning (eliminating the revised bool) as the second tournament implementation. Maybe comparing the fingerprints will tell us if it's essentially correct or not?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good call.

I'm struggling to get axelrod_fortran to work on my current machine (I blame an OS update), could you or @meatballs if you get time paste fingerprints for "k59r".

Something like:

import axelrod as axl
import axelrod_fortran as axlf

downing = axlf.Player("k59r")
ashlock_fp = axl.AshlockFingerprint(strategy=downing)
data = ashlock_fp.fingerprint()  # This will take a little while
p = ashlock_fp.plot()
p.savefig("k59r_ashlock_fingerprint.png")

transitive_fp = axl.TransitiveFingerprint(strategy=downing)
data = transitive_fp.fingerprint() 
p = transitive_fp.plot()
p.savefig("k59r_transitive_fingerprint.png")

Here are the equivalent for RevisedDowning:

Ashlock:

downing_ashlock_fingerprint

Transitive:

downing_transitive_fingerprint

Copy link
Member

@marcharper marcharper Dec 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are really similar but not 100% identical

transitive

ashlock

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gosh they're incredibly similar though. I'm happy and looking at the history of RevisedDowning you implemented it from the Fortran code and I suspect you did it right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep it and then tweak it in a follow-up PR

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine by me. 👍

axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
axelrod/strategies/axelrod_first.py Outdated Show resolved Hide resolved
@drvinceknight
Copy link
Member Author

What do you think about having a classifier entry for "in Axelrod's first tournament" and "in Axelrod's second tournament"? Then we can also easily add lists of first and second tournament strategies along side short_run_term_strategies for ease of use (and possibly a nice tutorial example, maybe even an advanced tutorial comparing to the Fortran implementations with fingerprints). We'd also need to add these classifiers to TFT.

Yeah I really like this idea. Perhaps a classifier is not worth doing as it's not something dynamic (we will never implement another strategy from the first tournament) - perhaps a hard coded list is the way to go?

Also - unrelated - I think we should just go ahead remove the cheating strategies (I know I've been on the other side of this for a long time), if only to clear up the classifiers. But that's a discussion for another time...

@drvinceknight
Copy link
Member Author

Thanks @meatballs I think I've addressed all those.

@marcharper
Copy link
Member

Yeah I really like this idea. Perhaps a classifier is not worth doing as it's not something dynamic (we will never implement another strategy from the first tournament) - perhaps a hard coded list is the way to go?

Hard-coded lists seem fine in this case.

Also - unrelated - I think we should just go ahead remove the cheating strategies (I know I've been on the other side of this for a long time), if only to clear up the classifiers. But that's a discussion for another time...

Sure, let's (eventually) at least silo them sufficiently so that no one inadvertently uses them.

@@ -2034,7 +2034,7 @@ def test_strategy(self):

class TestSeconodByDowning(TestPlayer):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seconod -> Second

Are we sure we don't want to call this one RevisedDowning ? (Here is where the classifier would separate the naming concerns from the inclusion in the first or second tournament...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, that'll be similar to TitForTat, Grudger etc... I'll change it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a6471e8 renames this and moves it to its own file so that the description at the top of axelrod_second.py is still accurate.

Copy link
Member

@Nikoleta-v3 Nikoleta-v3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following a meeting with @drvinceknight today going through the paper Downing (1975) I have made a few comments on the implementation of Downing. Please let me know if something does not make sense 👍

S, P(C_o | C_s) and the conditional probability that O will choose C
following D by S, P(C_o, D_s)."

Throughout the paper the strategy (S) assumes that the opponent (D) is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you meant (O)


EV_TOT = #CC(EV_CC) + #CD(EV_CD) + #DC(EV_DC) + #DD(EV_DD)

I.E. The player aims to maximise the expected value of being in each state
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After our conversation today I feel (not sure) that this might need to re-written. #CC is not a state but the number of times the strategy S cooperated twice...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree, thanks @Nikoleta-v3 I'll work on this tomorrow (long day!).

Then the opponent's first cooperation counts as a cooperation in response to
the non existent cooperation of round 0. The total number of cooperations in
response to a cooperation is 1. We need to take in to account that extra
phantom cooperation to estimate the probability alpha=P(C|C) as 1 / 1 = 1.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one question, why we don't start with alpha=P(C|C) = 0.5 as stated above?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because that doesn't necessarily always imply 2 defections as an opening which is one of the "stronger" points made in the various literature. (But I'm guessing here.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll investigate more and get back to you (and add to the docstring as well) 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6c46483 adds to the docstring on this topic and also no the other points you raised. Let me know what you think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explanation is really good now! One minor comment would be to change P(C | C) to P(C_o | C_s) to be consistent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup good call.

@drvinceknight
Copy link
Member Author

b9b00a2 adds a tutorial that just reproduces the first tournament (or fails to). I thought I might as well do this after your suggestion @marcharper, let me know what you think.

FYI, I'm currently running some code to iterate through random seeds to see if we can get the same results as Axelrod reported (no luck so far but it did give me the couple of examples I use in the tutorial). Here is the code I'm using to do that:

import axelrod as axl
import pandas as pd
import csv

def get_players():
    first_tournament_participants_ordered_by_reported_rank = [s() for s in axl.axelrod_first_strategies]
    return first_tournament_participants_ordered_by_reported_rank

def obtain_ranked_names(players, seed=0):
    axl.seed(seed)
    tournament = axl.Tournament(players=players, 
                                turns=200, 
                                repetitions=5)  # Axelrod's original tournament ran with 5 repetitions
    results = tournament.play(progress_bar=False)
    return results.ranked_names

def count_matches(ranked_names, ranked_players):
    first_tournament_ranked_names = [str(p) for p in ranked_players]
    return sum(reported == reproduced for reported, reproduced in zip(first_tournament_ranked_names, ranked_names))

def write_data(seed, number, winner, tit_for_tat_rank, filename, mode="a"):
    with open(filename, mode) as f:
        writer = csv.writer(f)
        writer.writerow([seed, number, tit_for_tat_rank, winner])
        
def check_seed(seed, players, filename):
    ranked_names = obtain_ranked_names(players=players, seed=seed)
    number_of_matches = count_matches(ranked_names=ranked_names, ranked_players=players)
    
    write_data(
        seed=seed, 
        number=number_of_matches, 
        tit_for_tat_rank=ranked_names.index("Tit For Tat"),
        winner=ranked_names[0], 
        filename=filename)
    
    return number_of_matches

players = get_players()
number_of_players = len(players)
filename = "seed_search.csv"

try:
    seed = pd.read_csv("seed_search.csv")["seed"].max() + 1
except FileNotFoundError:
    seed = 0
    write_data(seed="seed", 
               number="number", 
               winner="winner", 
               tit_for_tat_rank="tit_for_tat_rank", 
               filename=filename, 
               mode="w",
              )

while check_seed(
    seed=seed, 
    players=players, 
    filename=filename,
) != number_of_players:
    seed += 1

playing C and the second D etc...
In this case the author uses an argument based on the sequence of plays by
the player (S) so #CC denotes the number of times the player plays C twice
in a row. This is then used to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to...

@drvinceknight
Copy link
Member Author

@Nikoleta-v3 could you confirm you're happy with this now when you get a moment?

@Nikoleta-v3
Copy link
Member

Looks good to me @drvinceknight!

@drvinceknight
Copy link
Member Author

@marcharper @meatballs when either of your have time, I believe this is good to go now.

@marcharper
Copy link
Member

LGTM. @meatballs want to take a final look since there were some post-approval changes?

@meatballs meatballs merged commit ea5b4b9 into master Dec 11, 2019
@meatballs meatballs deleted the fix-first-tournament-second-tournament-confusion branch December 11, 2019 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Possible Implementation errors for first tournament strategies
4 participants