algorhino-anemone 0.1.1__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.
- algorhino_anemone-0.1.1.dist-info/METADATA +151 -0
- algorhino_anemone-0.1.1.dist-info/RECORD +82 -0
- algorhino_anemone-0.1.1.dist-info/WHEEL +5 -0
- algorhino_anemone-0.1.1.dist-info/licenses/LICENSE +674 -0
- algorhino_anemone-0.1.1.dist-info/top_level.txt +1 -0
- anemone/__init__.py +27 -0
- anemone/basics.py +36 -0
- anemone/factory.py +161 -0
- anemone/indices/__init__.py +0 -0
- anemone/indices/index_manager/__init__.py +12 -0
- anemone/indices/index_manager/factory.py +50 -0
- anemone/indices/index_manager/node_exploration_manager.py +549 -0
- anemone/indices/node_indices/__init__.py +22 -0
- anemone/indices/node_indices/factory.py +121 -0
- anemone/indices/node_indices/index_data.py +166 -0
- anemone/indices/node_indices/index_types.py +20 -0
- anemone/nn/torch_evaluator.py +108 -0
- anemone/node_evaluation/__init__.py +0 -0
- anemone/node_evaluation/node_direct_evaluation/__init__.py +22 -0
- anemone/node_evaluation/node_direct_evaluation/factory.py +12 -0
- anemone/node_evaluation/node_direct_evaluation/node_direct_evaluator.py +192 -0
- anemone/node_evaluation/node_tree_evaluation/node_minmax_evaluation.py +885 -0
- anemone/node_evaluation/node_tree_evaluation/node_tree_evaluation.py +137 -0
- anemone/node_evaluation/node_tree_evaluation/node_tree_evaluation_factory.py +43 -0
- anemone/node_factory/__init__.py +14 -0
- anemone/node_factory/algorithm_node_factory.py +123 -0
- anemone/node_factory/base.py +76 -0
- anemone/node_selector/__init__.py +32 -0
- anemone/node_selector/branch_explorer.py +89 -0
- anemone/node_selector/factory.py +65 -0
- anemone/node_selector/node_selector.py +44 -0
- anemone/node_selector/node_selector_args.py +22 -0
- anemone/node_selector/node_selector_types.py +15 -0
- anemone/node_selector/notations_and_statics.py +88 -0
- anemone/node_selector/opening_instructions.py +249 -0
- anemone/node_selector/recurzipf/__init__.py +0 -0
- anemone/node_selector/recurzipf/recur_zipf_base.py +141 -0
- anemone/node_selector/sequool/__init__.py +19 -0
- anemone/node_selector/sequool/factory.py +102 -0
- anemone/node_selector/sequool/sequool.py +395 -0
- anemone/node_selector/uniform/__init__.py +16 -0
- anemone/node_selector/uniform/uniform.py +113 -0
- anemone/nodes/__init__.py +15 -0
- anemone/nodes/algorithm_node/__init__.py +7 -0
- anemone/nodes/algorithm_node/algorithm_node.py +204 -0
- anemone/nodes/itree_node.py +136 -0
- anemone/nodes/tree_node.py +240 -0
- anemone/nodes/tree_traversal.py +108 -0
- anemone/nodes/utils.py +146 -0
- anemone/progress_monitor/__init__.py +0 -0
- anemone/progress_monitor/progress_monitor.py +375 -0
- anemone/recommender_rule/__init__.py +12 -0
- anemone/recommender_rule/recommender_rule.py +140 -0
- anemone/search_factory/__init__.py +14 -0
- anemone/search_factory/search_factory.py +192 -0
- anemone/state_transition.py +47 -0
- anemone/tree_and_value_branch_selector.py +99 -0
- anemone/tree_exploration.py +274 -0
- anemone/tree_manager/__init__.py +29 -0
- anemone/tree_manager/algorithm_node_tree_manager.py +246 -0
- anemone/tree_manager/factory.py +77 -0
- anemone/tree_manager/tree_expander.py +122 -0
- anemone/tree_manager/tree_manager.py +254 -0
- anemone/trees/__init__.py +14 -0
- anemone/trees/descendants.py +765 -0
- anemone/trees/factory.py +80 -0
- anemone/trees/tree.py +70 -0
- anemone/trees/tree_visualization.py +143 -0
- anemone/updates/__init__.py +33 -0
- anemone/updates/algorithm_node_updater.py +157 -0
- anemone/updates/factory.py +36 -0
- anemone/updates/index_block.py +91 -0
- anemone/updates/index_updater.py +100 -0
- anemone/updates/minmax_evaluation_updater.py +108 -0
- anemone/updates/updates_file.py +248 -0
- anemone/updates/value_block.py +133 -0
- anemone/utils/comparable.py +32 -0
- anemone/utils/dataclass.py +64 -0
- anemone/utils/dict_of_numbered_dict_with_pointer_on_max.py +128 -0
- anemone/utils/logger.py +94 -0
- anemone/utils/my_value_sorted_dict.py +27 -0
- anemone/utils/small_tools.py +103 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sequool
|
|
3
|
+
|
|
4
|
+
This module contains the implementation of the Sequool node selector. The Sequool node selector is responsible for
|
|
5
|
+
choosing the best node to open in a move tree based on various selection strategies.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
- TreeDepthSelector: Protocol defining the interface for a half-move selector.
|
|
9
|
+
- StaticNotOpenedSelector: A node selector that considers the number of visits and selects half-moves based on zipf distribution.
|
|
10
|
+
- RandomAllSelector: A node selector that selects half-moves randomly.
|
|
11
|
+
- Sequool: The main class implementing the Sequool node selector.
|
|
12
|
+
|
|
13
|
+
Functions:
|
|
14
|
+
- consider_nodes_from_all_lesser_tree_depths_in_descendants: Consider all nodes in smaller half-moves than the picked half-move using the descendants object.
|
|
15
|
+
- consider_nodes_from_all_lesser_tree_depths_in_sub_stree: Consider all nodes in smaller half-moves than the picked half-move using tree traversal.
|
|
16
|
+
- consider_nodes_only_from_tree_depths_in_descendants: Consider only the nodes at the picked depth.
|
|
17
|
+
- get_best_node_from_candidates: Get the best node from a list of candidate nodes based on their exploration index data.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from random import Random
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Callable, Protocol
|
|
23
|
+
|
|
24
|
+
from anemone import trees
|
|
25
|
+
from anemone.basics import TreeDepth
|
|
26
|
+
from anemone.indices.node_indices.index_data import (
|
|
27
|
+
MaxDepthDescendants,
|
|
28
|
+
)
|
|
29
|
+
from anemone.node_selector.notations_and_statics import (
|
|
30
|
+
zipf_picks,
|
|
31
|
+
zipf_picks_random,
|
|
32
|
+
)
|
|
33
|
+
from anemone.node_selector.opening_instructions import (
|
|
34
|
+
OpeningInstructions,
|
|
35
|
+
OpeningInstructor,
|
|
36
|
+
create_instructions_to_open_all_branches,
|
|
37
|
+
)
|
|
38
|
+
from anemone.nodes.algorithm_node.algorithm_node import AlgorithmNode
|
|
39
|
+
from anemone.nodes.tree_traversal import (
|
|
40
|
+
get_descendants_candidate_not_over,
|
|
41
|
+
)
|
|
42
|
+
from anemone.trees.descendants import Descendants
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from anemone import tree_manager as tree_man
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TreeDepthSelector[NodeT: AlgorithmNode[Any] = AlgorithmNode[Any]](Protocol):
|
|
49
|
+
"""
|
|
50
|
+
Protocol defining the interface for a half-move selector.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def update_from_expansions(
|
|
54
|
+
self, latest_tree_expansions: "tree_man.TreeExpansions[NodeT]"
|
|
55
|
+
) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Update the half-move selector with the latest tree expansions.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
latest_tree_expansions: The latest tree expansions.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
None
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def select_tree_depth(
|
|
67
|
+
self, from_node: NodeT, random_generator: Random
|
|
68
|
+
) -> TreeDepth:
|
|
69
|
+
"""
|
|
70
|
+
Select the next half-move to consider based on the given node and random generator.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
from_node: The current node.
|
|
74
|
+
random_generator: The random generator.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The selected half-move.
|
|
78
|
+
"""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _make_count_visits() -> dict[TreeDepth, int]:
|
|
83
|
+
"""Helper function to create a default count visits dictionary."""
|
|
84
|
+
return {}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
count_visits: dict[TreeDepth, int] = _make_count_visits()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class StaticNotOpenedSelector[NodeT: AlgorithmNode[Any] = AlgorithmNode[Any]]:
|
|
92
|
+
"""
|
|
93
|
+
A node selector that considers the number of visits and selects half-moves based on zipf distribution.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
all_nodes_not_opened: Descendants[NodeT]
|
|
97
|
+
|
|
98
|
+
# counting the visits for each tree_depth
|
|
99
|
+
count_visits: dict[TreeDepth, int] = field(default_factory=_make_count_visits)
|
|
100
|
+
|
|
101
|
+
def update_from_expansions(
|
|
102
|
+
self, latest_tree_expansions: "tree_man.TreeExpansions[NodeT]"
|
|
103
|
+
) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Update the node selector with the latest tree expansions.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
latest_tree_expansions: The latest tree expansions.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
None
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
# Update internal info with the latest tree expansions
|
|
115
|
+
expansion: tree_man.TreeExpansion[NodeT]
|
|
116
|
+
for expansion in latest_tree_expansions:
|
|
117
|
+
if expansion.creation_child_node:
|
|
118
|
+
self.all_nodes_not_opened.add_descendant(expansion.child_node)
|
|
119
|
+
|
|
120
|
+
# if a new tree_depth is being created then init the visits to 1
|
|
121
|
+
# (0 would bug as it would automatically be selected with zipf computation)
|
|
122
|
+
tree_depth: int = expansion.child_node.tree_depth
|
|
123
|
+
if tree_depth not in self.count_visits:
|
|
124
|
+
self.count_visits[tree_depth] = 1
|
|
125
|
+
|
|
126
|
+
def select_tree_depth(
|
|
127
|
+
self, from_node: NodeT, random_generator: Random
|
|
128
|
+
) -> TreeDepth:
|
|
129
|
+
"""
|
|
130
|
+
Select the next half-move to consider based on the given node and random generator.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
from_node: The current node.
|
|
134
|
+
random_generator: The random generator.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The selected half-move.
|
|
138
|
+
"""
|
|
139
|
+
_ = from_node # not used here
|
|
140
|
+
|
|
141
|
+
filtered_count_visits: dict[int, int | float] = {
|
|
142
|
+
hm: value
|
|
143
|
+
for hm, value in self.count_visits.items()
|
|
144
|
+
if hm in self.all_nodes_not_opened
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# choose a half move based on zipf
|
|
148
|
+
tree_depth_picked: int = zipf_picks(
|
|
149
|
+
ranks_values=filtered_count_visits,
|
|
150
|
+
random_generator=random_generator,
|
|
151
|
+
shift=True,
|
|
152
|
+
random_pick=False,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
self.count_visits[tree_depth_picked] += 1
|
|
156
|
+
|
|
157
|
+
return tree_depth_picked
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
type ConsiderNodesFromTreeDepths[NodeT: AlgorithmNode[Any]] = Callable[
|
|
161
|
+
[TreeDepth, NodeT],
|
|
162
|
+
list[NodeT],
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def consider_nodes_from_all_lesser_tree_depths_in_descendants[N: AlgorithmNode[Any]](
|
|
167
|
+
tree_depth_picked: TreeDepth,
|
|
168
|
+
from_node: N,
|
|
169
|
+
descendants: Descendants[N],
|
|
170
|
+
) -> list[N]:
|
|
171
|
+
"""
|
|
172
|
+
Consider all the nodes that are in smaller half-moves than the picked half-move using the descendants object.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
tree_depth_picked: The picked half-move.
|
|
176
|
+
from_node: The current node.
|
|
177
|
+
descendants: The descendants object.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
A list of nodes to consider.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
_ = from_node # not used here
|
|
184
|
+
|
|
185
|
+
nodes_to_consider: list[N] = []
|
|
186
|
+
tree_depth: int
|
|
187
|
+
# considering all half-move smaller than the half move picked
|
|
188
|
+
for tree_depth in filter(lambda hm: hm < tree_depth_picked + 1, descendants):
|
|
189
|
+
nodes_to_consider += list(descendants[tree_depth].values())
|
|
190
|
+
|
|
191
|
+
return nodes_to_consider
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def consider_nodes_from_all_lesser_tree_depths_in_sub_stree[N: AlgorithmNode[Any]](
|
|
195
|
+
tree_depth_picked: TreeDepth,
|
|
196
|
+
from_node: N,
|
|
197
|
+
) -> list[N]:
|
|
198
|
+
"""
|
|
199
|
+
Consider all the nodes that are in smaller half-moves than the picked half-move using tree traversal.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
tree_depth_picked: The picked half-move.
|
|
203
|
+
from_node: The current node.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
A list of nodes to consider.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
nodes_to_consider: list[N] = get_descendants_candidate_not_over(
|
|
210
|
+
from_tree_node=from_node, max_depth=tree_depth_picked - from_node.tree_depth
|
|
211
|
+
)
|
|
212
|
+
return nodes_to_consider
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def consider_nodes_only_from_tree_depths_in_descendants[N: AlgorithmNode[Any]](
|
|
216
|
+
tree_depth_picked: TreeDepth,
|
|
217
|
+
from_node: N,
|
|
218
|
+
descendants: Descendants[N],
|
|
219
|
+
) -> list[N]:
|
|
220
|
+
"""
|
|
221
|
+
Consider only the nodes at the picked depth.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
tree_depth_picked: The picked half-move.
|
|
225
|
+
from_node: The current node.
|
|
226
|
+
descendants: The descendants object.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
A list of nodes to consider.
|
|
230
|
+
"""
|
|
231
|
+
_ = from_node # not used here
|
|
232
|
+
return list(descendants[tree_depth_picked].values())
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@dataclass
|
|
236
|
+
class RandomAllSelector[NodeT: AlgorithmNode[Any] = AlgorithmNode[Any]]:
|
|
237
|
+
"""
|
|
238
|
+
A node selector that selects half-moves randomly.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def update_from_expansions(
|
|
242
|
+
self, latest_tree_expansions: "tree_man.TreeExpansions[NodeT]"
|
|
243
|
+
) -> None:
|
|
244
|
+
"""
|
|
245
|
+
Update the node selector with the latest tree expansions.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
latest_tree_expansions: The latest tree expansions.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
None
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def select_tree_depth(
|
|
255
|
+
self, from_node: NodeT, random_generator: Random
|
|
256
|
+
) -> TreeDepth:
|
|
257
|
+
"""
|
|
258
|
+
Select the next half-move to consider based on the given node and random generator.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
from_node: The current node.
|
|
262
|
+
random_generator: The random generator.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
The selected half-move.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
tree_depth_picked: int
|
|
269
|
+
# choose a half move based on zipf
|
|
270
|
+
assert isinstance(from_node.exploration_index_data, MaxDepthDescendants)
|
|
271
|
+
max_descendants_depth: int = (
|
|
272
|
+
from_node.exploration_index_data.max_depth_descendants
|
|
273
|
+
)
|
|
274
|
+
if max_descendants_depth:
|
|
275
|
+
depth_picked: int = zipf_picks_random(
|
|
276
|
+
ordered_list_elements=list(range(1, max_descendants_depth + 1)),
|
|
277
|
+
random_generator=random_generator,
|
|
278
|
+
)
|
|
279
|
+
tree_depth_picked = from_node.tree_depth + depth_picked
|
|
280
|
+
else:
|
|
281
|
+
tree_depth_picked = from_node.tree_depth
|
|
282
|
+
return tree_depth_picked
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def get_best_node_from_candidates[N: AlgorithmNode[Any]](
|
|
286
|
+
nodes_to_consider: list[N],
|
|
287
|
+
) -> N:
|
|
288
|
+
"""
|
|
289
|
+
Returns the best node from a list of candidate nodes based on their exploration index and half move.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
nodes_to_consider (list[ITreeNode]): A list of candidate nodes to consider.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
AlgorithmNode: The best node from the list of candidates.
|
|
296
|
+
"""
|
|
297
|
+
best_node: N = nodes_to_consider[0]
|
|
298
|
+
assert best_node.exploration_index_data is not None
|
|
299
|
+
best_value = (best_node.exploration_index_data.index, best_node.tree_depth)
|
|
300
|
+
|
|
301
|
+
node: N
|
|
302
|
+
for node in nodes_to_consider:
|
|
303
|
+
assert node.exploration_index_data is not None
|
|
304
|
+
if node.exploration_index_data.index is not None:
|
|
305
|
+
assert best_node.exploration_index_data is not None
|
|
306
|
+
if (
|
|
307
|
+
best_node.exploration_index_data.index is None
|
|
308
|
+
or (node.exploration_index_data.index, node.tree_depth) < best_value
|
|
309
|
+
):
|
|
310
|
+
best_node = node
|
|
311
|
+
best_value = (node.exploration_index_data.index, node.tree_depth)
|
|
312
|
+
return best_node
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@dataclass
|
|
316
|
+
class Sequool[NodeT: AlgorithmNode[Any] = AlgorithmNode[Any]]:
|
|
317
|
+
"""
|
|
318
|
+
The main class implementing the Sequool node selector.
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
opening_instructor: OpeningInstructor
|
|
322
|
+
all_nodes_not_opened: Descendants[NodeT]
|
|
323
|
+
recursif: bool
|
|
324
|
+
random_depth_pick: bool
|
|
325
|
+
tree_depth_selector: TreeDepthSelector[NodeT]
|
|
326
|
+
random_generator: Random
|
|
327
|
+
consider_nodes_from_tree_depths: ConsiderNodesFromTreeDepths[NodeT]
|
|
328
|
+
|
|
329
|
+
def choose_node_and_branch_to_open(
|
|
330
|
+
self,
|
|
331
|
+
tree: trees.Tree[NodeT],
|
|
332
|
+
latest_tree_expansions: "tree_man.TreeExpansions[NodeT]",
|
|
333
|
+
) -> OpeningInstructions[NodeT]:
|
|
334
|
+
"""
|
|
335
|
+
Choose the best node to open in the move tree and return the opening instructions.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
tree: The move tree.
|
|
339
|
+
latest_tree_expansions: The latest tree expansions.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
The opening instructions.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
self.tree_depth_selector.update_from_expansions(
|
|
346
|
+
latest_tree_expansions=latest_tree_expansions
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
opening_instructions: OpeningInstructions[NodeT] = (
|
|
350
|
+
self.choose_node_and_move_to_open_recur(from_node=tree.root_node)
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
return opening_instructions
|
|
354
|
+
|
|
355
|
+
def choose_node_and_move_to_open_recur(
|
|
356
|
+
self, from_node: NodeT
|
|
357
|
+
) -> OpeningInstructions[NodeT]:
|
|
358
|
+
"""
|
|
359
|
+
Recursively choose the best node to open in the move tree and return the opening instructions.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
from_node: The current node.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
The opening instructions.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
tree_depth_selected: TreeDepth = self.tree_depth_selector.select_tree_depth(
|
|
369
|
+
from_node=from_node, random_generator=self.random_generator
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
nodes_to_consider: list[NodeT] = self.consider_nodes_from_tree_depths(
|
|
373
|
+
tree_depth_selected, from_node
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
best_node: NodeT = get_best_node_from_candidates(
|
|
377
|
+
nodes_to_consider=nodes_to_consider
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if not self.recursif:
|
|
381
|
+
self.all_nodes_not_opened.remove_descendant(best_node)
|
|
382
|
+
|
|
383
|
+
if self.recursif and best_node.tree_node.all_branches_generated:
|
|
384
|
+
return self.choose_node_and_move_to_open_recur(from_node=best_node)
|
|
385
|
+
|
|
386
|
+
all_branches_to_open = self.opening_instructor.all_branches_to_open(
|
|
387
|
+
node_to_open=best_node
|
|
388
|
+
)
|
|
389
|
+
opening_instructions: OpeningInstructions[NodeT] = (
|
|
390
|
+
create_instructions_to_open_all_branches(
|
|
391
|
+
branches_to_play=all_branches_to_open, node_to_open=best_node
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
return opening_instructions
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a uniform selection strategy for nodes in a tree.
|
|
3
|
+
|
|
4
|
+
The Uniform selection strategy selects nodes uniformly at random from the available options.
|
|
5
|
+
|
|
6
|
+
Example usage:
|
|
7
|
+
from .uniform import Uniform
|
|
8
|
+
|
|
9
|
+
uniform_selector = Uniform()
|
|
10
|
+
selected_node = uniform_selector.select_node(nodes)
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .uniform import Uniform
|
|
15
|
+
|
|
16
|
+
__all__ = ["Uniform"]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the implementation of the Uniform Node selector.
|
|
3
|
+
|
|
4
|
+
The Uniform Node selector is responsible for selecting nodes to expand in a tree-based move selector algorithm.
|
|
5
|
+
It uses an opening instructor to determine the moves to open for each node and generates opening instructions accordingly.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
- Uniform: The Uniform Node selector class.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from anemone import tree_manager as tree_man
|
|
15
|
+
from anemone import trees
|
|
16
|
+
from anemone.node_selector.opening_instructions import (
|
|
17
|
+
OpeningInstructions,
|
|
18
|
+
OpeningInstructor,
|
|
19
|
+
create_instructions_to_open_all_branches,
|
|
20
|
+
)
|
|
21
|
+
from anemone.nodes.algorithm_node import AlgorithmNode
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from valanga import BranchKey
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Uniform[NodeT: AlgorithmNode[Any] = AlgorithmNode[Any]]:
|
|
28
|
+
"""The Uniform Node selector"""
|
|
29
|
+
|
|
30
|
+
opening_instructor: OpeningInstructor
|
|
31
|
+
|
|
32
|
+
def __init__(self, opening_instructor: OpeningInstructor) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Initializes a new instance of the Uniform class.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
- opening_instructor (OpeningInstructor): The opening instructor to be used for determining moves to open.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
self.opening_instructor = opening_instructor
|
|
41
|
+
self.current_depth_to_expand = 0
|
|
42
|
+
|
|
43
|
+
def get_current_depth_to_expand(self) -> int:
|
|
44
|
+
"""
|
|
45
|
+
Gets the current depth to expand.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
- int: The current depth to expand.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
return self.current_depth_to_expand
|
|
52
|
+
|
|
53
|
+
def choose_node_and_branch_to_open(
|
|
54
|
+
self,
|
|
55
|
+
tree: trees.Tree[NodeT],
|
|
56
|
+
latest_tree_expansions: tree_man.TreeExpansions[NodeT],
|
|
57
|
+
) -> OpeningInstructions[NodeT]:
|
|
58
|
+
"""
|
|
59
|
+
Chooses a node to expand and determines the moves to open for that node.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
- tree (trees.Tree[AlgorithmNode]): The move and value tree.
|
|
63
|
+
- latest_tree_expansions (tree_man.TreeExpansions): The latest tree expansions.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
- OpeningInstructions: The opening instructions for the chosen node.
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
_ = latest_tree_expansions # not used here
|
|
70
|
+
|
|
71
|
+
opening_instructions_batch: OpeningInstructions[NodeT] = OpeningInstructions()
|
|
72
|
+
|
|
73
|
+
# generate the nodes to expand
|
|
74
|
+
current_half_move_to_expand = (
|
|
75
|
+
tree.tree_root_tree_depth + self.current_depth_to_expand
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# self.tree.descendants.print_info()
|
|
79
|
+
nodes_to_consider = list(tree.descendants[current_half_move_to_expand].values())
|
|
80
|
+
|
|
81
|
+
# filter the game-over ones and the ones with values
|
|
82
|
+
nodes_to_consider_not_over: list[NodeT] = [
|
|
83
|
+
node for node in nodes_to_consider if not node.is_over()
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# sort them by order of importance for the player
|
|
87
|
+
nodes_to_consider_sorted_by_value = sorted(
|
|
88
|
+
nodes_to_consider_not_over,
|
|
89
|
+
key=lambda x: tree.root_node.tree_evaluation.subjective_value_of(
|
|
90
|
+
x.tree_evaluation
|
|
91
|
+
),
|
|
92
|
+
) # best last
|
|
93
|
+
|
|
94
|
+
for node in nodes_to_consider_sorted_by_value:
|
|
95
|
+
all_branches_to_open: list[BranchKey] = (
|
|
96
|
+
self.opening_instructor.all_branches_to_open(node_to_open=node)
|
|
97
|
+
)
|
|
98
|
+
opening_instructions: OpeningInstructions[NodeT] = (
|
|
99
|
+
create_instructions_to_open_all_branches(
|
|
100
|
+
branches_to_play=all_branches_to_open, node_to_open=node
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
opening_instructions_batch.merge(opening_instructions)
|
|
104
|
+
|
|
105
|
+
self.current_depth_to_expand += 1
|
|
106
|
+
return opening_instructions_batch
|
|
107
|
+
|
|
108
|
+
def print_info(self) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Prints information about the Uniform Node selector.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
print("Uniform")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the implementation of tree nodes for move selection.
|
|
3
|
+
|
|
4
|
+
The tree nodes are used in the move selector to represent different moves and their values.
|
|
5
|
+
|
|
6
|
+
Classes:
|
|
7
|
+
- TreeNode: Represents a tree node for move selection.
|
|
8
|
+
- ITreeNode: Interface for tree nodes.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .itree_node import ITreeNode
|
|
13
|
+
from .tree_node import TreeNode
|
|
14
|
+
|
|
15
|
+
__all__ = ["TreeNode", "ITreeNode"]
|