LCOV - code coverage report
Current view: top level - Source - hash.cpp (source / functions) Coverage Total Hit
Test: coverage Lines: 96.2 % 79 76
Test Date: 2026-03-02 16:42:41 Functions: 100.0 % 6 6

            Line data    Source code
       1              : #include "hash.hpp"
       2              : 
       3              : #include "bitboardTools.hpp"
       4              : #include "logging.hpp"
       5              : #include "position.hpp"
       6              : #include "tools.hpp"
       7              : 
       8              : namespace Zobrist {
       9              : array2d<Hash,NbSquare,14> ZT;
      10              : array1d<Hash,16> ZTCastling;
      11              : 
      12              : inline constexpr int hash_seed = 42;
      13              : 
      14              : namespace {
      15              :    // Test bit distribution across all Zobrist hash values
      16           22 :    void testBitDistribution() {
      17           22 :       std::array<uint64_t, 64> bitCounts = {0};
      18              :       uint64_t totalHashes = 0;
      19              :       
      20              :       // Count bits across ZT table
      21         1430 :       for (int k = 0; k < NbSquare; ++k) {
      22        21120 :          for (int j = 0; j < 14; ++j) {
      23        19712 :             Hash h = ZT[k][j];
      24      1281280 :             for (int bit = 0; bit < 64; ++bit) {
      25      1261568 :                if (h & (1ULL << bit)) bitCounts[bit]++;
      26              :             }
      27        19712 :             totalHashes++;
      28              :          }
      29              :       }
      30              :       
      31              :       // Count bits in castling table
      32          374 :       for (int k = 0; k < 16; ++k) {
      33          352 :          Hash h = ZTCastling[k];
      34        22880 :          for (int bit = 0; bit < 64; ++bit) {
      35        22528 :             if (h & (1ULL << bit)) bitCounts[bit]++;
      36              :          }
      37          352 :          totalHashes++;
      38              :       }
      39              :       
      40           22 :       Logging::LogIt(Logging::logInfo) << "Zobrist bit distribution test (should be ~0.5 for each bit):";
      41              :       bool allGood = true;
      42         1430 :       for (int bit = 0; bit < 64; ++bit) {
      43         1408 :          double ratio = bitCounts[bit] / static_cast<double>(totalHashes);
      44         1408 :          if (ratio < 0.45 || ratio > 0.55) {
      45            0 :             Logging::LogIt(Logging::logWarn) << "  Bit " << bit << " has poor distribution: " << ratio;
      46              :             allGood = false;
      47              :          }
      48              :       }
      49           22 :       if (allGood) {
      50           22 :          Logging::LogIt(Logging::logInfo) << "  All bits have good distribution (between 0.45 and 0.55)";
      51              :       }
      52           22 :    }
      53              :    
      54              :    // Calculate Hamming distance between two hashes
      55              :    int hammingDistance(Hash a, Hash b) {
      56        39116 :       return __builtin_popcountll(a ^ b);
      57              :    }
      58              :    
      59              :    // Test average Hamming distance (should be around 32 for random 64-bit values)
      60           22 :    void testHammingDistance() {
      61              :       const int sampleSize = 1000;
      62              :       uint64_t totalDistance = 0;
      63              :       int comparisons = 0;
      64              :       
      65              :       // Sample random pairs from ZT table
      66        19734 :       for (int i = 0; i < sampleSize && i < NbSquare * 14; ++i) {
      67        19712 :          int k1 = (i * 7) % NbSquare;
      68        19712 :          int j1 = (i * 13) % 14;
      69        19712 :          int k2 = ((i + 1) * 11) % NbSquare;
      70        19712 :          int j2 = ((i + 1) * 17) % 14;
      71              :          
      72        19712 :          if (k1 != k2 || j1 != j2) {
      73        19712 :             totalDistance += hammingDistance(ZT[k1][j1], ZT[k2][j2]);
      74        19712 :             comparisons++;
      75              :          }
      76              :       }
      77              :       
      78           22 :       double avgDistance = static_cast<double>(totalDistance) / static_cast<double>(comparisons);
      79           22 :       Logging::LogIt(Logging::logInfo) << "Zobrist Hamming distance test:";
      80           22 :       Logging::LogIt(Logging::logInfo) << "  Average Hamming distance: " << avgDistance << " (ideal: ~32)";
      81              :       
      82           22 :       if (avgDistance < 28 || avgDistance > 36) {
      83            0 :          Logging::LogIt(Logging::logWarn) << "  Hamming distance outside expected range [28, 36]";
      84              :       }
      85           22 :    }
      86              :    
      87              :    // Test for unwanted correlations (adjacent squares)
      88           22 :    void testCorrelations() {
      89           22 :       Logging::LogIt(Logging::logInfo) << "Zobrist correlation test (adjacent squares):";
      90              :       
      91              :       // Check adjacent squares for the same piece type
      92              :       int poorDistances = 0;
      93              :       int totalComparisons = 0;
      94              :       
      95         1408 :       for (int k = 0; k < NbSquare - 1; ++k) {
      96        20790 :          for (int j = 0; j < 14; ++j) {
      97        19404 :             int dist = hammingDistance(ZT[k][j], ZT[k+1][j]);
      98        19404 :             totalComparisons++;
      99              :             // Count outliers (very low or very high Hamming distance)
     100        19404 :             if (dist < 20 || dist > 44) {
     101           44 :                poorDistances++;
     102              :             }
     103              :          }
     104              :       }
     105              :       
     106           22 :       double poorRatio = poorDistances / static_cast<double>(totalComparisons);
     107           22 :       Logging::LogIt(Logging::logInfo) << "  Outliers: " << poorDistances << "/" << totalComparisons 
     108           22 :                                         << " (" << (poorRatio * 100) << "%)";
     109              :       
     110              :       // More than 10% outliers would indicate a real problem
     111           22 :       if (poorRatio > 0.10) {
     112            0 :          Logging::LogIt(Logging::logWarn) << "  WARNING: High correlation detected (>10% outliers)";
     113              :       } else {
     114           22 :          Logging::LogIt(Logging::logInfo) << "  No significant correlations detected";
     115              :       }
     116           22 :    }
     117              : }
     118              : 
     119           22 : void initHash() {
     120           22 :    Logging::LogIt(Logging::logInfo) << "Init hash";
     121           22 :    Logging::LogIt(Logging::logInfo) << "Size of zobrist table " << (sizeof(ZT)+sizeof(ZTCastling)) / 1024 << "Kb";
     122         1430 :    for (int k = 0; k < NbSquare; ++k)
     123        21120 :       for (int j = 0; j < 14; ++j) ZT[k][j] = randomInt<Hash, hash_seed>(std::numeric_limits<Hash>::min(), std::numeric_limits<Hash>::max());
     124          374 :    for (int k = 0; k < 16; ++k) ZTCastling[k] = randomInt<Hash, hash_seed>(std::numeric_limits<Hash>::min(), std::numeric_limits<Hash>::max());
     125              :    
     126              :    // Run quality tests on generated Zobrist values
     127           22 :    testBitDistribution();
     128           22 :    testHammingDistance();
     129           22 :    testCorrelations();
     130           22 : }
     131              : } // namespace Zobrist
     132              : 
     133     23447380 : Hash computeHash(const Position &p) {
     134              : #ifdef DEBUG_HASH
     135              :    Hash h = p.h;
     136              :    p.h    = nullHash;
     137              : #endif
     138     23447380 :    if (p.h != nullHash) return p.h;
     139              :    //++ThreadPool::instance().main().stats.counters[Stats::sid_hashComputed]; // shall of course never happend !
     140        20020 :    for (Square k = 0; k < NbSquare; ++k) { ///todo try if BB is faster here ?
     141        19712 :       const Piece pp = p.board_const(k);
     142        19712 :       if (pp != P_none) p.h ^= Zobrist::ZT[k][PieceIdx(pp)];
     143              :    }
     144          308 :    if (p.ep != INVALIDSQUARE) p.h ^= Zobrist::ZT[p.ep][NbPiece];
     145          308 :    p.h ^= Zobrist::ZTCastling[p.castling];
     146          308 :    if (p.c == Co_White) p.h ^= Zobrist::ZT[3][NbPiece];
     147          308 :    if (p.c == Co_Black) p.h ^= Zobrist::ZT[4][NbPiece];
     148              : #ifdef DEBUG_HASH
     149              :    if (h != nullHash && h != p.h) { Logging::LogIt(Logging::logFatal) << "Hash error " << ToString(p.lastMove) << ToString(p, true); }
     150              : #endif
     151          308 :    return p.h;
     152              : }
     153              : 
     154       946030 : Hash computePHash(const Position &p) {
     155              : #ifdef DEBUG_PHASH
     156              :    Hash h = p.ph;
     157              :    p.ph   = nullHash;
     158              : #endif
     159       946030 :    if (p.ph != nullHash) return p.ph;
     160              :    //++ThreadPool::instance().main().stats.counters[Stats::sid_hashComputed]; // shall of course never happend !
     161         1379 :    BB::applyOn(p.whitePawn(), [&](const Square & k){ p.ph ^= Zobrist::ZT[k][PieceIdx(P_wp)]; });
     162         1313 :    BB::applyOn(p.blackPawn(), [&](const Square & k){ p.ph ^= Zobrist::ZT[k][PieceIdx(P_bp)]; });
     163          308 :    BB::applyOn(p.whiteKing(), [&](const Square & k){ p.ph ^= Zobrist::ZT[k][PieceIdx(P_wk)]; });
     164          308 :    BB::applyOn(p.blackKing(), [&](const Square & k){ p.ph ^= Zobrist::ZT[k][PieceIdx(P_bk)]; });
     165              : #ifdef DEBUG_PHASH
     166              :    if (h != nullHash && h != p.ph) {
     167              :       Logging::LogIt(Logging::logFatal) << "Pawn Hash error " << ToString(p.lastMove) << ToString(p, true) << p.ph << " != " << h;
     168              :    }
     169              : #endif
     170          308 :    return p.ph;
     171              : }
        

Generated by: LCOV version 2.0-1