330 lines
10 KiB
Julia
330 lines
10 KiB
Julia
module interface
|
|
|
|
export runMCTS
|
|
|
|
using Base.Threads, PrettyPrinting
|
|
using ..type, ..mcts, ..util
|
|
|
|
|
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
|
|
|
|
""" Search for the best action to take for a given state and task.
|
|
|
|
This function runs the MCTS algorithm through multiple iterations of expansion,
|
|
simulation, and backpropagation to find optimal decisions.
|
|
|
|
Does **not** mutate the input state; it creates new MCTS nodes during search.
|
|
|
|
# Arguments
|
|
- `initialstate::T`
|
|
The initial state from which to start the search.
|
|
- `transition::Function`
|
|
A function that defines how the state transitions.
|
|
- `transitionargs::NamedTuple`
|
|
Arguments passed to the transition function.
|
|
|
|
# Keyword Arguments
|
|
- `horizontalSampleExpansionPhase::Integer=3`
|
|
Number of child states sampled at each node during expansion phase.
|
|
- `horizontalSampleSimulationPhase::Integer=3`
|
|
Number of child states sampled at each node during simulation's expansion phase.
|
|
- `maxSimulationDepth::Integer=3`
|
|
Maximum depth MCTS goes during simulation phase.
|
|
- `maxiterations::Integer=10`
|
|
Number of iterations MCTS performs through expansion → simulation → backpropagation cycles.
|
|
- `explorationweight::Number=1.0`
|
|
Exploration weight controls how much MCTS explores new states versus exploiting known states.
|
|
A value of 1.0 balances exploration and exploitation equally. Higher values (e.g., 2.0)
|
|
encourage more aggressive exploration.
|
|
- `earlystop::Union{Function,Nothing}=nothing`
|
|
Optional function to check early stopping condition. If satisfied, MCTS breaks iterations.
|
|
- `saveSimulatedNode::Bool=false`
|
|
Whether to save nodes created during simulation phase.
|
|
- `multithread::Bool=false`
|
|
Whether to use multithreading during simulation.
|
|
|
|
# Return
|
|
- `NamedTuple{(:root, :bestNextState, :bestTerminalState, :highValueStateList),
|
|
Tuple{MCTSNode,T,T,Vector{Dict{String,Any}}}}`
|
|
- `root`: the complete MCTS tree with root node
|
|
- `bestNextState`: the best immediate next state
|
|
- `bestTerminalState`: the best final state along the best trajectory
|
|
- `highValueStateList`: list of high-value terminal states (reward >= 8)
|
|
|
|
# Example
|
|
```jldoctest
|
|
julia> using LLMMCTS
|
|
julia> initialState = Dict(:reward=>0.0)
|
|
julia> result = runMCTS(initialState, transition_func, transition_args; maxiterations=5)
|
|
```
|
|
"""
|
|
function runMCTS(
|
|
initialstate::T,
|
|
transition::Function,
|
|
transitionargs::NamedTuple,
|
|
;
|
|
horizontalSampleExpansionPhase::Integer=3,
|
|
horizontalSampleSimulationPhase::Integer=3,
|
|
maxSimulationDepth::Integer=3,
|
|
maxiterations::Integer=10,
|
|
explorationweight::Number=1.0,
|
|
earlystop::Union{Function,Nothing}=nothing,
|
|
saveSimulatedNode::Bool=false,
|
|
multithread=false,
|
|
)::NamedTuple{(:root, :bestNextState, :bestTerminalState, :highValueStateList),
|
|
Tuple{MCTSNode,T,T,Vector{Dict{String,Any}}}} where {T<:Any}
|
|
println("--> LLMMCTS runMCTS 1")
|
|
# Initialize the MCTS tree with a root node representing the initial state
|
|
# root.visits=0: no visits yet
|
|
# root.statevalue=0: no simulation results yet
|
|
root = MCTSNode("root", initialstate, 0, 0, 0, 0, false, nothing, Dict{String,MCTSNode}(),
|
|
Dict{String,Any}())
|
|
|
|
# Channel to collect high-value terminal states (reward >= 8)
|
|
# These are "good solutions" that can be returned to the user
|
|
highValueState = Channel{Any}(100)
|
|
|
|
# Main MCTS loop: perform iterations to build the search tree
|
|
# Each iteration: SELECTION → EXPANSION → SIMULATION → BACKPROPAGATION
|
|
for nth in 1:maxiterations
|
|
# Start from root and traverse down using UCT selection
|
|
node = root
|
|
node.visits += 1 # Count this iteration's visit to root
|
|
println("--> LLMMCTS runMCTS 2")
|
|
# Phase 1: SELECTION - Traverse tree using UCT until reaching a leaf node
|
|
# UCT balances exploration (new branches) vs exploitation (promising branches)
|
|
while !isleaf(node)
|
|
println("--> LLMMCTS runMCTS 3")
|
|
node = UCTselect(node, explorationweight)
|
|
end
|
|
println("--> LLMMCTS runMCTS 4")
|
|
# Phase 2: TERMINAL CHECK - If leaf is terminal, just backpropagate
|
|
if node.isterminal
|
|
println("--> LLMMCTS runMCTS 5")
|
|
# If this terminal state has high reward (>= 8), store it for later
|
|
if node.state[:reward] >= 8
|
|
println("--> LLMMCTS runMCTS 6")
|
|
put!(highValueState, deepcopy(node.state))
|
|
end
|
|
println("--> LLMMCTS runMCTS 7")
|
|
# Backpropagate the terminal node's own reward up to root
|
|
# This updates all ancestors with this path's outcome
|
|
backpropagate(node, node.reward)
|
|
else
|
|
println("--> LLMMCTS runMCTS 8")
|
|
# Phase 3: EXPANSION - Generate children for this non-terminal leaf
|
|
# Horizontal sampling: create multiple child nodes via LLM transition
|
|
_ = expand(node, transition, transitionargs;
|
|
horizontalSample=horizontalSampleExpansionPhase,
|
|
multithread=multithread)
|
|
println("--> LLMMCTS runMCTS 9")
|
|
# Phase 4: SIMULATION + BACKPROPAGATION
|
|
# For each newly expanded child, run simulation and update statistics
|
|
if multithread
|
|
println("--> LLMMCTS runMCTS 10")
|
|
# Parallel simulation: spawn threads for each child node
|
|
@sync for (leafNodeKey, leafNode) in node.children
|
|
@spawn simulateThenBackpropagate(leafNode, transition, transitionargs;
|
|
maxSimulationDepth=maxSimulationDepth,
|
|
horizontalSampleSimulationPhase=horizontalSampleSimulationPhase,
|
|
saveSimulatedNode=saveSimulatedNode,
|
|
multithread=multithread,
|
|
highValueState=highValueState,
|
|
)
|
|
end
|
|
else
|
|
println("--> LLMMCTS runMCTS 11")
|
|
# Sequential simulation: process each child one at a time
|
|
for (leafNodeKey, leafNode) in node.children
|
|
println("--> LLMMCTS runMCTS 11-1")
|
|
simulateThenBackpropagate(leafNode, transition, transitionargs;
|
|
maxSimulationDepth=maxSimulationDepth,
|
|
horizontalSampleSimulationPhase=horizontalSampleSimulationPhase,
|
|
saveSimulatedNode=saveSimulatedNode,
|
|
multithread=multithread,
|
|
highValueState=highValueState)
|
|
end
|
|
end
|
|
end
|
|
println("--> LLMMCTS runMCTS 12")
|
|
# Phase 5: EARLY STOP CHECK
|
|
# Optional: stop search early if a condition is met
|
|
if typeof(earlystop) <: Function && earlystop(node.state)
|
|
println("--> LLMMCTS runMCTS 13")
|
|
break
|
|
end
|
|
end
|
|
println("--> LLMMCTS runMCTS 14")
|
|
# After all iterations, extract results from the search tree
|
|
# Select best immediate next state (best child of root)
|
|
bestNextState = selectBestNextNode(root)
|
|
println("--> LLMMCTS runMCTS 15")
|
|
# Select best terminal state along the optimal trajectory
|
|
bestTerminalState = selectBestTrajectoryNode(root)
|
|
|
|
# Collect all high-value states from the channel into a list
|
|
highValueStateList = Vector{Dict{String, Any}}()
|
|
while !isempty(highValueState)
|
|
println("--> LLMMCTS runMCTS 16")
|
|
push!(highValueStateList, take!(highValueState))
|
|
end
|
|
println("--> LLMMCTS runMCTS 17")
|
|
# Return complete search results
|
|
result = (
|
|
root=root,
|
|
bestNextState=bestNextState.state,
|
|
bestTerminalState=bestTerminalState.state,
|
|
highValueStateList=highValueStateList
|
|
)
|
|
|
|
return result
|
|
end
|
|
|
|
""" Run simulation from a given node and backpropagate the reward.
|
|
|
|
This function performs simulation (rollout) from the given node, collects the
|
|
cumulative reward along the trajectory, and backpropagates it up the tree to update
|
|
visit counts and state values.
|
|
|
|
Does **not** mutate the input node's children (unless `saveSimulatedNode=true`).
|
|
|
|
# Arguments
|
|
- `node::MCTSNode`
|
|
The current node to simulate from.
|
|
- `transition::Function`
|
|
A function that defines how the state transitions.
|
|
- `transitionargs::NamedTuple`
|
|
Arguments passed to the transition function.
|
|
|
|
# Keyword Arguments
|
|
- `maxSimulationDepth::Integer=3`
|
|
Maximum depth MCTS goes during simulation phase.
|
|
- `horizontalSampleSimulationPhase::Integer=3`
|
|
Number of child states sampled at each node during simulation phase.
|
|
- `saveSimulatedNode::Bool=false`
|
|
Whether to save nodes created during simulation phase. If false, children are
|
|
cleared after simulation.
|
|
- `multithread::Bool=false`
|
|
Whether to use multithreading during simulation.
|
|
|
|
# Return
|
|
- `Nothing`
|
|
|
|
# Signature
|
|
"""
|
|
function simulateThenBackpropagate(node::MCTSNode, transition::Function, transitionargs::NamedTuple;
|
|
maxSimulationDepth::Integer=3, horizontalSampleSimulationPhase::Integer=3,
|
|
saveSimulatedNode::Bool=false,
|
|
multithread=false,
|
|
highValueState=Union{Nothing,Any}=nothing)
|
|
println("--> LLMMCTS simulateThenBackpropagate 1")
|
|
# Phase 1: RUN SIMULATION (rollout)
|
|
# Perform a rollout from this node, accumulating rewards along the way
|
|
simTrajectoryReward, terminalstate =
|
|
simulate(node, transition, transitionargs;
|
|
maxSimulationDepth=maxSimulationDepth,
|
|
horizontalSample=horizontalSampleSimulationPhase,
|
|
multithread=multithread)
|
|
println("--> LLMMCTS simulateThenBackpropagate 2")
|
|
# Phase 2: HIGH-VALUE STATE TRACKING
|
|
# If we reached a terminal state with high reward (>= 8), store it
|
|
# This allows users to access multiple good solutions, not just the best one
|
|
if highValueState !== nothing &&
|
|
terminalstate !== nothing &&
|
|
terminalstate["reward"] >= 8
|
|
println("--> LLMMCTS simulateThenBackpropagate 3")
|
|
put!(highValueState, deepcopy(terminalstate))
|
|
end
|
|
println("--> LLMMCTS simulateThenBackpropagate 4")
|
|
# Phase 3: BACKPROPAGATE
|
|
# Update statistics (visits, statevalue) for all ancestors up to root
|
|
# The simulation result is now incorporated into the tree
|
|
backpropagate(node, simTrajectoryReward)
|
|
println("--> LLMMCTS simulateThenBackpropagate 5")
|
|
# Phase 4: MEMORY MANAGEMENT
|
|
# Clear children unless user wants to keep them for analysis
|
|
# This frees memory for the next iteration while preserving tree structure
|
|
if saveSimulatedNode == false
|
|
println("--> LLMMCTS simulateThenBackpropagate 6")
|
|
node.children = Dict{String, MCTSNode}()
|
|
end
|
|
println("--> LLMMCTS simulateThenBackpropagate 7")
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end # module interface |