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

Add the K86R (MoreGrofman) Strategy #1124

Merged
merged 18 commits into from
Sep 19, 2017

Conversation

buckbaskin
Copy link
Contributor

This strategy is implemented in response to issue #1093

I've implemented the MoreGrofman strategy as well as added tests to verify the functionality.

I'm opening a PR for early feedback and to check test coverage. I don't know if the pull request is ready to merge (probably not yet) and I'd appreciate feedback for how to improve the pull request.

@buckbaskin buckbaskin mentioned this pull request Aug 23, 2017
Copy link
Member

@drvinceknight drvinceknight left a comment

Choose a reason for hiding this comment

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

Thanks for this: looking great 👍

I've picked up a couple of minor things. I haven't looked through the logic but it is not implemented correctly as it's not matching up with the Fortran strategy.

Here is one such discrepancy:

>>> import axelrod as axl
>>> import axelrod_fortran as axlf
>>> moreG = axl.MoreGrofman()
>>> fortranG = axlf.Player('k86r')
>>> opponent = axl.AntiTitForTat()
>>> match = axl.Match((moreG, opponent), turns=15)
>>> match.play()
[(C, C),
 (C, D),
 (D, D),
 (D, C),
 (C, C),
 (C, D),
 (D, D),
 (D, C),
 (D, C),
 (D, C),
 (D, C),
 (D, C),
 (D, C),
 (C, C),
 (C, D)]
>>> fortran_match = axl.Match((fortranG, opponent), turns=15)
>>> fortran_match.play()
[(C, C),
 (C, D),
 (D, D),
 (D, C),
 (C, C),
 (C, D),
 (D, D),
 (D, C),
 (D, C),
 (D, C),
 (D, C),
 (D, C),
 (D, C),
 (D, C),
 (C, C)]

As this strategy is not stochastic the above (the 14th element is different) should be a good target for you to aim for.

Hopefully that's enough to find what might just be a small little error but if not we can help and identify what's wrong (perhaps our interpretation of the Fortran code is mistaken somewhere).

