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,885 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the implementation of the NodeMinmaxEvaluation class, which represents a node in a tree structure
|
|
3
|
+
used for the Minimax algorithm evaluation.
|
|
4
|
+
|
|
5
|
+
The NodeMinmaxEvaluation class stores information about the evaluation of a tree node, including the estimated value
|
|
6
|
+
for the white player, the computed value using the Minimax procedure, the best node sequence, and the children of
|
|
7
|
+
the tree node sorted by their evaluations.
|
|
8
|
+
|
|
9
|
+
It also provides methods for accessing and manipulating the evaluation values, determining the subjective value from
|
|
10
|
+
the point of view of the player to branch, finding the best child node, checking if the node is over, and printing
|
|
11
|
+
information about the node.
|
|
12
|
+
|
|
13
|
+
Note: This code snippet is a partial implementation and may require additional code to work properly.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# todo maybe further split values from over?
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from math import log
|
|
20
|
+
from random import choice
|
|
21
|
+
from typing import Any, Protocol, Self, runtime_checkable
|
|
22
|
+
|
|
23
|
+
from valanga import (
|
|
24
|
+
BoardEvaluation,
|
|
25
|
+
BranchKey,
|
|
26
|
+
Color,
|
|
27
|
+
FloatyStateEvaluation,
|
|
28
|
+
ForcedOutcome,
|
|
29
|
+
OverEvent,
|
|
30
|
+
TurnState,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from anemone.nodes.itree_node import ITreeNode
|
|
34
|
+
from anemone.nodes.tree_node import TreeNode
|
|
35
|
+
from anemone.utils.logger import anemone_logger
|
|
36
|
+
from anemone.utils.my_value_sorted_dict import sort_dic
|
|
37
|
+
from anemone.utils.small_tools import nth_key
|
|
38
|
+
|
|
39
|
+
type BranchSortValue = tuple[float, int, int]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@runtime_checkable
|
|
43
|
+
# Class created to avoid circular import and defines what is seen and needed by the NodeMinmaxEvaluation class
|
|
44
|
+
class NodeWithValue(ITreeNode[TurnState], Protocol):
|
|
45
|
+
"""
|
|
46
|
+
Represents a node with a value in a tree structure.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
tree_evaluation (NodeMinmaxEvaluation): The minmax evaluation associated with the node.
|
|
50
|
+
tree_node (TreeNode[Self]): The tree node associated with the node.
|
|
51
|
+
|
|
52
|
+
Note: Uses Self to indicate that tree_node's children type should match the node itself.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
tree_evaluation: "NodeMinmaxEvaluation"
|
|
56
|
+
tree_node: TreeNode[Self, TurnState]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(slots=True)
|
|
60
|
+
class NodeMinmaxEvaluation[
|
|
61
|
+
NodeWithValueT: NodeWithValue = NodeWithValue,
|
|
62
|
+
StateT: TurnState = TurnState,
|
|
63
|
+
]:
|
|
64
|
+
r"""
|
|
65
|
+
Represents a node in a tree structure used for the Minimax algorithm evaluation.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
tree_node (TreeNode): A reference to the original tree node that is evaluated.
|
|
69
|
+
value_white_evaluator (float | None): The absolute value with respect to the white player as estimated
|
|
70
|
+
by an evaluator.
|
|
71
|
+
value_white_minmax (float | None): The absolute value with respect to the white player as computed from
|
|
72
|
+
the value_white_* of the descendants of this node (self) by a Minimax procedure.
|
|
73
|
+
best_node_sequence (list[ITreeNode]): The sequence of best nodes found during the Minimax evaluation.
|
|
74
|
+
children_sorted_by_value\_ (dict[ITreeNode, Any]): The children of the tree node kept in a dictionary
|
|
75
|
+
that can be sorted by their evaluations.
|
|
76
|
+
best_index_for_value (int): The index of the best value in the children_sorted_by_value dictionary.
|
|
77
|
+
children_not_over (list[ITreeNode]): The list of children that have not yet been found to be over.
|
|
78
|
+
over_event (OverEvent): The event that determines if the node is over.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# a reference to the original tree node that is evaluated
|
|
82
|
+
tree_node: TreeNode[NodeWithValueT, StateT]
|
|
83
|
+
|
|
84
|
+
# absolute value wrt to white player as estimated by a state evaluator
|
|
85
|
+
value_white_direct_evaluation: float | None = None
|
|
86
|
+
|
|
87
|
+
# absolute value wrt to white player as computed from the value_white_* of the descendants
|
|
88
|
+
# of this node (self) by a minmax procedure.
|
|
89
|
+
value_white_minmax: float | None = None
|
|
90
|
+
|
|
91
|
+
# the sequence of best branches from this node
|
|
92
|
+
best_branch_sequence: list[BranchKey] = field(
|
|
93
|
+
default_factory=lambda: list[BranchKey]()
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# the children of the tree node are kept in a dictionary that can be sorted by their evaluations ()
|
|
97
|
+
|
|
98
|
+
# children_sorted_by_value records subjective values of children by descending order
|
|
99
|
+
# subjective value means the values is from the point of view of player_to_branch
|
|
100
|
+
# careful, I have hard coded in the self.best_child() function the descending order for
|
|
101
|
+
# fast access to the best element, so please do not change!
|
|
102
|
+
# self.children_sorted_by_value_vsd = ValueSortedDict({})
|
|
103
|
+
branches_sorted_by_value_: dict[BranchKey, BranchSortValue] = field(
|
|
104
|
+
default_factory=lambda: dict[BranchKey, BranchSortValue]()
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# self.children_sorted_by_value = {}
|
|
108
|
+
|
|
109
|
+
# convention of descending order, careful if changing read above!!
|
|
110
|
+
best_index_for_value: int = 0
|
|
111
|
+
|
|
112
|
+
# the list of branches that have not yet be found to be over
|
|
113
|
+
# using atm a list instead of set as atm python set are not insertion ordered which adds randomness
|
|
114
|
+
# and makes debug harder
|
|
115
|
+
branches_not_over: list[BranchKey] = field(
|
|
116
|
+
default_factory=lambda: list[BranchKey]()
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# creating a base Over event that is set to None
|
|
120
|
+
over_event: OverEvent = field(default_factory=OverEvent)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def branches_sorted_by_value(self) -> dict[BranchKey, BranchSortValue]:
|
|
124
|
+
"""
|
|
125
|
+
Returns a dictionary containing the branches of the node sorted by their values.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
dict[BranchKey, BranchSortValue]: A dictionary where the keys are the branches in the node and
|
|
129
|
+
the values are the corresponding sort values.
|
|
130
|
+
"""
|
|
131
|
+
return self.branches_sorted_by_value_
|
|
132
|
+
|
|
133
|
+
def get_value_white(self) -> float:
|
|
134
|
+
"""Returns the best estimation of the value for white in this node.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
float: The best estimation of the value for white in this node.
|
|
138
|
+
"""
|
|
139
|
+
assert self.value_white_minmax is not None
|
|
140
|
+
return self.value_white_minmax
|
|
141
|
+
|
|
142
|
+
def set_evaluation(self, evaluation: float) -> None:
|
|
143
|
+
"""sets the evaluation from the board evaluator
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
evaluation (float): The evaluation value to be set.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
None
|
|
150
|
+
"""
|
|
151
|
+
self.value_white_direct_evaluation = evaluation
|
|
152
|
+
self.value_white_minmax = (
|
|
153
|
+
evaluation # base value before knowing values of the children
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def subjective_value_(self, value_white: float) -> float:
|
|
157
|
+
"""
|
|
158
|
+
Return the subjective value of `value_white` from the point of view of the `self.tree_node.player_to_branch`.
|
|
159
|
+
|
|
160
|
+
The subjective value is calculated based on the player to branch. If the player to branch is `Color.WHITE`, then the
|
|
161
|
+
`value_white` is returned as is. Otherwise, the negative of `value_white` is returned.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
value_white (float): The value from the point of view of the white player.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
float: The subjective value of `value_white` based on the player to branch.
|
|
168
|
+
"""
|
|
169
|
+
subjective_value = (
|
|
170
|
+
value_white if self.tree_node.state.turn is Color.WHITE else -value_white
|
|
171
|
+
)
|
|
172
|
+
return subjective_value
|
|
173
|
+
|
|
174
|
+
def subjective_value(self) -> float:
|
|
175
|
+
"""Return the subjective value of self.value_white from the point of view of the self.tree_node.player_to_branch.
|
|
176
|
+
|
|
177
|
+
If the player to branch is Color.WHITE, the subjective value is self.get_value_white().
|
|
178
|
+
If the player to branch is not Color.WHITE, the subjective value is -self.get_value_white().
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
float: The subjective value of self.value_white.
|
|
182
|
+
"""
|
|
183
|
+
subjective_value = (
|
|
184
|
+
self.get_value_white()
|
|
185
|
+
if self.tree_node.state.turn is Color.WHITE
|
|
186
|
+
else -self.get_value_white()
|
|
187
|
+
)
|
|
188
|
+
return subjective_value
|
|
189
|
+
|
|
190
|
+
def subjective_value_of(self, another_node_eval: Self) -> float:
|
|
191
|
+
"""
|
|
192
|
+
Calculates the subjective value of the current node evaluation based on the player to branch.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
another_node_eval (Self): The evaluation of another node.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
float: The subjective value of the current node evaluation.
|
|
199
|
+
"""
|
|
200
|
+
if self.tree_node.state.turn is Color.WHITE:
|
|
201
|
+
subjective_value = another_node_eval.get_value_white()
|
|
202
|
+
else:
|
|
203
|
+
subjective_value = -another_node_eval.get_value_white()
|
|
204
|
+
return subjective_value
|
|
205
|
+
|
|
206
|
+
def best_branch(self) -> BranchKey | None:
|
|
207
|
+
"""
|
|
208
|
+
Returns the best branch node based on the subjective value.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
The best branch based on the subjective value, or None if there are no branch open.
|
|
212
|
+
"""
|
|
213
|
+
best_branch: BranchKey | None
|
|
214
|
+
if self.branches_sorted_by_value:
|
|
215
|
+
best_branch = next(iter(self.branches_sorted_by_value))
|
|
216
|
+
else:
|
|
217
|
+
best_branch = None
|
|
218
|
+
return best_branch
|
|
219
|
+
|
|
220
|
+
def best_branch_not_over(self) -> BranchKey:
|
|
221
|
+
"""
|
|
222
|
+
Returns the best branch that is not leading to a game-over.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
The best branch that is not leading to a game-over.
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
Exception: If no branch is found that is not over.
|
|
229
|
+
"""
|
|
230
|
+
branch_key: BranchKey
|
|
231
|
+
for branch_key in self.branches_sorted_by_value:
|
|
232
|
+
child = self.tree_node.branches_children[branch_key]
|
|
233
|
+
assert child is not None
|
|
234
|
+
if not child.is_over():
|
|
235
|
+
return branch_key
|
|
236
|
+
raise Exception("Not ok")
|
|
237
|
+
|
|
238
|
+
def best_branch_value(self) -> BranchSortValue | None:
|
|
239
|
+
"""
|
|
240
|
+
Returns the value of the best branch.
|
|
241
|
+
|
|
242
|
+
If the `branches_sorted_by_value` dictionary is not empty, it returns the value of the first child node with
|
|
243
|
+
the highest subjective value. Otherwise, it returns None.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
BranchSortValue | None: The sort value of the best branch, or None if there are no opened branches.
|
|
247
|
+
"""
|
|
248
|
+
best_value: BranchSortValue | None
|
|
249
|
+
# fast way to access first key with the highest subjective value
|
|
250
|
+
if self.branches_sorted_by_value:
|
|
251
|
+
best_value = next(iter(self.branches_sorted_by_value.values()))
|
|
252
|
+
else:
|
|
253
|
+
best_value = None
|
|
254
|
+
return best_value
|
|
255
|
+
|
|
256
|
+
def second_best_branch(self) -> BranchKey:
|
|
257
|
+
"""
|
|
258
|
+
Returns the second-best branch based on the subjective value.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
The second-best branch.
|
|
262
|
+
"""
|
|
263
|
+
assert len(self.branches_sorted_by_value) >= 2
|
|
264
|
+
# fast way to access second key with the highest subjective value
|
|
265
|
+
second_best_branch: BranchKey = nth_key(self.branches_sorted_by_value, 1)
|
|
266
|
+
return second_best_branch
|
|
267
|
+
|
|
268
|
+
def is_over(self) -> bool:
|
|
269
|
+
"""
|
|
270
|
+
Checks if the game is over.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
bool: True if the game is over, False otherwise.
|
|
274
|
+
"""
|
|
275
|
+
return self.over_event.is_over()
|
|
276
|
+
|
|
277
|
+
def is_win(self) -> bool:
|
|
278
|
+
"""
|
|
279
|
+
Checks if the current game state is a win.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
bool: True if the game state is a win, False otherwise.
|
|
283
|
+
"""
|
|
284
|
+
return self.over_event.is_win()
|
|
285
|
+
|
|
286
|
+
def is_draw(self) -> bool:
|
|
287
|
+
"""
|
|
288
|
+
Checks if the current game state is a draw.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
bool: True if the game state is a draw, False otherwise.
|
|
292
|
+
"""
|
|
293
|
+
return self.over_event.is_draw()
|
|
294
|
+
|
|
295
|
+
def is_winner(self, player: Color) -> bool:
|
|
296
|
+
"""
|
|
297
|
+
Determines if the specified player is the winner.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
player (Color): The color of the player to check.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
bool: True if the player is the winner, False otherwise.
|
|
304
|
+
"""
|
|
305
|
+
return self.over_event.is_winner(player)
|
|
306
|
+
|
|
307
|
+
def print_branches_sorted_by_value(self) -> None:
|
|
308
|
+
"""
|
|
309
|
+
Prints the branches sorted by their subjective sort value.
|
|
310
|
+
|
|
311
|
+
The method iterates over the branch_sorted_by_value dictionary and prints each branch along with its
|
|
312
|
+
subjective sort value. The output is formatted as follows:
|
|
313
|
+
"<branch>: <subjective_sort_value> $$"
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
None
|
|
317
|
+
"""
|
|
318
|
+
print(
|
|
319
|
+
"here are the ",
|
|
320
|
+
len(self.branches_sorted_by_value),
|
|
321
|
+
" branch sorted by value: ",
|
|
322
|
+
)
|
|
323
|
+
branch_key: BranchKey
|
|
324
|
+
for branch_key, subjective_sort_value in self.branches_sorted_by_value.items():
|
|
325
|
+
print(
|
|
326
|
+
self.tree_node.state.branch_name_from_key(branch_key),
|
|
327
|
+
subjective_sort_value[0],
|
|
328
|
+
end=" $$ ",
|
|
329
|
+
)
|
|
330
|
+
print("")
|
|
331
|
+
|
|
332
|
+
def print_branches_sorted_by_value_and_exploration(self) -> None:
|
|
333
|
+
"""
|
|
334
|
+
Prints the branch of the node sorted by their value and exploration.
|
|
335
|
+
|
|
336
|
+
This method prints the branches of the node along with their subjective sort value.
|
|
337
|
+
The branches are sorted based on their value and exploration.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
None
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
None
|
|
344
|
+
"""
|
|
345
|
+
branch_key: BranchKey
|
|
346
|
+
anemone_logger.info(
|
|
347
|
+
f"here are the {len(self.branches_sorted_by_value)} branches sorted by value: "
|
|
348
|
+
)
|
|
349
|
+
string_info: str = ""
|
|
350
|
+
for branch_key, subjective_sort_value in self.branches_sorted_by_value.items():
|
|
351
|
+
string_info += f" {self.tree_node.state.branch_name_from_key(branch_key)} {subjective_sort_value[0]} $$ "
|
|
352
|
+
anemone_logger.info(string_info)
|
|
353
|
+
|
|
354
|
+
def print_branches_not_over(self) -> None:
|
|
355
|
+
"""
|
|
356
|
+
Prints the branches that are not over.
|
|
357
|
+
|
|
358
|
+
This method prints the branches that are not marked as 'over'.
|
|
359
|
+
It iterates over the `branches_not_over` list and prints each child's ID.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
None
|
|
363
|
+
"""
|
|
364
|
+
print(
|
|
365
|
+
"here are the ", len(self.branches_not_over), " branch not over: ", end=" "
|
|
366
|
+
)
|
|
367
|
+
for branch in self.branches_not_over:
|
|
368
|
+
print(branch, end=" ")
|
|
369
|
+
print(" ")
|
|
370
|
+
|
|
371
|
+
def print_info(self) -> None:
|
|
372
|
+
"""
|
|
373
|
+
Prints information about the node.
|
|
374
|
+
|
|
375
|
+
This method prints the ID of the node, the branches of its children, the children sorted by value,
|
|
376
|
+
and the children that are not over.
|
|
377
|
+
"""
|
|
378
|
+
print("Soy el Node", self.tree_node.id)
|
|
379
|
+
self.tree_node.print_branches_children()
|
|
380
|
+
self.print_branches_sorted_by_value()
|
|
381
|
+
self.print_branches_not_over()
|
|
382
|
+
# todo probably more to print...
|
|
383
|
+
|
|
384
|
+
def record_sort_value_of_child(self, branch_key: BranchKey) -> None:
|
|
385
|
+
"""Stores the subjective value of the branch in the self.branches_sorted_by_value (automatically sorted).
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
branch_key (BranchKey): The branch key whose value needs to be recorded.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
None
|
|
392
|
+
"""
|
|
393
|
+
# - branches_sorted_by_value records subjective value of branches by descending order
|
|
394
|
+
# therefore we have to convert the value_white of children into a subjective value that depends
|
|
395
|
+
# on the player to branch
|
|
396
|
+
# - subjective best branch/children is at index 0 however sortedValueDict are sorted ascending (best index: -1),
|
|
397
|
+
# therefore for white we have negative values
|
|
398
|
+
child = self.tree_node.branches_children[branch_key]
|
|
399
|
+
assert child is not None
|
|
400
|
+
child_value_white = child.tree_evaluation.get_value_white()
|
|
401
|
+
subjective_value_of_child = (
|
|
402
|
+
-child_value_white
|
|
403
|
+
if self.tree_node.state.turn is Color.WHITE
|
|
404
|
+
else child_value_white
|
|
405
|
+
)
|
|
406
|
+
if self.is_over():
|
|
407
|
+
# the shorter the check the better now
|
|
408
|
+
self.branches_sorted_by_value_[branch_key] = (
|
|
409
|
+
subjective_value_of_child,
|
|
410
|
+
-len(child.tree_evaluation.best_branch_sequence),
|
|
411
|
+
child.tree_node.id,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
else:
|
|
415
|
+
# the longer the line the better now
|
|
416
|
+
self.branches_sorted_by_value_[branch_key] = (
|
|
417
|
+
subjective_value_of_child,
|
|
418
|
+
len(child.tree_evaluation.best_branch_sequence),
|
|
419
|
+
child.tree_node.id,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
def are_equal_values[T](self, value_1: T, value_2: T) -> bool:
|
|
423
|
+
"""
|
|
424
|
+
Check if two values are equal.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
value_1 (T): The first value to compare.
|
|
428
|
+
value_2 (T): The second value to compare.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
bool: True if the values are equal, False otherwise.
|
|
432
|
+
"""
|
|
433
|
+
return value_1 == value_2
|
|
434
|
+
|
|
435
|
+
def are_considered_equal_values[T](
|
|
436
|
+
self, value_1: tuple[T, ...], value_2: tuple[T, ...]
|
|
437
|
+
) -> bool:
|
|
438
|
+
"""
|
|
439
|
+
Check if two values are considered equal.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
value_1 (tuple[T]): The first value to compare.
|
|
443
|
+
value_2 (tuple[T]): The second value to compare.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
bool: True if the values are considered equal, False otherwise.
|
|
447
|
+
"""
|
|
448
|
+
return value_1[:2] == value_2[:2]
|
|
449
|
+
|
|
450
|
+
def are_almost_equal_values(self, value_1: float, value_2: float) -> bool:
|
|
451
|
+
"""
|
|
452
|
+
Check if two float values are almost equal within a small epsilon.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
value_1 (float): The first value to compare.
|
|
456
|
+
value_2 (float): The second value to compare.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
bool: True if the values are almost equal, False otherwise.
|
|
460
|
+
"""
|
|
461
|
+
epsilon = 0.01
|
|
462
|
+
return value_1 > value_2 - epsilon and value_2 > value_1 - epsilon
|
|
463
|
+
|
|
464
|
+
def becoming_over_from_children(self) -> None:
|
|
465
|
+
"""This node is asked to switch to over status.
|
|
466
|
+
|
|
467
|
+
This method is called when the node is requested to switch to the "over" status. It performs the necessary
|
|
468
|
+
operations to update the node's status and determine the winner.
|
|
469
|
+
|
|
470
|
+
Raises:
|
|
471
|
+
AssertionError: If the node is already in the "over" status.
|
|
472
|
+
|
|
473
|
+
"""
|
|
474
|
+
assert not self.is_over()
|
|
475
|
+
|
|
476
|
+
# becoming over triggers a full update record_sort_value_of_child
|
|
477
|
+
# where ties are now broken to reach over as fast as possible
|
|
478
|
+
# todo we should reach it asap if we are winning and think about what to ddo in other scenarios....
|
|
479
|
+
branch_key: BranchKey
|
|
480
|
+
for branch_key in self.tree_node.branches_children:
|
|
481
|
+
self.record_sort_value_of_child(branch_key=branch_key)
|
|
482
|
+
|
|
483
|
+
# fast way to access first key with the highest subjective value
|
|
484
|
+
best_branch_key: BranchKey | None = self.best_branch()
|
|
485
|
+
assert best_branch_key is not None
|
|
486
|
+
best_child = self.tree_node.branches_children[best_branch_key]
|
|
487
|
+
assert best_child is not None
|
|
488
|
+
self.over_event.becomes_over(
|
|
489
|
+
how_over=best_child.tree_evaluation.over_event.how_over,
|
|
490
|
+
who_is_winner=best_child.tree_evaluation.over_event.who_is_winner,
|
|
491
|
+
termination=best_child.tree_evaluation.over_event.termination,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
def update_over(self, branches_with_updated_over: set[BranchKey]) -> bool:
|
|
495
|
+
"""
|
|
496
|
+
Update the over_event of the node based on notification of change of over_event in children.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
branches_with_updated_over (set[BranchKey]): A set of branch keys linking to the children
|
|
500
|
+
nodes that have been updated with their over_event.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
bool: True if the node has become newly over, False otherwise.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
is_newly_over = False
|
|
507
|
+
|
|
508
|
+
# Two cases can make this node (self) become over:
|
|
509
|
+
# 1. One of the children of this node is over and is a win for the node.player_to_branch.
|
|
510
|
+
# 2. All children are now over, then choose your best over event (choose draw if you can avoid a loss).
|
|
511
|
+
|
|
512
|
+
for branch in branches_with_updated_over:
|
|
513
|
+
child = self.tree_node.branches_children[branch]
|
|
514
|
+
assert child is not None
|
|
515
|
+
assert child.is_over()
|
|
516
|
+
if branch in self.branches_not_over:
|
|
517
|
+
self.branches_not_over.remove(branch)
|
|
518
|
+
|
|
519
|
+
# Check if child is already not in children_not_over.
|
|
520
|
+
if not self.is_over() and child.tree_evaluation.is_winner(
|
|
521
|
+
self.tree_node.state.turn
|
|
522
|
+
):
|
|
523
|
+
self.becoming_over_from_children()
|
|
524
|
+
is_newly_over = True
|
|
525
|
+
|
|
526
|
+
# Check if all children are over but not winning for self.tree_node.player_to_branch.
|
|
527
|
+
if not self.is_over() and not self.branches_not_over:
|
|
528
|
+
self.becoming_over_from_children()
|
|
529
|
+
is_newly_over = True
|
|
530
|
+
|
|
531
|
+
return is_newly_over
|
|
532
|
+
|
|
533
|
+
def update_branches_values(self, branches_to_consider: set[BranchKey]) -> None:
|
|
534
|
+
"""
|
|
535
|
+
Updates the values of the branches based on the given set of branches to consider.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
branches_to_consider (set[BranchKey]): The set of branches to consider.
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
None
|
|
542
|
+
"""
|
|
543
|
+
for branch_key in branches_to_consider:
|
|
544
|
+
self.record_sort_value_of_child(branch_key=branch_key)
|
|
545
|
+
self.branches_sorted_by_value_ = sort_dic(self.branches_sorted_by_value_)
|
|
546
|
+
|
|
547
|
+
def sort_branches_not_over(self) -> list[BranchKey]:
|
|
548
|
+
"""
|
|
549
|
+
Sorts the branches that are not over based on their value.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
A sorted list of branches that are not over.
|
|
553
|
+
"""
|
|
554
|
+
# todo: looks like the determinism of the sort induces some determinisin the play like always
|
|
555
|
+
# playing the same actions when a lot of them have equal value: introduce some randomness?
|
|
556
|
+
return [
|
|
557
|
+
branch
|
|
558
|
+
for branch in self.branches_sorted_by_value
|
|
559
|
+
if branch in self.branches_not_over
|
|
560
|
+
] # todo is this a fast way to do it?
|
|
561
|
+
|
|
562
|
+
def update_value_minmax(self) -> None:
|
|
563
|
+
"""
|
|
564
|
+
Updates the minmax value for the current node based on the best child node's evaluation.
|
|
565
|
+
|
|
566
|
+
If all the children of the current node have been evaluated, the minmax value is set to the best child's
|
|
567
|
+
evaluation value. Otherwise, if not all children have been evaluated, the minmax value is determined by
|
|
568
|
+
comparing the best child's evaluation value with the current node's own evaluation value.
|
|
569
|
+
|
|
570
|
+
Note: The evaluation values are specific to the white player.
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
None
|
|
574
|
+
"""
|
|
575
|
+
best_branch_key: BranchKey | None = self.best_branch()
|
|
576
|
+
assert best_branch_key is not None
|
|
577
|
+
best_child = self.tree_node.branches_children[best_branch_key]
|
|
578
|
+
assert best_child is not None
|
|
579
|
+
if self.tree_node.all_branches_generated:
|
|
580
|
+
self.value_white_minmax = best_child.tree_evaluation.get_value_white()
|
|
581
|
+
elif self.tree_node.state.turn is Color.WHITE:
|
|
582
|
+
assert self.value_white_direct_evaluation is not None
|
|
583
|
+
self.value_white_minmax = max(
|
|
584
|
+
best_child.tree_evaluation.get_value_white(),
|
|
585
|
+
self.value_white_direct_evaluation,
|
|
586
|
+
)
|
|
587
|
+
else:
|
|
588
|
+
assert self.value_white_direct_evaluation is not None
|
|
589
|
+
self.value_white_minmax = min(
|
|
590
|
+
best_child.tree_evaluation.get_value_white(),
|
|
591
|
+
self.value_white_direct_evaluation,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
def update_best_branch_sequence(
|
|
595
|
+
self, branches_with_updated_best_branch_seq: set[BranchKey]
|
|
596
|
+
) -> bool:
|
|
597
|
+
"""Updates the best branch sequence based on the notification from children nodes identified through their
|
|
598
|
+
corresponding branch.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
branches_with_updated_best_branch_seq (set[Ibranch]): A set of branch that have
|
|
602
|
+
notified an updated best-branch sequence.
|
|
603
|
+
|
|
604
|
+
Returns:
|
|
605
|
+
bool: True if self.best_branch_sequence is modified, False otherwise.
|
|
606
|
+
"""
|
|
607
|
+
has_best_branch_seq_changed: bool = False
|
|
608
|
+
best_branch_key: BranchKey = self.best_branch_sequence[0]
|
|
609
|
+
best_node: NodeWithValue | None = self.tree_node.branches_children[
|
|
610
|
+
best_branch_key
|
|
611
|
+
]
|
|
612
|
+
|
|
613
|
+
if (
|
|
614
|
+
best_branch_key in branches_with_updated_best_branch_seq
|
|
615
|
+
and best_node is not None
|
|
616
|
+
):
|
|
617
|
+
self.best_branch_sequence = [
|
|
618
|
+
best_branch_key
|
|
619
|
+
] + best_node.tree_evaluation.best_branch_sequence
|
|
620
|
+
has_best_branch_seq_changed = True
|
|
621
|
+
|
|
622
|
+
return has_best_branch_seq_changed
|
|
623
|
+
|
|
624
|
+
def one_of_best_children_becomes_best_next_node(self) -> None:
|
|
625
|
+
"""Triggered when the value of the current best branch does not match the best value.
|
|
626
|
+
|
|
627
|
+
This method selects one of the best children nodes as the next best node based on a specific condition.
|
|
628
|
+
It updates the `best_branch_sequence` attribute with the selected child node and its corresponding best branch sequence.
|
|
629
|
+
|
|
630
|
+
Raises:
|
|
631
|
+
AssertionError: If the number of best children is not equal to 1 when `how_equal_` is set to 'equal'.
|
|
632
|
+
AssertionError: If the selected best child is not an instance of `NodeWithValue`.
|
|
633
|
+
AssertionError: If the `best_node_sequence` attribute is empty after updating.
|
|
634
|
+
|
|
635
|
+
"""
|
|
636
|
+
how_equal_: str = "equal"
|
|
637
|
+
best_branches: list[BranchKey] = self.get_all_of_the_best_branches(
|
|
638
|
+
how_equal=how_equal_
|
|
639
|
+
)
|
|
640
|
+
if how_equal_ == "equal":
|
|
641
|
+
assert len(best_branches) == 1
|
|
642
|
+
best_branch_key = choice(best_branches)
|
|
643
|
+
best_child = self.tree_node.branches_children[best_branch_key]
|
|
644
|
+
# best_child = best_children[len(best_children) - 1] # for debug!!
|
|
645
|
+
assert best_child is not None
|
|
646
|
+
self.best_branch_sequence = [
|
|
647
|
+
best_branch_key
|
|
648
|
+
] + best_child.tree_evaluation.best_branch_sequence
|
|
649
|
+
assert self.best_branch_sequence
|
|
650
|
+
|
|
651
|
+
def is_value_subjectively_better_than_evaluation(self, value_white: float) -> bool:
|
|
652
|
+
"""
|
|
653
|
+
Checks if the given value_white is subjectively better than the value_white_evaluator.
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
value_white (float): The value to compare with the value_white_evaluator.
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
bool: True if the value_white is subjectively better than the value_white_evaluator, False otherwise.
|
|
660
|
+
"""
|
|
661
|
+
subjective_value = self.subjective_value_(value_white)
|
|
662
|
+
assert self.value_white_direct_evaluation is not None
|
|
663
|
+
return subjective_value >= self.value_white_direct_evaluation
|
|
664
|
+
|
|
665
|
+
def minmax_value_update_from_children(
|
|
666
|
+
self, branches_with_updated_value: set[BranchKey]
|
|
667
|
+
) -> tuple[bool, bool]:
|
|
668
|
+
"""
|
|
669
|
+
Updates the value and best branch of the node based on the updated values of its children.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
branches_with_updated_value (set[Ibranch]): A set of branches with updated values.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
tuple[bool, bool]: A tuple containing two boolean values indicating whether the value and best branch have
|
|
676
|
+
changed.
|
|
677
|
+
"""
|
|
678
|
+
|
|
679
|
+
# todo to be tested!!
|
|
680
|
+
|
|
681
|
+
# updates value
|
|
682
|
+
value_white_before_update = self.get_value_white()
|
|
683
|
+
|
|
684
|
+
best_branch_key_before_update: BranchKey | None = self.best_branch()
|
|
685
|
+
self.update_branches_values(branches_to_consider=branches_with_updated_value)
|
|
686
|
+
self.update_value_minmax()
|
|
687
|
+
|
|
688
|
+
value_white_after_update = self.get_value_white()
|
|
689
|
+
has_value_changed: bool = value_white_before_update != value_white_after_update
|
|
690
|
+
|
|
691
|
+
# # updates best_branch #todo maybe split in two functions but be careful one has to be done oft the other
|
|
692
|
+
if best_branch_key_before_update is None:
|
|
693
|
+
best_child_before_update_not_the_best_anymore = True
|
|
694
|
+
else:
|
|
695
|
+
# here we compare the values in the self.children_sorted_by_value which might include more
|
|
696
|
+
# than just the basic values #todo make that more clear at some point maybe even creating a value object
|
|
697
|
+
updated_value_of_best_child_before_update = self.branches_sorted_by_value[
|
|
698
|
+
best_branch_key_before_update
|
|
699
|
+
]
|
|
700
|
+
best_value_children_after = self.best_branch_value()
|
|
701
|
+
if best_value_children_after is None:
|
|
702
|
+
best_child_before_update_not_the_best_anymore = True
|
|
703
|
+
else:
|
|
704
|
+
best_child_before_update_not_the_best_anymore = (
|
|
705
|
+
not self.are_equal_values(
|
|
706
|
+
updated_value_of_best_child_before_update,
|
|
707
|
+
best_value_children_after,
|
|
708
|
+
)
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
best_branch_seq_before_update: list[BranchKey] = (
|
|
712
|
+
self.best_branch_sequence.copy()
|
|
713
|
+
)
|
|
714
|
+
if self.tree_node.all_branches_generated:
|
|
715
|
+
if best_child_before_update_not_the_best_anymore:
|
|
716
|
+
self.one_of_best_children_becomes_best_next_node()
|
|
717
|
+
else:
|
|
718
|
+
# we only consider a child as best if it is more promising than the evaluation of self
|
|
719
|
+
# in self.value_white_evaluator
|
|
720
|
+
assert best_branch_key_before_update is not None
|
|
721
|
+
best_child_before_update = self.tree_node.branches_children[
|
|
722
|
+
best_branch_key_before_update
|
|
723
|
+
]
|
|
724
|
+
assert best_child_before_update is not None
|
|
725
|
+
if self.is_value_subjectively_better_than_evaluation(
|
|
726
|
+
best_child_before_update.tree_evaluation.get_value_white()
|
|
727
|
+
):
|
|
728
|
+
self.one_of_best_children_becomes_best_next_node()
|
|
729
|
+
else:
|
|
730
|
+
self.best_branch_sequence = []
|
|
731
|
+
best_branch_seq_after_update = self.best_branch_sequence
|
|
732
|
+
has_best_node_seq_changed = (
|
|
733
|
+
best_branch_seq_before_update != best_branch_seq_after_update
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
return has_value_changed, has_best_node_seq_changed
|
|
737
|
+
|
|
738
|
+
def dot_description(self) -> str:
|
|
739
|
+
"""
|
|
740
|
+
Returns a string representation of the node's description in DOT format.
|
|
741
|
+
|
|
742
|
+
The description includes the values of `value_white_minmax` and `value_white_evaluator`,
|
|
743
|
+
as well as the best branch sequence and the over event tag.
|
|
744
|
+
|
|
745
|
+
Returns:
|
|
746
|
+
A string representation of the node's description in DOT format.
|
|
747
|
+
"""
|
|
748
|
+
value_mm = (
|
|
749
|
+
"{:.3f}".format(self.value_white_minmax)
|
|
750
|
+
if self.value_white_minmax is not None
|
|
751
|
+
else "None"
|
|
752
|
+
)
|
|
753
|
+
value_eval = (
|
|
754
|
+
"{:.3f}".format(self.value_white_direct_evaluation)
|
|
755
|
+
if self.value_white_direct_evaluation is not None
|
|
756
|
+
else "None"
|
|
757
|
+
)
|
|
758
|
+
return (
|
|
759
|
+
"\n wh_val_mm: "
|
|
760
|
+
+ value_mm
|
|
761
|
+
+ "\n wh_val_eval: "
|
|
762
|
+
+ value_eval
|
|
763
|
+
+ "\n branches*"
|
|
764
|
+
+ self.description_best_branch_sequence()
|
|
765
|
+
+ "\nover: "
|
|
766
|
+
+ self.over_event.get_over_tag()
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
def description_best_branch_sequence(self) -> str:
|
|
770
|
+
"""
|
|
771
|
+
Returns a string representation of the best branch sequence.
|
|
772
|
+
|
|
773
|
+
This method iterates over the best node sequence and constructs a string representation
|
|
774
|
+
of the branches in the sequence. Each branch is appended to the result string, separated by an underscore.
|
|
775
|
+
|
|
776
|
+
Returns:
|
|
777
|
+
A string representation of the best branch sequence.
|
|
778
|
+
"""
|
|
779
|
+
res = ""
|
|
780
|
+
branch_key: BranchKey
|
|
781
|
+
for branch_key in self.best_branch_sequence:
|
|
782
|
+
res += "_" + str(branch_key)
|
|
783
|
+
return res
|
|
784
|
+
|
|
785
|
+
def description_tree_visualizer_branch(self, child: ITreeNode[StateT]) -> str:
|
|
786
|
+
"""
|
|
787
|
+
Returns a string representation of the branch for the tree visualizer.
|
|
788
|
+
|
|
789
|
+
Parameters:
|
|
790
|
+
- child (Any): The child node representing the branch.
|
|
791
|
+
|
|
792
|
+
Returns:
|
|
793
|
+
- str: A string representation of the branch for the tree visualizer.
|
|
794
|
+
"""
|
|
795
|
+
return ""
|
|
796
|
+
|
|
797
|
+
def print_best_line(self) -> None:
|
|
798
|
+
"""
|
|
799
|
+
Prints the best line from the current node to the leaf node.
|
|
800
|
+
|
|
801
|
+
The best line is determined by following the sequence of child nodes with the highest values.
|
|
802
|
+
Each child node is printed along with its corresponding branch and node ID.
|
|
803
|
+
|
|
804
|
+
Returns:
|
|
805
|
+
None
|
|
806
|
+
"""
|
|
807
|
+
info_string: str = f"Best line from node {str(self.tree_node.id)}: "
|
|
808
|
+
minmax: Any = self
|
|
809
|
+
for branch in self.best_branch_sequence:
|
|
810
|
+
child = minmax.tree_node.branches_children[branch]
|
|
811
|
+
assert child is not None
|
|
812
|
+
info_string += f"{branch} ({str(child.tree_node.id)}) "
|
|
813
|
+
minmax = child.tree_evaluation
|
|
814
|
+
anemone_logger.info(info_string)
|
|
815
|
+
|
|
816
|
+
def my_logit(self, x: float) -> float:
|
|
817
|
+
"""
|
|
818
|
+
Applies the logit function to the input value.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
x (float): The input value.
|
|
822
|
+
|
|
823
|
+
Returns:
|
|
824
|
+
float: The result of applying the logit function to the input value.
|
|
825
|
+
"""
|
|
826
|
+
y = min(max(x, 0.000000000000000000000001), 0.9999999999999999)
|
|
827
|
+
return log(y / (1 - y)) * max(
|
|
828
|
+
1, abs(x)
|
|
829
|
+
) # the * min(1,x) is a hack to prioritize game over
|
|
830
|
+
|
|
831
|
+
def get_all_of_the_best_branches(
|
|
832
|
+
self, how_equal: str | None = None
|
|
833
|
+
) -> list[BranchKey]:
|
|
834
|
+
"""
|
|
835
|
+
Returns a list of all the best branches based on the specified equality criteria.
|
|
836
|
+
|
|
837
|
+
Args:
|
|
838
|
+
how_equal (str | None): The equality criteria to determine the best branches.
|
|
839
|
+
Possible values are 'equal', 'considered_equal', 'almost_equal', 'almost_equal_logistic'.
|
|
840
|
+
Defaults to None.
|
|
841
|
+
|
|
842
|
+
Returns:
|
|
843
|
+
list[Ibranch]: A list of Ibranch representing the best branches.
|
|
844
|
+
|
|
845
|
+
"""
|
|
846
|
+
best_branches: list[BranchKey] = []
|
|
847
|
+
best_branch: BranchKey | None = self.best_branch()
|
|
848
|
+
assert best_branch is not None
|
|
849
|
+
best_value = self.branches_sorted_by_value[best_branch]
|
|
850
|
+
branch_key: BranchKey
|
|
851
|
+
for branch_key in self.branches_sorted_by_value:
|
|
852
|
+
if how_equal == "equal":
|
|
853
|
+
if self.are_equal_values(
|
|
854
|
+
self.branches_sorted_by_value[branch_key], best_value
|
|
855
|
+
):
|
|
856
|
+
best_branches.append(branch_key)
|
|
857
|
+
assert len(best_branches) == 1
|
|
858
|
+
elif how_equal == "considered_equal":
|
|
859
|
+
if self.are_considered_equal_values(
|
|
860
|
+
self.branches_sorted_by_value[branch_key], best_value
|
|
861
|
+
):
|
|
862
|
+
best_branches.append(branch_key)
|
|
863
|
+
elif how_equal == "almost_equal":
|
|
864
|
+
if self.are_almost_equal_values(
|
|
865
|
+
self.branches_sorted_by_value[branch_key][0], best_value[0]
|
|
866
|
+
):
|
|
867
|
+
best_branches.append(branch_key)
|
|
868
|
+
elif how_equal == "almost_equal_logistic":
|
|
869
|
+
best_value_logit = self.my_logit(best_value[0] * 0.5 + 0.5)
|
|
870
|
+
child_value_logit = self.my_logit(
|
|
871
|
+
self.branches_sorted_by_value[branch_key][0] * 0.5 + 0.5
|
|
872
|
+
)
|
|
873
|
+
if self.are_almost_equal_values(child_value_logit, best_value_logit):
|
|
874
|
+
best_branches.append(branch_key)
|
|
875
|
+
return best_branches
|
|
876
|
+
|
|
877
|
+
def evaluate(self) -> BoardEvaluation:
|
|
878
|
+
"""Build a BoardEvaluation from current minmax state."""
|
|
879
|
+
if self.over_event.is_over():
|
|
880
|
+
return ForcedOutcome(
|
|
881
|
+
outcome=self.over_event,
|
|
882
|
+
line=[branch_key for branch_key in self.best_branch_sequence],
|
|
883
|
+
)
|
|
884
|
+
else:
|
|
885
|
+
return FloatyStateEvaluation(value_white=self.value_white_minmax)
|