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

Implement asymmetric games #1411

Closed
alexhroom opened this issue Mar 14, 2023 · 6 comments
Closed

Implement asymmetric games #1411

alexhroom opened this issue Mar 14, 2023 · 6 comments

Comments

@alexhroom
Copy link
Contributor

@drvinceknight suggested I open an issue sharing some code for asymmetric games (see asymmetric_game.py and trade_tournament.py here) which I have been using to run asymmetric tournaments in Axelrod. In particular, it generalises the Game object to take two matrices, rather than just one.

This could be more fully implemented into Axelrod (and I'm happy to do so after discussing it here) in one of two ways:

  1. Create the AsymmetricGame as a separate object, with AsymmetricTournament class etc. Via duck typing it would still be able to be put into Match objects and so on.
  2. Implement the AsymmetricGame as the 'base' game class, and then refactor the symmetric game code to inherit from there: e.g. in (truncated) code form:
class AsymmetricGame():
    def __init__(self, A, B): # where A and B are the game matrices
    # ... so on...

class Game(AsymmetricGame):
    def __init__(self, A): # where A is the shared game matrix
        super().__init__(A, A)
    # ... the regular Game we all know and love...

Similarly, an asymmetric tournament takes two groups (one of row players, one of column players), and then a regular Tournament would become one where the same list of players is passed to both groups. This would build asymmetric Axelrod 'on top' of the existing code with minimal damage to the existing code.

One potential issue I've identified is with the RPST method, which doesn't make sense for asymmetric games; if any important code requires RPST this would need some deeper refactoring (to a 'self' and 'opponent' RPST), but otherwise this could just throw an error if used on an asymmetric game.

Finally, I had written a Paired Moran algorithm which generalises the Moran process to asymmetric games (which takes two populations, and uses fitness functions based on each players' performance against the other population), but this would be a separate PR. @marcharper Vince said your thoughts on this would be valuable.

I would angle towards implementation 2 if given full choice as it reduces code complexity and builds asymmetric games directly into Axelrod. However, it would be a deeper refactoring of the library, so I understand if that is undesirable :-)

@drvinceknight
Copy link
Member

I'd be keen to hear @marcharper's thoughts on this in terms of how this aligns with other theoretic constructs.

I would angle towards implementation 2 if given full choice as it reduces code complexity and builds asymmetric games directly into Axelrod. However, it would be a deeper refactoring of the library, so I understand if that is undesirable :-)

There has been an ongoing discussion/plan of Axelrod 5.0.0 which is to do something like this on a more generic scale. This might be worth keeping in mind (but could not necessarily be relevant at this stage).

@marcharper
Copy link
Member

Hi, I think this contribution raises some interesting questions about the library's abstractions, as @drvinceknight mentions (see this discussion). The abstractions emerged organically and are still tied to assumptions about the nature of the game itself. In particular, the Game class has the weakest abstractions compared to say the Tournament class, which is sufficiently abstract to ignore a lot of specifics.

The Game class is really more of a fitness evaluation / utility function class than a specification of the game and rules of the game. The Player class depends heavily on the assumption that the game is IPD, as do most of the strategies. While many of them would be fine playing an asymmetric version, some do rely on the specific values of RPST, the length of the game, and other assumptions. However virtually none of the strategies could play a higher dimension game like rock-paper-scissors. (See also #1328 and the ultimatum branch for an attempt at generalizing to another Game.)

I'm certainly in favor of adding more game types to the library but it's worth thinking about whether we need new abstract base classes for Player, Match, Tournament, etc. or if they can be adapted to be more agnostic to the game. In principle, a Tournament is just a collection of Matches, so given a game, a game configuration, and players compatible with that game, it shouldn't need anything else. Player classes track their own history but maybe that's more the Match classes responsibility (does it depend on the game being played?). It's worth thinking about some examples far from the IPD, for example if the game was something like Monopoly, how would the abstractions need to change?

For Game, rather than make AsymmetricGame the new base, maybe we need a generic Game base class with [a]symmetric matrix games as child classes. Or maybe Game is something else and we need to factor out how scoring works and not equate a game with a game matrix or matricies. We should also think about how we know which Players are compatible with which Games, and so on.

The PairedMoran class seems almost orthogonal. In principle the Moran process should be playable with other games, so an asymmetric game wouldn't necessarily require two populations. But perhaps there's a different use case / model that it serves. If that's the case, it feels like an independent contribution.

@alexhroom
Copy link
Contributor Author

alexhroom commented Mar 15, 2023

@marcharper Thank you for your thoughts. Match, Tournament etc. work perfectly well with an asymmetric game class (Match doesn't require any changes at all!); the reason I suggested refactoring Tournaments is because in an asymmetric tournament, we would not necessarily want the same population playing both row and column players. Take, for example, a game studying biological co-evolution where one player plays an animal, and the other a plant; we wouldn't necessarily want a strategy cooked up for an animal being used for the plant's choice set (and it wouldn't make sense model-wise), but round-robin tournaments mean that every strategy does at least one game in either role.

The reason I suggested making AsymmetricGame the new base is that symmetric ones are a 'special case' of the generic game where the payoff matrices satisfy B = A^T. This means the two classes would be so similar (essentially identical, except for Game having an RPST method and taking four floats instead of two arrays) that an abstract base class would be unnecessary.

The use case/model for the paired Moran algorithm is exactly the same as that for asymmetric tournaments; in many asymmetric models, every player playing against every other player makes no sense. As another example, take a game theoretic model of several 'tribes', who have different army tactics. A regular Moran process would account for the utility of members of one tribe fighting against their own tribe, which again doesn't make sense in the model.

I think that the existing strategies not necessarily working for larger games is fine theoretically speaking (much in the same way as in real life, my strategy for Rock Paper Scissors could never be isomorphic to my strategy for poker). As you said, this is already the case if you play the already-implemented symmetric Game with different RPST. That said, to manage the added complexity, this should start by just implementing asymmetric 2x2 games - then larger games would be a separate issue (which would require wider refactoring of the regular Game class and quite possibly breaking changes)

My current code just treats the action C as 'action 1' and D as 'action 2' in the matrix. For aiding generality, a better implementation would take integers x and y from the row and column players respectively, and return the scores as (A[x][y], B[x][y]). For compatibility (and ease of use), a map that converts C and D into 0 and 1 would allow the existing code to work with this implementation.

I'm happy to create a PR where we can see what breaks in the tests and how the implementation actually looks, which is easier than me trying to explain it.

@marcharper
Copy link
Member

I'm happy to create a PR where we can see what breaks in the tests and how the implementation actually looks, which is easier than me trying to explain it.

Sounds good.

@drvinceknight
Copy link
Member

Sounds good.

Yup 👍

We can chat about this when we meet later today @alexhroom 👍

@alexhroom
Copy link
Contributor Author

Closed by #1413.

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

No branches or pull requests

3 participants