-
Notifications
You must be signed in to change notification settings - Fork 269
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
Add the K86R (MoreGrofman) Strategy #1124
Conversation
There was a problem hiding this 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).
axelrod/strategies/axelrod_second.py
Outdated
|
||
|
||
class MoreGrofman(Player): | ||
''' |
There was a problem hiding this comment.
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 "
axelrod/strategies/axelrod_second.py
Outdated
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 |
There was a problem hiding this comment.
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
axelrod/strategies/axelrod_second.py
Outdated
return opponent.history[-1] | ||
# Logic for the rest of the game | ||
else: | ||
opponent_defections_last_7_rounds = 0 |
There was a problem hiding this comment.
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)
@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. |
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() >>> axl.seed(0)
>>> _ = fortranGtf.fingerprint(processes=0, repetitions=20000)
>>> fortranGtf.plot(); These match up well. >>> plt.imshow(np.abs(moreGtf.data - fortranGtf.data))
>>> plt.colorbar() 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); >>> _ = fortranGtf_against_basic.fingerprint(processes=0)
>>> fortranGtf_against_basic.plot(display_names=True); Again a good match: >>> np.array_equal(moreGtf_against_basic.data, fortranGtf_against_basic.data)
True |
There was a problem hiding this 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.
axelrod/strategies/axelrod_second.py
Outdated
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
... ?
axelrod/strategies/axelrod_second.py
Outdated
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: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
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:
|
axelrod/strategies/axelrod_second.py
Outdated
if self.history[-1] == C: | ||
if opponent_defections_last_8_rounds <= 2: | ||
return C | ||
else: |
There was a problem hiding this comment.
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.)
There was a problem hiding this 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: |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
axelrod/strategies/axelrod_second.py
Outdated
'manipulates_state': False | ||
} | ||
|
||
def __init__(self) -> None: |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
axelrod/strategies/axelrod_second.py
Outdated
|
||
def strategy(self, opponent: Player) -> Action: | ||
# Cooperate on the first two moves | ||
if not self.history or len(self.history) in [1]: |
There was a problem hiding this comment.
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:
axelrod/strategies/axelrod_second.py
Outdated
# 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: |
There was a problem hiding this comment.
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
?
axelrod/strategies/axelrod_second.py
Outdated
# 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 |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
…grofman Resolve conflicts with the new Tranquilizer strategy.
@marcharper I believe all your request have now been addressed and this is ready to go? :) |
Merging as all the stylistic requests have been addressed. Thanks @buckbaskin👍👍 |
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.