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

History class #1241

Merged
merged 20 commits into from
Apr 5, 2019
Merged

History class #1241

merged 20 commits into from
Apr 5, 2019

Conversation

marcharper
Copy link
Member

@marcharper marcharper commented Feb 22, 2019

Adds a History class, refactors Player a bit, and simplifies a number of tests and constructions. Note there were some subtle errors in the Meta players.

Increased runtime for a noisy tournament is ~25% but there are some optimizations possible:

  • refactoring strategies to avoid the unnecessary property wrappers on the Player class
  • Using a single joint history (adding the opponent's cooperations and defections) per Match
  • Adding a version of the History class that doesn't track the state distributions and such when not needed (could detect with classifiers)
  • Ultimately I'd like to change Player to only take a history object rather than the full opponent object

Also, I believe we could further refactor the dual transformer to be more efficient now but I didn't try that yet.

Updates:

  • Added a LimitedHistory class that only retains N rounds of history, which improves the memory_depth test
  • Removed nearly all instances of direct history assignment on the Player class, which should always be discouraged IMO
  • Made various related minor updates
  • I changed the meta classes so that each player on the meta team tracks its own history. Previously they were not updated with the history and so may not have properly set their internal variables properly as play proceeds. This PR has caused or exposed the issue.

I have fixed the remaining issues:

  • the generators were not equal because the meta class was updating team histories on the next round, so the generators were off by one. I introduced an update_history method to the Player and MetaPlayer classes to fix, this method allows "post processing" of the moves by the players to fully end the round.
  • transformed SequencePlayers could not be pickled (there was no test). They can now and there are various new tests. I ended up adding a __getstate__ method to Player (overridden in SequencePlayer and needed an abstract version in Player).

Tournaments take about 25% longer overall. In addition to the tasks in the first comment, we can get some or all of that back by optimizing in a subsequent PR or on the 5.0.0 branch:

  • removing unnecessary function calls: inlining simultaneously_play, for example, and potentially optimizing how the history class is used in various places
  • revisiting how the tournament class marshalls players

Increased runtime for the full standard tournament is 15% (30 hours versus 26 on my desktop). I think we'll easily get that back since removing function call overhead in #1239 yielded an improvement of ~25%.

@marcharper
Copy link
Member Author

The pickle issue has something to do with the MetaPlayer having a sequence player in the team (ThueMorse or ThueMorseInverse). When unpickled the subplayer isn't equal to itself, but its own test passes...

@drvinceknight
Copy link
Member

Sounds interesting @marcharper, I'll try and get to this next week. 👍

@marcharper marcharper changed the title History class 2019 History class Feb 23, 2019
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.

Some minor comments after an initial read through, it all looks good to me though 👍

I haven't had a go at debugging the failing test, I'll try and get to that when I get a bit more time :)

axelrod/history.py Show resolved Hide resolved
return self._plays == other._plays and self._coplays == other._coplays
raise TypeError("Cannot compare types.")

def __getitem__(self, key):
Copy link
Member

Choose a reason for hiding this comment

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

Just a comment: This could be useful if we want to incorporate misperception (as opposed to noise). 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice idea!

axelrod/player.py Show resolved Hide resolved
axelrod/random_.py Outdated Show resolved Hide resolved
@@ -172,101 +167,3 @@ def test_alt_strategy_stops_after_round_180(self):
expected_actions=expected_actions,
match_attributes={"length": 200},
)


class TestModuleMethods(unittest.TestCase):
Copy link
Member

Choose a reason for hiding this comment

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

Why can we remove all of these?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question. Some are just testing Player class properties that already have their own tests. But it looks like I was a bit overzealous.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually I think all of these tests are redundant. If we want to keep any I'd prefer we re-write them to use an actual match rather than simply overwriting the history, which is a practice that we shouldn't encourage.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I agree about not encouraging using history overwriting, could we either rewrite as part of this PR or leave them in and open an issue to remove them?

Copy link
Member Author

@marcharper marcharper Feb 27, 2019

Choose a reason for hiding this comment

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

I'll check but I think these tests are just directly targeting the subfuctions of the strategy and are all covered by the "standard" tests above in the file, so rewriting them would produce near identical tests

axelrod/history.py Outdated Show resolved Hide resolved
@marcharper
Copy link
Member Author

marcharper commented Feb 27, 2019

Updates:

  • Added a LimitedHistory class that only retains N rounds of history, which improves the memory_depth test
  • Removed nearly all instances of direct history assignment on the Player class, which should always be discouraged IMO
  • Made various related minor updates

I suspect the pickle errors have something to do with the decorated meta classes and how they are rebuilt. I changed the meta classes so that each player on the meta team tracks its own history. Previously they were not updated with the history and so may not have properly set their internal variables properly as play proceeds. This PR has caused or exposed the issue.

@marcharper
Copy link
Member Author

marcharper commented Mar 4, 2019

