treequest 0.3.0__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.
- treequest/__init__.py +33 -0
- treequest/algos/ab_mcts_a/algo.py +390 -0
- treequest/algos/ab_mcts_a/prob_state.py +472 -0
- treequest/algos/ab_mcts_m/_ab_mcts_m_imports.py +29 -0
- treequest/algos/ab_mcts_m/algo.py +311 -0
- treequest/algos/ab_mcts_m/numpyro_utils.py +13 -0
- treequest/algos/ab_mcts_m/pymc_interface.py +635 -0
- treequest/algos/base.py +73 -0
- treequest/algos/best_first_search.py +200 -0
- treequest/algos/multi_armed_bandit_ucb.py +184 -0
- treequest/algos/standard_mcts.py +266 -0
- treequest/algos/tree.py +131 -0
- treequest/algos/tree_of_thought_bfs.py +237 -0
- treequest/imports.py +97 -0
- treequest/py.typed +0 -0
- treequest/ranker.py +29 -0
- treequest/trial.py +238 -0
- treequest/types.py +21 -0
- treequest/vis/__init__.py +82 -0
- treequest/vis/algo_adapters/__init__.py +68 -0
- treequest/vis/algo_adapters/ab_mcts_a.py +116 -0
- treequest/vis/algo_adapters/ab_mcts_m.py +78 -0
- treequest/vis/algo_adapters/base.py +39 -0
- treequest/vis/algo_adapters/best_first_search.py +52 -0
- treequest/vis/algo_adapters/multi_armed_bandit_ucb.py +99 -0
- treequest/vis/algo_adapters/standard_mcts.py +72 -0
- treequest/vis/algo_adapters/tree_of_thoughts_bfs.py +25 -0
- treequest/vis/assets/colormaps.json +1 -0
- treequest/vis/assets/d3.LICENSE.txt +13 -0
- treequest/vis/assets/d3.v7.min.js +2 -0
- treequest/vis/assets/d3_tree.html.jinja2 +1383 -0
- treequest/vis/build_snapshot.py +249 -0
- treequest/vis/errors.py +31 -0
- treequest/vis/render.py +112 -0
- treequest/vis/renderers/__init__.py +27 -0
- treequest/vis/renderers/color_utils.py +265 -0
- treequest/vis/renderers/graphviz_renderer.py +140 -0
- treequest/vis/renderers/html.py +157 -0
- treequest/vis/renderers/json_yaml.py +107 -0
- treequest/vis/renderers/mermaid.py +124 -0
- treequest/vis/snapshot.py +121 -0
- treequest/visualization.py +105 -0
- treequest-0.3.0.dist-info/METADATA +355 -0
- treequest-0.3.0.dist-info/RECORD +46 -0
- treequest-0.3.0.dist-info/WHEEL +4 -0
- treequest-0.3.0.dist-info/licenses/LICENSE +201 -0
treequest/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from .algos.ab_mcts_m._ab_mcts_m_imports import _import as _ab_mcts_m_import
|
|
2
|
+
|
|
3
|
+
if not _ab_mcts_m_import.is_successful():
|
|
4
|
+
# Create a placeholder that raises an informative error when accessed
|
|
5
|
+
class _ABMCTSMPlaceholder:
|
|
6
|
+
def __getattr__(self, name): # type: ignore
|
|
7
|
+
_ab_mcts_m_import.check()
|
|
8
|
+
raise ImportError("ABMCTSM import failed.")
|
|
9
|
+
|
|
10
|
+
def __call__(self, *args, **kwargs): # type: ignore
|
|
11
|
+
_ab_mcts_m_import.check()
|
|
12
|
+
raise ImportError("ABMCTSM import failed.")
|
|
13
|
+
|
|
14
|
+
ABMCTSM = _ABMCTSMPlaceholder() # type: ignore
|
|
15
|
+
else:
|
|
16
|
+
from .algos.ab_mcts_m.algo import ABMCTSM # type: ignore[assignment]
|
|
17
|
+
|
|
18
|
+
from .algos.ab_mcts_a.algo import ABMCTSA
|
|
19
|
+
from .algos.base import Algorithm
|
|
20
|
+
from .algos.standard_mcts import StandardMCTS
|
|
21
|
+
from .algos.tree_of_thought_bfs import TreeOfThoughtsBFSAlgo
|
|
22
|
+
from .ranker import top_k
|
|
23
|
+
from .vis import render
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"StandardMCTS",
|
|
27
|
+
"top_k",
|
|
28
|
+
"TreeOfThoughtsBFSAlgo",
|
|
29
|
+
"ABMCTSA",
|
|
30
|
+
"ABMCTSM",
|
|
31
|
+
"Algorithm",
|
|
32
|
+
"render",
|
|
33
|
+
]
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from logging import getLogger
|
|
6
|
+
from typing import Dict, Generic, List, Literal, Optional, Tuple, TypeVar, Union
|
|
7
|
+
|
|
8
|
+
from treequest.algos.ab_mcts_a.prob_state import NodeProbState, PriorConfig
|
|
9
|
+
from treequest.algos.base import Algorithm
|
|
10
|
+
from treequest.algos.tree import Node, Tree
|
|
11
|
+
from treequest.trial import Trial, TrialStore
|
|
12
|
+
from treequest.types import GenerateFnType, StateScoreType, TrialId
|
|
13
|
+
|
|
14
|
+
# Type variable for state
|
|
15
|
+
StateT = TypeVar("StateT")
|
|
16
|
+
|
|
17
|
+
logger = getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ABMCTSAStateManager(Generic[StateT]):
|
|
22
|
+
"""Manager for ABMCTSAState instances associated with expand_idx values."""
|
|
23
|
+
|
|
24
|
+
states: Dict[int, NodeProbState] = field(default_factory=dict[int, NodeProbState])
|
|
25
|
+
default_prior_config: Optional["PriorConfig"] = None
|
|
26
|
+
default_reward_average_priors: Optional[Union[float, Dict[str, float]]] = None
|
|
27
|
+
default_model_selection_strategy: str = "multiarm_bandit_thompson"
|
|
28
|
+
|
|
29
|
+
def __contains__(self, expand_idx: int) -> bool:
|
|
30
|
+
"""Check if a thompson state exists for the given expand_idx."""
|
|
31
|
+
return expand_idx in self.states
|
|
32
|
+
|
|
33
|
+
def __len__(self) -> int:
|
|
34
|
+
return len(self.states)
|
|
35
|
+
|
|
36
|
+
def get(self, node: Node[StateT]) -> Optional[NodeProbState]:
|
|
37
|
+
"""Get thompson state for the given expand_idx if it exists."""
|
|
38
|
+
return self.states.get(node.expand_idx)
|
|
39
|
+
|
|
40
|
+
def create(
|
|
41
|
+
self,
|
|
42
|
+
node: Node[StateT],
|
|
43
|
+
actions: List[str],
|
|
44
|
+
prior_config: Optional["PriorConfig"] = None,
|
|
45
|
+
reward_average_priors: Optional[Union[float, Dict[str, float]]] = None,
|
|
46
|
+
model_selection_strategy: Optional[str] = None,
|
|
47
|
+
) -> NodeProbState:
|
|
48
|
+
"""Create a new thompson state for the given expand_idx with optional prior configuration."""
|
|
49
|
+
# Use provided configs or fall back to defaults
|
|
50
|
+
prior_config = prior_config or self.default_prior_config
|
|
51
|
+
reward_average_priors = (
|
|
52
|
+
reward_average_priors or self.default_reward_average_priors
|
|
53
|
+
)
|
|
54
|
+
model_selection_strategy = (
|
|
55
|
+
model_selection_strategy or self.default_model_selection_strategy
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
state = NodeProbState(
|
|
59
|
+
actions=actions,
|
|
60
|
+
prior_config=prior_config,
|
|
61
|
+
reward_average_priors=reward_average_priors,
|
|
62
|
+
model_selection_strategy=model_selection_strategy,
|
|
63
|
+
)
|
|
64
|
+
self.states[node.expand_idx] = state
|
|
65
|
+
return state
|
|
66
|
+
|
|
67
|
+
def get_or_create(
|
|
68
|
+
self,
|
|
69
|
+
node: Node[StateT],
|
|
70
|
+
actions: List[str],
|
|
71
|
+
prior_config: Optional["PriorConfig"] = None,
|
|
72
|
+
reward_average_priors: Optional[Union[float, Dict[str, float]]] = None,
|
|
73
|
+
model_selection_strategy: Optional[str] = None,
|
|
74
|
+
) -> NodeProbState:
|
|
75
|
+
"""Get existing thompson state or create a new one if it doesn't exist."""
|
|
76
|
+
if node.expand_idx in self.states:
|
|
77
|
+
return self.states[node.expand_idx]
|
|
78
|
+
return self.create(
|
|
79
|
+
node,
|
|
80
|
+
actions,
|
|
81
|
+
prior_config=prior_config,
|
|
82
|
+
reward_average_priors=reward_average_priors,
|
|
83
|
+
model_selection_strategy=model_selection_strategy,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def build_default_dict_of_list() -> Dict[str, List[float]]:
|
|
88
|
+
"""
|
|
89
|
+
For pickle we define it at the top-module level as named function.
|
|
90
|
+
Ref: https://docs.python.org/3.13/library/pickle.html#what-can-be-pickled-and-unpickled
|
|
91
|
+
"""
|
|
92
|
+
return defaultdict(list)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class ABMCTSAAlgoState(Generic[StateT]):
|
|
97
|
+
"""State for ABMCTSA algorithm."""
|
|
98
|
+
|
|
99
|
+
tree: Tree[StateT]
|
|
100
|
+
thompson_states: ABMCTSAStateManager[StateT] = field(
|
|
101
|
+
default_factory=ABMCTSAStateManager[StateT]
|
|
102
|
+
)
|
|
103
|
+
all_rewards_store: Dict[str, List[float]] = field(
|
|
104
|
+
default_factory=build_default_dict_of_list
|
|
105
|
+
)
|
|
106
|
+
trial_store: TrialStore[StateT] = field(default_factory=TrialStore[StateT])
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ABMCTSA(Algorithm[StateT, ABMCTSAAlgoState[StateT]]):
|
|
110
|
+
"""
|
|
111
|
+
Adaptive Monte Carlo Tree Search algorithm with Node Aggregation.
|
|
112
|
+
|
|
113
|
+
This algorithm uses Thompson Sampling to decide whether to generate a new node (GEN)
|
|
114
|
+
or continue exploring from an existing one (CONT).
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
dist_type: Literal["gaussian", "beta"] = "gaussian",
|
|
120
|
+
reward_average_priors: Optional[Union[float, Dict[str, float]]] = None,
|
|
121
|
+
prior_config: Optional[PriorConfig] = None,
|
|
122
|
+
model_selection_strategy: str = "multiarm_bandit_thompson",
|
|
123
|
+
):
|
|
124
|
+
"""
|
|
125
|
+
Initialize the AB-MCTS-A algorithm.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
dist_type: Type of Probability distribution. Either "gaussian" or "beta".
|
|
129
|
+
reward_average_priors: Optional prior reward average values (global or per-action)
|
|
130
|
+
prior_config: Optional prior configuration for Thompson sampling distributions. If specified, dist_type will not be used.
|
|
131
|
+
model_selection_strategy: Strategy for model selection. One of:
|
|
132
|
+
- "stack": Perform separate fits for each model (traditional approach)
|
|
133
|
+
- "multiarm_bandit_thompson": Use Thompson Sampling for joint selection
|
|
134
|
+
- "multiarm_bandit_ucb": Use UCB for joint selection
|
|
135
|
+
"""
|
|
136
|
+
if prior_config is None:
|
|
137
|
+
prior_config = PriorConfig(dist_type=dist_type)
|
|
138
|
+
else:
|
|
139
|
+
if dist_type != prior_config.dist_type:
|
|
140
|
+
logger.warning(
|
|
141
|
+
f"dist_type argument {dist_type} is different from the one specified by prior_config {prior_config.dist_type}. {dist_type} is ignored, and {prior_config.dist_type} will be used."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
self.prior_config = prior_config
|
|
145
|
+
self.reward_average_priors = reward_average_priors
|
|
146
|
+
|
|
147
|
+
# Strategy for model selection:
|
|
148
|
+
# "stack": Perform separate fits for each model (traditional approach)
|
|
149
|
+
# "multiarm_bandit_thompson": Use Thompson Sampling for joint selection
|
|
150
|
+
# "multiarm_bandit_ucb": Use UCB for joint selection
|
|
151
|
+
if model_selection_strategy not in [
|
|
152
|
+
"stack",
|
|
153
|
+
"multiarm_bandit_thompson",
|
|
154
|
+
"multiarm_bandit_ucb",
|
|
155
|
+
]:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"Invalid model_selection_strategy: {model_selection_strategy}. "
|
|
158
|
+
f"Must be one of: 'stack', 'multiarm_bandit_thompson', 'multiarm_bandit_ucb'"
|
|
159
|
+
)
|
|
160
|
+
self.model_selection_strategy = model_selection_strategy
|
|
161
|
+
|
|
162
|
+
def init_tree(self) -> ABMCTSAAlgoState[StateT]:
|
|
163
|
+
"""
|
|
164
|
+
Initialize the algorithm state with an empty tree.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Initial algorithm state
|
|
168
|
+
"""
|
|
169
|
+
tree: Tree[StateT] = Tree.with_root_node()
|
|
170
|
+
state_manager = ABMCTSAStateManager[StateT](
|
|
171
|
+
default_prior_config=self.prior_config,
|
|
172
|
+
default_reward_average_priors=self.reward_average_priors,
|
|
173
|
+
default_model_selection_strategy=self.model_selection_strategy,
|
|
174
|
+
)
|
|
175
|
+
return ABMCTSAAlgoState(tree=tree, thompson_states=state_manager)
|
|
176
|
+
|
|
177
|
+
def step(
|
|
178
|
+
self,
|
|
179
|
+
state: ABMCTSAAlgoState[StateT],
|
|
180
|
+
generate_fn: Mapping[str, GenerateFnType[StateT]],
|
|
181
|
+
inplace: bool = False,
|
|
182
|
+
) -> ABMCTSAAlgoState[StateT]:
|
|
183
|
+
"""
|
|
184
|
+
Perform one step of the AB-MCTS-A algorithm and generate a new node.
|
|
185
|
+
"""
|
|
186
|
+
if not inplace:
|
|
187
|
+
state = copy.deepcopy(state)
|
|
188
|
+
|
|
189
|
+
actions = list(generate_fn.keys())
|
|
190
|
+
state, trial = self.ask(state, actions)
|
|
191
|
+
|
|
192
|
+
action = trial.action
|
|
193
|
+
node = state.tree.get_node(trial.node_to_expand)
|
|
194
|
+
result = generate_fn[action](node.state)
|
|
195
|
+
|
|
196
|
+
self.tell(state, trial.trial_id, result)
|
|
197
|
+
return state
|
|
198
|
+
|
|
199
|
+
def _get_expand_node_and_action(
|
|
200
|
+
self,
|
|
201
|
+
state: ABMCTSAAlgoState[StateT],
|
|
202
|
+
actions: list[str],
|
|
203
|
+
) -> tuple[Node[StateT], str]:
|
|
204
|
+
# If the tree is empty (only root), expand the root
|
|
205
|
+
if not state.tree.root.children:
|
|
206
|
+
return state.tree.root, self._get_generation_action(
|
|
207
|
+
state, state.tree.root, actions
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Run one simulation step
|
|
211
|
+
node = state.tree.root
|
|
212
|
+
|
|
213
|
+
# Selection phase: traverse tree until we reach a leaf node or need to create a new node
|
|
214
|
+
while node.children:
|
|
215
|
+
node, action = self._select_child(state, node, actions)
|
|
216
|
+
|
|
217
|
+
# If action is not None, we will generate a new node from `node``
|
|
218
|
+
if action is not None:
|
|
219
|
+
return node, action
|
|
220
|
+
action = self._get_generation_action(state, node, actions)
|
|
221
|
+
return node, action
|
|
222
|
+
|
|
223
|
+
def _select_child(
|
|
224
|
+
self,
|
|
225
|
+
state: ABMCTSAAlgoState[StateT],
|
|
226
|
+
node: Node[StateT],
|
|
227
|
+
actions: list[str],
|
|
228
|
+
) -> Tuple[Node[StateT], Optional[str]]:
|
|
229
|
+
"""
|
|
230
|
+
Select a child node using Thompson Sampling.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
state: Current algorithm state
|
|
234
|
+
node: Node to select child from
|
|
235
|
+
generate_fn: Mapping of action names to generation functions
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Tuple of (selected node, action if new node was generated)
|
|
239
|
+
"""
|
|
240
|
+
# Get or create thompson state for this node
|
|
241
|
+
thompson_state = state.thompson_states.get_or_create(
|
|
242
|
+
node,
|
|
243
|
+
actions,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Ask for next node or action using Thompson Sampling
|
|
247
|
+
selection = thompson_state.select_next(state.all_rewards_store)
|
|
248
|
+
|
|
249
|
+
# If string returned, we need to generate a new node with that action
|
|
250
|
+
if isinstance(selection, str):
|
|
251
|
+
return node, selection
|
|
252
|
+
else:
|
|
253
|
+
# Otherwise, we return the existing child with that index
|
|
254
|
+
if selection >= len(node.children):
|
|
255
|
+
raise RuntimeError(
|
|
256
|
+
f"Something went wrong in ABMCTSA algorithm: selected index {selection} is out of bounds."
|
|
257
|
+
)
|
|
258
|
+
return node.children[selection], None
|
|
259
|
+
|
|
260
|
+
def _get_generation_action(
|
|
261
|
+
self, state: ABMCTSAAlgoState[StateT], node: Node[StateT], actions: list[str]
|
|
262
|
+
) -> str:
|
|
263
|
+
# Create thompson state for this node if it doesn't exist
|
|
264
|
+
thompson_state = state.thompson_states.get_or_create(
|
|
265
|
+
node,
|
|
266
|
+
actions,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Get action to use for generating child
|
|
270
|
+
selection = thompson_state.select_next(state.all_rewards_store)
|
|
271
|
+
|
|
272
|
+
# Ensure we get a string action name, not an index
|
|
273
|
+
if not isinstance(selection, str):
|
|
274
|
+
raise RuntimeError(
|
|
275
|
+
f"Something went wrong in ABMCTSA algorithm: selection should always be str when the expansion is from the leaf node, whle got {selection}"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return selection
|
|
279
|
+
|
|
280
|
+
def _backpropagate(
|
|
281
|
+
self,
|
|
282
|
+
state: ABMCTSAAlgoState[StateT],
|
|
283
|
+
node: Node[StateT],
|
|
284
|
+
score: float,
|
|
285
|
+
action: str,
|
|
286
|
+
) -> None:
|
|
287
|
+
"""
|
|
288
|
+
Update Thompson Sampling statistics for all nodes in the path from node to root.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
state: Current algorithm state
|
|
292
|
+
node: Leaf node to start backpropagation from
|
|
293
|
+
score: Score to backpropagate
|
|
294
|
+
action: The action which have generated node
|
|
295
|
+
"""
|
|
296
|
+
# Update all_rewards_score for multiarm bandit use
|
|
297
|
+
state.all_rewards_store[action].append(score)
|
|
298
|
+
|
|
299
|
+
# NOTE: For the newly created node, we always update the score for GEN node
|
|
300
|
+
assert node.parent is not None
|
|
301
|
+
thompson_state = state.thompson_states.get(node.parent)
|
|
302
|
+
if thompson_state is None:
|
|
303
|
+
raise RuntimeError(
|
|
304
|
+
"Internal Error in ABMCTSA: ThompsonState should have been already initialized"
|
|
305
|
+
)
|
|
306
|
+
thompson_state.update_action_reward(action=action, reward=score)
|
|
307
|
+
|
|
308
|
+
current: Optional[Node] = node.parent
|
|
309
|
+
while current is not None and current.parent is not None:
|
|
310
|
+
thompson_state = state.thompson_states.get(current.parent)
|
|
311
|
+
if thompson_state is None:
|
|
312
|
+
raise RuntimeError(
|
|
313
|
+
"Internal Error in ABMCTSA: ThompsonState should have been already initialized"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Update the Thompson state with the score
|
|
317
|
+
thompson_state.update_node_reward(current, score)
|
|
318
|
+
|
|
319
|
+
# Move up to the parent
|
|
320
|
+
current = current.parent
|
|
321
|
+
|
|
322
|
+
def get_state_score_pairs(
|
|
323
|
+
self, state: ABMCTSAAlgoState[StateT]
|
|
324
|
+
) -> List[StateScoreType[StateT]]:
|
|
325
|
+
"""
|
|
326
|
+
Get all the state-score pairs from the tree.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
state: Current algorithm state
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
List of (state, score) pairs
|
|
333
|
+
"""
|
|
334
|
+
return state.tree.get_state_score_pairs()
|
|
335
|
+
|
|
336
|
+
def ask_batch(
|
|
337
|
+
self, state: ABMCTSAAlgoState[StateT], batch_size: int, actions: list[str]
|
|
338
|
+
) -> tuple[ABMCTSAAlgoState[StateT], list[Trial[StateT]]]:
|
|
339
|
+
"""
|
|
340
|
+
ABMCTSA is lightweight, so we don't parallelize it.
|
|
341
|
+
TODO: If we need to optimize it, ProcessPoolExecutor seems suffice
|
|
342
|
+
"""
|
|
343
|
+
# initialize all_rewards_store
|
|
344
|
+
if len(state.all_rewards_store) == 0:
|
|
345
|
+
for a in actions:
|
|
346
|
+
state.all_rewards_store[a] = []
|
|
347
|
+
|
|
348
|
+
trials: list[Trial[StateT]] = []
|
|
349
|
+
for _ in range(batch_size):
|
|
350
|
+
node, action = self._get_expand_node_and_action(state, actions)
|
|
351
|
+
trials.append(state.trial_store.create_trial(node, action))
|
|
352
|
+
|
|
353
|
+
return state, trials
|
|
354
|
+
|
|
355
|
+
def tell(
|
|
356
|
+
self,
|
|
357
|
+
state: ABMCTSAAlgoState[StateT],
|
|
358
|
+
trial_id: TrialId,
|
|
359
|
+
result: tuple[StateT, float],
|
|
360
|
+
) -> ABMCTSAAlgoState[StateT]:
|
|
361
|
+
_new_state, new_score = result
|
|
362
|
+
|
|
363
|
+
finished_trial = state.trial_store.get_finished_trial(trial_id, new_score)
|
|
364
|
+
if (
|
|
365
|
+
finished_trial is None
|
|
366
|
+
): # Trial is no longer valid, so we do not reflect the result to state
|
|
367
|
+
return state
|
|
368
|
+
|
|
369
|
+
parent_node = state.tree.get_node(finished_trial.node_to_expand)
|
|
370
|
+
action = finished_trial.action
|
|
371
|
+
# Add new node to the tree
|
|
372
|
+
new_node = state.tree.add_node(
|
|
373
|
+
result, parent_node, trial_id=finished_trial.trial_id
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Update Thompson state with the new node
|
|
377
|
+
thompson_state = state.thompson_states.get(parent_node)
|
|
378
|
+
if thompson_state:
|
|
379
|
+
thompson_state.register_new_child_node(
|
|
380
|
+
action, new_node, self.model_selection_strategy
|
|
381
|
+
)
|
|
382
|
+
else:
|
|
383
|
+
raise RuntimeError(
|
|
384
|
+
f"Internal Error in ABMCTSA: thompson_state should not be None for node {parent_node}"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# Backpropagate the score through the parents
|
|
388
|
+
self._backpropagate(state, new_node, new_score, action)
|
|
389
|
+
|
|
390
|
+
return state
|