1use crate::tree::*;
2
3#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
5pub struct Action(usize);
6
7impl Action{
8 pub fn new(action: usize) -> Self{
16 Action(action)
17 }
18
19 pub fn action(&self) -> usize{
24 self.0
25 }
26}
27
28#[derive(Copy, Clone, Debug, PartialEq)]
30enum MctsNodeState{
31 Active,
33 Terminal(f32)
35}
36
37#[derive(Debug, Copy, Clone, PartialEq)]
43struct MctsNode<const N: usize>{
44 children_scores: [f32; N],
45 children_visits: [i32; N],
46 children_policies: [f32; N],
47
48 visits: i32,
49 state: MctsNodeState,
50 action: Action,
51}
52
53
54pub trait SelectionFunction: Fn(f32, i32, i32, f32, f32) -> f32 {}
72impl<T: Fn(f32, i32, i32, f32, f32) -> f32> SelectionFunction for T {}
73
74pub trait ScoreTransformer: FnMut(f32) -> f32 {}
79impl<T: FnMut(f32) -> f32> ScoreTransformer for T {}
80
81pub trait ResettableBuffer {
83 fn push(&mut self, value: Action);
85 fn clear(&mut self);
87}
88
89#[macro_export]
91macro_rules! impl_resettable_buffer {
92 ($type:ty) => {
93 impl ResettableBuffer for $type {
94 fn push(&mut self, value: Action) {
95 self.push(value);
96 }
97 fn clear(&mut self) {
98 self.clear();
99 }
100 }
101 };
102}
103
104impl_resettable_buffer!(Vec<Action>);
105
106impl<const N: usize> MctsNode<N> {
107 fn new(policies: [f32; N], action: Action, state: MctsNodeState) -> Self {
109 MctsNode{
110 children_scores: [0.; N],
111 children_visits: [0; N],
112 children_policies: policies,
113 visits: 0,
114 action,
115 state
116 }
117 }
118
119 fn best_child(&self, score_f: &impl SelectionFunction, c: f32) -> usize {
129 self.children_scores.iter()
130 .zip(self.children_visits.iter())
131 .zip(self.children_policies.iter())
132 .enumerate()
133 .filter_map(
134 |(i, ((c_scores, c_visits), policy))| {
135 if *policy <= 0. { None } else { Some((i, score_f(*c_scores, *c_visits, self.visits, *policy, c))) }
136 }
137 ).max_by(
138 |(_a, a), (_b, b)| { a.total_cmp(b) }
139 ).expect("Error during the selection of the best action.").0
140 }
141}
142
143#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
145pub struct MctsNodeId(NodeId);
146
147pub struct Engine<const N: usize> {
152 tree: Tree<MctsNode<N>, N>
153}
154
155#[derive(Copy, Clone, Debug, PartialEq)]
157pub enum SelectionResult {
158 Empty,
160 Active(MctsNodeId, Action),
162 Terminal(MctsNodeId, f32)
164}
165
166#[derive(Copy, Clone, Debug, PartialEq)]
178pub enum StateEvaluation<const N: usize> {
179 Active(f32, [f32; N]),
182
183 Terminal(f32)
185}
186
187impl<const N: usize> StateEvaluation<N>{
188 pub fn score(&self) -> f32{
194 match self {
195 StateEvaluation::Active(score, _) => *score,
196 StateEvaluation::Terminal(score) => *score
197 }
198 }
199}
200
201#[derive(Debug, Clone, Copy, PartialEq)]
203pub enum MctsEngineError{
204 UnknownError,
205 InvalidMctsNode,
206 ChildAlreadyExists,
207 InvalidAction,
208 SelectionIsTerminal
209}
210
211impl<const N: usize> Engine<N> {
212 pub fn new() -> Self {
221 Engine::<N> {
222 tree: Tree::<MctsNode<N>, N>::new()
223 }
224 }
225
226 pub fn with_capacity(capacity: usize) -> Self {
237 Engine::<N> {
238 tree: Tree::<MctsNode<N>, N>::with_capacity(capacity)
239 }
240 }
241
242 pub fn select(&self, path_out: &mut impl ResettableBuffer, score_f: &impl SelectionFunction, c: f32) -> SelectionResult{
271 path_out.clear();
272
273 if let Some(mut current_id) = self.tree.root(){
274 loop {
275 let current = self.tree.get(current_id).unwrap();
276
277 match current.data().state {
278 MctsNodeState::Terminal(score) => {
279 return SelectionResult::Terminal(MctsNodeId(current_id), score);
280 }
281 MctsNodeState::Active => {
282 let action = current.data().best_child(score_f, c);
283
284 path_out.push(Action(action));
285 if let Some(child_id) = current.child(action){
286 current_id = child_id;
287 }
288 else{
289 return SelectionResult::Active(MctsNodeId(current_id), Action(action));
290 }
291 }
292 }
293 }
294 }
295 else{ SelectionResult::Empty }
296 }
297
298 pub fn expand(&mut self, evaluation: StateEvaluation<N>, selection: SelectionResult) -> Result<MctsNodeId, MctsEngineError>{
330 let (state, policies) = match evaluation {
331 StateEvaluation::Active(_, policies) => (MctsNodeState::Active, policies),
332 StateEvaluation::Terminal(score) => (MctsNodeState::Terminal(score), [0.; N])
333 };
334
335 match selection {
336 SelectionResult::Empty => {
337 match self.tree.set_root(MctsNode::new(policies, Action(0), state)) {
338 Err(TreeError::RootAlreadyExists) => Err(MctsEngineError::ChildAlreadyExists),
339 Err(_) => Err(MctsEngineError::UnknownError),
340 Ok(node) => Ok(MctsNodeId(node)),
341 }
342 },
343 SelectionResult::Active(child_id, action) => {
344 match self.tree.add(child_id.0, action.0, MctsNode::new(policies, action, state)) {
345 Err(TreeError::ParentDoesntExist) => Err(MctsEngineError::InvalidMctsNode),
346 Err(TreeError::ChildAlreadyExists) => Err(MctsEngineError::ChildAlreadyExists),
347 Err(_) => Err(MctsEngineError::UnknownError),
348 Ok(node) => Ok(MctsNodeId(node))
349 }
350 },
351 SelectionResult::Terminal(_, _) => Err(MctsEngineError::SelectionIsTerminal)
352 }
353 }
354
355 pub fn backpropagate(&mut self, node: MctsNodeId, score: f32, mut score_updater: impl ScoreTransformer) -> Result<(), MctsEngineError> {
386 let mut current = Some(node.0);
387 let mut score = score;
388
389 while let Some(node) = current {
390 let node = self.tree.get_mut(node).map_err(|_| MctsEngineError::InvalidMctsNode)?;
391 node.data_mut().visits += 1;
392
393 let action = node.data().action.0;
394 current = node.parent();
395
396 if let Some(parent) = current {
397 let parent = self.tree.get_mut(parent).map_err(|_| MctsEngineError::InvalidMctsNode)?;
398 parent.data_mut().children_visits[action] += 1;
399 parent.data_mut().children_scores[action] += score;
400 }
401
402 score = score_updater(score);
403 }
404
405 Ok(())
406 }
407
408 pub fn update(&mut self, evaluation: StateEvaluation<N>, selection: SelectionResult, score_updater: impl ScoreTransformer) -> Result<(), MctsEngineError> {
433 let score = evaluation.score();
434
435 let node = match selection {
436 SelectionResult::Empty => self.expand(evaluation, selection)?,
437 SelectionResult::Active(_child_id, _action) => self.expand(evaluation, selection)?,
438 SelectionResult::Terminal(child_id, _score) => child_id
439 };
440
441 self.backpropagate(node, score, score_updater)?;
442
443 Ok(())
444 }
445
446 pub fn scores(&self) -> [i32; N]{
469 if let Some(root) = self.tree.root() {
470 self.tree.get(root).unwrap().data().children_visits
471 }
472 else {
473 [0; N]
474 }
475 }
476
477 pub fn commit_action(&mut self, action: Action) {
501 if let Some(root) = self.tree.root() {
502 let child = self.tree.child(root, action.0).unwrap();
503
504 if let Some(new_root_id) = child {
505 self.tree.move_root_to(new_root_id).unwrap();
506
507 let new_root_data = self.tree.data(new_root_id).unwrap();
508 if self.tree.allocated_nodes() > 2*new_root_data.visits as usize {
509 self.tree.compact();
510 }
511 }
512 else{
513 self.tree.clear();
514 }
515 }
516 }
517}
518
519#[cfg(test)]
520mod tests {
521 use crate::{puct, negate_score};
522 use super::*;
523
524 #[test]
525 fn test_action_creation() {
526 let action = Action::new(42);
527 assert_eq!(action.action(), 42);
528 }
529
530 #[test]
531 fn test_action_equality() {
532 let a1 = Action::new(10);
533 let a2 = Action::new(10);
534 let a3 = Action::new(11);
535
536 assert_eq!(a1, a2, "Two actions with the same index should be equal");
537 assert_ne!(a1, a3, "Actions with different indices should not be equal");
538 }
539
540 #[test]
541 fn test_action_hashability() {
542 use std::collections::HashSet;
543
544 let mut set = HashSet::new();
545 let a1 = Action::new(5);
546 let a2 = Action::new(5);
547
548 set.insert(a1);
549
550 assert!(set.contains(&a2), "Action should be hashable and work in a HashSet");
551 assert_eq!(set.len(), 1, "HashSet should handle identical Actions correctly");
552 }
553
554 fn dummy_select(_score: f32, _node_visits: i32, _parent_visits: i32, policy: f32, _c: f32) -> f32 {
555 policy
556 }
557
558 fn identity_score(s: f32) -> f32 {
559 s
560 }
561
562 const N: usize = 2;
563
564 #[test]
565 fn test_engine_initialization() {
566 let engine = Engine::<N>::new();
567 assert!(engine.tree.root().is_none());
568 }
569
570 #[test]
571 fn test_select_on_empty_tree_returns_empty() {
572 let engine = Engine::<N>::new();
573 let mut path = Vec::new();
574 let selection = engine.select(&mut path, &dummy_select, 1.0);
575
576 assert_eq!(selection, SelectionResult::Empty);
577 assert!(path.is_empty());
578 }
579
580 #[test]
581 fn test_update_empty_creates_root() {
582 let mut engine = Engine::<N>::new();
583 let mut path = Vec::new();
584
585 let selection = engine.select(&mut path, &dummy_select, 1.0);
586
587 let evaluation = StateEvaluation::Active(1.0, [0.7, 0.3]);
588 let result = engine.update(evaluation, selection, negate_score);
589 assert!(result.is_ok());
590
591 let root_id = engine.tree.root().unwrap();
592 let root = engine.tree.get(root_id).unwrap();
593 assert_eq!(root.data().visits, 1);
594 assert_eq!(root.data().state, MctsNodeState::Active);
595 }
596
597 #[test]
598 fn test_select_active_node_and_expand_child() {
599 let mut engine = Engine::<N>::new();
600 let mut path = Vec::new();
601
602 let selection = engine.select(&mut path, &dummy_select, 1.0);
603 let evaluation = StateEvaluation::Active(0.0, [0.7, 0.3]);
604 engine.update(evaluation, selection, negate_score).unwrap();
605
606 let selection2 = engine.select(&mut path, &dummy_select, 1.0);
607
608 match selection2 {
609 SelectionResult::Active(id, action) => {
610 assert_eq!(id.0, engine.tree.root().unwrap());
611 assert_eq!(action, Action(0));
612 },
613 _ => panic!("Expected Active selection"),
614 }
615 assert_eq!(path, vec![Action(0)]);
616
617 let evaluation = StateEvaluation::Active(1.0, [0.5, 0.5]);
618 let result = engine.update(evaluation, selection2, negate_score);
619 assert!(result.is_ok());
620 }
621
622 #[test]
623 fn test_backpropagate_values_correctly() {
624 let mut engine = Engine::<N>::new();
625 let mut path = Vec::new();
626
627 let sel_empty = engine.select(&mut path, &dummy_select, 1.0);
628 let evaluation = StateEvaluation::Active(0.0, [0.7, 0.3]);
629 engine.update(evaluation, sel_empty, negate_score).unwrap();
630
631 let sel_active = engine.select(&mut path, &dummy_select, 1.0);
632 let evaluation = StateEvaluation::Active(1.0, [0.5, 0.5]);
633 engine.update(evaluation, sel_active, negate_score).unwrap();
634
635 let root_id = engine.tree.root().unwrap();
636 let root = engine.tree.get(root_id).unwrap();
637
638 assert_eq!(root.data().visits, 2);
639 assert_eq!(root.data().children_visits[0], 1);
640 assert_eq!(root.data().children_scores[0], 1.0);
641 }
642
643 #[test]
644 fn test_terminal_node_selection_and_state() {
645 let mut engine = Engine::<N>::new();
646 let mut path = Vec::new();
647
648 let sel_empty = engine.select(&mut path, &dummy_select, 1.0);
649 let evaluation = StateEvaluation::Terminal(42.0);
650 engine.update(evaluation, sel_empty, identity_score).unwrap();
651
652 let root_id = engine.tree.root().unwrap();
653 let root = engine.tree.get(root_id).unwrap();
654 assert_eq!(root.data().state, MctsNodeState::Terminal(42.0));
655
656 let sel_terminal = engine.select(&mut path, &dummy_select, 1.0);
657
658 match sel_terminal {
659 SelectionResult::Terminal(id, score) => {
660 assert_eq!(id.0, root_id);
661 assert_eq!(score, 42.0);
662 },
663 _ => panic!("Expected Terminal selection"),
664 }
665 }
666
667 #[test]
668 #[should_panic(expected = "Error during the selection of the best action.")]
669 fn test_best_child_panics_if_no_legal_moves() {
670 let mut engine = Engine::<N>::new();
671 let mut path = Vec::new();
672
673 let sel_empty = engine.select(&mut path, &dummy_select, 1.0);
674 let evaluation = StateEvaluation::Active(0.0, [0.0, 0.0]);
675 engine.update(evaluation, sel_empty, negate_score).unwrap();
676
677 engine.select(&mut path, &dummy_select, 1.0);
678 }
679
680 #[test]
681 fn test_expand_on_terminal_returns_error() {
682 let mut engine = Engine::<N>::new();
683 let mut path = Vec::new();
684
685 let sel_empty = engine.select(&mut path, &dummy_select, 1.0);
686 let evaluation = StateEvaluation::Terminal(1.0);
687 engine.update(evaluation, sel_empty, identity_score).unwrap();
688
689 let sel_terminal = engine.select(&mut path, &dummy_select, 1.0);
690 let evaluation = StateEvaluation::Active(1.0, [0.5, 0.5]);
691 let result = engine.expand(evaluation, sel_terminal);
692
693 assert!(matches!(result, Err(MctsEngineError::SelectionIsTerminal)));
694 }
695
696 #[test]
697 fn test_adding_existing_child_returns_error() {
698 let mut engine = Engine::<N>::new();
699 let mut path = Vec::new();
700
701 let sel_empty = engine.select(&mut path, &dummy_select, 1.0);
702 let evaluation = StateEvaluation::Active(0.0, [0.7, 0.3]);
703 engine.update(evaluation, sel_empty, negate_score).unwrap();
704
705 let sel_active = engine.select(&mut path, &dummy_select, 1.0);
706 let evaluation = StateEvaluation::Active(1.0, [0.5, 0.5]);
707 engine.update(evaluation, sel_active, negate_score).unwrap();
708
709 let evaluation = StateEvaluation::Active(1.0, [0.5, 0.5]);
710 let result = engine.update(evaluation, sel_active, negate_score);
711 assert!(matches!(result, Err(MctsEngineError::ChildAlreadyExists)));
712 }
713
714 #[test]
715 fn test_scores_empty_engine() {
716 let engine = Engine::<N>::new();
717 assert_eq!(engine.scores(), [0; N]);
719 }
720
721 #[test]
722 fn test_scores_after_root_expansion() {
723 let mut engine = Engine::<N>::new();
724 let mut path = Vec::new();
725
726 let sel = engine.select(&mut path, &dummy_select, 1.0);
727 let evaluation = StateEvaluation::Active(0.0, [0.5, 0.5]);
728 engine.update(evaluation, sel, identity_score).unwrap();
729
730 assert_eq!(engine.scores(), [0; N]);
731 }
732
733 #[test]
734 fn test_scores_after_multiple_updates() {
735 let mut engine = Engine::<N>::new();
736 let mut path = Vec::new();
737
738 let sel_empty = engine.select(&mut path, &dummy_select, 1.0);
739 let evaluation = StateEvaluation::Active(0.0, [0.7, 0.3]);
740 engine.update(evaluation, sel_empty, identity_score).unwrap();
741
742 for _ in 0..3 {
743 let sel = engine.select(&mut path, &dummy_select, 1.0);
744 let evaluation = StateEvaluation::Active(1.0, [0.5, 0.5]);
745 engine.update(evaluation, sel, identity_score).unwrap();
746 }
747
748 let sel = engine.select(&mut path, &dummy_select, 1.0);
749 let evaluation = StateEvaluation::Active(1.0, [0.5, 0.5]);
750 engine.update(evaluation, sel, identity_score).unwrap();
751
752 let expected = [4, 0];
753 assert_eq!(engine.scores(), expected);
754 }
755
756 #[test]
757 #[allow(unused_assignments)]
758 fn test_scores_are_independent_copies() {
759 let mut engine = Engine::<N>::new();
760 let mut path = Vec::new();
761
762 let sel = engine.select(&mut path, &dummy_select, 1.0);
763 let evaluation = StateEvaluation::Active(0.0, [0.5, 0.5]);
764 engine.update(evaluation, sel, identity_score).unwrap();
765
766 let mut scores = engine.scores();
767
768
769 scores[0] = 999;
770
771 assert_ne!(engine.scores()[0], 999);
772 assert_eq!(engine.scores()[0], 0);
773 }
774
775 #[test]
776 fn test_scnerario_1(){
777 let c = std::f32::consts::SQRT_2;
778 let mut path = Vec::new();
779 let mut engine = Engine::<3>::new();
780
781 let selection = engine.select(&mut path, &puct, c);
782 let evaluation = StateEvaluation::Active(0.1, [0.5, 0.3, 0.2]);
783 let node = engine.expand(evaluation, selection).unwrap();
784 engine.backpropagate(node, 0.1, negate_score).unwrap();
785
786 assert_eq!(engine.scores(), [0, 0, 0]);
787 assert_eq!(path.len(), 0);
788
789 let selection = engine.select(&mut path, &puct, c);
790 let evaluation = StateEvaluation::Active(0.5, [1., 0., 0.]);
791 let node = engine.expand(evaluation, selection).unwrap();
792 engine.backpropagate(node, 0.5, negate_score).unwrap();
793
794 assert_eq!(engine.scores(), [1, 0, 0]);
795 assert_eq!(path.as_slice(), &[Action(0)]);
796
797 let selection = engine.select(&mut path, &puct, c);
798 let evaluation = StateEvaluation::Active(1., [1., 0., 0.]);
799 let node = engine.expand(evaluation, selection).unwrap();
800 engine.backpropagate(node, 1.0, negate_score).unwrap();
801
802 assert_eq!(engine.scores(), [2, 0, 0]);
803 assert_eq!(path.as_slice(), &[Action(0), Action(0)]);
804
805 let selection = engine.select(&mut path, &puct, c);
806 let evaluation = StateEvaluation::Terminal(-0.9);
807 let node = engine.expand(evaluation, selection).unwrap();
808 engine.backpropagate(node, -0.9, negate_score).unwrap();
809
810 assert_eq!(engine.scores(), [2, 1, 0]);
811 assert_eq!(path.as_slice(), &[Action(1)]);
812
813 let selection = engine.select(&mut path, &puct, c);
814 let evaluation = StateEvaluation::Terminal(-0.9);
815 let node = engine.expand(evaluation, selection).unwrap();
816 engine.backpropagate(node, -0.9, negate_score).unwrap();
817
818 assert_eq!(engine.scores(), [2, 1, 1]);
819 assert_eq!(path.as_slice(), &[Action(2)]);
820 }
821
822 fn setup_engine() -> Engine<3> {
823 let mut engine = Engine::<3>::new();
824 let mut path = Vec::new();
825
826 let sel = engine.select(&mut path, &|_,_,_,_,_| 0.0, 1.0);
827 let evaluation = StateEvaluation::Active(0., [1., 0., 0.]);
828 let node = engine.expand(evaluation, sel).unwrap();
829 engine.backpropagate(node, 0.0, negate_score).unwrap();
830
831
832 let sel = engine.select(&mut path, &|_,_,_,_,_| 0.0, 1.0);
833 let evaluation = StateEvaluation::Active(1., [1., 0., 0.]);
834 let node = engine.expand(evaluation, sel).unwrap();
835 engine.backpropagate(node, 1.0, negate_score).unwrap();
836
837 engine
838 }
839
840 #[test]
841 fn test_commit_action_success() {
842 let mut engine = setup_engine();
843
844 engine.tree.root().unwrap();
845 engine.commit_action(Action(0));
846
847 let new_root = engine.tree.root().unwrap();
848
849 assert_eq!(engine.tree.data(new_root).unwrap().visits, 1);
850 assert_eq!(engine.tree.get(new_root).unwrap().data().action, Action(0));
851 }
852
853 #[test]
854 fn test_commit_action_unexplored_leads_to_clear() {
855 let mut engine = setup_engine();
856
857 engine.commit_action(Action(2));
858 assert!(engine.tree.root().is_none());
859 }
860
861 #[test]
862 fn test_commit_action_no_root_does_nothing() {
863 let mut engine = Engine::<3>::new();
864
865 engine.commit_action(Action(0));
866
867 assert!(engine.tree.root().is_none());
868 }
869
870 #[test]
871 #[should_panic(expected = "index out of bounds")]
872 fn test_commit_action_out_of_bounds_panics() {
873 let mut engine = setup_engine();
874
875 engine.commit_action(Action(5));
876 }
877
878 #[test]
879 fn test_compact_trigger() {
880 let mut engine = setup_engine();
881
882 let root = engine.tree.root().unwrap();
883 engine.tree.get_mut(root).unwrap().data_mut().visits = 100;
884
885 let initial_nodes = engine.tree.allocated_nodes();
886
887 engine.commit_action(Action(0));
888 assert!(engine.tree.allocated_nodes() <= initial_nodes);
889 }
890}