Skip to content

Commit

Permalink
Merge pull request #18 from alan-turing-institute/aoife/fixgit
Browse files Browse the repository at this point in the history
Aoife/fixgit
  • Loading branch information
phinate authored Jun 14, 2023
2 parents c0d668f + 2188cf7 commit 77381f8
Show file tree
Hide file tree
Showing 12 changed files with 795 additions and 122 deletions.
554 changes: 554 additions & 0 deletions analysis/team_gen_analysis.ipynb

Large diffs are not rendered by default.

63 changes: 32 additions & 31 deletions hypotheses_pseudocode.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,36 @@
- Team size: 1
- Total Pokemon: 151
- Battle Mode: ???
- Starting number of teams: 151*151
- Number of Battles per pairing: ???
- Starting number of teams: 151\*151
- Number of Battles per pairing: ???
- Generations: `0`
- Mutation rate: `0`
- Breeding: `Off`
- Loss Shuffle: `Off`

### Hypotheses:
### Hypotheses:

Is there a decay function for the number of battles? The win ratio should converge which will give us a good understanding of the number of battles required.
Is there a decay function for the number of battles? The win ratio should
converge which will give us a good understanding of the number of battles
required.

How does this change based on the battle-mode? Is there a "good enough" mode that doesn't take too long?
How does this change based on the battle-mode? Is there a "good enough" mode
that doesn't take too long?

Do we find all the S tier pokemon from the fan rankings?
Do we find all the D tier pokemon from the fan rankings?
Do we find all the S tier pokemon from the fan rankings? Do we find all the D
tier pokemon from the fan rankings?

Plotting in 2d using the sprites. The first dimension is the fitness, second dimension:
- Base stat total = sum (hp, attack, defence, special attack, special defence, special speed)
Plotting in 2d using the sprites. The first dimension is the fitness, second
dimension:

- Base stat total = sum (hp, attack, defence, special attack, special defence,
special speed)
- HP
- Attack
- Anything else

Nearest k-nearest neighbours clusters using fitness values, what tiers have we found?


Nearest k-nearest neighbours clusters using fitness values, what tiers have we
found?

## 2v2 Pokemon Battles

Expand All @@ -47,26 +52,29 @@ Nearest k-nearest neighbours clusters using fitness values, what tiers have we f
- Breeding: `Off`
- Loss Shuffle: `On`

### Hypotheses:
### Hypotheses:

Are there synergestic pairs that have greater wins compared with the top tier solo pokemon?
Are there synergestic pairs that have greater wins compared with the top tier
solo pokemon?

Are there pairings of Pokemon types that stand out? (fire and water)

What is the base rate of synergy? Can we see clusters of all the [Type1&Type2] pairs or are there some stand out power couples?
What is the base rate of synergy? Can we see clusters of all the [Type1&Type2]
pairs or are there some stand out power couples?

2d clusterplot: first dimension is fitness, second dimension:

- Categorical Pairs by type
- Sum of base stats

Can we find a diminishing returns number of battle repeats that converge on an
answer?

Can we find a diminishing returns number of battle repeats that converge on an answer?

Can we find a diminishing returns number of generations that converge on an answer?

What happens with a high versus low mutation rate? to how quickly it finds an answer and how variable the result is.

Can we find a diminishing returns number of generations that converge on an
answer?

What happens with a high versus low mutation rate? to how quickly it finds an
answer and how variable the result is.

## 6v6 Pokemon Battles

Expand All @@ -82,20 +90,13 @@ What happens with a high versus low mutation rate? to how quickly it finds an an
- Breeding: `On`
- Loss Shuffle: `Off`

### Hypotheses:
### Hypotheses:

Which is the best Pokemon team?

Are the best teams very related to the top Pokemon, or are there plenty of wildcards?
Are the best teams very related to the top Pokemon, or are there plenty of
wildcards?

Are the synergestic pairs in the top teams?

What are the spread of types in the top teams?








4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"