I'm still not sure why the meta players with sequence players on their teams don't pass the pickle test. (It's sufficient to use just ThueMorse as the team.) When you examine the players on team you see this:

{'init_kwargs': OrderedDict(),
'_history': [C, D, D, C, D, C, C, D, D],
'classifier': {'memory_depth': inf, 'stochastic': False, 'makes_use_of': set(), 
'long_run_time': False, 'inspects_source': False, 'manipulates_source': False, 'manipulates_state': False}, 
'match_attributes': {'length': -1, 'game': Axelrod game: (R,P,S,T) = (3, 1, 0, 5), 'noise': 0}, 
'sequence_generator': <generator object Player.__eq__.<locals>.<genexpr> at 0x7fc6aef72c50>}

{'init_kwargs': OrderedDict(),
'_history': [C, D, D, C, D, C, C, D, D],
'classifier': {'memory_depth': inf, 'stochastic': False, 'makes_use_of': set(),
'long_run_time': False, 'inspects_source': False, 'manipulates_source': False, 'manipulates_state': False}, 
'match_attributes': {'length': -1, 'game': Axelrod game: (R,P,S,T) = (3, 1, 0, 5), 'noise': 0}, 
'sequence_generator': <generator object Player.__eq__.<locals>.<genexpr> at 0x7fc6aef72b48>}

Note that the sequence_generator attribute seem to correlate the instances when passed through the Player.__eq__ function .

Oddly enough when I add similar tests to the TestPlayer I don't get the same failure.

@marcharper
Copy link
Member Author

Also note that I made a significant change to the metaplayers in that they now pass along the team member's individual play choices rather than all sharing a single history. I think this makes more sense but it will impact how they play.

@drvinceknight
Copy link
Member

Also note that I made a significant change to the metaplayers in that they now pass along the team member's individual play choices rather than all sharing a single history. I think this makes more sense but it will impact how they play.

Yeah I'm fine with this change, they'll still always be looking at the correct history for the opponent. 👍

@drvinceknight
Copy link
Member

Note that the sequence_generator attribute seem to correlate the instances when passed through the Player.__eq__ function .

Oddly enough when I add similar tests to the TestPlayer I don't get the same failure.

I've started to have a look (really sorry it's taking me so long to try and be helpful). The __eq__ method in the player class needs to be a bit smart with the equality of a sequence, see https://github.com/Axelrod-Python/Axelrod/blob/master/axelrod/player.py#L145 where it essentially needs to split the generator in to two copies of itself. Use one of the copies to check equality and then set the attribute with the other copy.

I'm not entirely sure why this would fall over when checking pickling equality but perhaps that's something worth considering? (If you haven't already).

@marcharper
Copy link
Member Author

marcharper commented Mar 19, 2019

I believe I have fixed the remaining issues:

  • the generators were not equal because the meta class was updating team histories on the next round, so the generators were off by one. I introduced an update_history method to the Player and MetaPlayer classes to fix, this method allows "post processing" of the moves by the players to fully end the round.
  • transformed SequencePlayers could not be pickled. They can now and there are various new tests. I ended up adding a __getstate__ method to Player (overridden in SequencePlayer and needed an abstract version in Player).

Tournaments take about 25% longer overall. In addition to the tasks in the first comment, we can get some or all of that back by optimizing in a subsequent PR or on the 5.0.0 branch:

  • removing unnecessary function calls: inlining simultaneously_play, for example, and potentially optimizing how the history class is used in various places
  • revisiting how the tournament class marshalls players -- there's no need to rebuild them now (if more efficient)

@drvinceknight
Copy link
Member

Nice work!

Tournaments take about 25% longer overall. In addition to the tasks in the first comment, we can get some or all of that back by optimizing in a subsequent PR or on the 5.0.0 branch:

Is there any merit in me running a full tournament to see if the 25% increase scales well?

@marcharper
Copy link
Member Author

Is there any merit in me running a full tournament to see if the 25% increase scales well?

Yes, and we should take a look at the impact on the Meta strategies

@drvinceknight
Copy link
Member

Yes, and we should take a look at the impact on the Meta strategies

Ok cool, I'll aim to get some things running for that.

@drvinceknight
Copy link
Member

I've got some code running. Will report back once it's done 👍

@marcharper
Copy link
Member Author

marcharper commented Mar 23, 2019

Increased runtime for the full standard tournament is 15% (30 hours versus 26 on my desktop). I think we'll easily get that back since removing function call overhead in #1239 yielded an improvement of ~25%.

@marcharper
Copy link
Member Author

@drvinceknight @meatballs I think this is good to go in, PTAL when you get a chance.

@drvinceknight
Copy link
Member

I agree it's pretty much there (sorry for being so slow to get back to you, I've got a tonne of marking at the moment), I've got some long (probably too long) tournaments running for timing experiments but we can merge and if they come back atrocious (which I doubt they will) we can always figure that out later.

I've already looked through and everything looks good to me, but a close look from @meatballs would be great 👍

@marcharper
Copy link
Member Author

@meatballs PTAL

@meatballs meatballs merged commit 2349c49 into master Apr 5, 2019
@meatballs meatballs deleted the history-class-2019 branch April 5, 2019 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants