Merge Stats tables
authorMarco Costalba <mcostalba@gmail.com>
Sat, 3 Mar 2018 10:29:29 +0000 (11:29 +0100)
committerStéphane Nicolet <cassio@free.fr>
Sat, 3 Mar 2018 10:35:33 +0000 (11:35 +0100)
Use a recursive std::array with variadic template
parameters to get rid of the last redundacy.

The first template T parameter is the base type of
the array, the W parameter is the weight applied to
the bonuses when we update values with the << operator,
the D parameter limits the range of updates (range is
[-W * D, W * D]), and the last parameters (Size and
Sizes) encode the dimensions of the array.

This allows greater flexibility because we can now tweak
the range [-W * D, W * D] for each table.

Patch removes more lines than what adds and streamlines
the Stats soup in movepick.h

Closes PR#1422 and PR#1421

No functional change.

src/movepick.cpp
src/movepick.h
src/search.cpp
src/thread.cpp

index 35f9d25..a71d307 100644 (file)
@@ -127,7 +127,7 @@ void MovePicker::score() {
   for (auto& m : *this)
       if (Type == CAPTURES)
           m.value =  PieceValue[MG][pos.piece_on(to_sq(m))]
-                   + Value((*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]);
+                   + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
 
       else if (Type == QUIETS)
           m.value =  (*mainHistory)[pos.side_to_move()][from_to(m)]
index 0aba61d..d9f0457 100644 (file)
 
 #include <array>
 #include <limits>
+#include <type_traits>
 
 #include "movegen.h"
 #include "position.h"
 #include "types.h"
 
-/// StatBoards is a generic 2-dimensional array used to store various statistics
-template<int Size1, int Size2, typename T = int16_t>
-struct StatBoards : public std::array<std::array<T, Size2>, Size1> {
+/// StatsEntry stores the stat table value. It is usually a number but could
+/// be a move or even a nested history. We use a class instead of naked value
+/// to directly call history update operator<<() on the entry so to use stats
+/// tables at caller sites as simple multi-dim arrays.
+template<typename T, int W, int D>
+class StatsEntry {
 
-  void fill(const T& v) {
-    T* p = &(*this)[0][0];
-    std::fill(p, p + sizeof(*this) / sizeof(*p), v);
-  }
-
-  void update(T& entry, int bonus, const int D) {
-
-    assert(abs(bonus) <= D); // Ensure range is [-32 * D, 32 * D]
-    assert(abs(32 * D) < (std::numeric_limits<T>::max)()); // Ensure we don't overflow
-
-    entry += bonus * 32 - entry * abs(bonus) / D;
-
-    assert(abs(entry) <= 32 * D);
-  }
-};
+  static const bool IsInt = std::is_integral<T>::value;
+  typedef typename std::conditional<IsInt, int, T>::type TT;
 
-/// StatCubes is a generic 3-dimensional array used to store various statistics
-template<int Size1, int Size2, int Size3, typename T = int16_t>
-struct StatCubes : public std::array<std::array<std::array<T, Size3>, Size2>, Size1> {
+  T entry;
 
-  void fill(const T& v) {
-    T* p = &(*this)[0][0][0];
-    std::fill(p, p + sizeof(*this) / sizeof(*p), v);
-  }
+public:
+  T* get() { return &entry; }
+  void operator=(const T& v) { entry = v; }
+  operator TT() const { return entry; }
 
-  void update(T& entry, int bonus, const int D, const int W) {
+  void operator<<(int bonus) {
 
     assert(abs(bonus) <= D); // Ensure range is [-W * D, W * D]
-    assert(abs(W * D) < (std::numeric_limits<T>::max)()); // Ensure we don't overflow
+    assert(abs(W * D) < std::numeric_limits<T>::max()); // Ensure we don't overflow
 
     entry += bonus * W - entry * abs(bonus) / D;
 
@@ -68,50 +57,50 @@ struct StatCubes : public std::array<std::array<std::array<T, Size3>, Size2>, Si
   }
 };
 
-/// ButterflyBoards are 2 tables (one for each color) indexed by the move's from
-/// and to squares, see chessprogramming.wikispaces.com/Butterfly+Boards
-typedef StatBoards<COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyBoards;
-
-/// PieceToBoards are addressed by a move's [piece][to] information
-typedef StatBoards<PIECE_NB, SQUARE_NB> PieceToBoards;
+/// Stats is a generic N-dimensional array used to store various statistics.
+/// The first template T parameter is the base type of the array, the W parameter
+/// is the weight applied to the bonuses when we update values with the << operator,
+/// the D parameter limits the range of updates (range is [-W * D, W * D]), and
+/// the last parameters (Size and Sizes) encode the dimensions of the array.
+template <typename T, int W, int D, int Size, int... Sizes>
+struct Stats : public std::array<Stats<T, W, D, Sizes...>, Size>
+{
+  T* get() { return this->at(0).get(); }
 
-/// CapturePieceToBoards are addressed by a move's [piece][to][captured piece type] information
-typedef StatCubes<PIECE_NB, SQUARE_NB, PIECE_TYPE_NB> CapturePieceToBoards;
-
-/// ButterflyHistory records how often quiet moves have been successful or
-/// unsuccessful during the current search, and is used for reduction and move
-/// ordering decisions. It uses ButterflyBoards as backing store.
-struct ButterflyHistory : public ButterflyBoards {
-
-  void update(Color c, Move m, int bonus) {
-    StatBoards::update((*this)[c][from_to(m)], bonus, 324);
+  void fill(const T& v) {
+    T* p = get();
+    std::fill(p, p + sizeof(*this) / sizeof(*p), v);
   }
 };
 
-/// PieceToHistory is like ButterflyHistory, but is based on PieceToBoards
-struct PieceToHistory : public PieceToBoards {
-
-  void update(Piece pc, Square to, int bonus) {
-    StatBoards::update((*this)[pc][to], bonus, 936);
-  }
+template <typename T, int W, int D, int Size>
+struct Stats<T, W, D, Size> : public std::array<StatsEntry<T, W, D>, Size> {
+  T* get() { return this->at(0).get(); }
 };
 
-/// CapturePieceToHistory is like PieceToHistory, but is based on CapturePieceToBoards
-struct CapturePieceToHistory : public CapturePieceToBoards {
+/// Different tables use different W/D parameter, name them to ease readibility
+enum StatsParams { W2 = 2, W32 = 32, D324 = 324, D936 = 936, NOT_USED = 0 };
 
-  void update(Piece pc, Square to, PieceType captured, int bonus) {
-    StatCubes::update((*this)[pc][to][captured], bonus, 324, 2);
-  }
-};
+/// ButterflyHistory records how often quiet moves have been successful or
+/// unsuccessful during the current search, and is used for reduction and move
+/// ordering decisions. It uses 2 tables (one for each color) indexed by
+/// the move's from and to squares, see chessprogramming.wikispaces.com/Butterfly+Boards
+typedef Stats<int16_t, W32, D324, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
 
 /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
 /// move, see chessprogramming.wikispaces.com/Countermove+Heuristic
-typedef StatBoards<PIECE_NB, SQUARE_NB, Move> CounterMoveHistory;
+typedef Stats<Move, NOT_USED, NOT_USED, PIECE_NB, SQUARE_NB> CounterMoveHistory;
+
+/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
+typedef Stats<int16_t, W2, D324, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB> CapturePieceToHistory;
+
+/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
+typedef Stats<int16_t, W32, D936, PIECE_NB, SQUARE_NB> PieceToHistory;
 
-/// ContinuationHistory is the history of a given pair of moves, usually the
-/// current one given a previous one. History table is based on PieceToBoards
-/// instead of ButterflyBoards.
-typedef StatBoards<PIECE_NB, SQUARE_NB, PieceToHistory> ContinuationHistory;
+/// ContinuationHistory is the combined history of a given pair of moves, usually
+/// the current one given a previous one. The nested history table is based on
+/// PieceToHistory instead of ButterflyBoards.
+typedef Stats<PieceToHistory, W32, NOT_USED, PIECE_NB, SQUARE_NB> ContinuationHistory;
 
 
 /// MovePicker class is used to pick one pseudo legal move at a time from the
index 2acca99..f6cf8de 100644 (file)
@@ -289,7 +289,7 @@ void Thread::search() {
 
   std::memset(ss-4, 0, 7 * sizeof(Stack));
   for (int i = 4; i > 0; i--)
-     (ss-i)->contHistory = &this->contHistory[NO_PIECE][0]; // Use as sentinel
+     (ss-i)->contHistory = this->contHistory[NO_PIECE][0].get(); // Use as sentinel
 
   bestValue = delta = alpha = -VALUE_INFINITE;
   beta = VALUE_INFINITE;
@@ -550,7 +550,7 @@ namespace {
 
     (ss+1)->ply = ss->ply + 1;
     ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE;
-    ss->contHistory = &thisThread->contHistory[NO_PIECE][0];
+    ss->contHistory = thisThread->contHistory[NO_PIECE][0].get();
     (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
     Square prevSq = to_sq((ss-1)->currentMove);
 
@@ -595,7 +595,7 @@ namespace {
             else if (!pos.capture_or_promotion(ttMove))
             {
                 int penalty = -stat_bonus(depth);
-                thisThread->mainHistory.update(pos.side_to_move(), ttMove, penalty);
+                thisThread->mainHistory[pos.side_to_move()][from_to(ttMove)] << penalty;
                 update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty);
             }
         }
@@ -715,7 +715,7 @@ namespace {
         Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min((eval - beta) / PawnValueMg, 3)) * ONE_PLY;
 
         ss->currentMove = MOVE_NULL;
-        ss->contHistory = &thisThread->contHistory[NO_PIECE][0];
+        ss->contHistory = thisThread->contHistory[NO_PIECE][0].get();
 
         pos.do_null_move(st);
         Value nullValue = depth-R < ONE_PLY ? -qsearch<NonPV, false>(pos, ss+1, -beta, -beta+1)
@@ -762,7 +762,7 @@ namespace {
             if (pos.legal(move))
             {
                 ss->currentMove = move;
-                ss->contHistory = &thisThread->contHistory[pos.moved_piece(move)][to_sq(move)];
+                ss->contHistory = thisThread->contHistory[pos.moved_piece(move)][to_sq(move)].get();
 
                 assert(depth >= 5 * ONE_PLY);
 
@@ -937,7 +937,7 @@ moves_loop: // When in check, search starts from here
 
       // Update the current move (this must be done after singular extension search)
       ss->currentMove = move;
-      ss->contHistory = &thisThread->contHistory[movedPiece][to_sq(move)];
+      ss->contHistory = thisThread->contHistory[movedPiece][to_sq(move)].get();
 
       // Step 15. Make the move
       pos.do_move(move, st, givesCheck);
@@ -1401,7 +1401,7 @@ moves_loop: // When in check, search starts from here
 
     for (int i : {1, 2, 4})
         if (is_ok((ss-i)->currentMove))
-            (ss-i)->contHistory->update(pc, to, bonus);
+            (*(ss-i)->contHistory)[pc][to] << bonus;
   }
 
 
@@ -1413,14 +1413,14 @@ moves_loop: // When in check, search starts from here
       CapturePieceToHistory& captureHistory =  pos.this_thread()->captureHistory;
       Piece moved_piece = pos.moved_piece(move);
       PieceType captured = type_of(pos.piece_on(to_sq(move)));
-      captureHistory.update(moved_piece, to_sq(move), captured, bonus);
+      captureHistory[moved_piece][to_sq(move)][captured] << bonus;
 
       // Decrease all the other played capture moves
       for (int i = 0; i < captureCnt; ++i)
       {
           moved_piece = pos.moved_piece(captures[i]);
           captured = type_of(pos.piece_on(to_sq(captures[i])));
-          captureHistory.update(moved_piece, to_sq(captures[i]), captured, -bonus);
+          captureHistory[moved_piece][to_sq(captures[i])][captured] << -bonus;
       }
   }
 
@@ -1438,7 +1438,7 @@ moves_loop: // When in check, search starts from here
 
     Color us = pos.side_to_move();
     Thread* thisThread = pos.this_thread();
-    thisThread->mainHistory.update(us, move, bonus);
+    thisThread->mainHistory[us][from_to(move)] << bonus;
     update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus);
 
     if (is_ok((ss-1)->currentMove))
@@ -1450,7 +1450,7 @@ moves_loop: // When in check, search starts from here
     // Decrease all the other played quiet moves
     for (int i = 0; i < quietsCnt; ++i)
     {
-        thisThread->mainHistory.update(us, quiets[i], -bonus);
+        thisThread->mainHistory[us][from_to(quiets[i])] << -bonus;
         update_continuation_histories(ss, pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus);
     }
   }
index 6516192..ec62c3f 100644 (file)
@@ -62,9 +62,9 @@ void Thread::clear() {
 
   for (auto& to : contHistory)
       for (auto& h : to)
-          h.fill(0);
+          h.get()->fill(0);
 
-  contHistory[NO_PIECE][0].fill(Search::CounterMovePruneThreshold - 1);
+  contHistory[NO_PIECE][0].get()->fill(Search::CounterMovePruneThreshold - 1);
 }
 
 /// Thread::start_searching() wakes up the thread that will start the search