[project]
name = "P2Lab"
name = "p2lab"
authors = [
{ name = "Scientists of the P2 Laboratory", email = "[email protected]" },
]
Expand Down Expand Up @@ -148,4 +148,4 @@ isort.required-imports = ["from __future__ import annotations"]
"noxfile.py" = ["T20"]

[project.scripts]
p2lab = "P2Lab.__main__:main"
p2lab = "p2lab.__main__:main"
33 changes: 23 additions & 10 deletions src/p2lab/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,39 @@
"""
from __future__ import annotations

import asyncio

from tqdm import tqdm

from .evaluator.poke_env import PokeEnv
from .stats.team_recorder import TeamRecorder
from .teams.builder import Builder

N_generations = 10 # Number of generations to run
N_teams = 3 # Number of teams to generate per generation
N_generations = 50 # Number of generations to run
N_teams = 2 # Number of teams to generate per generation
N_battles = 3 # Number of battles to run per team
RECORD = True


def main():
async def main_loop():
builder = Builder(N_seed_teams=N_teams)
builder.build_N_teams_from_poke_pool(N_teams)
curr_gen = 0 # Current generation
teams = [builder.yield_team() for n in range(N_teams)]
evaluator = PokeEnv()
evaluator = PokeEnv(n_battles=N_battles)
recorder = TeamRecorder()

# Main expected loop
# while curr_gen < N_generations:
# team_fitness = evaluator.evaluate_teams(teams)
print("Starting main loop and running on Generation: ")
for _ in tqdm(range(N_generations)):
if RECORD:
recorder.record_teams(builder.get_teams(), curr_gen)
await evaluator.evaluate_teams(builder.get_teams())
builder.generate_new_teams()
curr_gen += 1


# poke_pool = [] # List of Pokemon
# teams = [] # list of teams (of Pokemon)
# curr_gen += 1
def main():
asyncio.get_event_loop().run_until_complete(main_loop())


if __name__ == "__main__":
Expand Down
33 changes: 30 additions & 3 deletions src/p2lab/evaluator/poke_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,37 @@
"""
from __future__ import annotations

from itertools import combinations

import numpy as np
from poke_env import PlayerConfiguration
from poke_env.player import RandomPlayer
from tqdm import tqdm

SHOW_MATCH_PROGRESS = True


class PokeEnv:
def __init__(self):
def __init__(self, n_battles=100, battle_format="gen7anythinggoes"):
self.p1 = RandomPlayer(
PlayerConfiguration("Player 1", None), battle_format=battle_format
)
self.p2 = RandomPlayer(
PlayerConfiguration("Player 2", None), battle_format=battle_format
)
self.n_battles = n_battles
pass

def evaluate_teams(self, teams):
pass
async def evaluate_teams(self, teams):
match_ups = list(combinations(np.arange(len(teams)), 2))

for t1, t2 in tqdm(match_ups) if SHOW_MATCH_PROGRESS else match_ups:
res1, res2 = await self.battle(teams[t1], teams[t2])
teams[t1].set_fitness(res1)
teams[t2].set_fitness(res2)

async def battle(self, team1, team2):
self.p1.update_team(team1.to_packed_str())
self.p2.update_team(team2.to_packed_str())
await self.p1.battle_against(self.p2, n_battles=self.n_battles)
return self.p1.n_won_battles, self.p2.n_won_battles
84 changes: 22 additions & 62 deletions src/p2lab/genetic/fitness.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,82 +8,42 @@
def BTmodel(
teams: list[Team],
matches: np.ndarray,
results: np.ndarray,
max_iter: int,
tol: float,
results: list[int],
) -> list[float]:
"""
Bradley-Terry Models are used to estimate latent team 'abilities'
in sports contests.
They do this by assuming that the probability of team 1 beating team 1
has the following form:
Pr(team1>team2) = exp(a_1) / (exp(a_1) + exp(a_2))
Where a_1 is team 1's latent ability.
The abilities are estimated via an iterative algorithm.
Which means we only need access to win/loss data and match data in order
to produce estimates
Bradley-Terry Models
Args:
teams: A list of teams. Used mainly for generating team IDs
teams: A list of unique team names
matches: A numpy.ndarray with the following format:
team 1 | team 2
-------+-------
A | B
B | C
B | D
A | D
B | A
team 1 | team 2
-------+-------
A | B
B | C
B | D
A | D
B | A
'team 1' and 'team 2' are columns containing the IDs of the teams
that played in the role of team 1 and team 2 respectively
results: An array of 1s and 0s, where each entry corresponds to a row
in matches denoting whether or not team 1 won the match
max_iter: Maximum number of iterations for Bradley-Terry model to run
tol: Tolerence to check for convergence at the end of each iteration
'team 1' and 'team 2' are columns containing the IDs of the teams
that played in the role of team 1 and team 2 respectively
results: A list of 1s and 0s, where each entry corresponds to a row
in matches denoting whether or not team 1 won the match
"""

