Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
move_ordering_test.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
31#include <random>
32#include <string>
33#include <cmath>
34
35#include <gtest/gtest.h>
36
38
39using namespace Aleph;
40
41// --- Killer_Move_Table tests ---
42
44{
46 EXPECT_FALSE(table.is_killer(0, 42));
47 EXPECT_FALSE(table.is_killer(5, 99));
48}
49
51{
53 table.record(0, 10);
54
55 EXPECT_TRUE(table.is_killer(0, 10));
56 EXPECT_FALSE(table.is_killer(0, 20));
57 EXPECT_FALSE(table.is_killer(1, 10));
58}
59
61{
63 table.record(0, 10);
64 table.record(0, 20);
65
66 EXPECT_TRUE(table.is_killer(0, 10));
67 EXPECT_TRUE(table.is_killer(0, 20));
68}
69
71{
73 table.record(0, 10);
74 table.record(0, 20);
75 table.record(0, 30);
76
77 EXPECT_FALSE(table.is_killer(0, 10));
78 EXPECT_TRUE(table.is_killer(0, 20));
79 EXPECT_TRUE(table.is_killer(0, 30));
80}
81
83{
85 table.record(0, 10);
86 table.record(0, 20);
87 // 20 is now primary; recording it again must be a no-op (no rotation).
88 table.record(0, 20);
89
90 EXPECT_TRUE(table.is_killer(0, 10));
91 EXPECT_TRUE(table.is_killer(0, 20));
92}
93
95{
97 table.record(0, 10);
98 table.record(1, 20);
99
100 EXPECT_TRUE(table.is_killer(0, 10));
101 EXPECT_FALSE(table.is_killer(0, 20));
102 EXPECT_TRUE(table.is_killer(1, 20));
103 EXPECT_FALSE(table.is_killer(1, 10));
104}
105
107{
109 table.record(0, 10);
110 table.record(1, 20);
111 table.clear();
112
113 EXPECT_FALSE(table.is_killer(0, 10));
114 EXPECT_FALSE(table.is_killer(1, 20));
115}
116
118{
120 table.record(100, 42);
121 EXPECT_TRUE(table.is_killer(100, 42));
122 EXPECT_FALSE(table.is_killer(50, 42));
123}
124
126{
127 struct Opaque
128 {
129 int value;
130 };
131
134 EXPECT_FALSE(table.is_killer(0, Opaque{1}));
135 table.record(0, Opaque{1});
136 EXPECT_FALSE(table.is_killer(0, Opaque{1}));
137}
138
140{
143
144 table.record(0, "e2e4");
145 table.record(0, "d2d4");
146
147 EXPECT_TRUE(table.is_killer(0, "e2e4"));
148 EXPECT_TRUE(table.is_killer(0, "d2d4"));
149 EXPECT_FALSE(table.is_killer(0, "c2c4"));
150}
151
152// --- History_Heuristic_Table tests ---
153
159
161{
163 table.record(10, 5);
164 EXPECT_EQ(table.score(10), 5u);
165}
166
168{
170 table.record(10, 3);
171 table.record(10, 7);
172 EXPECT_EQ(table.score(10), 10u);
173}
174
176{
178 table.record(10);
179 table.record(10);
180 table.record(10);
181 EXPECT_EQ(table.score(10), 3u);
182}
183
185{
187 table.record(1, 10);
188 table.record(2, 20);
189
190 EXPECT_EQ(table.score(1), 10u);
191 EXPECT_EQ(table.score(2), 20u);
192 EXPECT_EQ(table.score(3), 0u);
193}
194
196{
198 table.record(1, 100);
199 table.record(2, 200);
200 table.clear();
201
202 EXPECT_EQ(table.score(1), 0u);
203 EXPECT_EQ(table.score(2), 0u);
204}
205
207{
210 EXPECT_EQ(table.score(42), 0u);
211 table.record(42, 100);
212 EXPECT_EQ(table.score(42), 0u);
213}
214
215// --- sort_ranked_moves tests ---
216
218{
220 moves.append({10, 1, 0, false, 0});
221 moves.append({20, 3, 1, false, 0});
222 moves.append({30, 2, 2, false, 0});
223
224 sort_ranked_moves(moves,
225 [](int a, int b) { return a > b; },
226 false, false);
227
228 EXPECT_EQ(moves[0].move, 20);
229 EXPECT_EQ(moves[1].move, 30);
230 EXPECT_EQ(moves[2].move, 10);
231}
232
234{
236 moves.append({10, 5, 0, false, 0});
237 moves.append({20, 1, 1, true, 0});
238 moves.append({30, 3, 2, false, 0});
239
240 sort_ranked_moves(moves,
241 [](int a, int b) { return a > b; },
242 true, false);
243
244 EXPECT_EQ(moves[0].move, 20); // killer first
245 EXPECT_EQ(moves[1].move, 10); // priority 5 > 3
246 EXPECT_EQ(moves[2].move, 30); // priority 3
247}
248
250{
252 moves.append({10, 5, 0, false, 10});
253 moves.append({20, 5, 1, false, 50});
254 moves.append({30, 5, 2, false, 30});
255
256 sort_ranked_moves(moves,
257 [](int a, int b) { return a > b; },
258 false, true);
259
260 EXPECT_EQ(moves[0].move, 20);
261 EXPECT_EQ(moves[1].move, 30);
262 EXPECT_EQ(moves[2].move, 10);
263}
264
266{
268 moves.append({10, 5, 2, false, 0});
269 moves.append({20, 5, 0, false, 0});
270 moves.append({30, 5, 1, false, 0});
271
272 sort_ranked_moves(moves,
273 [](int a, int b) { return a > b; },
274 false, false);
275
276 EXPECT_EQ(moves[0].move, 20);
277 EXPECT_EQ(moves[1].move, 30);
278 EXPECT_EQ(moves[2].move, 10);
279}
280
282{
284 moves.append({1, 100, 0, false, 0}); // high priority, no killer, no history
285 moves.append({2, 1, 1, true, 0}); // low priority, killer
286 moves.append({3, 50, 2, false, 999}); // medium priority, high history
287 moves.append({4, 90, 3, false, 1}); // high-ish priority, low history
288
289 sort_ranked_moves(moves,
290 [](int a, int b) { return a > b; },
291 true, true);
292
293 EXPECT_EQ(moves[0].move, 2); // killer first
294 EXPECT_EQ(moves[1].move, 3); // history 999
295 EXPECT_EQ(moves[2].move, 4); // history 1
296 EXPECT_EQ(moves[3].move, 1); // history 0, highest priority among remaining
297}
298
300{
302 moves.append({42, 1, 0, false, 0});
303
304 sort_ranked_moves(moves,
305 [](int a, int b) { return a > b; },
306 true, true);
307
308 EXPECT_EQ(moves[0].move, 42);
309}
310
312{
314
315 sort_ranked_moves(moves,
316 [](int a, int b) { return a > b; },
317 true, true);
318
319 EXPECT_TRUE(moves.is_empty());
320}
321
322// --- Large-input edge-case coverage ---
323
325{
327 constexpr size_t N = 2000;
328
329 // Record one move per depth across a large range.
330 for (size_t d = 0; d < N; ++d)
331 table.record(d, static_cast<int>(d * 10));
332
333 for (size_t d = 0; d < N; ++d)
334 EXPECT_TRUE(table.is_killer(d, static_cast<int>(d * 10)));
335
336 // Unrecorded moves must not appear.
337 for (size_t d = 0; d < N; ++d)
338 EXPECT_FALSE(table.is_killer(d, -1));
339
340 table.clear();
341 for (size_t d = 0; d < N; ++d)
342 EXPECT_FALSE(table.is_killer(d, static_cast<int>(d * 10)));
343}
344
346{
348 // Insert 10 distinct moves at depth 0; only the last 2 should survive.
349 for (int i = 1; i <= 10; ++i)
350 table.record(0, i);
351
352 EXPECT_FALSE(table.is_killer(0, 1));
353 EXPECT_FALSE(table.is_killer(0, 8));
354 EXPECT_TRUE(table.is_killer(0, 9));
355 EXPECT_TRUE(table.is_killer(0, 10));
356}
357
359{
360 struct Opaque { int v; };
363
364 for (size_t d = 0; d < 500; ++d)
365 {
366 table.record(d, Opaque{static_cast<int>(d)});
367 EXPECT_FALSE(table.is_killer(d, Opaque{static_cast<int>(d)}));
368 }
369 table.clear(); // must not crash
370}
371
373{
375 constexpr int N = 5000;
376
377 for (int k = 0; k < N; ++k)
378 table.record(k, static_cast<size_t>(k + 1));
379
380 for (int k = 0; k < N; ++k)
381 EXPECT_EQ(table.score(k), static_cast<size_t>(k + 1));
382
383 table.clear();
384 for (int k = 0; k < N; ++k)
385 EXPECT_EQ(table.score(k), 0u);
386}
387
389{
391 constexpr size_t REPS = 1000;
392
393 for (size_t i = 0; i < REPS; ++i)
394 table.record(42, 3);
395
396 EXPECT_EQ(table.score(42), REPS * 3);
397}
398
400{
403
404 for (int k = 0; k < 1000; ++k)
405 table.record(k, 99);
406
407 for (int k = 0; k < 1000; ++k)
408 EXPECT_EQ(table.score(k), 0u);
409
410 table.clear(); // must not crash
411}
412
413// --- Randomized stress tests for sort_ranked_moves ---
414
416{
417 constexpr size_t N = 2000;
418 constexpr unsigned SEED = 42u;
419 std::mt19937 rng(SEED);
420
421 auto rand_int = [&](int lo, int hi)
422 { return std::uniform_int_distribution<int>(lo, hi)(rng); };
423 auto rand_uint = [&](size_t lo, size_t hi)
424 { return static_cast<size_t>(std::uniform_int_distribution<size_t>(lo, hi)(rng)); };
425 auto rand_bool = [&]() { return static_cast<bool>(rng() & 1u); };
426
427 // Generate a single deterministic baseline corpus for all combinations
429 for (size_t i = 0; i < N; ++i)
431 rand_int(0, 50),
432 i,
433 rand_bool(),
434 rand_uint(0, 200)});
435
436 for (bool killer_first : {false, true})
437 for (bool use_history : {false, true})
438 {
440 size_t comparisons = 0;
441 auto instrumented_cmp = [&](int a, int b) {
442 comparisons++;
443 return a > b;
444 };
445
447
448 ASSERT_EQ(moves.size(), N);
449
450 // Assert sorting complexity stays within budget (C * N * log2(N))
451 const double n_log_n = N * std::log2(static_cast<double>(N));
452 EXPECT_LT(comparisons, static_cast<size_t>(20.0 * n_log_n))
453 << "Performance regression in sort_ranked_moves hot path ("
454 << killer_first << ", " << use_history << ")";
455
456 for (size_t i = 0; i + 1 < N; ++i)
457 {
458 const auto &a = moves[i];
459 const auto &b = moves[i + 1];
460
461 if (killer_first)
462 {
463 if (a.killer and not b.killer)
464 continue; // correct: killer before non-killer
465 if (not a.killer and b.killer)
466 FAIL() << "Non-killer before killer at index " << i;
467 }
468
469 if (use_history and a.history_score != b.history_score)
470 EXPECT_GE(a.history_score, b.history_score)
471 << "History ordering violated at index " << i;
472 else
473 {
474 if (a.priority != b.priority)
475 EXPECT_GE(a.priority, b.priority)
476 << "Priority ordering violated at index " << i;
477 else
478 EXPECT_LE(a.ordinal, b.ordinal)
479 << "Ordinal tiebreak violated at index " << i;
480 }
481 }
482 }
483}
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
Sparse history heuristic table over Aleph hash maps.
size_t score(const Key &key) const noexcept
Read the current score for a move key.
void clear() noexcept
Reset all history scores to zero.
void record(const Key &key, const size_t bonus=1)
Increment the history score for a move key.
Two-slot killer heuristic table indexed by search depth.
#define FAIL(msg)
#define TEST(name)
static mt19937 rng
#define N
Definition fib.C:294
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.
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.
Shared support for configurable successor ordering heuristics.
Null history heuristic hook used when a domain has no move key.
static constexpr bool supported
Marker indicating that this table does nothing.
static void record(const Key &, const size_t=1) noexcept
No-op.
static size_t score(const Key &) noexcept
Always returns zero.
One move plus the metadata used by ordering comparators.
static int * k