Skip to main content

TreeSearch

Struct TreeSearch 

Source
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

§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>

Source

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>

Source

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);
Source

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.

Source

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.

Source

pub fn restore(checkpoint: TreeSearchCheckpoint<E>) -> Self
where 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.

Source

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));
Source

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.

Source

pub fn run_parallel(&mut self, threads: usize) -> Option<E::Action>
where E: Send + Sync, E::Action: Eq + Hash + Send + Sync,

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.
Source

pub fn root_stats(&self) -> Vec<(E::Action, NodeStats)>
where E::Action: Clone,

Returns statistics for each root child.

§Parameters

This function takes no additional parameters.

§Returns

Returns (action, stats) pairs for every expanded child of the root.

§Panics

This function does not panic.

Source

pub fn tree_size(&self) -> usize

Returns the total number of nodes currently stored in the search tree.

§Parameters

This function takes no additional parameters.

§Returns

Returns the arena length.

§Panics

This function does not panic.

Source

pub fn total_simulations(&self) -> u32

Returns the total number of simulations run so far.

§Parameters

This function takes no additional parameters.

§Returns

Returns the root visit count.

§Panics

This function does not panic.

Source

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));
Source

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.

Source

pub fn run_until<F>(&mut self, predicate: F) -> Option<E::Action>
where F: FnMut(&Self) -> bool,

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
});
Source

pub fn principal_variation_states(&self) -> Vec<E>
where E::Action: Clone,

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).

Source

pub fn export_dot(&self, depth: usize) -> String
where E::Action: Debug,

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.svg
Source

pub fn uses_rave(&self) -> bool

Reports whether the configured search uses RAVE blending.

§Parameters

This function takes no additional parameters.

§Returns

Returns true when config.rave.enabled is set.

§Panics

This function does not panic.

Source

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.).

Source

pub fn disable_dag(&mut self)

Disables the DAG transposition table and frees its memory.

Source

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.

Source

pub fn principal_variation(&self) -> Vec<E::Action>
where E::Action: Clone,

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).

Source

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.

Source

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>
where E::Action: Clone,

Source§

fn clone(&self) -> TreeSearch<E>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto 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>
where E: Unpin, <E as Environment>::Action: Unpin,

§

impl<E> UnsafeUnpin for TreeSearch<E>
where E: UnsafeUnpin,

§

impl<E> !UnwindSafe for TreeSearch<E>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V