Skip to content

Commit

Permalink
Merge pull request #9 from geeklhem/popdyn
Browse files Browse the repository at this point in the history
Added a simple population dynamics model.
  • Loading branch information
geeklhem committed Oct 16, 2014
2 parents 85f6b51 + eaaec2c commit 32d41ae
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 33 deletions.
22 changes: 18 additions & 4 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ to organize iterated prisoner's dilemma competition between different
strategies.

It has been written for the "[Mathematics and Biology Workgroup](http://www.gt-mathsbio.biologie.ens.fr/)" in the
École normale supérieure (Paris, France) and it gives [this kind of output](http://www.eleves.ens.fr/home/doulcier/projects/math/ipdt.html).
École normale supérieure (Paris, France) and it gives [this kind](http://www.eleves.ens.fr/home/doulcier/projects/math/ipdt.html) [of output](http://www.eleves.ens.fr/home/doulcier/projects/math/popdyn.html ).

## How to
### Just test it
### Play around
To test it, if you have python2.7 and pip available the installation is quite simple:

```bash
Expand Down Expand Up @@ -36,7 +36,21 @@ $ ipdt tournament --exclude naivecoop defector

```

You can also run a single match between two strategies:
We have implemented a small model of population dynamics:

- The payoffs are computed by a simple tournament.
- Each strategies start with an equal proportion in the population.
- Each generation, the geometric growth of their relative abundance is given
by their payoffs weighted by the encounter probability (product of abundances).

```bash
# Run a population dynamics model for 10 generations with a null mutation level.
$ ipdt popdyn --generations 10 --mu 0
```


Finally, you can also run a single match between two strategies:

```bash
$ ipdt match -p naivecoop randomplayer
```
Expand All @@ -46,7 +60,7 @@ If you want more detailed info on the output, you can use the options
`-vv` info or `-vvv` debug.

You can have a [nice HTML5
export](http://www.eleves.ens.fr/home/doulcier/projects/math/ipdt.html) by
export](http://www.eleves.ens.fr/home/doulcier/projects/math/ipdt.html) (and [for population dynamics](http://www.eleves.ens.fr/home/doulcier/projects/math/popdyn.html)) by
using the `--html filename` option: it will create a `filename.html`
file in your current folder.

Expand Down
55 changes: 36 additions & 19 deletions bin/ipdt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ parser = argparse.ArgumentParser(description=__doc__,formatter_class=argparse.Ra
parser.add_argument('command',
help='Command',
type=str,
choices=['tournament', 'list', 'match'])
choices=['tournament', 'list', 'match', 'popdyn'])
parser.add_argument('-p','--players',
nargs="*",
default=[],
Expand All @@ -28,7 +28,6 @@ parser.add_argument('-e','--exclude',
nargs="*",
default=[],
help="Strategies to exclude codenames")

parser.add_argument('-v','--verbose',
help='Verbosity level : -v warning, -vv info, -vvv debug',
action="count",
Expand All @@ -37,6 +36,15 @@ parser.add_argument('-T','--turns',
type=int,
help='Number of turns',
default=100)
parser.add_argument('--mu',
type=float,
help='Mutation level (used only if command is popdyn).',
default=0)

parser.add_argument('-g','--generations',
type=int,
help='Number of generations (used only if command is popdyn).',
default=100)
parser.add_argument('--replicas',
type=int,
help='Number of repetition of the tournament',
Expand Down Expand Up @@ -76,11 +84,14 @@ logger.addHandler(ch)
import ipdt.tournament
import ipdt.players
import ipdt.export
import ipdt.popdyn

# === SET THE PARAMETERS ===
param = {}
param["T"] = args.turns
param["replicas"] = args.replicas
param["generations"] = args.generations
param["mu"] = args.mu

# Payoff matrix
order = ["cc","cd","dc","dd"]
Expand All @@ -94,45 +105,51 @@ if len(args.players) == 0:
if len(args.exclude) != 0:
args.players = list(set(args.players) - set(args.exclude))

players = [ getattr(ipdt.players, name).Player for name in args.players]
if args.html:
info = {}
for code,P in zip(args.players,players):
info[P.name] = {"author":P.author,
"name":P.name,
"code":code,
"description":P.__doc__}


if args.command == "match":
if hasattr(ipdt.players, args.players[0]):
P1= getattr(ipdt.players, args.players[0]).Player
if hasattr(ipdt.players, args.players[1]):
P2= getattr(ipdt.players, args.players[1]).Player

payoff = ipdt.tournament.match(P1,P2,param)
payoff = ipdt.tournament.match(players[0],players[1],param)
if payoff[0]>payoff[1]:
winner = "P1"
winner = players[0].name
elif payoff[0]==payoff[1]:
winner = "NOBODY"
else:
winner = "P2"
print("Match endend: {} WINS !".format(winner))
winner = players[1].name
print("{} vs {}: {} WINS !".format(players[0].name,players[1].name,winner))
logger.info("Payoffs: {}".format(payoff))

if args.command == "tournament":
players = [ getattr(ipdt.players, name).Player for name in args.players]
ranking,details = ipdt.tournament.tournament(players,param)
print("Tournament ended ! Ranking:")
for n,(score,name) in enumerate(ranking):
print("{}: {} ({} points)".format(n+1,name,score))

if args.html:
info = {}
for code,P in zip(args.players,players):
info[P.name] = {"author":P.author,
"name":P.name,
"code":code,
"description":P.__doc__}
exporter = ipdt.export.HTMLexporter(args.html+".html",
ranking,
details,
param,
info)
exporter.save()


if args.command == "popdyn":
time_series, details = ipdt.popdyn.popdyn(players,param)

if args.html:
exporter = ipdt.export.HTMLexporterTS(args.html+".html",
time_series,
details,
param,
info)
exporter.save()

if args.command == "list":
print("Available strategies are:")
Expand Down
125 changes: 115 additions & 10 deletions ipdt/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,27 @@
font-family:monospace;
text-align:left;
}
#chart svg {
height: 600px;
}
table{
margin: auto;
}
#ts th, #ts td {
color: black;
text-align:center;
background-color: white;
}
.'twhite'{
fill: rgb(255,255,255);
stroke: rgb(255,255,255);
color: rgb(255,255,255);
}
"""

TEMPLATE = """
Expand All @@ -137,7 +158,7 @@
<title>
{title}
</title>
<link rel="stylesheet" href="http://www.eleves.ens.fr/home/doulcier/projects/math/nv.d3.css"></link>
<style>
{css}
</style>
Expand All @@ -156,6 +177,50 @@
</html>
"""

SCRIPT ="""
<script src="http://www.eleves.ens.fr/home/doulcier/projects/math/d3.v3.js"></script>
<script src="http://www.eleves.ens.fr/home/doulcier/projects/math/nv.d3.min.js"></script>
<script>
var data = {data}
nv.addGraph(function() {{
var chart = nv.models.stackedAreaChart()
.x(function(d) {{ return d[0] }})
.y(function(d) {{ return d[1] }})
.clipEdge(true)
.useInteractiveGuideline(true)
;
chart.xAxis
.showMaxMin(false)
;
chart.yAxis
.tickFormat(d3.format(',.2f'));
d3.select('#chart svg')
.datum(data)
.transition().duration(500).call(chart);
d3.selectAll('#chart svg text')
.style('fill','#839496');
d3.selectAll('#nv-controlsWrap')
.style('display','None');
nv.utils.windowResize(chart.update);
return chart;
}});
</script>
"""

FOOTER = '<a href="http://www.gt-mathsbio.biologie.ens.fr/">GT-MathsBio </a> -- Page generated on the {date} by <a href="https://github.com/geeklhem/ipdt"> ipdt</a>.'

class HTMLexporter(object):
Expand All @@ -173,7 +238,7 @@ def __init__(self,path,ranking,payoff_matrix,param,info_strategies):
self.sections = [
("info","Informations",self.general_info(param)),
("ranking","Ranking",self.ranking(ranking,info_strategies)),
("details","Detailed Results",self.details(ranking, payoff_matrix,
("details","Detailed Results",self.details(zip(*ranking)[1], payoff_matrix,
info_strategies, param)),
]

Expand Down Expand Up @@ -208,19 +273,18 @@ def general_info(self,param):


def get_color(self,score,param):
diff = sorted([(abs(score-param[m]*param["T"]),m)
diff = sorted([(abs(score-param[m]*param["T"]*param["replicas"]),m)
for m in ["cc","dc","cd","dd"]],
key=lambda x:x[0])
return(self.move_color[diff[0][1]])

def details(self,ranking,po,info,param):
def details(self,order,po,info,param):
s = '<table border="0" cellpadding="3" cellspacing="3">\n<tr><th></th>\n'
order = zip(*ranking)[1]

max_po = max([max(po[k].values()) for k in order])
min_po = min([min(po[k].values()) for k in order])

norm = lambda x: int(100*(x - min_po) / (max_po-min_po))
norm = lambda x: int(100*(x - min_po) / (max_po-min_po)) if (max_po-min_po) else x

if len(order)>5:
for n,k in enumerate(order) :
Expand Down Expand Up @@ -259,17 +323,17 @@ def ranking(self,ranking,info):
for score,code in ranking:
s+= ("<li><strong title=\"{3}\">{1}</strong> "
"(<em>{2}</em>) - {0} pts </li>\n").format(score,
info[code]["name"],
info[code]["author"],
info[code]["description"])
info[code]["name"],
info[code]["author"],
info[code]["description"])
s += "</ol>\n"
s += "<em>(Do a mouseover on the name of each strategy to get a short description.)</em>"
return s

def output(self):
body = ""
for code,title,text in self.sections:
body += "\n\n<div id='{}'><h2>{}</h2></div>\n{}".format(code,title,text)
body += "\n\n<div id='{}'><h2>{}</h2>\n{}\n</div>".format(code,title,text)
footer = FOOTER.format(date=time.asctime())
out = TEMPLATE.format(body=body, css=CSS, title="ipdt report",footer=footer)
return out
Expand All @@ -278,6 +342,45 @@ def save(self):
with open(self.path,'w') as f:
f.write(self.output())



class HTMLexporterTS(HTMLexporter):
def __init__(self,path,time_series,payoff_matrix,param,info_strategies):
self.path = path

moves = sorted([(move, param[move]) for move in ["cc","dc","cd","dd"]],
key=lambda x:-x[1])
colors = ['class="green"','class="blue"','class="yellow"','class="red"']
self.move_color = {}
for n,move in enumerate(moves):
self.move_color[move[0]] = colors[n]


self.sections = [
("info","Informations",self.general_info(param)),
("ts","Time series",self.time_series_d3(time_series,info_strategies)),
("details","Detailed Results",self.details(time_series.keys(), payoff_matrix,
info_strategies, param)),
]

def time_series_d3(self,time_series,info):
data = "[\n"
offset = None
for k,v in time_series.items():
values = []
if offset is None:
offset = [0]*len(v)
for j,p in enumerate(v):
values.append([j,p])
offset[j] += p
data += '{{ "key": "{}", "values":{}}},'.format(k,values)

data += "]\n"
s = SCRIPT.format(data=data)
s += '<div id="chart"> <svg></svg> </div>'
return s


if __name__ == "__main__":
from ipdt.tournament import DEFAULT_PARAM as param
a = HTMLexporter("test.html",
Expand All @@ -292,3 +395,5 @@ def save(self):
"naivecoop":{"name":"Naive cooperator","author":"Axelrod"}
})
a.save()


Loading

0 comments on commit 32d41ae

Please sign in to comment.