GrammaticalEvolutionTools 1.1.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- getools/__init__.py +6 -0
- getools/agents/__init__.py +4 -0
- getools/agents/agent.py +212 -0
- getools/agents/agent_program.py +105 -0
- getools/evolution/__init__.py +10 -0
- getools/evolution/cross_over.py +110 -0
- getools/evolution/mutation.py +21 -0
- getools/grammars/__init__.py +5 -0
- getools/grammars/grammar.py +140 -0
- getools/grammars/grammar_node.py +150 -0
- getools/meta/__init__.py +5 -0
- getools/meta/base_node.py +1261 -0
- getools/meta/meta.py +273 -0
- getools/programs/__init__.py +3 -0
- getools/programs/base/__init__.py +4 -0
- getools/programs/base/program_node.py +644 -0
- getools/programs/base/program_tree.py +627 -0
- getools/programs/mods/__init__.py +0 -0
- getools/programs/mods/grammar/__init__.py +4 -0
- getools/programs/mods/grammar/grammar_program_addin.py +53 -0
- getools/programs/mods/grammar/node_converter.py +147 -0
- getools/programs/nodes/__init__.py +7 -0
- getools/programs/nodes/basic_nodes/__init__.py +6 -0
- getools/programs/nodes/basic_nodes/executable_node.py +14 -0
- getools/programs/nodes/basic_nodes/non_terminal_node.py +66 -0
- getools/programs/nodes/basic_nodes/root_node.py +37 -0
- getools/programs/nodes/basic_nodes/terminal_node.py +14 -0
- getools/programs/nodes/factor_nodes/__init__.py +6 -0
- getools/programs/nodes/factor_nodes/factor_node.py +34 -0
- getools/programs/nodes/factor_nodes/integer_node.py +20 -0
- getools/programs/nodes/factor_nodes/number_node.py +23 -0
- getools/programs/nodes/factor_nodes/rand_int_node.py +12 -0
- getools/programs/nodes/logic_nodes/__init__.py +5 -0
- getools/programs/nodes/logic_nodes/condition_node.py +101 -0
- getools/programs/nodes/logic_nodes/repeat_node.py +46 -0
- getools/programs/nodes/logic_nodes/sequential_node.py +52 -0
- getools/worlds/__init__.py +3 -0
- getools/worlds/base/__init__.py +8 -0
- getools/worlds/base/animation.py +28 -0
- getools/worlds/base/layout.py +68 -0
- getools/worlds/base/objects/__init__.py +4 -0
- getools/worlds/base/objects/mixins/__init__.py +3 -0
- getools/worlds/base/objects/mixins/reward.py +93 -0
- getools/worlds/base/objects/world_object.py +71 -0
- getools/worlds/base/position.py +92 -0
- getools/worlds/base/world.py +142 -0
- getools/worlds/grid_world/__init__.py +10 -0
- getools/worlds/grid_world/grid_layout.py +268 -0
- getools/worlds/grid_world/grid_position.py +8 -0
- getools/worlds/grid_world/grid_world.py +335 -0
- getools/worlds/grid_world/grid_world_agent.py +186 -0
- getools/worlds/grid_world/grid_world_animation.py +182 -0
- getools/worlds/grid_world/grid_world_object.py +49 -0
- getools/worlds/grid_world/grid_world_reward.py +24 -0
- getools/worlds/hex_world/__init__.py +0 -0
- getools/worlds/open_world/__init__.py +0 -0
- grammaticalevolutiontools-1.1.0a1.dist-info/METADATA +192 -0
- grammaticalevolutiontools-1.1.0a1.dist-info/RECORD +60 -0
- grammaticalevolutiontools-1.1.0a1.dist-info/WHEEL +4 -0
- grammaticalevolutiontools-1.1.0a1.dist-info/licenses/LICENSE +21 -0
getools/__init__.py
ADDED
getools/agents/agent.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from .agent_program import AgentProgramTree
|
|
2
|
+
from ..grammars import Grammar
|
|
3
|
+
|
|
4
|
+
from numbers import Number
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from typing import Type, TYPE_CHECKING
|
|
8
|
+
import warnings
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..worlds import World
|
|
12
|
+
|
|
13
|
+
class Agent:
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def default_grammar(cls) -> Grammar:
|
|
17
|
+
return cls._default_grammar
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def default_program_cls(cls) -> Type[AgentProgramTree]:
|
|
21
|
+
mangled = f"_{cls.__name__}__default_program_cls"
|
|
22
|
+
|
|
23
|
+
if not hasattr(cls, mangled):
|
|
24
|
+
default_grammar = cls.default_grammar()
|
|
25
|
+
if default_grammar:
|
|
26
|
+
class AgentProgram(AgentProgramTree):
|
|
27
|
+
_grammar = default_grammar
|
|
28
|
+
pass
|
|
29
|
+
setattr(cls, mangled, AgentProgram)
|
|
30
|
+
|
|
31
|
+
return getattr(cls, mangled, None)
|
|
32
|
+
|
|
33
|
+
_default_grammar = None
|
|
34
|
+
_requires_world = False
|
|
35
|
+
|
|
36
|
+
# - - Exceptions - -
|
|
37
|
+
|
|
38
|
+
class WorldNotSetError(RuntimeError):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
class AlreadyInWorldError(RuntimeError):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
class AssignedProgramError(RuntimeError):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
class NotAssignedProgramError(RuntimeError):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
# - - Initialization - -
|
|
51
|
+
|
|
52
|
+
def __init__(self, program: AgentProgramTree = None, autogen=True):
|
|
53
|
+
self._world: World = None
|
|
54
|
+
self._program: AgentProgramTree = None
|
|
55
|
+
self._uuid = uuid4() # just to make hashable
|
|
56
|
+
|
|
57
|
+
self._score = 0
|
|
58
|
+
self._num_actions = 0
|
|
59
|
+
|
|
60
|
+
if program:
|
|
61
|
+
self._assert_valid_program(program)
|
|
62
|
+
self._set_program(program)
|
|
63
|
+
elif autogen:
|
|
64
|
+
program_cls = self.default_program_cls()
|
|
65
|
+
if not program_cls:
|
|
66
|
+
warnings.warn(
|
|
67
|
+
"`autogen` set to True, but no default " \
|
|
68
|
+
"grammar found for " \
|
|
69
|
+
f"{type(self).__name__}. Could not create a program.",
|
|
70
|
+
UserWarning
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
self._set_program(program_cls())
|
|
74
|
+
|
|
75
|
+
# - - Assertions - -
|
|
76
|
+
|
|
77
|
+
def _assert_not_in_world(self):
|
|
78
|
+
if self.assigned_to_world():
|
|
79
|
+
raise Agent.AlreadyInWorldError(
|
|
80
|
+
"Cannot add agent to world because agent is already in another world. "
|
|
81
|
+
"Remove agent from that world first. "
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def _assert_not_assigned_program(self):
|
|
85
|
+
if self._program:
|
|
86
|
+
raise Agent.AssignedProgramError(
|
|
87
|
+
"Cannot set program when agent is already assigned a program. "
|
|
88
|
+
"Please remove old program first."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def _assert_valid_program(self, program):
|
|
92
|
+
if not isinstance(program, AgentProgramTree):
|
|
93
|
+
raise TypeError('program must be an instance of AgentProgramTree.')
|
|
94
|
+
if program.agent and program.agent is not self:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"Cannot bind program to this agent when program is already "
|
|
97
|
+
"bound to another agent. Unbind the program first."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# - - World Internal Access - -
|
|
101
|
+
|
|
102
|
+
def _set_world(self, world: 'World'):
|
|
103
|
+
self._assert_not_in_world()
|
|
104
|
+
|
|
105
|
+
self._world = world
|
|
106
|
+
|
|
107
|
+
def _clear_world(self):
|
|
108
|
+
self._world = None
|
|
109
|
+
|
|
110
|
+
# - - Helpers - -
|
|
111
|
+
|
|
112
|
+
def _set_program(self, program: AgentProgramTree):
|
|
113
|
+
self._program = program
|
|
114
|
+
if program:
|
|
115
|
+
program._set_agent(self)
|
|
116
|
+
|
|
117
|
+
def _remove_program(self) -> AgentProgramTree:
|
|
118
|
+
program = self._program
|
|
119
|
+
program._set_agent(None)
|
|
120
|
+
self._program = None
|
|
121
|
+
|
|
122
|
+
return program
|
|
123
|
+
|
|
124
|
+
def _replace_program(self, new_program: AgentProgramTree) -> AgentProgramTree:
|
|
125
|
+
old = self._remove_program()
|
|
126
|
+
try:
|
|
127
|
+
self._set_program(new_program)
|
|
128
|
+
except (TypeError, ValueError) as e:
|
|
129
|
+
self._set_program(old)
|
|
130
|
+
raise e
|
|
131
|
+
|
|
132
|
+
return old
|
|
133
|
+
|
|
134
|
+
# - - Public - -
|
|
135
|
+
|
|
136
|
+
def reset(self, program: AgentProgramTree = None):
|
|
137
|
+
if program:
|
|
138
|
+
self._assert_valid_program(program)
|
|
139
|
+
self._replace_program(program)
|
|
140
|
+
|
|
141
|
+
if self._program and self._program.running():
|
|
142
|
+
self._program.kill()
|
|
143
|
+
|
|
144
|
+
self._num_actions = 0
|
|
145
|
+
self._score = 0
|
|
146
|
+
|
|
147
|
+
def copy_program(self):
|
|
148
|
+
return self._program.copy()
|
|
149
|
+
|
|
150
|
+
# - - Running Program - -
|
|
151
|
+
|
|
152
|
+
def execute_program(self, n=1):
|
|
153
|
+
"""
|
|
154
|
+
Resets the program and runs to completion once.
|
|
155
|
+
"""
|
|
156
|
+
if self._program.running():
|
|
157
|
+
self._program.kill()
|
|
158
|
+
|
|
159
|
+
self._program.run(n=n)
|
|
160
|
+
|
|
161
|
+
def tick(self, loop: bool=True):
|
|
162
|
+
"""
|
|
163
|
+
Runs the program through the next executable node. Exits if run and no nodes are left.
|
|
164
|
+
If loop is True (default), then the program will restart rather than exiting.
|
|
165
|
+
"""
|
|
166
|
+
if self._requires_world and not self.assigned_to_world():
|
|
167
|
+
raise Agent.WorldNotSetError("Cannot run program without agent being assigned a world unless this is a worldless agent.")
|
|
168
|
+
|
|
169
|
+
status = self._program.tick()
|
|
170
|
+
if status == AgentProgramTree.Status.EXITED and loop:
|
|
171
|
+
self._program.tick()
|
|
172
|
+
|
|
173
|
+
def give_reward(self, amount):
|
|
174
|
+
self._score += amount
|
|
175
|
+
|
|
176
|
+
# - - Listeners - -
|
|
177
|
+
|
|
178
|
+
def _on_action_taken(self):
|
|
179
|
+
self._record_action()
|
|
180
|
+
|
|
181
|
+
# - - Operations - -
|
|
182
|
+
|
|
183
|
+
def _record_action(self):
|
|
184
|
+
self._num_actions += 1
|
|
185
|
+
|
|
186
|
+
# - - Getters - -
|
|
187
|
+
|
|
188
|
+
def assigned_to_world(self):
|
|
189
|
+
return self._world is not None
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def program(self) -> AgentProgramTree:
|
|
193
|
+
return self._program
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def score(self) -> Number:
|
|
197
|
+
return self._score
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def num_actions(self) -> int:
|
|
201
|
+
return self._num_actions
|
|
202
|
+
|
|
203
|
+
# - - Other Methods - -
|
|
204
|
+
|
|
205
|
+
def __hash__(self):
|
|
206
|
+
return hash(self._uuid)
|
|
207
|
+
|
|
208
|
+
def __lt__(self, other):
|
|
209
|
+
if not isinstance(other, Agent):
|
|
210
|
+
raise TypeError("Cannot compare Agent to non-Agent")
|
|
211
|
+
|
|
212
|
+
return self.score < other.score
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from ..programs.base.program_tree import ProgramTree
|
|
2
|
+
from ..programs.mods.grammar import GrammarProgramAddin
|
|
3
|
+
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..agents import Agent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AgentProgramTree(ProgramTree, GrammarProgramAddin):
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def _assert_agent_valid(cls, agent):
|
|
14
|
+
grammar = cls.get_grammar()
|
|
15
|
+
if not isinstance(agent, grammar.target_agent_type):
|
|
16
|
+
raise TypeError(
|
|
17
|
+
"Cannot bind tree to an agent with which " \
|
|
18
|
+
"it is not compatible"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# - - Exceptions - -
|
|
22
|
+
|
|
23
|
+
class MissingAgentError(RuntimeError):
|
|
24
|
+
"""Exception raised when attempting to run a program not attached to an an agent."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
class BoundToAgentError(RuntimeError):
|
|
28
|
+
"""Exception raised for attempted operations requiring the program to be unbound from an agent when it is not."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# - - Local Assertions - -
|
|
32
|
+
|
|
33
|
+
def _assert_editable(self):
|
|
34
|
+
if self.bound_to_agent():
|
|
35
|
+
raise AgentProgramTree.BoundToAgentError(
|
|
36
|
+
"Cannot modify a program while it is bound to an agent. "
|
|
37
|
+
"Please remove the program from the agent first or create "
|
|
38
|
+
"a copy to modify. "
|
|
39
|
+
)
|
|
40
|
+
ProgramTree._assert_editable(self)
|
|
41
|
+
|
|
42
|
+
def _assert_runnable(self):
|
|
43
|
+
if not self.bound_to_agent():
|
|
44
|
+
raise AgentProgramTree.MissingAgentError(
|
|
45
|
+
"Cannot run program when it is not bound to an agent."
|
|
46
|
+
)
|
|
47
|
+
ProgramTree._assert_runnable(self)
|
|
48
|
+
|
|
49
|
+
# - - Initialization - -
|
|
50
|
+
|
|
51
|
+
def __init__(self, root=None, autofill=True):
|
|
52
|
+
self._agent = None
|
|
53
|
+
ProgramTree.__init__(self, root, autofill=autofill)
|
|
54
|
+
|
|
55
|
+
def _verify_and_set_root(self, root):
|
|
56
|
+
GrammarProgramAddin._verify_and_set_root(self, root)
|
|
57
|
+
|
|
58
|
+
# - - Agent Access - -
|
|
59
|
+
|
|
60
|
+
def _set_agent(self, agent):
|
|
61
|
+
"""Sets the agent instance to which this program is bound.
|
|
62
|
+
|
|
63
|
+
This method should typically only be called by an instance of
|
|
64
|
+
:py:class:`~.agents.Agent` itself to establish the binding.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
agent : Agent
|
|
69
|
+
The agent instance to bind the program to.
|
|
70
|
+
"""
|
|
71
|
+
self._assert_agent_valid(agent)
|
|
72
|
+
self._agent = agent
|
|
73
|
+
|
|
74
|
+
# - - Public Methods
|
|
75
|
+
|
|
76
|
+
def bound_to_agent(self) -> bool:
|
|
77
|
+
"""Checks if the program is currently bound to an agent.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
bool
|
|
82
|
+
:py:obj:`True` if an :py:class:`~.agents.Agent` instance is attached to the program
|
|
83
|
+
(:py:attr:`~.ProgramTree._agent` is not :py:obj:`None`), :py:obj:`False` otherwise.
|
|
84
|
+
"""
|
|
85
|
+
return self._agent is not None
|
|
86
|
+
|
|
87
|
+
def is_editable(self):
|
|
88
|
+
return ProgramTree.is_editable(self) and \
|
|
89
|
+
not self.bound_to_agent()
|
|
90
|
+
|
|
91
|
+
def is_runnable(self):
|
|
92
|
+
return ProgramTree.is_runnable() and \
|
|
93
|
+
self.bound_to_agent()
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def agent(self) -> 'Agent':
|
|
97
|
+
"""The agent instance to which this program is currently attached.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
Agent or None
|
|
102
|
+
The attached :py:class:`~.agents.Agent` instance, or :py:obj:`None`
|
|
103
|
+
if no agent is currently bound.
|
|
104
|
+
"""
|
|
105
|
+
return self._agent
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .cross_over import pick_compatible_nodes_same_type_only, \
|
|
2
|
+
pick_compatible_nodes_any_valid_replacement, \
|
|
3
|
+
cross_over_programs
|
|
4
|
+
from .mutation import mutate_terminals, replace_random_branch
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = ['pick_compatible_nodes_same_type_only',
|
|
8
|
+
'pick_compatible_nodes_any_valid_replacement',
|
|
9
|
+
'cross_over_programs', 'mutate_terminals',
|
|
10
|
+
'replace_random_branch']
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from ..programs.nodes.basic_nodes import TerminalNode
|
|
2
|
+
from ..programs import ProgramTree, ProgramNode
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
from typing import Literal, Type, Tuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
CrossOverOption = Literal['same', 'any']
|
|
9
|
+
|
|
10
|
+
def pick_compatible_nodes_same_type_only(
|
|
11
|
+
program1: ProgramTree, program2: ProgramTree,
|
|
12
|
+
exclude: list[Type[ProgramNode]] = None,
|
|
13
|
+
exclude_children_of_roots: bool = False
|
|
14
|
+
) -> Tuple[ProgramNode, ProgramNode]:
|
|
15
|
+
|
|
16
|
+
incompatible_types = tuple(exclude or [])
|
|
17
|
+
|
|
18
|
+
# Get all eligible node types from program1 (excluding root and incompatible types)
|
|
19
|
+
eligible_types = set()
|
|
20
|
+
possible_node1s = []
|
|
21
|
+
|
|
22
|
+
for node_type in program1.types_iter():
|
|
23
|
+
if not issubclass(node_type, incompatible_types) \
|
|
24
|
+
and node_type in program2.node_types:
|
|
25
|
+
eligible_types.add(node_type)
|
|
26
|
+
possible_node1s.extend(program1.get_nodes_by_type(node_type))
|
|
27
|
+
|
|
28
|
+
# if no matching types, return None, None
|
|
29
|
+
if not eligible_types:
|
|
30
|
+
return None, None
|
|
31
|
+
|
|
32
|
+
# attempt to pick a matching type
|
|
33
|
+
nodes_picked = False
|
|
34
|
+
|
|
35
|
+
while not nodes_picked:
|
|
36
|
+
possible_node1s = [node for node in possible_node1s \
|
|
37
|
+
if type(node) in eligible_types \
|
|
38
|
+
and node is not program1._root]
|
|
39
|
+
|
|
40
|
+
if not possible_node1s:
|
|
41
|
+
return None, None
|
|
42
|
+
|
|
43
|
+
# choose a random node from program1
|
|
44
|
+
node1: ProgramNode = random.choice(possible_node1s)
|
|
45
|
+
node1_child_of_root = node1._parent is program1.root
|
|
46
|
+
|
|
47
|
+
# get a possible list of nodes from program2
|
|
48
|
+
possible_node2s = [node for node in program2.node_iter(type=type(node1)) \
|
|
49
|
+
if type(node) == type(node1) \
|
|
50
|
+
and node is not program2._root \
|
|
51
|
+
and (not exclude_children_of_roots \
|
|
52
|
+
or not node1_child_of_root \
|
|
53
|
+
or node._parent is not program2.root) \
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
# if no matches, remove the type and try again
|
|
57
|
+
if not possible_node2s:
|
|
58
|
+
eligible_types.remove(type(node1))
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# once a compatible type is found, choose node2
|
|
62
|
+
node2 = random.choice(possible_node2s)
|
|
63
|
+
|
|
64
|
+
nodes_picked = True
|
|
65
|
+
|
|
66
|
+
return node1, node2
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def pick_compatible_nodes_any_valid_replacement(program1: ProgramTree, program2: ProgramTree) -> Tuple[ProgramNode, ProgramNode]:
|
|
70
|
+
"""Not Implemented"""
|
|
71
|
+
# TODO: Implement this function
|
|
72
|
+
picked = False
|
|
73
|
+
incompatible_types = set([TerminalNode])
|
|
74
|
+
possible_node1s = []
|
|
75
|
+
while not picked:
|
|
76
|
+
possible_node1s = [node for node in (possible_node1s or program1.node_iter()) \
|
|
77
|
+
if not isinstance(node, tuple(incompatible_types))]
|
|
78
|
+
node1: ProgramNode = random.choice(possible_node1s)
|
|
79
|
+
|
|
80
|
+
return NotImplemented
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def cross_over_programs(program1: ProgramTree, program2: ProgramTree,
|
|
84
|
+
cross_over_option: CrossOverOption = 'same') -> list[ProgramTree]:
|
|
85
|
+
|
|
86
|
+
# copy the nodes as to not modify the originals
|
|
87
|
+
program1 = program1.copy()
|
|
88
|
+
program2 = program2.copy()
|
|
89
|
+
|
|
90
|
+
# select a pair of compatible nodes.
|
|
91
|
+
# excludes terminals and prevents children of root nodes
|
|
92
|
+
# from being swapped with each other (root nodes excluded automatically)
|
|
93
|
+
if cross_over_option == 'same':
|
|
94
|
+
node1, node2 = pick_compatible_nodes_same_type_only(
|
|
95
|
+
program1, program2, exclude=[TerminalNode],
|
|
96
|
+
exclude_children_of_roots=True
|
|
97
|
+
)
|
|
98
|
+
elif cross_over_option == 'any':
|
|
99
|
+
raise NotImplementedError("The 'any' option is currently under development.")
|
|
100
|
+
# pick_compatible_nodes_any_valid_replacement(program1, program2)
|
|
101
|
+
|
|
102
|
+
if not node1 or not node2: # if no match was found
|
|
103
|
+
return []
|
|
104
|
+
|
|
105
|
+
# swap the nodes in each tree
|
|
106
|
+
program1.replace_node(node1, node2.copy())
|
|
107
|
+
program2.replace_node(node2, node1)
|
|
108
|
+
|
|
109
|
+
# return a list of the newly created nodes
|
|
110
|
+
return [program1, program2]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from ..programs import ProgramTree
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
def mutate_terminals(program:ProgramTree, num_mutations, terminal_types:list[type]):
|
|
5
|
+
possible_nodes = [node for node in program.nodes if type(node) \
|
|
6
|
+
in terminal_types]
|
|
7
|
+
|
|
8
|
+
num_mutations = min(num_mutations, len(possible_nodes))
|
|
9
|
+
for k in range(num_mutations):
|
|
10
|
+
node = random.choice(possible_nodes)
|
|
11
|
+
if node in program.nodes:
|
|
12
|
+
program.replace_node(node)
|
|
13
|
+
possible_nodes.remove(node)
|
|
14
|
+
else:
|
|
15
|
+
print('error')
|
|
16
|
+
|
|
17
|
+
def replace_random_branch(program: ProgramTree, possible_node_types:list[type]):
|
|
18
|
+
possible_nodes = [n for n in program.nodes if type(n) in possible_node_types]
|
|
19
|
+
node = random.choice(possible_nodes)
|
|
20
|
+
program.replace_node(node) # randomly replaces node by default
|
|
21
|
+
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from .grammar_node import GrammarNode
|
|
2
|
+
|
|
3
|
+
from typing import Type, TYPE_CHECKING
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..agents import Agent
|
|
8
|
+
|
|
9
|
+
class Grammar:
|
|
10
|
+
|
|
11
|
+
class MultipleRootsException(ValueError):
|
|
12
|
+
def __init__(self):
|
|
13
|
+
super(Grammar.MultipleRootsException, self).__init__("Multiple Root Nodes Defined within Grammar.")
|
|
14
|
+
|
|
15
|
+
class NoRootDefinedException(ValueError):
|
|
16
|
+
def __init__(self):
|
|
17
|
+
super(Grammar.NoRootDefinedException, self).__init__("No Root Nodes Defined within Grammar.")
|
|
18
|
+
|
|
19
|
+
current_grammar = None
|
|
20
|
+
|
|
21
|
+
def __init__(self, target_agent_type: 'Type[Agent]' = None,
|
|
22
|
+
warnings=True):
|
|
23
|
+
self._roots: list[Type[GrammarNode]] = []
|
|
24
|
+
self._all_node_classes: dict[str, Type[GrammarNode]] = {}
|
|
25
|
+
self._concrete_types: set[Type[GrammarNode]] = set()
|
|
26
|
+
self._abstract_types: set[Type[GrammarNode]] = set()
|
|
27
|
+
|
|
28
|
+
self._target_agent_type = target_agent_type
|
|
29
|
+
self._warnings = warnings
|
|
30
|
+
self._is_valid = False
|
|
31
|
+
|
|
32
|
+
def __enter__(self):
|
|
33
|
+
Grammar.current_grammar = self
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
37
|
+
Grammar.current_grammar = None
|
|
38
|
+
|
|
39
|
+
if exc_type is None:
|
|
40
|
+
self._validate()
|
|
41
|
+
self._is_valid = True
|
|
42
|
+
|
|
43
|
+
return self._is_valid
|
|
44
|
+
|
|
45
|
+
def _validate(self):
|
|
46
|
+
if len(self._roots) == 0:
|
|
47
|
+
raise Grammar.NoRootDefinedException()
|
|
48
|
+
elif len(self._roots) > 1:
|
|
49
|
+
raise Grammar.MultipleRootsException()
|
|
50
|
+
|
|
51
|
+
for cls in self._abstract_types.union(self._concrete_types):
|
|
52
|
+
self._all_node_classes[cls.__name__] = cls
|
|
53
|
+
|
|
54
|
+
# Make sure all possible children are also in the Grammar
|
|
55
|
+
seen_nodes = set()
|
|
56
|
+
for node_cls in self._all_node_classes.values():
|
|
57
|
+
try:
|
|
58
|
+
node_cls._resolve_possible_children()
|
|
59
|
+
|
|
60
|
+
except KeyError as e:
|
|
61
|
+
raise LookupError(f"Error resolving possible children for {node_cls.__name__}. " + \
|
|
62
|
+
"Child not defined within the scope of the Grammar.") from e
|
|
63
|
+
|
|
64
|
+
for chld_cls in node_cls._ALL_POSSIBLE_CHILDREN:
|
|
65
|
+
|
|
66
|
+
error = None
|
|
67
|
+
if chld_cls is self.root:
|
|
68
|
+
error = "Root node class"
|
|
69
|
+
if chld_cls in self._abstract_types:
|
|
70
|
+
error = "Abstract node class"
|
|
71
|
+
if error is not None:
|
|
72
|
+
raise ValueError(f"{error} cannot be listed as a possible child of another class. Class at fault: {node_cls.__name__} referencing {chld_cls.__name__}")
|
|
73
|
+
|
|
74
|
+
seen_nodes.update(node_cls._ALL_POSSIBLE_CHILDREN)
|
|
75
|
+
|
|
76
|
+
if self._warnings:
|
|
77
|
+
# Raise a warning if abstract classes exist that weren't used to make concrete classes
|
|
78
|
+
for abstract_class in self._abstract_types:
|
|
79
|
+
implemented = False
|
|
80
|
+
for cls in self._concrete_types:
|
|
81
|
+
if issubclass(cls, abstract_class):
|
|
82
|
+
implemented = True
|
|
83
|
+
break
|
|
84
|
+
if not implemented:
|
|
85
|
+
warnings.warn(f"Abstract class {abstract_class.__name__} was defined but no concrete classes were ever created from it. " +
|
|
86
|
+
"Please note that all concrete classes must have no parameters other than self in their __init__ function, " + \
|
|
87
|
+
"otherwise they will still be considered abstract classes.",
|
|
88
|
+
UserWarning)
|
|
89
|
+
|
|
90
|
+
# Raise a warning if a concrete class (besides the root) is not a possible child of any other class
|
|
91
|
+
for node_cls in self._concrete_types:
|
|
92
|
+
if node_cls is self.root:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
if node_cls not in seen_nodes:
|
|
96
|
+
warnings.warn(f"{node_cls.__name__} is not listed as a possible child type for any other node in its grammar.",
|
|
97
|
+
UserWarning)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# --------------------
|
|
101
|
+
|
|
102
|
+
def register(self, cls: Type[GrammarNode]):
|
|
103
|
+
"""Register a node class inside the grammar."""
|
|
104
|
+
|
|
105
|
+
cls._GRAMMAR = self
|
|
106
|
+
|
|
107
|
+
if cls.is_abstract_class():
|
|
108
|
+
self._abstract_types.add(cls)
|
|
109
|
+
else:
|
|
110
|
+
if cls._IS_ROOT:
|
|
111
|
+
self._roots.append(cls)
|
|
112
|
+
|
|
113
|
+
self._concrete_types.add(cls)
|
|
114
|
+
|
|
115
|
+
def get_class(self, cls_name: str) -> Type[GrammarNode]:
|
|
116
|
+
return self._all_node_classes[cls_name]
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def is_valid(self) -> bool:
|
|
120
|
+
return self._is_valid
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def root(self) -> Type[GrammarNode]:
|
|
124
|
+
return self._roots[0]
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def warnings_enabled(self) -> bool:
|
|
128
|
+
return self._warnings
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def abstract_classes(self) -> set[Type[GrammarNode]]:
|
|
132
|
+
return self._abstract_types.copy()
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def valid_node_classes(self) -> set[Type[GrammarNode]]:
|
|
136
|
+
return self._concrete_types.copy()
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def target_agent_type(self) -> 'Type[Agent]':
|
|
140
|
+
return self._target_agent_type
|