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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass fitness function moran process #1198

Merged
merged 8 commits into from
Aug 24, 2018
Merged

Pass fitness function moran process #1198

merged 8 commits into from
Aug 24, 2018

Conversation

Nikoleta-v3
Copy link
Member

Using a fitness function that scales the utility for the Moran process is commonly used in the literature.

This PR implements that feature and now the Moran process has a new argument, the fitness_function which is set as None as a default.

I added a test which tests that cooperators can take over a population of defectors when the selection is weak. Also added an example in the documentations.

Please note that after chatting with @drvinceknight, I run black and isort on the files
(commit 4919dd2). This might make the diffs more complicated than needed, please let me know if you want me to undo that 馃憤 .

axelrod/moran.py Outdated
def fitness_proportionate_selection(scores: List) -> int:
def fitness_proportionate_selection(
scores: List, fitness_function: FunctionType = None
) -> int:
"""Randomly selects an individual proportionally to score.

Parameters
Copy link
Member

Choose a reason for hiding this comment

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

Need to add fitness_function to the docstring (similarly for MoranProcess.__init__):

I suggest:

fitness_function: A function mapping a score to a (non-negative) float.

Copy link
Member Author

Choose a reason for hiding this comment

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

ah missed that. Fixing it

@drvinceknight
Copy link
Member

The failure is the same as on #1197:

FAIL: test_returns_foil_inspection_strategy_of_opponent (axelrod.tests.strategies.test_geller.TestGeller)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\projects\axelrod\axelrod\tests\strategies\test_geller.py", line 65, in test_returns_foil_inspection_strategy_of_opponent
    self.versus_test(axelrod.MindReader(), expected_actions=[(D, D), (D, C), (D, D)], seed=seed)
  File "C:\projects\axelrod\axelrod\tests\strategies\test_player.py", line 521, in versus_test
    self.assertEqual(match.play(), expected_actions)
AssertionError: Lists differ: [(D, D), (D, D), (D, D)] != [(D, D), (D, C), (D, D)]

@Nikoleta-v3
Copy link
Member Author

Nikoleta-v3 commented Aug 20, 2018

Regarding the failure if it is due to f474f2e, should I wait for @langner to undo the change?

@marcharper
Copy link
Member

This is a nice addition. Have you thought about making the transformation a property of the Moran process instead of an argument to fitness_proportionate_selection?

Generally in the literature we have a payoff that is associated to a game and a total payoff vector u associated to what we call a match. Then we typically apply a transformation such as f_i = exp(\beta u_i) or f_i = 1 - w + w * u_i. Fitness proportionate selection acts on the vector f. In other words, it's not clear to me that fitness_proportion_selection should apply the transformation, or if it should be functional composition: fitness_proportion_selection(transformation(payoffs)). Numerically the result is the same, but there's an API difference. It feels to me like it makes more sense to do the composition than it does to use a kwarg for fitness_proportionate_selection.

As a point of comparison, if we were to add these transformations to the tournament class, where would they go? It feels more like a property of the tournament than the game itself -- you can imagine running the same tournament with different strengths of selection.

In the same vein, we should think carefully about the names of these arguments. Applying exp(\beta ...) is commonly called "Fermi selection", and I think it's more appropriate to call these functions fitness_transformations or something similar rather than fitness_functions, which to me is the full composition transformation(payoffs) (i.e., what fitness_proportionate_selection acts on).

Regardless, glad to have this in the library. I've used these a lot in my other work / libraries (see e.g. here).

@langner
Copy link
Member

langner commented Aug 21, 2018

Regarding the failure if it is due to f474f2e, should I wait for @langner to undo the change?

Hmm... I'm not sure it's a matter of undoing that change, since I made it in an effort to squash exactly this error (or it's reverse). It could be that I introduced some indeterminacy in #1196, but I haven't investigated yet.

@drvinceknight
Copy link
Member

Generally in the literature we have a payoff that is associated to a game and a total payoff vector u associated to what we call a match. Then we typically apply a transformation such as f_i = exp(\beta u_i) or f_i = 1 - w + w * u_i. Fitness proportionate selection acts on the vector f. In other words, it's not clear to me that fitness_proportion_selection should apply the transformation, or if it should be functional composition: fitness_proportion_selection(transformation(payoffs)). Numerically the result is the same, but there's an API difference. It feels to me like it makes more sense to do the composition than it does to use a kwarg for fitness_proportionate_selection.

You're the expert here @marcharper and your suggestion makes sense to me.

We could even do this without add anything at all and just passing a transformed game I guess?

>>> R_f, P_f, S_f, T_f = some_transformation(R, P, S, T)
>>> game = axl.Game(R_f, P_f, S_f, T_f)
>>> mp = axl.MoranProcess(..., game=game...)

(Similarly a user could wrap this for tournaments etc...)

In the same vein, we should think carefully about the names of these arguments. Applying exp(\beta ...) is commonly called "Fermi selection", and I think it's more appropriate to call these functions fitness_transformations or something similar rather than fitness_functions, which to me is the full composition transformation(payoffs) (i.e., what fitness_proportionate_selection acts on).

Yeah 馃憤

Regardless, glad to have this in the library. I've used these a lot in my other work / libraries (see e.g. here).

If it's "simple" functional composition, does it need to be in the library at all? Perhaps just some documentation about applying a transformation to the game values? (Perhaps at the bottom of moran.rst?).

What do you reckon @marcharper?

@drvinceknight
Copy link
Member

drvinceknight commented Aug 21, 2018

Hmm... I'm not sure it's a matter of undoing that change, since I made it in an effort to squash exactly this error (or it's reverse)

Yeah I noticed that @langner , strange :) I've opened #1200 and have a possible idea of a fix. @Nikoleta-v3 I suggest you wait to see first what Marc says about the overall idea and second for this to be fixed 馃憤

@marcharper
Copy link
Member

I think the naming choices I mentioned are ultimately the more important thing to get right for now. If we decide to refactor where the transform is applied later it's not that big of a deal. Personally it feels more like a composition to me (from a math perspective) rather than a kwarg or property of the Game (from a python perspective), but I can imagine arguments to the contrary.

@drvinceknight
Copy link
Member

Regarding the failure if it is due to f474f2e, should I wait for @langner to undo the change?

If you rebase on to master @Nikoleta-v3 this error is now fixed 馃憤

@Nikoleta-v3
Copy link
Member Author

$ python run_mypy.py
axelrod/moran.py:239: error: Argument "fitness_function" to "fitness_proportionate_selection" has incompatible type "Optional[MethodType]"; expected "Optional[FunctionType]"
axelrod/moran.py:245: error: Argument "fitness_function" to "fitness_proportionate_selection" has incompatible type "Optional[MethodType]"; expected "Optional[FunctionType]"

@Nikoleta-v3
Copy link
Member Author

Fixed the type hint error 馃憤

axelrod/moran.py Outdated
if j >= index:
j += 1
else:
j = fitness_proportionate_selection(scores)
j = fitness_proportionate_selection(
scores, fitness_function=self.fitness_function
Copy link
Member

Choose a reason for hiding this comment

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

So this would be fitness_transformation=self.fitness_transformation.

axelrod/moran.py Outdated
"""Randomly selects an individual proportionally to score.

Parameters
----------
scores: Any sequence of real numbers
fitness_function: A function mapping a score to a (non-negative) float
Copy link
Member

Choose a reason for hiding this comment

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

Following @marcharper's suggestion can we change this to be fitness_transformation as the variable name? Similarly in MoranProcess.__init__ and the docs 馃憤 :)

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.

LGTM after fitness_function --> fitness_transformation

@drvinceknight
Copy link
Member

Thanks @Nikoleta-v3 馃憤

@drvinceknight drvinceknight merged commit f375af3 into Axelrod-Python:master Aug 24, 2018
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

4 participants