# Organise teams into X1 and X2 matrices
N_team = len(teams)
N_match = matches.shape[0]
team_ids = np.array(range(N_team))
X1 = np.zeros(shape=(N_match, N_team), dtype=np.int32)
X2 = np.zeros(shape=(N_match, N_team), dtype=np.int32)
for index, pair in enumerate(matches):
X1[index, pair[0]] = 1
X2[index, pair[1]] = 1

# Prepare win data
total_wins = count_team_wins(
team_ids=team_ids,
matches=matches,
results=results,
)
win_matrix = make_win_matrix(
N_team=N_team,
matches=matches,
results=results,
)
print(teams)

# Generate vector of team abilities
abilities = np.ones(shape=(N_team), dtype=np.float64)
print(matches)

# Iteratively update abilities as: p_i = W_i / sum_(j!=1) {(w_ij + w_ji)/(p_i + p_j)}
for _iter in range(max_iter):
old_abilities = abilities
abilities = total_wins / np.array(
[BT_iter_func(x, team_ids, win_matrix, abilities) for x in team_ids]
)
abilities = abilities / np.sum(abilities)
# Estimate logit model
print(results)

# Check for convergence
if np.sum(np.abs(old_abilities - abilities)) < tol:
break
# # Check for convergence
# if np.sum(np.abs(old_abilities - abilities)) < tol:
# break

return abilities

Expand Down
1 change: 0 additions & 1 deletion src/p2lab/genetic/genetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ def generate_teams(
for _i in range(num_teams):
pokemon = random.sample(population=pokemon_population, k=num_pokemon)
teams.append(Team(pokemon=pokemon))

return teams


Expand Down
8 changes: 4 additions & 4 deletions src/p2lab/pokemon/battle.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np


def run_battles(matches: np.ndarray) -> np.ndarray:
def run_battles(matches: np.ndarray) -> list[int]:
"""
Runs several matches.
Expand All @@ -18,12 +18,12 @@ def run_battles(matches: np.ndarray) -> np.ndarray:
Returns:
_type_: _description_
"""
results = np.ndarray([])
results = []
for match in matches:
results = np.append(results, run_battle(match))
results.append(run_battle(match))
return results


def run_battle(match: np.ndarray) -> int:
print(match) # delete this line, it's here to pass pre-commit
return 0 # this function should always return 0 or 1
return 0 # this function should always run 0 or 1
1 change: 1 addition & 0 deletions src/p2lab/stats/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
""""""
19 changes: 19 additions & 0 deletions src/p2lab/stats/team_recorder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

from pathlib import Path


class TeamRecorder:
def __init__(self) -> None:
pass

def create_folder(self, f):
Path(f"outputs/{f}").mkdir(parents=True, exist_ok=True)

def record(self, team, generation, uid):
team.to_df().to_csv(f"outputs/{generation}/{uid}.csv", index=False)

def record_teams(self, teams, generation):
self.create_folder(generation)
for idx, team in enumerate(teams):
self.record(team, generation, idx)
Loading

0 comments on commit 77381f8

Please sign in to comment.