Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
Alpha_Beta.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
42# ifndef ALPHA_BETA_H
43# define ALPHA_BETA_H
44
45#include <limits>
46#include <utility>
47
48#include <ah-errors.H>
49#include <Negamax.H>
50
51namespace Aleph {
52
69template <AdversarialGameDomain Domain>
71{
72public:
76 using State = typename Domain::State;
78 using Move = typename Domain::Move;
80 using Score = typename Domain::Score;
87
94 static constexpr bool supports_best_first = false;
95
101
105 const SearchLimits &limits = {})
107 {
108 // empty
109 }
110
113 {
114 return domain_;
115 }
116
119 {
120 return domain_;
121 }
122
125 {
126 return policy_;
127 }
128
131 {
132 return limits_;
133 }
134
136 void set_policy(const ExplorationPolicy &policy) noexcept
137 {
138 policy_ = policy;
139 }
140
142 void set_limits(const SearchLimits &limits) noexcept
143 {
144 limits_ = limits;
145 }
146
160
162 template <typename Tracer>
165 {
168 return search_impl(std::move(initial_state),
169 table,
170 keyer,
171 tracer,
172 adversarial_search_detail::score_floor<Score>(),
173 adversarial_search_detail::score_ceiling<Score>());
174 }
175
178 {
180 return search_with_window(std::move(initial_state), alpha, beta, tracer);
181 }
182
184 template <typename Tracer>
195
197 template <typename Table, typename Keyer>
204
206 template <typename Table, typename Keyer, typename Tracer>
210 {
211 return search_impl(std::move(initial_state),
212 table,
213 keyer,
214 tracer,
215 adversarial_search_detail::score_floor<Score>(),
216 adversarial_search_detail::score_ceiling<Score>());
217 }
218
220 template <typename Table, typename Keyer>
223 State initial_state, Table &table, Keyer keyer, const Score alpha, const Score beta)
224 {
226 return search_with_window(std::move(initial_state), table, keyer, alpha, beta, tracer);
227 }
228
230 template <typename Table, typename Keyer, typename Tracer>
234 Table &table,
235 Keyer keyer,
236 const Score alpha,
237 const Score beta,
238 Tracer &tracer)
239 {
240 return search_impl(std::move(initial_state), table, keyer, tracer, alpha, beta);
241 }
242
244 template <typename Table, typename D_ = Domain>
248 {
250 auto keyer = [this](const State &state)
251 {
252 return domain_.state_key(state);
253 };
254
255 return search_impl(std::move(initial_state),
256 table,
257 keyer,
258 tracer,
259 adversarial_search_detail::score_floor<Score>(),
260 adversarial_search_detail::score_ceiling<Score>());
261 }
262
264 template <typename Table, typename Tracer, typename D_ = Domain>
269 {
270 auto keyer = [this](const State &state)
271 {
272 return domain_.state_key(state);
273 };
274
275 return search_impl(std::move(initial_state),
276 table,
277 keyer,
278 tracer,
279 adversarial_search_detail::score_floor<Score>(),
280 adversarial_search_detail::score_ceiling<Score>());
281 }
282
284 template <typename Table, typename D_ = Domain>
288 Table &table,
289 const Score alpha,
290 const Score beta)
291 {
293 auto keyer = [this](const State &state)
294 {
295 return domain_.state_key(state);
296 };
297
298 return search_impl(std::move(initial_state), table, keyer, tracer, alpha, beta);
299 }
300
302 template <typename Table, typename Tracer, typename D_ = Domain>
307 State initial_state, Table &table, const Score alpha, const Score beta, Tracer &tracer)
308 {
309 auto keyer = [this](const State &state)
310 {
311 return domain_.state_key(state);
312 };
313
314 return search_impl(std::move(initial_state), table, keyer, tracer, alpha, beta);
315 }
316
317private:
321 bool stop_ = false;
324
330
332 {
334 << "Alpha_Beta does not support MoveOrderingMode::Estimated_Bound";
335
336 if constexpr (not Killer_Table::supported)
338 << "Alpha_Beta killer heuristic requires equality-comparable Move";
339
340 if constexpr (not MoveKeyProvider<Domain>)
342 << "Alpha_Beta history heuristic requires domain.move_key(move)";
343 }
344
346 {
347 killer_moves_.clear();
348 history_moves_.clear();
349 }
350
351 [[nodiscard]] static size_t history_bonus(const size_t depth, const size_t remaining) noexcept
352 {
353 const size_t base = remaining == Search_Unlimited ? depth + 1 : remaining + 1;
354 return base * base;
355 }
356
357 [[nodiscard]] size_t move_history_score(const Move &move) const noexcept
358 {
359 if constexpr (MoveKeyProvider<Domain>)
361 return history_moves_.score(domain_.move_key(move));
362
363 return 0;
364 }
365
366 void record_cutoff_move(const size_t depth, const size_t remaining, const Move &move)
367 {
369 killer_moves_.record(depth, move);
370
371 if constexpr (MoveKeyProvider<Domain>)
373 history_moves_.record(domain_.move_key(move), history_bonus(depth, remaining));
374 }
375
377 const size_t depth,
378 Result &result)
379 {
381 size_t ordinal = 0;
382
383 (void) domain_.for_each_successor(state,
384 [&](const Move &move) -> bool
385 {
387 ranked.move = move;
388 ranked.ordinal = ordinal++;
389
391 {
392 ranked.killer = killer_moves_.is_killer(depth, move);
393 if (ranked.killer)
395 }
396
398 {
399 ranked.history_score = move_history_score(move);
400 if (ranked.history_score > 0)
402 }
403
405 {
406 if constexpr (IncrementalEvaluator<Domain>)
407 {
408 // Fast path: estimate score without apply/undo.
409 ranked.priority = -domain_.evaluate_after(state, move);
410 }
411 else
412 {
413 bool applied = false;
414 try
415 {
416 domain_.apply(state, move);
417 applied = true;
418 ranked.priority = -domain_.evaluate(state);
419 }
420 catch (...)
421 {
422 if (applied)
423 domain_.undo(state, move);
424 throw;
425 }
426 domain_.undo(state, move);
427 }
430 }
431
432 moves.append(std::move(ranked));
433 return true;
434 });
435
436 if (not moves.is_empty())
437 {
439 result.stats.move_ordering.ordered_moves += moves.size();
440 }
441
442 sort_ranked_moves(moves,
443 [](const Score &lhs, const Score &rhs) noexcept
444 {
445 return lhs > rhs;
446 },
449
450 return moves;
451 }
452
453 template <typename Table, typename Keyer, typename Tracer>
456 Table &table,
457 Keyer &keyer,
458 Tracer &tracer,
459 const Score alpha,
460 const Score beta)
461 {
463 << "Alpha_Beta only supports depth-first exploration";
465 << "SearchLimits::max_solutions must be positive or Search_Unlimited";
466 ah_invalid_argument_unless(alpha < beta) << "Alpha_Beta root window requires alpha < beta";
468
469 Result result;
470 result.policy = policy_;
471 result.limits = limits_;
472 const auto start_time = SearchClock::now();
473
474 stop_ = false;
476 auto root = search_node(initial_state, 0, result, table, keyer, tracer, alpha, beta);
477 result.value = root.value;
478 result.principal_variation = std::move(root.principal_variation);
479
480 if (result.status == SearchStatus::NotStarted)
482
483 result.stats.elapsed_ms = search_elapsed_ms(SearchClock::now() - start_time);
484
485 return result;
486 }
488 {
490 {
491 stop_ = true;
492 return true;
493 }
494
495 return false;
496 }
497
498 template <typename Table, typename Keyer>
500 const size_t remaining,
501 Result &result,
502 Table &table,
503 Keyer &keyer,
505 const TranspositionBound bound)
506 {
507 adversarial_search_detail::store_adversarial_transposition<Move, Score>(
508 state, remaining, result, table, keyer, value, bound, stop_);
509 }
510
511 template <typename Table, typename Keyer>
513 const size_t remaining,
514 Result &result,
515 Table &table,
516 Keyer &keyer,
517 Score &alpha,
518 Score &beta,
520 {
521 auto *entry = adversarial_search_detail::probe_and_count_transposition<Move, Score>(
522 state, remaining, result, table, keyer);
523
524 if (entry != nullptr)
525 {
526 switch (entry->bound)
527 {
530 out.value = entry->value;
531 out.principal_variation = entry->principal_variation;
532 return true;
533
535 if (entry->value > alpha)
536 alpha = entry->value;
537 break;
538
540 if (entry->value < beta)
541 beta = entry->value;
542 break;
543 }
544
545 if (alpha >= beta)
546 {
548 out.value = entry->value;
549 out.principal_variation = entry->principal_variation;
550 return true;
551 }
552 }
553
554 return false;
555 }
556
557 template <typename Table, typename Keyer, typename Tracer>
560 const size_t depth,
561 Result &result,
562 Table &table,
563 Keyer &keyer,
564 Tracer &tracer,
565 const Score alpha,
566 const Score beta)
567 {
572
574
576
577 const size_t remaining = remaining_depth(limits_, depth);
579 tracer, AdversarialTraceEventKind::Enter_Node, depth, remaining, 0, 0, 0, Score{}, alpha, beta);
580 Score local_alpha = alpha;
581 Score local_beta = beta;
582 const Score alpha_orig = alpha;
583 const Score beta_orig = beta;
585 if (probe_transposition(state, remaining, result, table, keyer, local_alpha, local_beta, cached))
586 {
589 depth,
590 remaining,
591 0,
592 0,
593 0,
594 cached.value,
597 first_move_of(cached.principal_variation));
598 return cached;
599 }
600
601 if (domain_.is_terminal(state))
602 {
603 ++result.stats.terminal_states;
604 auto leaf = evaluate_leaf(domain_, state, result);
607 depth,
608 remaining,
609 0,
610 0,
611 0,
612 leaf.value,
614 local_beta);
615 store_transposition(state, remaining, result, table, keyer, leaf, TranspositionBound::Exact);
616 return leaf;
617 }
618
619 if (depth >= limits_.max_depth)
620 {
621 ++result.stats.pruned_by_depth;
622 auto leaf = evaluate_leaf(domain_, state, result);
625 depth,
626 remaining,
627 0,
628 0,
629 0,
630 leaf.value,
632 local_beta);
633 store_transposition(state, remaining, result, table, keyer, leaf, TranspositionBound::Exact);
634 return leaf;
635 }
636
638 {
639 ++result.stats.pruned_by_domain;
640 auto leaf = evaluate_leaf(domain_, state, result);
643 depth,
644 remaining,
645 0,
646 0,
647 0,
648 leaf.value,
650 local_beta);
651 store_transposition(state, remaining, result, table, keyer, leaf, TranspositionBound::Exact);
652 return leaf;
653 }
654
655 if (expansion_limit_reached(result))
656 {
657 auto leaf = evaluate_leaf(domain_, state, result);
660 depth,
661 remaining,
662 0,
663 0,
664 0,
665 leaf.value,
667 local_beta);
668 return leaf;
669 }
670
672 bool has_move = false;
673 bool has_best = false;
674 bool counted_expansion = false;
675
676 auto explore_move = [&](const Move &move) -> bool
677 {
678 if (stop_)
679 return false;
680
682 {
683 ++result.stats.expanded_states;
684 counted_expansion = true;
685 }
686
687 has_move = true;
690 bool applied = false;
691 try
692 {
693 domain_.apply(state, move);
694 applied = true;
695 child = search_node(
696 state, depth + 1, result, table, keyer, tracer, -local_beta, -local_alpha);
697 }
698 catch (...)
699 {
700 if (applied)
701 domain_.undo(state, move);
702 throw;
703 }
704 domain_.undo(state, move);
705
706 const Score candidate = (child.value == std::numeric_limits<Score>::lowest())
707 ? std::numeric_limits<Score>::max()
708 : -child.value;
709 if (not has_best or candidate > best.value)
710 {
711 best.value = candidate;
712 best.principal_variation = prepend_move(move, child.principal_variation);
713 has_best = true;
714 }
715
718
719 if (local_alpha >= local_beta)
720 {
721 ++result.stats.alpha_beta_cutoffs;
722 record_cutoff_move(depth, remaining, move);
725 depth,
726 remaining,
727 0,
728 0,
729 0,
730 best.value,
733 std::optional<Move>{move});
734 return false;
735 }
736
737 return not stop_;
738 };
739
740 if (ordering_active())
741 {
742 for (auto ordered_moves = collect_ordered_moves(state, depth, result);
743 const auto &ranked : ordered_moves)
744 if (not explore_move(ranked.move))
745 break;
746 }
747 else
748 (void) domain_.for_each_successor(state,
749 [&](const Move &move) -> bool
750 {
751 return explore_move(move);
752 });
753
754 if (not has_move)
755 {
756 ++result.stats.terminal_states;
757 auto leaf = evaluate_leaf(domain_, state, result);
760 depth,
761 remaining,
762 0,
763 0,
764 0,
765 leaf.value,
767 local_beta);
768 store_transposition(state, remaining, result, table, keyer, leaf, TranspositionBound::Exact);
769 return leaf;
770 }
771
772 auto bound = TranspositionBound::Exact;
773 if (best.value <= alpha_orig)
775 else if (best.value >= beta_orig)
777
780 depth,
781 remaining,
782 0,
783 0,
784 0,
785 best.value,
788 first_move_of(best.principal_variation));
789
790 store_transposition(state, remaining, result, table, keyer, best, bound);
791 return best;
792 }
793};
794
796template <AdversarialGameDomain Domain>
798 typename Domain::State initial_state,
800 SearchLimits limits = {})
801{
802 Alpha_Beta<Domain> engine(std::move(domain), policy, limits);
803 return engine.search(std::move(initial_state));
804}
805
807template <AdversarialGameDomain Domain, typename Tracer>
810 typename Domain::State initial_state,
811 Tracer &tracer,
813 SearchLimits limits = {})
814{
815 Alpha_Beta<Domain> engine(std::move(domain), policy, limits);
816 return engine.search(std::move(initial_state), tracer);
817}
818
820template <AdversarialGameDomain Domain, typename Table, typename Keyer>
823 typename Domain::State initial_state,
824 Table &table,
825 Keyer keyer,
827 SearchLimits limits = {})
828{
829 Alpha_Beta<Domain> engine(std::move(domain), policy, limits);
830 return engine.search(std::move(initial_state), table, keyer);
831}
832
834template <AdversarialGameDomain Domain, typename Table, typename Keyer, typename Tracer>
838 typename Domain::State initial_state,
839 Table &table,
840 Keyer keyer,
841 Tracer &tracer,
843 SearchLimits limits = {})
844{
845 Alpha_Beta<Domain> engine(std::move(domain), policy, limits);
846 return engine.search(std::move(initial_state), table, keyer, tracer);
847}
848
850template <AdversarialGameDomain Domain, typename Table>
854 typename Domain::State initial_state,
855 Table &table,
857 SearchLimits limits = {})
858{
859 Alpha_Beta<Domain> engine(std::move(domain), policy, limits);
860 return engine.search(std::move(initial_state), table);
861}
862
864template <AdversarialGameDomain Domain, typename Table, typename Tracer>
869 typename Domain::State initial_state,
870 Table &table,
871 Tracer &tracer,
873 SearchLimits limits = {})
874{
875 Alpha_Beta<Domain> engine(std::move(domain), policy, limits);
876 return engine.search(std::move(initial_state), table, tracer);
877}
878
880template <AdversarialGameDomain Domain, typename Tracer>
883 Domain domain,
884 typename Domain::State initial_state,
885 Tracer &tracer,
887 SearchLimits limits = {},
889{
890 using Move = typename Domain::Move;
891 using Score = typename Domain::Score;
892
893 ah_invalid_argument_if(limits.max_depth == Search_Unlimited)
894 << "iterative_deepening_alpha_beta_search requires a finite SearchLimits::max_depth";
895 ah_invalid_argument_if(options.depth_step == 0)
896 << "AdversarialIterativeDeepeningOptions::depth_step must be positive";
897 ah_invalid_argument_if(options.initial_depth > limits.max_depth)
898 << "AdversarialIterativeDeepeningOptions::initial_depth exceeds SearchLimits::max_depth";
899 ah_invalid_argument_if(options.aspiration.half_window < Score{})
900 << "AspirationWindow::half_window must be non-negative";
901
902 Alpha_Beta<Domain> engine(std::move(domain), policy, limits);
903 const Score full_alpha = adversarial_search_detail::score_floor<Score>();
904 const Score full_beta = adversarial_search_detail::score_ceiling<Score>();
905
906 return adversarial_search_detail::run_iterative_deepening<Move, Score>(
907 engine, policy, limits, options, tracer,
910 const size_t depth, const size_t iteration_index)
911 {
912 const bool use_aspiration = options.aspiration.enabled() and out.has_iterations();
914
915 Score center = Score{};
916 Score half_window = options.aspiration.half_window;
917 if (use_aspiration)
918 center = out.result.value;
919
920 for (;;)
921 {
922 const Score alpha
925 : full_alpha;
926 const Score beta
928 ? adversarial_search_detail::saturating_add(center, half_window)
929 : full_beta;
930
931 iteration.aspiration_alpha = alpha;
932 iteration.aspiration_beta = beta;
933 iteration.result = engine.search_with_window(initial_state, alpha, beta, tracer);
935 iteration.result.stats);
936
937 if (not use_aspiration or iteration.result.limit_reached())
938 break;
939
940 if (iteration.result.value <= alpha and alpha != full_alpha)
941 {
942 ++iteration.aspiration_researches;
943 ++out.aspiration_researches;
945 half_window, options.aspiration);
946 adversarial_search_detail::emit_trace<Move, Score>(
947 tracer,
949 0, depth, iteration_index, depth,
950 iteration.aspiration_researches,
951 iteration.result.value,
954 adversarial_search_detail::first_move_of(iteration.result.principal_variation));
955 continue;
956 }
957
958 if (iteration.result.value >= beta and beta != full_beta)
959 {
960 ++iteration.aspiration_researches;
961 ++out.aspiration_researches;
963 half_window, options.aspiration);
964 adversarial_search_detail::emit_trace<Move, Score>(
965 tracer,
967 0, depth, iteration_index, depth,
968 iteration.aspiration_researches,
969 iteration.result.value,
972 adversarial_search_detail::first_move_of(iteration.result.principal_variation));
973 continue;
974 }
975
976 break;
977 }
978 });
979}
980
982template <AdversarialGameDomain Domain, typename Table, typename Keyer, typename Tracer>
986 Domain domain,
987 typename Domain::State initial_state,
988 Table &table,
989 Keyer keyer,
990 Tracer &tracer,
992 SearchLimits limits = {},
994{
995 using Move = typename Domain::Move;
996 using Score = typename Domain::Score;
997
998 ah_invalid_argument_if(limits.max_depth == Search_Unlimited)
999 << "iterative_deepening_alpha_beta_search requires a finite SearchLimits::max_depth";
1000 ah_invalid_argument_if(options.depth_step == 0)
1001 << "AdversarialIterativeDeepeningOptions::depth_step must be positive";
1002 ah_invalid_argument_if(options.initial_depth > limits.max_depth)
1003 << "AdversarialIterativeDeepeningOptions::initial_depth exceeds SearchLimits::max_depth";
1004 ah_invalid_argument_if(options.aspiration.half_window < Score{})
1005 << "AspirationWindow::half_window must be non-negative";
1006
1007 Alpha_Beta<Domain> engine(std::move(domain), policy, limits);
1008 const Score full_alpha = adversarial_search_detail::score_floor<Score>();
1009 const Score full_beta = adversarial_search_detail::score_ceiling<Score>();
1010
1011 return adversarial_search_detail::run_iterative_deepening<Move, Score>(
1012 engine, policy, limits, options, tracer,
1015 const size_t depth, const size_t iteration_index)
1016 {
1017 const bool use_aspiration = options.aspiration.enabled() and out.has_iterations();
1019
1020 Score center = Score{};
1021 Score half_window = options.aspiration.half_window;
1022 if (use_aspiration)
1023 center = out.result.value;
1024
1025 for (;;)
1026 {
1027 const Score alpha
1030 : full_alpha;
1031 const Score beta
1033 ? adversarial_search_detail::saturating_add(center, half_window)
1034 : full_beta;
1035
1036 iteration.aspiration_alpha = alpha;
1037 iteration.aspiration_beta = beta;
1038 iteration.result
1039 = engine.search_with_window(initial_state, table, keyer, alpha, beta, tracer);
1041 iteration.result.stats);
1042
1043 if (not use_aspiration or iteration.result.limit_reached())
1044 break;
1045
1046 if (iteration.result.value <= alpha and alpha != full_alpha)
1047 {
1048 ++iteration.aspiration_researches;
1049 ++out.aspiration_researches;
1051 half_window, options.aspiration);
1052 adversarial_search_detail::emit_trace<Move, Score>(
1053 tracer,
1055 0, depth, iteration_index, depth,
1056 iteration.aspiration_researches,
1057 iteration.result.value,
1059 adversarial_search_detail::saturating_add(center, half_window),
1060 adversarial_search_detail::first_move_of(iteration.result.principal_variation));
1061 continue;
1062 }
1063
1064 if (iteration.result.value >= beta and beta != full_beta)
1065 {
1066 ++iteration.aspiration_researches;
1067 ++out.aspiration_researches;
1069 half_window, options.aspiration);
1070 adversarial_search_detail::emit_trace<Move, Score>(
1071 tracer,
1073 0, depth, iteration_index, depth,
1074 iteration.aspiration_researches,
1075 iteration.result.value,
1077 adversarial_search_detail::saturating_add(center, half_window),
1078 adversarial_search_detail::first_move_of(iteration.result.principal_variation));
1079 continue;
1080 }
1081
1082 break;
1083 }
1084 });
1085}
1086
1088template <AdversarialGameDomain Domain, typename Table, typename Tracer>
1093 Domain domain,
1094 typename Domain::State initial_state,
1095 Table &table,
1096 Tracer &tracer,
1098 SearchLimits limits = {},
1100{
1101 Alpha_Beta<Domain> engine(std::move(domain), policy, limits);
1103 std::move(initial_state),
1104 table,
1105 [&engine](const typename Domain::State &state)
1106 {
1107 return engine.domain().state_key(state);
1108 },
1109 tracer,
1110 policy,
1111 limits,
1112 options);
1113}
1114
1116template <AdversarialGameDomain Domain>
1128
1130template <AdversarialGameDomain Domain, typename Table, typename Keyer>
1133 Domain domain,
1134 typename Domain::State initial_state,
1135 Table &table,
1136 Keyer keyer,
1138 SearchLimits limits = {},
1140{
1143 std::move(domain), std::move(initial_state), table, keyer, tracer, policy, limits, options);
1144}
1145
1147template <AdversarialGameDomain Domain, typename Table>
1151 Domain domain,
1152 typename Domain::State initial_state,
1153 Table &table,
1155 SearchLimits limits = {},
1157{
1160 std::move(domain), std::move(initial_state), table, tracer, policy, limits, options);
1161}
1162
1163} // end namespace Aleph
1164
1165# endif // ALPHA_BETA_H
Negamax search for two-player zero-sum turn-based games.
Exception handling system with formatted messages for Aleph-w.
#define ah_invalid_argument_unless(C)
Throws std::invalid_argument if condition does NOT hold.
Definition ah-errors.H:655
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:639
Adversarial search engine with Alpha-Beta pruning.
Definition Alpha_Beta.H:71
typename Domain::Move Move
Move type.
Definition Alpha_Beta.H:78
void set_policy(const ExplorationPolicy &policy) noexcept
Replace the exploration policy for future runs.
Definition Alpha_Beta.H:136
and AdversarialSearchTracer< Tracer, Move, Score > Result search(State initial_state, Table &table, Keyer keyer, Tracer &tracer)
Execute Alpha-Beta with TT/keyer and tracing.
Definition Alpha_Beta.H:209
static size_t history_bonus(const size_t depth, const size_t remaining) noexcept
Definition Alpha_Beta.H:351
and AdversarialTranspositionMemo< Table, typename D_::State_Key, Move, Score > Result search_with_window(State initial_state, Table &table, const Score alpha, const Score beta)
Execute Alpha-Beta with domain-provided key and explicit root window.
Definition Alpha_Beta.H:287
Alpha_Beta(Domain domain, ExplorationPolicy policy=default_policy(), const SearchLimits &limits={})
Build an Alpha-Beta engine bound to one game domain.
Definition Alpha_Beta.H:103
void set_limits(const SearchLimits &limits) noexcept
Replace the hard limits for future runs.
Definition Alpha_Beta.H:142
Result search_with_window(State initial_state, const Score alpha, const Score beta)
Execute Alpha-Beta with an explicit root window.
Definition Alpha_Beta.H:177
Domain Domain_Type
Type of the problem domain.
Definition Alpha_Beta.H:74
Array< RankedMove< Move, Score > > collect_ordered_moves(State &state, const size_t depth, Result &result)
Definition Alpha_Beta.H:376
static ExplorationPolicy default_policy() noexcept
Return the default exploration policy for Alpha-Beta.
Definition Alpha_Beta.H:97
const SearchLimits & limits() const noexcept
Current hard limits.
Definition Alpha_Beta.H:130
Result search_impl(State initial_state, Table &table, Keyer &keyer, Tracer &tracer, const Score alpha, const Score beta)
Definition Alpha_Beta.H:455
adversarial_search_detail::NodeEvaluation< Move, Score > search_node(State &state, const size_t depth, Result &result, Table &table, Keyer &keyer, Tracer &tracer, const Score alpha, const Score beta)
Definition Alpha_Beta.H:559
and AdversarialTranspositionMemo< Table, typename D_::State_Key, Move, Score > Result search(State initial_state, Table &table)
Execute Alpha-Beta with a transposition table using domain().state_key().
Definition Alpha_Beta.H:247
const Domain & domain() const noexcept
Read-only access to the bound game domain.
Definition Alpha_Beta.H:112
bool ordering_active() const noexcept
Definition Alpha_Beta.H:325
Killer_Table killer_moves_
Definition Alpha_Beta.H:322
typename Domain::Score Score
Score type used for board evaluation.
Definition Alpha_Beta.H:80
History_Table history_moves_
Definition Alpha_Beta.H:323
bool expansion_limit_reached(Result &result)
Definition Alpha_Beta.H:487
bool probe_transposition(State &state, const size_t remaining, Result &result, Table &table, Keyer &keyer, Score &alpha, Score &beta, adversarial_search_detail::NodeEvaluation< Move, Score > &out)
Definition Alpha_Beta.H:512
void validate_ordering_configuration() const
Definition Alpha_Beta.H:331
static constexpr bool supports_best_first
Compile-time marker: Alpha_Beta only supports Depth_First strategy.
Definition Alpha_Beta.H:94
SearchLimits limits_
Definition Alpha_Beta.H:320
typename adversarial_search_detail::History_Table_Selector< Domain >::Type History_Table
History heuristic table type (sparse or no-op depending on domain).
Definition Alpha_Beta.H:86
void record_cutoff_move(const size_t depth, const size_t remaining, const Move &move)
Definition Alpha_Beta.H:366
size_t move_history_score(const Move &move) const noexcept
Definition Alpha_Beta.H:357
Domain & domain() noexcept
Mutable access to the bound game domain.
Definition Alpha_Beta.H:118
typename Domain::State State
Concrete search state type.
Definition Alpha_Beta.H:76
Result search(State initial_state, Table &table, Keyer keyer)
Execute Alpha-Beta with a transposition table and explicit keyer.
Definition Alpha_Beta.H:199
Result search(State initial_state)
Execute Alpha-Beta from initial_state.
Definition Alpha_Beta.H:155
Result search(State initial_state, Tracer &tracer)
Execute Alpha-Beta from initial_state while emitting trace events.
Definition Alpha_Beta.H:164
ExplorationPolicy policy_
Definition Alpha_Beta.H:319
void clear_ordering_heuristics()
Definition Alpha_Beta.H:345
const ExplorationPolicy & policy() const noexcept
Current exploration policy.
Definition Alpha_Beta.H:124
and AdversarialTranspositionMemo< Table, typename D_::State_Key, Move, Score > and AdversarialSearchTracer< Tracer, Move, Score > Result search(State initial_state, Table &table, Tracer &tracer)
Execute Alpha-Beta with domain-provided key and tracing.
Definition Alpha_Beta.H:268
and AdversarialSearchTracer< Tracer, Move, Score > Result search_with_window(State initial_state, Table &table, Keyer keyer, const Score alpha, const Score beta, Tracer &tracer)
Execute Alpha-Beta with TT/keyer, root window and tracing.
Definition Alpha_Beta.H:233
void store_transposition(State &state, const size_t remaining, Result &result, Table &table, Keyer &keyer, const adversarial_search_detail::NodeEvaluation< Move, Score > &value, const TranspositionBound bound)
Definition Alpha_Beta.H:499
and AdversarialTranspositionMemo< Table, typename D_::State_Key, Move, Score > and AdversarialSearchTracer< Tracer, Move, Score > Result search_with_window(State initial_state, Table &table, const Score alpha, const Score beta, Tracer &tracer)
Execute Alpha-Beta with domain-provided key, root window and tracing.
Definition Alpha_Beta.H:306
Result search_with_window(State initial_state, Table &table, Keyer keyer, const Score alpha, const Score beta)
Execute Alpha-Beta with TT/keyer and an explicit root window.
Definition Alpha_Beta.H:222
Result search_with_window(State initial_state, const Score alpha, const Score beta, Tracer &tracer)
Execute Alpha-Beta with an explicit root window and tracing.
Definition Alpha_Beta.H:186
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:348
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
Generic linear hash table.
Two-slot killer heuristic table indexed by search depth.
static ExplorationPolicy default_policy() noexcept
Return the default exploration policy for adversarial search.
Definition Negamax.H:862
Concept for explicit state-key providers used by adversarial TT overloads.
Definition Negamax.H:254
Concept for adversarial-search trace sinks.
Definition Negamax.H:246
Concept for memo tables storing adversarial-search entries.
Definition Negamax.H:156
Concept for adversarial-search domains that can estimate a child score without modifying state (incre...
Optional concept for domains that can key moves for history heuristics.
Generic concept for domains that can expose a stable state key.
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
Definition gmpfrxx.h:4060
std::optional< Move > first_move_of(const SearchPath< Move > &path)
Definition Negamax.H:595
constexpr size_t remaining_depth(const SearchLimits &limits, const size_t depth) noexcept
Definition Negamax.H:510
constexpr Score saturating_add(const Score value, const Score delta) noexcept
Definition Negamax.H:615
constexpr Score grow_aspiration_half_window(const Score current, const AspirationWindow< Score > &window) noexcept
Definition Negamax.H:626
constexpr Score saturating_subtract(const Score value, const Score delta) noexcept
Definition Negamax.H:604
void emit_trace(Tracer &tracer, const AdversarialTraceEventKind kind, const size_t depth, const size_t remaining, const size_t iteration=0, const size_t horizon=0, const size_t aspiration_retries=0, const Score value=Score{}, const Score alpha=Score{}, const Score beta=Score{}, const std::optional< Move > &move=std::nullopt, const SearchPath< Move > *principal_variation=nullptr)
Definition Negamax.H:565
void accumulate_adversarial_stats(AdversarialSearchStats &dst, const AdversarialSearchStats &src) noexcept
Definition Negamax.H:554
SearchPath< Move > prepend_move(const Move &move, const SearchPath< Move > &tail)
Definition Negamax.H:480
auto evaluate_leaf(Domain &domain, const typename Domain::State &state, Result &result)
Definition Negamax.H:498
bool should_prune_state(Domain &domain, const typename Domain::State &state, const size_t depth)
Dispatch helper for the optional should_prune hook.
void register_visit(const size_t depth, Result &result)
Update visit counters and max-depth statistic.
bool expansion_limit_reached(Result &result, const SearchLimits &limits)
Check whether the expansion limit has been reached.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
void sort_ranked_moves(Array< RankedMove< Move, Priority > > &moves, BetterPriority better_priority, const bool prefer_killer, const bool prefer_history)
Sort one materialized move batch using priority and optional hooks.
TranspositionBound
Stored bound classification for memoized search entries.
@ Upper_Bound
Value is an upper bound (fail-low style).
@ Exact
Exact value for the stored search configuration.
@ Lower_Bound
Value is a lower bound (fail-high style).
constexpr size_t Search_Unlimited
Sentinel used by SearchLimits to mean "no bound".
@ Exhausted
Search space within the configured bounds was exhausted.
@ NotStarted
Search object exists but no traversal has run yet.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
auto iterative_deepening_alpha_beta_search(Domain domain, typename Domain::State initial_state, Tracer &tracer, ExplorationPolicy policy=Alpha_Beta< Domain >::default_policy(), SearchLimits limits={}, AdversarialIterativeDeepeningOptions< typename Domain::Score > options={})
Iterative deepening over Alpha-Beta with optional aspiration windows.
Definition Alpha_Beta.H:882
auto alpha_beta_search(Domain domain, typename Domain::State initial_state, ExplorationPolicy policy=Alpha_Beta< Domain >::default_policy(), SearchLimits limits={})
Convenience wrapper for one-shot Alpha-Beta search.
Definition Alpha_Beta.H:797
@ Alpha_Beta_Cutoff
One Alpha-Beta fail-high cutoff occurred.
@ Expansion_Limit
Search stopped because the expansion limit was hit.
@ Aspiration_Retry
An aspiration window failed and was widened.
@ Depth_Cutoff
Search stopped at the configured horizon.
@ Transposition_Hit
One cached transposition entry was reused.
@ Enter_Node
One search node has just been entered.
@ Exit_Node
One search node finished with an exact local result.
@ Domain_Prune
Domain-side pruning hook discarded the state.
@ Terminal_Node
A terminal state was evaluated.
double search_elapsed_ms(const SearchClock::duration &duration) noexcept
@ Estimated_Bound
Rank successors by an optimistic child bound.
@ Domain
Preserve the order emitted by for_each_successor().
@ Estimated_Score
Rank successors by a cheap heuristic score estimate.
STL namespace.
static struct argp_option options[]
Definition ntreepic.C:1886
Result of one iterative-deepening iteration.
Definition Negamax.H:338
size_t aspiration_researches
Number of retries triggered by aspiration failure.
Definition Negamax.H:341
bool used_aspiration_window
Whether the iteration started from a bounded window.
Definition Negamax.H:340
Score aspiration_beta
Final upper bound used in the last search attempt.
Definition Negamax.H:343
AdversarialSearchResult< Move, Score > result
Final result returned for this depth.
Definition Negamax.H:344
AdversarialSearchStats total_stats
Aggregate cost of all attempts at this depth.
Definition Negamax.H:345
Score aspiration_alpha
Final lower bound used in the last search attempt.
Definition Negamax.H:342
Controls for adversarial iterative deepening.
Definition Negamax.H:329
Aggregate result of adversarial iterative deepening.
Definition Negamax.H:351
Result of one adversarial search execution.
Definition Negamax.H:167
SearchLimits limits
Limits used for the run.
Definition Negamax.H:173
AdversarialSearchStats stats
Collected adversarial-search statistics.
Definition Negamax.H:174
Score value
Root score from the current player's perspective.
Definition Negamax.H:175
ExplorationPolicy policy
Exploration policy used during the run.
Definition Negamax.H:172
SearchStatus status
Final execution state.
Definition Negamax.H:171
SearchPath< Move > principal_variation
Best line found from the root.
Definition Negamax.H:176
size_t alpha_beta_cutoffs
Number of cutoffs performed by Alpha-Beta.
Definition Negamax.H:76
size_t heuristic_evaluations
Number of calls to evaluate(state).
Definition Negamax.H:75
TranspositionStats transpositions
Transposition-table usage during this run.
Definition Negamax.H:77
Exploration controls shared across engines.
Strategy strategy
Traversal strategy.
MoveOrderingMode move_ordering
Successor-ordering mode.
@ Depth_First
Recursive depth-first traversal (all engines).
bool use_history_heuristic
Enable experimental history heuristic where supported.
bool use_killer_moves
Enable experimental killer heuristic where supported.
size_t priority_estimates
Number of score/bound estimates computed for ordering.
size_t killer_hits
Moves promoted because they matched a killer slot.
size_t history_hits
Moves promoted because they had non-zero history.
size_t ordered_moves
Number of individual moves considered by ordering.
size_t ordered_batches
Number of successor batches materialized and ordered.
Default no-op tracer used when the caller does not request tracing.
Definition Negamax.H:262
Null history heuristic hook used when a domain has no move key.
One move plus the metadata used by ordering comparators.
Move move
The candidate move.
Hard bounds applied by the search engine.
size_t max_solutions
Maximum accepted solutions.
size_t max_depth
Maximum expansion depth.
size_t pruned_by_domain
States discarded by domain-side pruning.
size_t terminal_states
Non-solution terminal states cut by the domain.
double elapsed_ms
Wall-clock time spent inside the search call.
MoveOrderingStats move_ordering
Successor-ordering activity for this run.
size_t pruned_by_depth
States not expanded due to max depth.
size_t generated_successors
Number of successor moves emitted.
size_t expanded_states
Number of non-terminal states expanded.
size_t cutoffs
Number of search cutoffs enabled by cached data.
static mt19937 engine