Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
state_search_benchmark.cc
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
44#include <cmath>
45#include <cstdint>
46#include <cstdlib>
47#include <iomanip>
48#include <iostream>
49#include <sstream>
50#include <stdexcept>
51#include <string>
52
53#include <Knapsack.H>
54#include <State_Search.H>
55#include <tpl_array.H>
56#include <tpl_sort_utils.H>
57
58using Aleph::Array;
60namespace Search = Aleph::Search;
61
62namespace
63{
64
65volatile long double benchmark_sink = 0.0L;
66
67struct Options
68{
69 bool validate = false;
70 bool json = false;
71 size_t repetitions = 32;
72 size_t warmup = 1;
73 size_t samples = 7;
74};
75
76struct TimingStats
77{
78 double mean_ms = 0.0;
79 double median_ms = 0.0;
80 double min_ms = 0.0;
81 double max_ms = 0.0;
82 double stddev_ms = 0.0;
83};
84
85struct BenchmarkRow
86{
87 std::string family;
88 std::string scenario;
89 std::string variant;
90 TimingStats timing;
91
92 size_t visited_states = 0;
93 size_t expanded_states = 0;
94 size_t generated_successors = 0;
95 size_t solutions_found = 0;
96 size_t terminal_states = 0;
97 size_t pruned_by_depth = 0;
98 size_t pruned_by_domain = 0;
99 size_t pruned_by_bound = 0;
100 size_t incumbent_updates = 0;
101 size_t alpha_beta_cutoffs = 0;
102 size_t tt_hits = 0;
103 size_t tt_cutoffs = 0;
104 size_t tt_stores = 0;
105 size_t ordered_batches = 0;
106 size_t priority_estimates = 0;
107 size_t killer_hits = 0;
108 size_t history_hits = 0;
109
110 double result_value = 0.0;
111 bool has_best_move = false;
112 size_t best_move = 0;
113};
114
115[[nodiscard]] Options
116parse_options(const int argc, char **argv)
117{
118 Options options;
119
120 for (int i = 1; i < argc; ++i)
121 {
122 const std::string arg = argv[i];
123 if (arg == "--validate")
124 options.validate = true;
125 else if (arg == "--json")
126 options.json = true;
127 else if (arg == "--repetitions" and i + 1 < argc)
128 options.repetitions = static_cast<size_t>(std::stoull(argv[++i]));
129 else if (arg == "--warmup" and i + 1 < argc)
130 options.warmup = static_cast<size_t>(std::stoull(argv[++i]));
131 else if (arg == "--samples" and i + 1 < argc)
132 options.samples = static_cast<size_t>(std::stoull(argv[++i]));
133 else if (arg == "--help")
134 {
135 std::cout
136 << "Usage: state_search_benchmark [--validate] [--json]"
137 << " [--repetitions N] [--warmup N] [--samples N]\n";
138 std::exit(0);
139 }
140 else
141 {
142 std::cerr << "Unknown option: " << arg << '\n';
143 std::exit(1);
144 }
145 }
146
147 if (options.repetitions == 0 or options.samples == 0)
148 {
149 std::cerr << "Repetitions and samples must be positive\n";
150 std::exit(1);
151 }
152
153 return options;
154}
155
156[[nodiscard]] TimingStats
158{
159 TimingStats stats;
160 if (sample_ms.is_empty())
161 return stats;
162
165
166 double sum = 0.0;
167 for (const double value : sample_ms)
168 sum += value;
169
170 stats.mean_ms = sum/static_cast<double>(sample_ms.size());
171 stats.min_ms = sorted[0];
172 stats.max_ms = sorted[sorted.size() - 1];
173
174 if (sorted.size() % 2 == 0)
175 stats.median_ms = (sorted[sorted.size()/2 - 1] + sorted[sorted.size()/2])/2.0;
176 else
177 stats.median_ms = sorted[sorted.size()/2];
178
179 if (sample_ms.size() > 1)
180 {
181 double sq = 0.0;
182 for (const double value : sample_ms)
183 {
184 const double delta = value - stats.mean_ms;
185 sq += delta*delta;
186 }
187
188 stats.stddev_ms = std::sqrt(sq/static_cast<double>(sample_ms.size() - 1));
189 }
190
191 return stats;
192}
193
194template <typename Runner, typename Capture>
195[[nodiscard]] BenchmarkRow
196measure_case(const std::string &family,
197 const std::string &scenario,
198 const std::string &variant,
199 const Options &options,
200 Runner runner,
202{
203 for (size_t i = 0; i < options.warmup; ++i)
204 (void) runner();
205
206 BenchmarkRow row;
207 row.family = family;
208 row.scenario = scenario;
209 row.variant = variant;
210
212 sample_ms.reserve(options.samples);
213
214 for (size_t sample = 0; sample < options.samples; ++sample)
215 {
216 double total_ms = 0.0;
217 for (size_t rep = 0; rep < options.repetitions; ++rep)
218 {
219 auto result = runner();
220 capture(row, result);
221 total_ms += result.stats.elapsed_ms;
222 benchmark_sink += static_cast<long double>(result.stats.visited_states +
223 result.stats.generated_successors +
224 result.stats.expanded_states);
225 }
226
227 sample_ms.append(total_ms/static_cast<double>(options.repetitions));
228 }
229
230 row.timing = compute_timing_stats(sample_ms);
231 return row;
232}
233
234[[nodiscard]] const BenchmarkRow *
235find_row(const Array<BenchmarkRow> &rows,
236 const std::string &scenario,
237 const std::string &variant)
238{
239 for (size_t i = 0; i < rows.size(); ++i)
240 if (rows[i].scenario == scenario and rows[i].variant == variant)
241 return &rows[i];
242
243 return nullptr;
244}
245
246struct NQueensState
247{
248 size_t n = 0;
249 size_t row = 0;
250 Array<int> queens;
251 Array<unsigned char> used_columns;
252 Array<unsigned char> used_diag_down;
253 Array<unsigned char> used_diag_up;
254
255 explicit NQueensState(const size_t size = 0)
256 : n(size),
257 row(0),
258 queens(size, -1),
259 used_columns(size, 0),
260 used_diag_down(size == 0 ? 0 : 2*size - 1, 0),
261 used_diag_up(size == 0 ? 0 : 2*size - 1, 0)
262 {
263 // empty
264 }
265};
266
267struct NQueensDomain
268{
269 struct Move
270 {
271 size_t col = 0;
272 };
273
274 using State = NQueensState;
275
276 size_t n = 0;
277
278 bool is_goal(const State &state) const
279 {
280 return state.row == n;
281 }
282
283 void apply(State &state, const Move &move) const
284 {
285 const size_t row = state.row;
286 const size_t down = row + state.n - 1 - move.col;
287 const size_t up = row + move.col;
288
289 state.queens[row] = static_cast<int>(move.col);
290 state.used_columns[move.col] = 1;
291 state.used_diag_down[down] = 1;
292 state.used_diag_up[up] = 1;
293 ++state.row;
294 }
295
296 void undo(State &state, const Move &move) const
297 {
298 --state.row;
299 const size_t row = state.row;
300 const size_t down = row + state.n - 1 - move.col;
301 const size_t up = row + move.col;
302
303 state.queens[row] = -1;
304 state.used_columns[move.col] = 0;
305 state.used_diag_down[down] = 0;
306 state.used_diag_up[up] = 0;
307 }
308
309 template <typename Visitor>
310 bool for_each_successor(const State &state, Visitor visit) const
311 {
312 if (state.row >= n)
313 return true;
314
315 for (size_t col = 0; col < n; ++col)
316 {
317 const size_t down = state.row + state.n - 1 - col;
318 const size_t up = state.row + col;
319 if (state.used_columns[col] or state.used_diag_down[down] or state.used_diag_up[up])
320 continue;
321
322 if (not visit(Move{col}))
323 return false;
324 }
325
326 return true;
327 }
328};
329
330struct KnapsackState
331{
332 size_t index = 0;
333 int weight = 0;
334 double value = 0.0;
336
337 explicit KnapsackState(const size_t n = 0)
338 : chosen(n, 0)
339 {
340 // empty
341 }
342};
343
344class KnapsackDomain
345{
346public:
347 struct Move
348 {
349 bool take = false;
350 };
351
352 using State = KnapsackState;
353 using Objective = double;
354
356 const int capacity,
357 const bool take_first = true)
358 : items_(items), capacity_(capacity), take_first_(take_first)
359 {
360 // empty
361 }
362
363 bool is_complete(const State &state) const
364 {
365 return state.index == items_.size();
366 }
367
368 Objective objective_value(const State &state) const
369 {
370 return state.value;
371 }
372
373 Objective bound(const State &state) const
374 {
375 Objective optimistic = state.value;
376 int remaining = capacity_ - state.weight;
377
378 for (size_t i = state.index; i < items_.size() and remaining > 0; ++i)
379 if (items_[i].weight <= remaining)
380 {
381 optimistic += items_[i].value;
382 remaining -= items_[i].weight;
383 }
384 else
385 {
386 optimistic += items_[i].value*
387 (static_cast<Objective>(remaining)/static_cast<Objective>(items_[i].weight));
388 break;
389 }
390
391 return optimistic;
392 }
393
394 void apply(State &state, const Move &move) const
395 {
396 if (move.take)
397 {
398 state.weight += items_[state.index].weight;
399 state.value += items_[state.index].value;
400 state.chosen[state.index] = 1;
401 }
402 else
403 state.chosen[state.index] = 0;
404
405 ++state.index;
406 }
407
408 void undo(State &state, const Move &move) const
409 {
410 --state.index;
411 if (move.take)
412 {
413 state.weight -= items_[state.index].weight;
414 state.value -= items_[state.index].value;
415 }
416
417 state.chosen[state.index] = 0;
418 }
419
420 template <typename Visitor>
421 bool for_each_successor(const State &state, Visitor visit) const
422 {
423 if (state.index >= items_.size())
424 return true;
425
426 const bool can_take = state.weight + items_[state.index].weight <= capacity_;
427
428 if (take_first_)
429 {
430 if (can_take and not visit(Move{true}))
431 return false;
432
433 return visit(Move{false});
434 }
435
436 if (not visit(Move{false}))
437 return false;
438
439 if (can_take)
440 return visit(Move{true});
441
442 return true;
443 }
444
445private:
447 int capacity_ = 0;
448 bool take_first_ = true;
449};
450
451struct TicTacToeState
452{
453 Array<int> board;
454 int player = 1;
455 size_t moves_played = 0;
456
457 TicTacToeState()
458 : board(size_t(9), 0)
459 {
460 // empty
461 }
462};
463
464class TicTacToeDomain
465{
466public:
467 struct Move
468 {
469 size_t cell = 0;
470
471 [[nodiscard]] bool operator == (const Move &) const noexcept = default;
472 };
473
474 using State = TicTacToeState;
475 using Score = int;
476 using State_Key = std::uint64_t;
477 using Move_Key = size_t;
478
479 bool is_terminal(const State &state) const
480 {
481 return winner(state) != 0 or state.moves_played == 9;
482 }
483
484 Score evaluate(const State &state) const
485 {
486 const int win = winner(state);
487 if (win != 0)
488 return win == state.player ? 100 : -100;
489
490 if (state.moves_played == 9)
491 return 0;
492
493 return heuristic(state, state.player) - heuristic(state, -state.player);
494 }
495
496 [[nodiscard]] State_Key state_key(const State &state) const noexcept
497 {
498 State_Key key = static_cast<State_Key>(state.player > 0 ? 1 : 2);
499 for (size_t i = 0; i < state.board.size(); ++i)
500 {
501 key *= 3;
502 if (state.board[i] == 1)
503 key += 1;
504 else if (state.board[i] == -1)
505 key += 2;
506 }
507
508 return key;
509 }
510
511 [[nodiscard]] Move_Key move_key(const Move &move) const noexcept
512 {
513 return move.cell;
514 }
515
516 void apply(State &state, const Move &move) const
517 {
518 state.board[move.cell] = state.player;
519 state.player = -state.player;
520 ++state.moves_played;
521 }
522
523 void undo(State &state, const Move &move) const
524 {
525 --state.moves_played;
526 state.player = -state.player;
527 state.board[move.cell] = 0;
528 }
529
530 template <typename Visitor>
531 bool for_each_successor(const State &state, Visitor visit) const
532 {
533 static constexpr size_t order[] = {1, 3, 5, 7, 0, 2, 6, 8, 4};
534
535 for (const size_t cell : order)
536 if (state.board[cell] == 0 and not visit(Move{cell}))
538
539 return true;
540 }
541
542private:
543 [[nodiscard]] static int winner(const State &state) noexcept
544 {
545 static constexpr size_t lines[8][3] = {
546 {0, 1, 2}, {3, 4, 5}, {6, 7, 8},
547 {0, 3, 6}, {1, 4, 7}, {2, 5, 8},
548 {0, 4, 8}, {2, 4, 6}
549 };
550
551 for (const auto &line : lines)
552 {
553 const int a = state.board[line[0]];
554 if (a != 0 and a == state.board[line[1]] and a == state.board[line[2]])
555 return a;
556 }
557
558 return 0;
559 }
560
561 [[nodiscard]] static Score heuristic(const State &state, const int player) noexcept
562 {
563 static constexpr size_t lines[8][3] = {
564 {0, 1, 2}, {3, 4, 5}, {6, 7, 8},
565 {0, 3, 6}, {1, 4, 7}, {2, 5, 8},
566 {0, 4, 8}, {2, 4, 6}
567 };
568
569 Score total = 0;
570 for (const auto &line : lines)
571 {
572 size_t mine = 0;
573 size_t empty = 0;
574 bool blocked = false;
575
576 for (const size_t cell : line)
577 if (state.board[cell] == player)
578 ++mine;
579 else if (state.board[cell] == 0)
580 ++empty;
581 else
582 {
583 blocked = true;
584 break;
585 }
586
587 if (blocked)
588 continue;
589
590 if (mine == 2 and empty == 1)
591 total += 10;
592 else if (mine == 1 and empty == 2)
593 total += 1;
594 }
595
596 return total;
597 }
598};
599
602{
603 return {
604 {2, 30.0}, {3, 42.0}, {4, 52.0}, {5, 60.0},
605 {6, 66.0}, {7, 70.0}, {8, 72.0}, {9, 72.0},
606 {10, 70.0}, {11, 66.0}, {12, 60.0}, {13, 52.0}
607 };
608}
609
611benchmark_nqueens(const Options &options)
612{
613 constexpr size_t n = 8;
615 policy.stop_at_first_solution = false;
616
618 rows.append(measure_case(
619 "backtracking",
620 "nqueens-8",
621 "depth_first",
622 options,
623 [&]()
624 {
626 limits.max_depth = n;
627 return Search::backtracking_search(NQueensDomain{n}, NQueensState(n), policy, limits);
628 },
629 [](BenchmarkRow &row, const auto &result)
630 {
631 row.visited_states = result.stats.visited_states;
632 row.expanded_states = result.stats.expanded_states;
633 row.generated_successors = result.stats.generated_successors;
634 row.solutions_found = result.stats.solutions_found;
635 row.terminal_states = result.stats.terminal_states;
636 row.pruned_by_depth = result.stats.pruned_by_depth;
637 row.pruned_by_domain = result.stats.pruned_by_domain;
638 row.result_value = static_cast<double>(result.stats.solutions_found);
639 }));
640
641 return rows;
642}
643
645benchmark_knapsack(const Options &options)
646{
647 const auto items = make_knapsack_items();
648 constexpr int capacity = 35;
649 const double optimum = Aleph::knapsack_01_value<double, int>(items, capacity);
650
652
653 rows.append(measure_case(
654 "branch_and_bound",
655 "knapsack-medium",
656 "depth_first",
657 options,
658 [&]()
659 {
662 limits.max_depth = items.size();
663 return Search::branch_and_bound_search(KnapsackDomain(items, capacity, false),
664 KnapsackState(items.size()),
665 policy,
666 limits);
667 },
668 [optimum](BenchmarkRow &row, const auto &result)
669 {
670 row.visited_states = result.stats.visited_states;
671 row.expanded_states = result.stats.expanded_states;
672 row.generated_successors = result.stats.generated_successors;
673 row.solutions_found = result.stats.solutions_found;
674 row.terminal_states = result.stats.terminal_states;
675 row.pruned_by_depth = result.stats.pruned_by_depth;
676 row.pruned_by_domain = result.stats.pruned_by_domain;
677 row.pruned_by_bound = result.stats.pruned_by_bound;
678 row.incumbent_updates = result.stats.incumbent_updates;
679 row.result_value = result.incumbent.best_value();
680 benchmark_sink += row.result_value + optimum;
681 }));
682
683 rows.append(measure_case(
684 "branch_and_bound",
685 "knapsack-medium",
686 "depth_first_bound_ordered",
687 options,
688 [&]()
689 {
692 policy.stop_at_first_solution = false;
693
695 limits.max_depth = items.size();
696 return Search::branch_and_bound_search(KnapsackDomain(items, capacity, false),
697 KnapsackState(items.size()),
698 policy,
699 limits);
700 },
701 [](BenchmarkRow &row, const auto &result)
702 {
703 row.visited_states = result.stats.visited_states;
704 row.expanded_states = result.stats.expanded_states;
705 row.generated_successors = result.stats.generated_successors;
706 row.solutions_found = result.stats.solutions_found;
707 row.terminal_states = result.stats.terminal_states;
708 row.pruned_by_depth = result.stats.pruned_by_depth;
709 row.pruned_by_domain = result.stats.pruned_by_domain;
710 row.pruned_by_bound = result.stats.pruned_by_bound;
711 row.incumbent_updates = result.stats.incumbent_updates;
712 row.ordered_batches = result.stats.move_ordering.ordered_batches;
713 row.priority_estimates = result.stats.move_ordering.priority_estimates;
714 row.result_value = result.incumbent.best_value();
715 }));
716
717 rows.append(measure_case(
718 "branch_and_bound",
719 "knapsack-medium",
720 "best_first",
721 options,
722 [&]()
723 {
725 policy.strategy = Search::ExplorationPolicy::Strategy::Best_First;
726 policy.stop_at_first_solution = false;
727
729 limits.max_depth = items.size();
730 return Search::branch_and_bound_search(KnapsackDomain(items, capacity, false),
731 KnapsackState(items.size()),
732 policy,
733 limits);
734 },
735 [](BenchmarkRow &row, const auto &result)
736 {
737 row.visited_states = result.stats.visited_states;
738 row.expanded_states = result.stats.expanded_states;
739 row.generated_successors = result.stats.generated_successors;
740 row.solutions_found = result.stats.solutions_found;
741 row.terminal_states = result.stats.terminal_states;
742 row.pruned_by_depth = result.stats.pruned_by_depth;
743 row.pruned_by_domain = result.stats.pruned_by_domain;
744 row.pruned_by_bound = result.stats.pruned_by_bound;
745 row.incumbent_updates = result.stats.incumbent_updates;
746 row.result_value = result.incumbent.best_value();
747 }));
748
749 return rows;
750}
751
753benchmark_tictactoe(const Options &options)
754{
755 TicTacToeState root;
756 using TT = Search::GameTT<TicTacToeDomain::State_Key,
757 TicTacToeDomain::Move,
758 TicTacToeDomain::Score>;
759
761
762 rows.append(measure_case(
763 "adversarial",
764 "tictactoe-full",
765 "alpha_beta_plain",
766 options,
767 [&]()
768 {
769 return Search::alpha_beta_search(TicTacToeDomain{}, root);
770 },
771 [](BenchmarkRow &row, const auto &result)
772 {
773 row.visited_states = result.stats.visited_states;
774 row.expanded_states = result.stats.expanded_states;
775 row.generated_successors = result.stats.generated_successors;
776 row.terminal_states = result.stats.terminal_states;
777 row.pruned_by_depth = result.stats.pruned_by_depth;
778 row.pruned_by_domain = result.stats.pruned_by_domain;
779 row.alpha_beta_cutoffs = result.stats.alpha_beta_cutoffs;
780 row.result_value = static_cast<double>(result.value);
781 row.has_best_move = result.has_principal_variation();
782 row.best_move = result.first_move().cell;
783 }));
784
785 rows.append(measure_case(
786 "adversarial",
787 "tictactoe-full",
788 "alpha_beta_ordered",
789 options,
790 [&]()
791 {
794 policy.use_killer_moves = true;
795 policy.use_history_heuristic = true;
796 return Search::alpha_beta_search(TicTacToeDomain{}, root, policy);
797 },
798 [](BenchmarkRow &row, const auto &result)
799 {
800 row.visited_states = result.stats.visited_states;
801 row.expanded_states = result.stats.expanded_states;
802 row.generated_successors = result.stats.generated_successors;
803 row.terminal_states = result.stats.terminal_states;
804 row.pruned_by_depth = result.stats.pruned_by_depth;
805 row.pruned_by_domain = result.stats.pruned_by_domain;
806 row.alpha_beta_cutoffs = result.stats.alpha_beta_cutoffs;
807 row.ordered_batches = result.stats.move_ordering.ordered_batches;
808 row.priority_estimates = result.stats.move_ordering.priority_estimates;
809 row.killer_hits = result.stats.move_ordering.killer_hits;
810 row.history_hits = result.stats.move_ordering.history_hits;
811 row.result_value = static_cast<double>(result.value);
812 row.has_best_move = result.has_principal_variation();
813 row.best_move = result.first_move().cell;
814 }));
815
816 rows.append(measure_case(
817 "adversarial",
818 "tictactoe-full",
819 "alpha_beta_tt",
820 options,
821 [&]()
822 {
823 TT table;
824 return Search::alpha_beta_search(TicTacToeDomain{}, root, table);
825 },
826 [](BenchmarkRow &row, const auto &result)
827 {
828 row.visited_states = result.stats.visited_states;
829 row.expanded_states = result.stats.expanded_states;
830 row.generated_successors = result.stats.generated_successors;
831 row.terminal_states = result.stats.terminal_states;
832 row.pruned_by_depth = result.stats.pruned_by_depth;
833 row.pruned_by_domain = result.stats.pruned_by_domain;
834 row.alpha_beta_cutoffs = result.stats.alpha_beta_cutoffs;
835 row.tt_hits = result.stats.transpositions.hits;
836 row.tt_cutoffs = result.stats.transpositions.cutoffs;
837 row.tt_stores = result.stats.transpositions.stores;
838 row.result_value = static_cast<double>(result.value);
839 row.has_best_move = result.has_principal_variation();
840 row.best_move = result.first_move().cell;
841 }));
842
843 rows.append(measure_case(
844 "adversarial",
845 "tictactoe-full",
846 "alpha_beta_ordered_tt",
847 options,
848 [&]()
849 {
852 policy.use_killer_moves = true;
853 policy.use_history_heuristic = true;
854 TT table;
855 return Search::alpha_beta_search(TicTacToeDomain{}, root, table, policy);
856 },
857 [](BenchmarkRow &row, const auto &result)
858 {
859 row.visited_states = result.stats.visited_states;
860 row.expanded_states = result.stats.expanded_states;
861 row.generated_successors = result.stats.generated_successors;
862 row.terminal_states = result.stats.terminal_states;
863 row.pruned_by_depth = result.stats.pruned_by_depth;
864 row.pruned_by_domain = result.stats.pruned_by_domain;
865 row.alpha_beta_cutoffs = result.stats.alpha_beta_cutoffs;
866 row.tt_hits = result.stats.transpositions.hits;
867 row.tt_cutoffs = result.stats.transpositions.cutoffs;
868 row.tt_stores = result.stats.transpositions.stores;
869 row.ordered_batches = result.stats.move_ordering.ordered_batches;
870 row.priority_estimates = result.stats.move_ordering.priority_estimates;
871 row.killer_hits = result.stats.move_ordering.killer_hits;
872 row.history_hits = result.stats.move_ordering.history_hits;
873 row.result_value = static_cast<double>(result.value);
874 row.has_best_move = result.has_principal_variation();
875 row.best_move = result.first_move().cell;
876 }));
877
878 return rows;
879}
880
881void validate_rows(const Array<BenchmarkRow> &rows)
882{
883 const auto *nqueens = find_row(rows, "nqueens-8", "depth_first");
884 const auto *knapsack_plain = find_row(rows, "knapsack-medium", "depth_first");
885 const auto *knapsack_ordered = find_row(rows, "knapsack-medium", "depth_first_bound_ordered");
886 const auto *knapsack_best = find_row(rows, "knapsack-medium", "best_first");
887 const auto *plain = find_row(rows, "tictactoe-full", "alpha_beta_plain");
888 const auto *ordered = find_row(rows, "tictactoe-full", "alpha_beta_ordered");
889 const auto *tt = find_row(rows, "tictactoe-full", "alpha_beta_tt");
890 const auto *ordered_tt = find_row(rows, "tictactoe-full", "alpha_beta_ordered_tt");
891
892 if (nqueens == nullptr or knapsack_plain == nullptr or knapsack_ordered == nullptr or
893 knapsack_best == nullptr or plain == nullptr or ordered == nullptr or
894 tt == nullptr or ordered_tt == nullptr)
895 throw std::runtime_error("Benchmark validation failed: missing benchmark row");
896
897 if (nqueens->solutions_found != 92u)
898 throw std::runtime_error("N-Queens validation failed: expected 92 solutions");
899
900 if (knapsack_plain->result_value != knapsack_ordered->result_value or
901 knapsack_plain->result_value != knapsack_best->result_value)
902 throw std::runtime_error("Knapsack validation failed: inconsistent optimum");
903 if (knapsack_ordered->visited_states > knapsack_plain->visited_states)
904 throw std::runtime_error("Knapsack validation failed: bound ordering did not help");
905 if (knapsack_best->pruned_by_bound == 0)
906 throw std::runtime_error("Knapsack validation failed: best-first did not prune");
907
908 if (plain->result_value != ordered->result_value or
909 plain->result_value != tt->result_value or
910 plain->result_value != ordered_tt->result_value)
911 throw std::runtime_error("Alpha-Beta validation failed: inconsistent root value");
912 if (ordered->visited_states >= plain->visited_states)
913 throw std::runtime_error("Alpha-Beta validation failed: move ordering did not reduce nodes");
914 if (tt->visited_states >= plain->visited_states)
915 throw std::runtime_error("Alpha-Beta validation failed: TT did not reduce nodes");
916}
917
918void print_row_text(const BenchmarkRow &row)
919{
920 std::cout << row.family << " / " << row.scenario << " / " << row.variant << '\n';
921 std::cout << " mean_ms: " << row.timing.mean_ms
922 << " median_ms: " << row.timing.median_ms
923 << " min_ms: " << row.timing.min_ms
924 << " max_ms: " << row.timing.max_ms << '\n';
925 std::cout << " visited: " << row.visited_states
926 << " expanded: " << row.expanded_states
927 << " generated: " << row.generated_successors << '\n';
928
929 if (row.solutions_found > 0)
930 std::cout << " solutions: " << row.solutions_found << '\n';
931
932 if (row.pruned_by_bound > 0)
933 std::cout << " pruned_by_bound: " << row.pruned_by_bound
934 << " incumbent_updates: " << row.incumbent_updates << '\n';
935
936 if (row.alpha_beta_cutoffs > 0 or row.tt_hits > 0)
937 std::cout << " alpha_beta_cutoffs: " << row.alpha_beta_cutoffs
938 << " tt_hits: " << row.tt_hits
939 << " tt_cutoffs: " << row.tt_cutoffs << '\n';
940
941 if (row.ordered_batches > 0 or row.priority_estimates > 0)
942 std::cout << " ordered_batches: " << row.ordered_batches
943 << " priority_estimates: " << row.priority_estimates
944 << " killer_hits: " << row.killer_hits
945 << " history_hits: " << row.history_hits << '\n';
946
947 std::cout << " result_value: " << row.result_value;
948 if (row.has_best_move)
949 std::cout << " best_move: " << row.best_move;
950 std::cout << '\n';
951}
952
953void print_json(const Array<BenchmarkRow> &rows, const Options &options)
954{
955 std::cout << "{\n";
956 std::cout << " \"repetitions\": " << options.repetitions << ",\n";
957 std::cout << " \"warmup\": " << options.warmup << ",\n";
958 std::cout << " \"samples\": " << options.samples << ",\n";
959 std::cout << " \"rows\": [\n";
960
961 for (size_t i = 0; i < rows.size(); ++i)
962 {
963 const auto &row = rows[i];
964 std::cout
965 << " {\n"
966 << " \"family\": \"" << row.family << "\",\n"
967 << " \"scenario\": \"" << row.scenario << "\",\n"
968 << " \"variant\": \"" << row.variant << "\",\n"
969 << " \"mean_ms\": " << row.timing.mean_ms << ",\n"
970 << " \"median_ms\": " << row.timing.median_ms << ",\n"
971 << " \"min_ms\": " << row.timing.min_ms << ",\n"
972 << " \"max_ms\": " << row.timing.max_ms << ",\n"
973 << " \"stddev_ms\": " << row.timing.stddev_ms << ",\n"
974 << " \"visited_states\": " << row.visited_states << ",\n"
975 << " \"expanded_states\": " << row.expanded_states << ",\n"
976 << " \"generated_successors\": " << row.generated_successors << ",\n"
977 << " \"solutions_found\": " << row.solutions_found << ",\n"
978 << " \"pruned_by_bound\": " << row.pruned_by_bound << ",\n"
979 << " \"alpha_beta_cutoffs\": " << row.alpha_beta_cutoffs << ",\n"
980 << " \"tt_hits\": " << row.tt_hits << ",\n"
981 << " \"tt_cutoffs\": " << row.tt_cutoffs << ",\n"
982 << " \"ordered_batches\": " << row.ordered_batches << ",\n"
983 << " \"priority_estimates\": " << row.priority_estimates << ",\n"
984 << " \"result_value\": " << row.result_value << ",\n"
985 << " \"has_best_move\": " << (row.has_best_move ? "true" : "false") << ",\n"
986 << " \"best_move\": " << row.best_move << '\n'
987 << " }";
988 if (i + 1 != rows.size())
989 std::cout << ',';
990 std::cout << '\n';
991 }
992
993 std::cout << " ]\n}\n";
994}
995
996} // end namespace
997
998int main(const int argc, char **argv)
999{
1000 const Options options = parse_options(argc, argv);
1001
1006
1007 rows.append(nqueens_rows);
1008 rows.append(knapsack_rows);
1009 rows.append(tictactoe_rows);
1010
1011 if (options.validate)
1012 validate_rows(rows);
1013
1014 std::cout << std::fixed << std::setprecision(3);
1015
1016 if (options.json)
1017 print_json(rows, options);
1018 else
1019 {
1020 std::cout << "State-space search benchmark suite\n";
1021 std::cout << "repetitions=" << options.repetitions
1022 << " warmup=" << options.warmup
1023 << " samples=" << options.samples << "\n\n";
1024
1025 for (const auto &row : rows)
1026 {
1027 print_row_text(row);
1028 std::cout << '\n';
1029 }
1030
1031 if (options.validate)
1032 std::cout << "validation: ok\n";
1033 }
1034
1035 return 0;
1036}
Classical knapsack problem variants (0/1, unbounded, bounded).
Umbrella header for the implicit state-space search framework.
static ExplorationPolicy default_policy() noexcept
Return the default exploration policy for Alpha-Beta.
Definition Alpha_Beta.H:97
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
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:315
static ExplorationPolicy default_policy() noexcept
Return the default branch-and-bound exploration policy.
Transposition-table container built on top of Aleph hash maps.
void undo(State &s, const Move &m) const noexcept
Objective bound(const State &s) const noexcept
Greedy fractional upper bound on the remaining items.
bool for_each_successor(const State &s, Visitor visit) const
void apply(State &s, const Move &m) const noexcept
bool is_complete(const State &s) const noexcept
True when all items have been decided.
Objective objective_value(const State &s) const noexcept
Objective value for a complete assignment.
__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
User-facing facade exported by the umbrella search header.
and
Check uniqueness with explicit hash + equality functors.
bool operator==(const DynList< T > &l1, const DynList< T > &l2)
Equality operator for DynList.
size_t size(Node *root) noexcept
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.
DynList< T > rep(const size_t n, const T &item)
Create a sequence of repeated items.
@ Estimated_Bound
Rank successors by an optimistic child bound.
@ Estimated_Score
Rank successors by a cheap heuristic score estimate.
void introsort(T *a, const long l, const long r, const Compare &cmp=Compare())
Sort an array using introsort (introspective sort).
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
static struct argp_option options[]
Definition ntreepic.C:1886
#define TT
Definition ran_array.c:29
Exploration controls shared across engines.
bool stop_at_first_solution
Stop when the first goal is found.
Strategy strategy
Traversal strategy.
MoveOrderingMode move_ordering
Successor-ordering mode.
bool use_history_heuristic
Enable experimental history heuristic where supported.
bool use_killer_moves
Enable experimental killer heuristic where supported.
An item for knapsack problems.
Definition Knapsack.H:66
Hard bounds applied by the search engine.
size_t max_depth
Maximum expansion depth.
Dynamic array container with automatic resizing.
Comprehensive sorting algorithms and search utilities for Aleph-w.