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

Fragment/streamline the Moran process beginning documentation #962

Merged
merged 3 commits into from
Apr 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/tutorials/further_topics/approximate_moran_processes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.. _approximate-moran-process:

Approximate Moran Process
=========================

Due to the high computational cost of a single Moran process, an approximate
Moran process is implemented that can make use of cached outcomes of games. The
following code snippet will generate a Moran process in which the outcomes of
the matches played by a :code:`Random: 0.5` are sampled from one possible
outcome against each opponent (:code:`Defector` and :code:`Random: 0.5`). First
the cache is built by passing counter objects of outcomes::

>>> import axelrod as axl
>>> from collections import Counter
>>> cached_outcomes = {}
>>> cached_outcomes[("Random: 0.5", "Defector")] = axl.Pdf(Counter([(1, 1)]))
>>> cached_outcomes[("Random: 0.5", "Random: 0.5")] = axl.Pdf(Counter([(3, 3)]))
>>> cached_outcomes[("Defector", "Defector")] = axl.Pdf(Counter([(1, 1)]))

Now let us create an Approximate Moran Process::

>>> axl.seed(2)
>>> players = [axl.Defector(), axl.Random(), axl.Random()]
>>> amp = axl.ApproximateMoranProcess(players, cached_outcomes)
>>> results = amp.play()
>>> amp.population_distribution()
Counter({'Random: 0.5': 3})

We see that the :code:`Random: 0.5` won this Moran process. This is not what happens in a
standard Moran process where the `Random: 0.5` player will not win::

>>> axl.seed(2)
>>> amp = axl.MoranProcess(players)
>>> results = amp.play()
>>> amp.population_distribution()
Counter({'Defector': 3})
2 changes: 2 additions & 0 deletions docs/tutorials/further_topics/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Contents:
noisy_tournaments.rst
probabilistict_end_tournaments.rst
spatial_tournaments.rst
moran_processes_on_graphs.rst
approximate_moran_processes.rst
morality_metrics.rst
ecological_variant.rst
fingerprinting.rst
43 changes: 43 additions & 0 deletions docs/tutorials/further_topics/moran_processes_on_graphs.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.. _moran-process-on-graphs:

Moran Process on Graphs
=======================

The library also provides a graph-based Moran process [Shakarian2013]_ with
:code:`MoranProcessGraph`. To use this class you must supply at least one
:code:`Axelrod.graph.Graph` object, which can be initialized with just a list of
edges::

edges = [(source_1, target1), (source2, target2), ...]

The nodes can be any hashable object (integers, strings, etc.). For example::

>>> import axelrod as axl
>>> from axelrod.graph import Graph
>>> edges = [(0, 1), (1, 2), (2, 3), (3, 1)]
>>> graph = Graph(edges)

Graphs are undirected by default. Various intermediates such as the list of
neighbors are cached for efficiency by the graph object.

A Moran process can be invoked with one or two graphs. The first graph, the
*interaction graph*, dictates how players are matched up in the scoring phase.
Each player plays a match with each neighbor. The second graph dictates how
players replace another during reproduction. When an individual is selected to
reproduce, it replaces one of its neighbors in the *reproduction graph*. If only
one graph is supplied to the process, the two graphs are assumed to be the same.

To create a graph-based Moran process, use a graph as follows::

>>> from axelrod.graph import Graph
>>> axl.seed(40)
>>> edges = [(0, 1), (1, 2), (2, 3), (3, 1)]
>>> graph = Graph(edges)
>>> players = [axl.Cooperator(), axl.Cooperator(), axl.Cooperator(), axl.Defector()]
>>> mp = axl.MoranProcessGraph(players, interaction_graph=graph)
>>> results = mp.play()
>>> mp.population_distribution()
Counter({'Cooperator': 4})

You can supply the `reproduction_graph` as a keyword argument. The standard Moran
process is equivalent to using a complete graph for both graphs.
70 changes: 4 additions & 66 deletions docs/tutorials/getting_started/moran.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ the library, proceed as follows::
>>> import axelrod as axl
>>> axl.seed(0)
>>> players = [axl.Cooperator(), axl.Defector(),
... axl.TitForTat(), axl.Grudger()]
... axl.TitForTat(), axl.Grudger()]
>>> mp = axl.MoranProcess(players)
>>> populations = mp.play()
>>> mp.winning_strategy_name
Expand Down Expand Up @@ -90,69 +90,7 @@ function like :code:`takewhile` from :code:`itertools`)::
>>> mp.population_distribution()
Counter({'Cooperator': 4})

Other types of implemented Moran processes:

Moran Process on Graphs
-----------------------

The library also provides a graph-based Moran process [Shakarian2013]_ with
:code:`MoranProcessGraph`. To use this class you must supply at least one
:code:`Axelrod.graph.Graph` object, which can be initialized with just a list of
edges::

edges = [(source_1, target1), (source2, target2), ...]

The nodes can be any hashable object (integers, strings, etc.). For example::

>>> from axelrod.graph import Graph
>>> edges = [(0, 1), (1, 2), (2, 3), (3, 1)]
>>> graph = Graph(edges)

Graphs are undirected by default. Various intermediates such as the list of
neighbors are cached for efficiency by the graph object.

A Moran process can be invoked with one or two graphs. The first graph, the
*interaction graph*, dictates how players are matched up in the scoring phase.
Each player plays a match with each neighbor. The second graph dictates how
players replace another during reproduction. When an individual is selected to
reproduce, it replaces one of its neighbors in the *reproduction graph*. If only
one graph is supplied to the process, the two graphs are assumed to be the same.

To create a graph-based Moran process, use a graph as follows::

>>> from axelrod.graph import Graph
>>> axl.seed(40)
>>> edges = [(0, 1), (1, 2), (2, 3), (3, 1)]
>>> graph = Graph(edges)
>>> players = [axl.Cooperator(), axl.Cooperator(), axl.Cooperator(), axl.Defector()]
>>> mp = axl.MoranProcessGraph(players, interaction_graph=graph)
>>> results = mp.play()
>>> mp.population_distribution()
Counter({'Cooperator': 4})

You can supply the `reproduction_graph` as a keyword argument. The standard Moran
process is equivalent to using a complete graph for both graphs.


Approximate Moran Process
-------------------------

Due to the high computational cost of a single Moran process, an approximate
Moran process is implemented that can make use of cached outcomes of games. The
following code snippet will generate a Moran process in which a `Defector`
cooperates (gets a high score) against another `Defector`. First the cache is
built by passing counter objects of outcomes::

>>> from collections import Counter
>>> cached_outcomes = {}
>>> cached_outcomes[("Cooperator", "Defector")] = axl.Pdf(Counter([(0, 5)]))
>>> cached_outcomes[("Cooperator", "Cooperator")] = axl.Pdf(Counter([(3, 3)]))
>>> cached_outcomes[("Defector", "Defector")] = axl.Pdf(Counter([(10, 10), (9, 9)]))

Now let us create an Approximate Moran Process::

>>> axl.seed(0)
>>> players = [axl.Cooperator(), axl.Defector(), axl.Defector(), axl.Defector()]
>>> amp = axl.ApproximateMoranProcess(players, cached_outcomes)
>>> results = amp.play()
>>> amp.population_distribution()
Counter({'Defector': 4})
- :ref:`moran-process-on-graphs`
- :ref:`approximate-moran-process`