Axelrod-Python / Axelrod

A research tool for the Iterated Prisoner's Dilemma
http://axelrod.readthedocs.org/
Other
726 stars 264 forks source link

Restructure strategies folder #1420

Open alexhroom opened 1 year ago

alexhroom commented 1 year ago

Moving some discussion from #1418. This issue is also related to #1414.

Essentially, as the library is opened up to non-IPD games, the flat axl. namespace for strategies has become unfit for purpose. It should be restructured into a more sorted system (e.g. axl.strategies.ipd, axl.strategies.rps, axl.strategies.generic)

marcharper:

I certainly agree that we don't want to make contributing a new strategy more tedious or necessitating many changes. As we start supporting a larger variety of games, there's more chance for confusion, as existing parts of the library like axelrod.strategies change semantics from "strategies usable with IPD" to a mix of strategies intended for different games that now need to be filtered somehow for use with any specific game without producing errors or non-intuitive results. (Imagine a RPS strategy all-Scissors strategy -- it won't work with IPD.) The classifier approach seems like a good way to handle this, we already have some filters for "obeys [the] axelrod [tournament rules]". However we might want to take a different approach, making axelrod.strategies a more complex datastructure, or splitting it per game, or something else.

drvinceknight:

Not against a classifier approach but I do like the sound of axelrod.strategies.rps, axelrod.strategies.ipd, axelrod.strategies.ultimatum_game etc... I feel it would allow for a more "automatic"/easier classification.

alexhroom:

I guess my issue with axelrod.strategies.rps, axelrod.strategies.ipd etc. would be where we put generic strategies; e.g. if we created some generic strategy axl.Static which just takes an action and plays it every turn (this is a generalisation of Cooperator, Defector, etc) or even specifically IPD strategies like axl.TitForTat which would be a valid strategy on, e.g. the Hawk-Dove game (where it withdraws on turn 1, then copies the opponent's previous move in future). maybe there's something cool with namespaces that would get the best of both worlds - i.e. we take a classifier approach but use some Python import wizardry to make it accessible both from axl.TitForTat or axl.strategies.ipd.TitForTat, where axl.strategies.ipd provides a namespace for both strategies specific to the IPD (which are in a specific strategies.ipd folder) and strategies that work with the IPD. i think i've seen similar before in another package (but I'm not sure where). that way users can get strategies from axl.strategies.ipd for a safety guarantee that their strategies will work with the game, or just from axl. if they want to mix and match some generic strategies, or borrow the strategy to add it to their own new game without having to remember what game it's "originally for".

drvinceknight:

Like the idea of something smart with namespaces (with the caveat for ease of maintenance...) -- I think the flat axelrod. namespace was something we would/should have done differently so would be in favour of taking advantage of a 5.0.0 to do that.

A thought: would a axl.strategies.generic namespace be helpful? For things like axl.strategies.generic.Static?

alexhroom:

maybe - if we're trashing the flat axl. namespace (good idea) then that'd be useful. what I had in mind for the specific game strategies is something like the following:

  • generic strategies by default just take integers, but they can be given an action set. This can be done programmatically on the user end (e.g. axl.strategies.generic.Static(actions={C, D})
  • we add some function equip_with_actions() which returns a class but equipped with the action set. e.g. equip_with_actions(axl.strategies.generic.Static, {C, D}) returns an axl.strategies.generic.Static class object (uninstantiated) but where the action set for the class has been set to {C, D}. This should be easy enough with class methods.
  • Then, for example, the strategies/ipd/__init__.py file contains something like the following (note this isn't working python code, i don't think list comprehensions work on modules like this!):
    from . import *
    from ..generic import [equip_with_actions(strategy, {C, D}) for strategy in generic if strategy.assumptions_satisfy(ipd_game_attributes)]

    i.e. the generic strategy axl.strategies.generic.Static is a static strategy that plays a given integer action, whereas axl.strategies.ipd.Static is a static strategy that plays specifically C or D, i.e. it contains all generic strategies that work with the IPD and equips them with the correct action set too.

alexhroom commented 1 year ago

As a side note, if we are working towards breaking changes like this, @drvinceknight I recommend the repo adds a 5.0.0_dev branch to push 5.0.0 changes to so that PRs etc. towards 5.0.0 can be made

drvinceknight commented 1 year ago

I recommend the repo adds a 5.0.0_dev branch to push 5.0.0 changes to so that PRs etc. towards 5.0.0 can be made

Good idea. Could just call it 5.0.0 which would serve the same purpose (don't actually care what we call it).

drvinceknight commented 1 year ago

Have created 5.0.0 (we can easily change the name).

alexhroom commented 1 year ago

on the part of the discussion regarding importing 'generic' strategies into specific game namespaces: I've managed to write a way of conditionally importing classes from a shared 'generic' library file based on features of said classes. A minimal working example is here!

e.g. with this, it's possible for the initialisation of, say, axelrod.strategies.ipd to get the set of generic strategies and import the ones with strategy.assumptions_satisfy(ipd_characteristics) in the axelrod.strategies.ipd namespace with whatever modifications; so axelrod.strategies.generic.TitForTat would tit for tat between {0, 1} whereas axelrod.strategies.ipd.TitForTat would 'borrow' the generic strategy but modify it to produce {C, D}!