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.
Files changed (82) hide show
  1. algorhino_anemone-0.1.1.dist-info/METADATA +151 -0
  2. algorhino_anemone-0.1.1.dist-info/RECORD +82 -0
  3. algorhino_anemone-0.1.1.dist-info/WHEEL +5 -0
  4. algorhino_anemone-0.1.1.dist-info/licenses/LICENSE +674 -0
  5. algorhino_anemone-0.1.1.dist-info/top_level.txt +1 -0
  6. anemone/__init__.py +27 -0
  7. anemone/basics.py +36 -0
  8. anemone/factory.py +161 -0
  9. anemone/indices/__init__.py +0 -0
  10. anemone/indices/index_manager/__init__.py +12 -0
  11. anemone/indices/index_manager/factory.py +50 -0
  12. anemone/indices/index_manager/node_exploration_manager.py +549 -0
  13. anemone/indices/node_indices/__init__.py +22 -0
  14. anemone/indices/node_indices/factory.py +121 -0
  15. anemone/indices/node_indices/index_data.py +166 -0
  16. anemone/indices/node_indices/index_types.py +20 -0
  17. anemone/nn/torch_evaluator.py +108 -0
  18. anemone/node_evaluation/__init__.py +0 -0
  19. anemone/node_evaluation/node_direct_evaluation/__init__.py +22 -0
  20. anemone/node_evaluation/node_direct_evaluation/factory.py +12 -0
  21. anemone/node_evaluation/node_direct_evaluation/node_direct_evaluator.py +192 -0
  22. anemone/node_evaluation/node_tree_evaluation/node_minmax_evaluation.py +885 -0
  23. anemone/node_evaluation/node_tree_evaluation/node_tree_evaluation.py +137 -0
  24. anemone/node_evaluation/node_tree_evaluation/node_tree_evaluation_factory.py +43 -0
  25. anemone/node_factory/__init__.py +14 -0
  26. anemone/node_factory/algorithm_node_factory.py +123 -0
  27. anemone/node_factory/base.py +76 -0
  28. anemone/node_selector/__init__.py +32 -0
  29. anemone/node_selector/branch_explorer.py +89 -0
  30. anemone/node_selector/factory.py +65 -0
  31. anemone/node_selector/node_selector.py +44 -0
  32. anemone/node_selector/node_selector_args.py +22 -0
  33. anemone/node_selector/node_selector_types.py +15 -0
  34. anemone/node_selector/notations_and_statics.py +88 -0
  35. anemone/node_selector/opening_instructions.py +249 -0
  36. anemone/node_selector/recurzipf/__init__.py +0 -0
  37. anemone/node_selector/recurzipf/recur_zipf_base.py +141 -0
  38. anemone/node_selector/sequool/__init__.py +19 -0
  39. anemone/node_selector/sequool/factory.py +102 -0
  40. anemone/node_selector/sequool/sequool.py +395 -0
  41. anemone/node_selector/uniform/__init__.py +16 -0
  42. anemone/node_selector/uniform/uniform.py +113 -0
  43. anemone/nodes/__init__.py +15 -0
  44. anemone/nodes/algorithm_node/__init__.py +7 -0
  45. anemone/nodes/algorithm_node/algorithm_node.py +204 -0
  46. anemone/nodes/itree_node.py +136 -0
  47. anemone/nodes/tree_node.py +240 -0
  48. anemone/nodes/tree_traversal.py +108 -0
  49. anemone/nodes/utils.py +146 -0
  50. anemone/progress_monitor/__init__.py +0 -0
  51. anemone/progress_monitor/progress_monitor.py +375 -0
  52. anemone/recommender_rule/__init__.py +12 -0
  53. anemone/recommender_rule/recommender_rule.py +140 -0
  54. anemone/search_factory/__init__.py +14 -0
  55. anemone/search_factory/search_factory.py +192 -0
  56. anemone/state_transition.py +47 -0
  57. anemone/tree_and_value_branch_selector.py +99 -0
  58. anemone/tree_exploration.py +274 -0
  59. anemone/tree_manager/__init__.py +29 -0
  60. anemone/tree_manager/algorithm_node_tree_manager.py +246 -0
  61. anemone/tree_manager/factory.py +77 -0
  62. anemone/tree_manager/tree_expander.py +122 -0
  63. anemone/tree_manager/tree_manager.py +254 -0
  64. anemone/trees/__init__.py +14 -0
  65. anemone/trees/descendants.py +765 -0
  66. anemone/trees/factory.py +80 -0
  67. anemone/trees/tree.py +70 -0
  68. anemone/trees/tree_visualization.py +143 -0
  69. anemone/updates/__init__.py +33 -0
  70. anemone/updates/algorithm_node_updater.py +157 -0
  71. anemone/updates/factory.py +36 -0
  72. anemone/updates/index_block.py +91 -0
  73. anemone/updates/index_updater.py +100 -0
  74. anemone/updates/minmax_evaluation_updater.py +108 -0
  75. anemone/updates/updates_file.py +248 -0
  76. anemone/updates/value_block.py +133 -0
  77. anemone/utils/comparable.py +32 -0
  78. anemone/utils/dataclass.py +64 -0
  79. anemone/utils/dict_of_numbered_dict_with_pointer_on_max.py +128 -0
  80. anemone/utils/logger.py +94 -0
  81. anemone/utils/my_value_sorted_dict.py +27 -0
  82. 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"]
@@ -0,0 +1,7 @@
1
+ """
2
+ init file for algorithm_node module
3
+ """
4
+
5
+ from .algorithm_node import AlgorithmNode
6
+
7
+ __all__ = ["AlgorithmNode"]