-
Notifications
You must be signed in to change notification settings - Fork 268
/
Copy pathevolvable_player.py
100 lines (74 loc) · 3.45 KB
/
evolvable_player.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import base64
from pickle import dumps, loads
from typing import Dict, List
from .player import Player
class InsufficientParametersError(Exception):
"""Error indicating that insufficient parameters were specified to initialize an Evolvable Player."""
def __init__(self, *args):
super().__init__(*args)
class EvolvablePlayer(Player):
"""A class for a player that can evolve, for use in the Moran process or with reinforcement learning algorithms.
This is an abstract base class, not intended to be used directly.
"""
name = "EvolvablePlayer"
parent_class = Player
parent_kwargs = [] # type: List[str]
def __init__(self, seed=None):
# Parameter seed is required for reproducibility. Player will throw
# a warning to the user otherwise.
super().__init__()
self.set_seed(seed=seed)
def overwrite_init_kwargs(self, **kwargs):
"""Use to overwrite parameters for proper cloning and testing."""
for k, v in kwargs.items():
self.init_kwargs[k] = v
def create_new(self, **kwargs):
"""Creates a new variant with parameters overwritten by kwargs. This differs from
cloning the Player because it propagates a seed forward, and is intended to be
used by the mutation and crossover methods."""
init_kwargs = self.init_kwargs.copy()
init_kwargs.update(kwargs)
# Propagate seed forward for reproducibility.
if "seed" not in kwargs:
init_kwargs["seed"] = self._random.random_seed_int()
return self.__class__(**init_kwargs)
# Serialization and deserialization. You may overwrite to obtain more human readable serializations
# but you must overwrite both.
def serialize_parameters(self):
"""Serialize parameters."""
pickled = dumps(self.init_kwargs) # bytes
s = base64.b64encode(pickled).decode("utf8") # string
return s
@classmethod
def deserialize_parameters(cls, serialized):
"""Deserialize parameters to a Player instance."""
init_kwargs = loads(base64.b64decode(serialized))
return cls(**init_kwargs)
# Optional methods for evolutionary algorithms and Moran processes.
def mutate(self):
"""Optional method to allow Player to produce a variant (not in place)."""
pass # pragma: no cover
def crossover(self, other):
"""Optional method to allow Player to produce variants in combination with another player. Returns a new
Player."""
pass # pragma: no cover
# Optional methods for particle swarm algorithm.
def receive_vector(self, vector):
"""Receive a vector of params and overwrite the Player."""
pass # pragma: no cover
def create_vector_bounds(self):
"""Creates the bounds for the decision variables for Particle Swarm Algorithm."""
pass # pragma: no cover
def copy_lists(lists: List[List]) -> List[List]:
return list(map(list, lists))
def crossover_lists(list1: List, list2: List, rng) -> List:
cross_point = rng.randint(0, len(list1))
new_list = list(list1[:cross_point]) + list(list2[cross_point:])
return new_list
def crossover_dictionaries(table1: Dict, table2: Dict, rng) -> Dict:
keys = list(table1.keys())
cross_point = rng.randint(0, len(keys))
new_items = [(k, table1[k]) for k in keys[:cross_point]]
new_items += [(k, table2[k]) for k in keys[cross_point:]]
new_table = dict(new_items)
return new_table