pub struct TreeSearch<E: Environment> {
pub transposition_table: Option<HashMap<u64, u32>>,
/* private fields */
}Expand description
Game-tree MCTS engine.
Given an Environment, searches for the best action by running
repeated simulations through a growing tree of explored states.
§Type Parameters
E: the environment type implementingEnvironment.
§Examples
use mctrust::{Environment, Outcome, TreeSearch, SearchConfig, Reward};
#[derive(Clone)]
struct Counter { value: i32, target: i32 }
#[derive(Clone, Debug, PartialEq)]
enum Step { Inc, Dec }
impl Environment for Counter {
type Action = Step;
fn legal_actions(&self) -> Vec<Step> {
vec![Step::Inc, Step::Dec]
}
fn apply(&mut self, action: &Step) {
match action {
Step::Inc => self.value += 1,
Step::Dec => self.value -= 1,
}
}
fn evaluate(&self) -> Outcome {
if self.value == self.target {
Outcome::Success(Reward::WIN)
} else if (self.value - self.target).abs() > 10 {
Outcome::Failure
} else {
Outcome::Ongoing
}
}
}
let game = Counter { value: 0, target: 3 };
let config = SearchConfig::builder().iterations(1_000).max_depth(15).build();
let mut search = TreeSearch::new(game, config);
if let Some(best) = search.run() {
// best is the action with most visits from root
println!("Best action: {best:?}");
}
let _all_moves = vec![Step::Inc, Step::Dec];Fields§
§transposition_table: Option<HashMap<u64, u32>>Optional Transposition Table (DAG) storing HashMap<EnvironmentHash, NodeIndex>.
Implementations§
Source§impl<E: Environment> TreeSearch<E>
impl<E: Environment> TreeSearch<E>
Sourcepub fn best_root_action(&self) -> Option<E::Action>
pub fn best_root_action(&self) -> Option<E::Action>
Returns the root child with the most visits (robust child policy).
Use this to retrieve the best action after manual iteration via
run_step() or run_until().
Source§impl<E: Environment> TreeSearch<E>
impl<E: Environment> TreeSearch<E>
Sourcepub fn new(environment: E, config: SearchConfig) -> Self
pub fn new(environment: E, config: SearchConfig) -> Self
Creates a new game-tree search from an environment and configuration.
§Parameters
environment: Root state to search from.config: Search hyperparameters.
§Returns
Returns a TreeSearch with a root node populated from the environment’s legal
actions.
§Panics
This function does not panic.
§Examples
use mctrust::{Environment, TreeSearch, Outcome, SearchConfig};
#[derive(Clone)]
struct Env;
impl Environment for Env {
type Action = ();
fn legal_actions(&self) -> Vec<Self::Action> { vec![()] }
fn apply(&mut self, _action: &Self::Action) {}
fn evaluate(&self) -> Outcome { Outcome::Neutral }
}
let search = TreeSearch::new(Env, SearchConfig::default());
assert_eq!(search.tree_size(), 1);Sourcepub fn with_seed(environment: E, config: SearchConfig, seed: u64) -> Self
pub fn with_seed(environment: E, config: SearchConfig, seed: u64) -> Self
Creates a new game-tree search with a deterministic RNG seed.
§Parameters
environment: Root state to search from.config: Search hyperparameters.seed: Seed used for rollout randomness and tie-breaking.
§Returns
Returns a deterministic TreeSearch.
§Panics
This function does not panic.
Sourcepub fn checkpoint(&self) -> TreeSearchCheckpoint<E>where
E: Serialize + for<'de> Deserialize<'de>,
E::Action: Serialize + for<'de> Deserialize<'de> + Clone,
pub fn checkpoint(&self) -> TreeSearchCheckpoint<E>where
E: Serialize + for<'de> Deserialize<'de>,
E::Action: Serialize + for<'de> Deserialize<'de> + Clone,
Creates a serialisable checkpoint of the current search state.
This can be used to pause and resume search work in a separate process.
§Parameters
This function takes no additional parameters.
§Returns
Returns a TreeSearchCheckpoint containing the root environment, config, and tree.
§Panics
This function does not panic.
Sourcepub fn restore(checkpoint: TreeSearchCheckpoint<E>) -> Selfwhere
E: Serialize + for<'de> Deserialize<'de>,
E::Action: Serialize + for<'de> Deserialize<'de> + Clone,
pub fn restore(checkpoint: TreeSearchCheckpoint<E>) -> Selfwhere
E: Serialize + for<'de> Deserialize<'de>,
E::Action: Serialize + for<'de> Deserialize<'de> + Clone,
Restores a search state from a checkpoint.
§Parameters
checkpoint: Previously captured search state.
§Returns
Returns a TreeSearch resumed from the checkpoint.
§Panics
This function does not panic.
Sourcepub fn run(&mut self) -> Option<E::Action>
pub fn run(&mut self) -> Option<E::Action>
Runs the search for the configured number of iterations, or until the optional time budget is exhausted, whichever comes first.
§Returns
Returns the most-visited root action, or None if the environment has
no legal actions.
§Examples
use mctrust::{Environment, TreeSearch, Outcome, Reward, SearchConfig};
#[derive(Clone)]
struct Env(bool);
#[derive(Clone, Debug, PartialEq)]
enum Action { Win }
impl Environment for Env {
type Action = Action;
fn legal_actions(&self) -> Vec<Self::Action> { if self.0 { vec![] } else { vec![Action::Win] } }
fn apply(&mut self, _action: &Self::Action) { self.0 = true; }
fn evaluate(&self) -> Outcome { if self.0 { Outcome::Success(Reward::WIN) } else { Outcome::Ongoing } }
}
let mut search = TreeSearch::with_seed(Env(false), SearchConfig::builder().iterations(8).build(), 1);
assert_eq!(search.run(), Some(Action::Win));Sourcepub fn run_step(&mut self)
pub fn run_step(&mut self)
Executes exactly one MCTS iteration (select → expand → simulate → backpropagate).
This is the fundamental building block for fine-grained control. Use it when:
- You need to interleave search with network I/O (e.g., batched NN evaluation)
- You want to stream intermediate results to a progress bar or TUI
- You need to check an external cancellation token between iterations
- You are building a custom time-control or pondering loop
After calling this N times, use best_root_action(),
principal_variation(), or
root_stats() to inspect the result.
Sourcepub fn run_parallel(&mut self, threads: usize) -> Option<E::Action>
pub fn run_parallel(&mut self, threads: usize) -> Option<E::Action>
Runs the search utilizing root-level parallelism across available threads.
This runs multiple distinct search trees in parallel, effectively achieving lock-free linear scaling, before merging the visitation statistics of the root branches optimally.
§Parameters
threads: Number of simultaneous MCTS instances to run.
Sourcepub fn root_stats(&self) -> Vec<(E::Action, NodeStats)>
pub fn root_stats(&self) -> Vec<(E::Action, NodeStats)>
Sourcepub fn total_simulations(&self) -> u32
pub fn total_simulations(&self) -> u32
Sourcepub fn with_evaluator(&mut self, evaluator: Arc<dyn Evaluator<E>>)
pub fn with_evaluator(&mut self, evaluator: Arc<dyn Evaluator<E>>)
Attaches a pluggable evaluator that replaces random rollout with a domain-specific or neural network-based leaf evaluation.
When set, the engine calls evaluator.evaluate(env) instead of running
a random rollout to a terminal state.
§Examples
use mctrust::{Environment, Evaluator, TreeSearch, Outcome, Reward, SearchConfig};
#[derive(Clone)]
struct Env;
impl Environment for Env {
type Action = ();
fn legal_actions(&self) -> Vec<()> { vec![()] }
fn apply(&mut self, _: &()) {}
fn evaluate(&self) -> Outcome { Outcome::Neutral }
}
struct ConstEval;
impl Evaluator<Env> for ConstEval {
fn evaluate(&self, _: &Env) -> Reward { Reward::new(0.8) }
}
let mut search = TreeSearch::new(Env, SearchConfig::builder().iterations(10).build());
search.with_evaluator(std::sync::Arc::new(ConstEval));Sourcepub fn with_max_nodes(&mut self, limit: usize)
pub fn with_max_nodes(&mut self, limit: usize)
Sets the maximum number of tree nodes the search may allocate.
When this limit is reached, expansion stops and the engine runs selection + simulation on existing nodes only. Use this to bound memory usage in resource-constrained environments.
Sourcepub fn run_until<F>(&mut self, predicate: F) -> Option<E::Action>
pub fn run_until<F>(&mut self, predicate: F) -> Option<E::Action>
Runs the search until predicate returns true.
The predicate receives the current search state after each iteration, allowing arbitrary stopping conditions: target reward, time limit, external cancellation, convergence detection, etc.
§Examples
// Stop when we've found a line with > 0.9 reward, or 50k iterations
search.run_until(|s| {
s.best_root_reward().unwrap_or(0.0) > 0.9
|| s.total_simulations() >= 50_000
});Sourcepub fn principal_variation_states(&self) -> Vec<E>
pub fn principal_variation_states(&self) -> Vec<E>
Replays the principal variation through the environment, returning the full sequence of intermediate states.
This is invaluable for debugging, visualization, and post-search analysis. The first element is always the root state; the last is the state after applying the final PV move.
§Panics
This function does not panic. The internal unwrap on states.last()
is safe because the vector always has at least one element (the root).
Sourcepub fn export_dot(&self, depth: usize) -> String
pub fn export_dot(&self, depth: usize) -> String
Exports a Graphviz DOT representation of the top depth levels of the
search tree, with node visit counts and average rewards as labels.
dot -Tsvg tree.dot -o tree.svgSourcepub fn enable_dag(&mut self)
pub fn enable_dag(&mut self)
Enables the DAG transposition table, allowing identical states reached through different action sequences to share a single node in the search tree.
This dramatically reduces memory usage and accelerates convergence in environments with many transpositions (board games, planning problems, etc.).
Sourcepub fn disable_dag(&mut self)
pub fn disable_dag(&mut self)
Disables the DAG transposition table and frees its memory.
Sourcepub fn dag_hit_count(&self) -> usize
pub fn dag_hit_count(&self) -> usize
Returns the number of state hashes stored in the transposition table, a proxy for how many unique states the engine has explored.
Sourcepub fn principal_variation(&self) -> Vec<E::Action>
pub fn principal_variation(&self) -> Vec<E::Action>
Extracts the principal variation — the sequence of actions the engine considers optimal from the root, chosen by following the most-visited child at every level of the tree.
This is the single most important debugging and visualization primitive for any tree search engine. Safe in DAG mode (cycle-aware).
Sourcepub fn best_root_reward(&self) -> Option<f64>
pub fn best_root_reward(&self) -> Option<f64>
Returns the average reward of the best root action, or None if no
simulations have been performed.
Sourcepub fn advance_to_action(&mut self, action: &E::Action) -> bool
pub fn advance_to_action(&mut self, action: &E::Action) -> bool
Tree reuse — re-roots the search tree at the child reached by action,
preserving the entire subtree and all accumulated statistics.
This is the technique used by every elite game engine (Stockfish, Leela,
KataGo). Instead of discarding the tree after each move and starting from
scratch, advance_to_action keeps all the work done in the selected
subtree, giving the next search a massive warm start.
Returns true if the action was found among the root’s children and the
tree was successfully re-rooted. Returns false if the action didn’t
match any child (the tree remains unchanged — caller should create a fresh
search instead).
§Examples
// After choosing an action, re-root to reuse the subtree:
if let Some(best) = search.run() {
search.advance_to_action(&best);
// Next search.run() starts with the preserved subtree.
}Trait Implementations§
Source§impl<E: Clone + Environment> Clone for TreeSearch<E>
impl<E: Clone + Environment> Clone for TreeSearch<E>
Source§fn clone(&self) -> TreeSearch<E>
fn clone(&self) -> TreeSearch<E>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl<E> Freeze for TreeSearch<E>where
E: Freeze,
impl<E> !RefUnwindSafe for TreeSearch<E>
impl<E> Send for TreeSearch<E>
impl<E> Sync for TreeSearch<E>
impl<E> Unpin for TreeSearch<E>
impl<E> UnsafeUnpin for TreeSearch<E>where
E: UnsafeUnpin,
impl<E> !UnwindSafe for TreeSearch<E>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more