Note: the axelrod_fortran library (https://github.com/Axelrod-Python/axelrod-fortran) is a python interface to the actual fortran code. You don't need to install it but if you wanted to you'd need to compile the fortran code (https://github.com/Axelrod-Python/TourExec).



class MoreGrofman(Player):
'''
Copy link
Member

Choose a reason for hiding this comment

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

Can we change these to "

1. First it cooperates on the first two rounds
2. For rounds 3-7 inclusive, it plays the same as the opponent's last move
3. Thereafter, it applies the following logic
- If its own previous move was C and the opponent has defected less than
Copy link
Member

Choose a reason for hiding this comment

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

CI is failing because this isn't building in the documentation. Need a space:

    3. Thereafter, it applies the following logic

      - If its own previous move was C and the opponent has defected less than

return opponent.history[-1]
# Logic for the rest of the game
else:
opponent_defections_last_7_rounds = 0
Copy link
Member

Choose a reason for hiding this comment

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

opponent_defections_last_7_rounds = opponent.history[-7:].count(D)

@buckbaskin
Copy link
Contributor Author

@drvinceknight I believe I've updated the code to fix the areas you highlighted in your review comments. I also added some tests that use matches run with the Fortran library to verify that the Python implementation matches how the Fortran library behaves (up to 60 rounds vs. AntiTitForTat). I've pushed the changes to see if the docs-building is fixed, and I'd appreciate any additional feedback.

@drvinceknight
Copy link
Member

drvinceknight commented Aug 24, 2017

I also added some tests that use matches run with the Fortran library to verify that the Python implementation matches how the Fortran library behaves (up to 60 rounds vs. AntiTitForTat).

Thanks @buckbaskin this is looking good. Before reviewing properly just to verify that we have a good implementation. I've run some of the new fingerprints implemented on #1125:

>>> import axelrod as axl
>>> import axelrod_fortran as axlf
>>> import numpy as np
>>> import matplotlib.pyplot as plt

>>> moreG = axl.MoreGrofman()
>>> fortranG = axlf.Player('k86r')

>>> moreGtf = axl.TransitiveFingerprint(moreG, number_opponents=20)
>>> fortranGtf = axl.TransitiveFingerprint(fortranG, number_opponents=20)

Against a spectrum of random opponents:

>>> axl.seed(0)
>>> _ = moreGtf.fingerprint(processes=0, repetitions=20000)  # Huge reps for stochasticity
>>> moreGtf.plot()

download 31

>>> axl.seed(0)
>>> _ = fortranGtf.fingerprint(processes=0, repetitions=20000)
>>> fortranGtf.plot();

download 32

These match up well.

>>> plt.imshow(np.abs(moreGtf.data - fortranGtf.data))
>>> plt.colorbar()

download 33

Against given deterministic strategies:

>>> basic = [s() for s in axl.basic_strategies]
>>> moreGtf_against_basic = axl.TransitiveFingerprint(moreG, opponents=basic)
>>> fortranGtf_against_basic = axl.TransitiveFingerprint(fortranG, opponents=basic)
>>> _ = moreGtf_against_basic.fingerprint(processes=0)
>>> moreGtf_against_basic.plot(display_names=True);

download 34

>>> _ = fortranGtf_against_basic.fingerprint(processes=0)
>>> fortranGtf_against_basic.plot(display_names=True);

download 35

Again a good match:

>>> np.array_equal(moreGtf_against_basic.data, fortranGtf_against_basic.data)
True

Copy link
Member

@drvinceknight drvinceknight left a comment

Choose a reason for hiding this comment

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

So I see some discrepancies between the description and the code.

If these differences were there to ensure we have the same behaviour of the strategy (against AntiTitForTat for example), we should explain this in the docstring.

return opponent.history[-1]
# Logic for the rest of the game
else:
opponent_defections_last_7_rounds = opponent.history[-8:-1].count(D) # (-7:) fails at 13, (-7:-1), (-8:), (-8:-1), (-6:), (-6:-1)
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't seem correct: why doesn't opponent.history[-7:] work?

>>> ten_numbers = list(range(10))
>>> len(ten_numbers[-7:])  # 7 numbers
7
>>> len(ten_numbers[-7:-1])  # Only 6 numbers
 6

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I originally tried [-7:] but that resulted in what amounted to an off by one error for certain cases. This came to light when comparing the Python implementation with the Fortran implementation. I believe that the difference in indexes comes from the difference in Fortran indexing during do-loops/arrays (1 indexed and inclusive-inclusive) vs Python indexing into lists. The [-8:-1] indexing appears to match the behavior of the Fortran. I will remove the comment after because I was writing down potential off by one candidates to experiment with because I didn't really understand the Fortran indexing at the time.

Copy link
Member

Choose a reason for hiding this comment

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

Ok can you adjust the docstring description then because they're not what you are implemented:

If its own previous move was C and the opponent has defected less than twice in the last 7 rounds

I think we should say something like the mouthful that is the 7 rounds before the last one ... ?

else:
opponent_defections_last_7_rounds = opponent.history[-8:-1].count(D) # (-7:) fails at 13, (-7:-1), (-8:), (-8:-1), (-6:), (-6:-1)
if self.history[-1] == C:
if opponent_defections_last_7_rounds <= 2:
Copy link
Member

Choose a reason for hiding this comment

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

The description says:

If its own previous move was C and the opponent has defected twice or more in the last 7 rounds, defect

So I believe this should be a strict inequality.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on the behavior in testing compared with the Fortran implementation and my attempt to understand the Fortran implementation, the logic is correct and I will adjust the comment. Is that sufficient?

name = "MoreGrofman"
player = axelrod.MoreGrofman
expected_classifier = {
'memory_depth': 7,
Copy link
Member

Choose a reason for hiding this comment

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

This now needs to be 8.

@drvinceknight
Copy link
Member

drvinceknight commented Aug 26, 2017

I believe that the difference in indexes comes from the difference in Fortran indexing during do-loops/arrays (1 indexed and inclusive-inclusive) vs Python indexing into lists. The [-8:-1] indexing appears to match the behavior of the Fortran.

Having looked through the code I agree. Although I think it's actually due to the fact that the Fortran strategies all assume that prior to actually starting a match the opponent has cooperated (it's this "dummy" initial cooperation that I think is making the difference here).

Here is another example that shows that it's not in fact the last 7 moves but the 7 moves prior to the last one:

>>> import axelrod as axl
>>> import axelrod_fortran as axlf
>>> player = axlf.Player("k86r")
>>> sequence = "C" * 6 + "DDD"
>>> opponent = axl.Cycler(sequence)
>>> match = axl.Match((player, opponent), turns=11)
>>> match.play()
[(C, C),
 (C, C),
 (C, C),
 (C, C),
 (C, C),
 (C, C),
 (C, D),
 (C, D),
 (C, D),
 (C, C),
 (D, C)]
  • We see that the first 6 rounds are just pairs of cooperations.
  • The 7th round: the player cooperates
  • The 8th round: the player considers the first 6th moves of the opponent (keeping in mind that the Fortran strategies use an initial dummy cooperation move of the opponent I suspect this is the critical thing going on here) and there are no defections so it cooperates.
  • The 9th round: the player considers the first 7 moves: there is a single defection so it cooperates
  • The 10th round: the player considers moves from round 2 till 8: there are two defections so it cooperates
  • The 11th round: the player considers moves from round 3 till 9: there are 3 defections so it defects.

if self.history[-1] == C:
if opponent_defections_last_8_rounds <= 2:
return C
else:
Copy link
Member

Choose a reason for hiding this comment

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

no need to have an else, just replace the else with return D

Similarly below (line 264).

(Not a big deal: just a stylistic suggestion.)

Copy link
Member

@drvinceknight drvinceknight left a comment

Choose a reason for hiding this comment

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

A part from getting the docs to build and other potential stylistic things that the other core maintainers might want I believe this is the correct implementation of the corresponding Fortran strategy.

"""
Submitted to Axelrod's second tournament by Bernard Grofman.

This strategy has 3 phases:
Copy link
Member

Choose a reason for hiding this comment

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

Also need a space here.

This strategy has 3 phases:

1. First

The sphinx make html documentation build works locally. Hopefully
this will correct ongoing documentation CI build errors.
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.

Just some minor style comments, I'm assuming that strategy is correct as implemented.

'manipulates_state': False
}

def __init__(self) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need this __init__ if it simply calls the parent class __init__


class MoreGrofman(Player):
"""
Submitted to Axelrod's second tournament by Bernard Grofman.
Copy link
Member

Choose a reason for hiding this comment

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

Can we add the Fortran rule name (K86R) to the docstring?


def strategy(self, opponent: Player) -> Action:
# Cooperate on the first two moves
if not self.history or len(self.history) in [1]:
Copy link
Member

Choose a reason for hiding this comment

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

if len(self.history) < 2:

# in the last round and instead looks at the first 7 of the last
# 8 rounds.
opponent_defections_last_8_rounds = opponent.history[-8:-1].count(D)
if self.history[-1] == C:
Copy link
Member

Choose a reason for hiding this comment

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

Can this be simplified to:

if self.history[-1] == C and opponent_defections_last_8_rounds <= 2:
    return C
if self.history[-1] == D and opponent_defections_last_8_rounds <= 1:
   return C
return D

?

# For rounds 3-7, play the opponent's last move
elif 2 <= len(self.history) <= 6:
return opponent.history[-1]
# Logic for the rest of the game
Copy link
Member

Choose a reason for hiding this comment

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

This comment isn't necessary

opponent = axelrod.AntiTitForTat()
# Actions come from a match run by Axelrod Fortran using Player('k86r')
actions = [
(C, C),
Copy link
Member

Choose a reason for hiding this comment

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

The spacing here is perhaps a little excessive, can we compress to just a couple of lines?

# Test to match the Fortran implementation for 30 rounds
opponent = axelrod.AntiTitForTat()
actions = [(C, C),
(C, D),
Copy link
Member

Choose a reason for hiding this comment

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

same

@drvinceknight
Copy link
Member

@marcharper I believe all your request have now been addressed and this is ready to go? :)

@drvinceknight
Copy link
Member

Merging as all the stylistic requests have been addressed. Thanks @buckbaskin👍👍

@drvinceknight drvinceknight merged commit 7bd9c95 into Axelrod-Python:master Sep 19, 2017
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.

None yet

3 participants