diff --git a/src/Evaluation.cpp b/src/Evaluation.cpp index 03b9cd0..bea5965 100644 --- a/src/Evaluation.cpp +++ b/src/Evaluation.cpp @@ -3,6 +3,7 @@ #include "Position.hpp" #include "Evaluation.hpp" #include "PieceSquareTables.hpp" +#include "Thread.hpp" #include #include @@ -22,15 +23,48 @@ EvalData::EvalData(const Board& board) } -MixedScore material(Board board, EvalData& eval) +void MaterialEntry::store(Age age, Hash hash, const Board& board) +{ + // Store hash + m_hash = board.material_hash(); + + // Compute the imbalance terms + m_imbalance = MixedScore(0, 0); + for (PieceType p1 : { PAWN, KNIGHT, BISHOP, ROOK, QUEEN }) + for (int p2 = PAWN; p2 <= p1; p2++) + m_imbalance += imbalance_terms[p1][p2] + * (board.get_pieces(WHITE, p1).count() * board.get_pieces(WHITE, PieceType(p2)).count() + - board.get_pieces(BLACK, p1).count() * board.get_pieces(BLACK, PieceType(p2)).count()); +} + + +MaterialEntry* probe_material(const Board& board, HashTable& table) { - eval.fields[WHITE].material = MixedScore(0, 0); - eval.fields[BLACK].material = MixedScore(0, 0); + // Probe the table and return the entry if we get a hit + MaterialEntry* entry; + if (table.query(board.material_hash(), &entry)) + return entry; + + // We did not get a hit, recompute all terms and store them in the table + table.store(board.material_hash(), board); + + // The entry points to the same entry that we have just written + return entry; +} - for (auto piece : { PAWN, KNIGHT, BISHOP, ROOK, QUEEN }) + +MixedScore material(Board board, EvalData& eval) +{ + for (Turn t : { WHITE, BLACK }) { - eval.fields[WHITE].material += piece_value[piece] * board.get_pieces(WHITE, piece).count(); - eval.fields[BLACK].material += piece_value[piece] * board.get_pieces(BLACK, piece).count(); + eval.fields[t].material = MixedScore(0, 0); + for (auto piece : { PAWN, KNIGHT, BISHOP, ROOK, QUEEN }) + { + eval.fields[t].material += piece_value[piece] * board.get_pieces(t, piece).count(); + for (int p2 = PAWN; p2 <= piece; p2++) + eval.fields[t].imbalance += imbalance_terms[piece][p2] * board.get_pieces(t, piece).count() + * board.get_pieces(t, PieceType(p2)).count(); + } } return eval.fields[WHITE].material - eval.fields[BLACK].material; } @@ -443,10 +477,14 @@ Score scale(const Board& board, EvalData& data, MixedScore mixed) } -Score evaluation(const Board& board, EvalData& data) +Score evaluation(const Board& board, EvalData& data, Thread& thread) { MixedScore mixed_result(0, 0); + // Probe the material hash table + MaterialEntry* me = probe_material(board, thread.m_material_table); + mixed_result += me->imbalance(); + // Material and PSQT: incrementally updated in the position mixed_result += board.material() + board.psq(); @@ -497,6 +535,7 @@ void eval_table(const Board& board, EvalData& data, Score score) for (Turn t : { WHITE, BLACK }) { total[t] = data.fields[t].material + data.fields[t].placement + + data.fields[t].imbalance + data.fields[t].space + data.fields[t].threats + data.fields[t].passed + data.fields[t].scale; for (PieceType p : { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }) @@ -510,6 +549,7 @@ void eval_table(const Board& board, EvalData& data, Score score) std::cout << " Term | MG EG | MG EG | MG EG " << std::endl; std::cout << "---------------------------------------------------------------" << std::endl; std::cout << " Material | " << Term< true>(data.fields[WHITE].material, data.fields[BLACK].material) << std::endl; + std::cout << " Imbalance | " << Term< true>(data.fields[WHITE].imbalance, data.fields[BLACK].imbalance) << std::endl; std::cout << " Placement | " << Term< true>(data.fields[WHITE].placement, data.fields[BLACK].placement) << std::endl; std::cout << " Pawns | " << Term(data.fields[WHITE].pieces[PAWN], data.fields[BLACK].pieces[PAWN]) << std::endl; std::cout << " Knights | " << Term(data.fields[WHITE].pieces[KNIGHT], data.fields[BLACK].pieces[KNIGHT]) << std::endl; diff --git a/src/Evaluation.hpp b/src/Evaluation.hpp index 742befa..18e9ba3 100644 --- a/src/Evaluation.hpp +++ b/src/Evaluation.hpp @@ -1,10 +1,13 @@ #pragma once #include "Types.hpp" #include "Position.hpp" +#include "Hash.hpp" #include #include +class Thread; + namespace Evaluation { class Attacks @@ -58,6 +61,7 @@ namespace Evaluation struct EvalFields { MixedScore material; + MixedScore imbalance; MixedScore placement; MixedScore space; MixedScore passed; @@ -82,7 +86,41 @@ namespace Evaluation }; - Score evaluation(const Board& board, EvalData& data); + class MaterialEntry + { + Hash m_hash; + MixedScore m_imbalance; + + public: + inline MaterialEntry() + : m_hash(0), + m_imbalance(0, 0) + {} + + inline bool query(Age age, Hash hash, MaterialEntry** entry) + { + (void)age; + *entry = this; + return hash == m_hash; + } + + void store(Age age, Hash hash, const Board& board); + + inline bool empty() const { return m_hash == 0; } + + inline Hash hash() const { return m_hash; } + inline MixedScore imbalance() const { return m_imbalance; } + + friend MaterialEntry material_eval(const Board& board); + }; + + MaterialEntry material_eval(const Board& board); + + + MaterialEntry* probe_material(const Board& board, HashTable& table); + + + Score evaluation(const Board& board, EvalData& data, Thread& thread); void eval_table(const Board& board, EvalData& data, Score score); @@ -129,18 +167,4 @@ namespace Evaluation out << " "; return out; } -} - -template -Score evaluate(const Position& pos) -{ - const Board& board = pos.board(); - Evaluation::EvalData data(board); - - Score score = Evaluation::evaluation(board, data); - - if (OUTPUT) - Evaluation::eval_table(board, data, score); - - return score; } \ No newline at end of file diff --git a/src/Hash.hpp b/src/Hash.hpp index da3db5a..5ae3e1c 100644 --- a/src/Hash.hpp +++ b/src/Hash.hpp @@ -145,8 +145,8 @@ class HashTable : HashTable(0) {} - HashTable(std::size_t size_mb) - : m_table(size_from_mb(size_mb)), + HashTable(std::size_t size, bool size_in_mb = true) + : m_table(size_in_mb ? size_from_mb(size) : size), m_full(0), m_age(0) {} diff --git a/src/PieceSquareTables.hpp b/src/PieceSquareTables.hpp index a8dbbfb..f21d83d 100644 --- a/src/PieceSquareTables.hpp +++ b/src/PieceSquareTables.hpp @@ -103,6 +103,16 @@ constexpr S psq_table_pawns[6][8] = }; +constexpr S imbalance_terms[][NUM_PIECE_TYPES] = +{ // Pawn Knight Bishop Rook Queen + { S( 0, 0) }, // Pawn + { S(14, 10), S(-5, -6) }, // Knight + { S( 6, 7), S( 1, 2), S( 0, 0) }, // Bishop + { S( 0, 0), S( 7, 4), S( 9, 8), S(-12, -11) }, // Rook + { S( 0, 0), S( 9, 9), S(10, 7), S(-11, -13), S(0, 0) } // Queen +}; + + constexpr MixedScore piece_square(PieceType piece, Square square, Turn turn) { // Correct piece position for table lookup diff --git a/src/Position.cpp b/src/Position.cpp index 2ec588b..3e33d82 100644 --- a/src/Position.cpp +++ b/src/Position.cpp @@ -61,6 +61,7 @@ Board::Board() Board::Board(std::string fen) : m_hash(0), + m_material_hash(Zobrist::get_initial_material_hash()), m_psq(0, 0), m_phase(Phases::Total) { @@ -68,6 +69,7 @@ Board::Board(std::string fen) // Default initialisation for board pieces std::memset(m_board_pieces, PIECE_NONE, sizeof(m_board_pieces)); + std::memset(m_piece_count, 0, sizeof(m_piece_count)); // Read position Square square = SQUARE_A8; @@ -340,19 +342,29 @@ bool Board::is_valid() const // Material and phase evaluation uint8_t phase = Phases::Total; - auto eval = MixedScore(0, 0); + MixedScore material(0, 0); + MixedScore psq(0, 0); + Hash material_hash = 0; for (PieceType piece : { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }) for (Turn turn : { WHITE, BLACK }) { Bitboard bb = get_pieces(turn, piece); - eval += piece_value[piece] * bb.count() * turn_to_color(turn); + if (bb.count() != m_piece_count[piece][turn]) + return false; + material += piece_value[piece] * bb.count() * turn_to_color(turn); phase -= bb.count() * Phases::Pieces[piece]; + material_hash ^= Zobrist::get_piece_turn_square(piece, turn, bb.count()); while (bb) - eval += piece_square(piece, bb.bitscan_forward_reset(), turn) * turn_to_color(turn); + psq += piece_square(piece, bb.bitscan_forward_reset(), turn) * turn_to_color(turn); } + MixedScore total_material = m_material[WHITE] - m_material[BLACK]; if (phase != m_phase) return false; - if (eval.middlegame() != m_psq.middlegame() || eval.endgame() != m_psq.endgame()) + if (material.middlegame() != total_material.middlegame() || material.endgame() != total_material.endgame()) + return false; + if (psq.middlegame() != m_psq.middlegame() || psq.endgame() != m_psq.endgame()) + return false; + if (material_hash != m_material_hash) return false; return true; @@ -442,6 +454,12 @@ Hash Board::hash() const } +Hash Board::material_hash() const +{ + return m_material_hash; +} + + Square Board::least_valuable(Bitboard bb) const { // Return the least valuable piece in the bitboard diff --git a/src/Position.hpp b/src/Position.hpp index 861837e..52d708e 100644 --- a/src/Position.hpp +++ b/src/Position.hpp @@ -27,11 +27,13 @@ class Board // Updated fields Hash m_hash; + Hash m_material_hash; Bitboard m_checkers; MixedScore m_material[NUM_COLORS]; MixedScore m_psq; uint8_t m_phase; Piece m_board_pieces[NUM_SQUARES]; + uint8_t m_piece_count[NUM_PIECE_TYPES][NUM_COLORS]; protected: @@ -263,6 +265,9 @@ class Board m_psq += piece_square(piece, square, turn) * turn_to_color(turn); m_material[turn] += piece_value[piece]; m_phase -= Phases::Pieces[piece]; + m_piece_count[piece][turn]++; + m_material_hash ^= Zobrist::get_piece_turn_square(piece, turn, m_piece_count[piece][turn]) + ^ Zobrist::get_piece_turn_square(piece, turn, m_piece_count[piece][turn] - 1); } @@ -274,6 +279,9 @@ class Board m_psq -= piece_square(piece, square, turn) * turn_to_color(turn); m_material[turn] -= piece_value[piece]; m_phase += Phases::Pieces[piece]; + m_piece_count[piece][turn]--; + m_material_hash ^= Zobrist::get_piece_turn_square(piece, turn, m_piece_count[piece][turn]) + ^ Zobrist::get_piece_turn_square(piece, turn, m_piece_count[piece][turn] + 1); } @@ -607,6 +615,9 @@ class Board Hash hash() const; + Hash material_hash() const; + + Square least_valuable(Bitboard bb) const; diff --git a/src/Search.cpp b/src/Search.cpp index 5aea723..79d217b 100644 --- a/src/Search.cpp +++ b/src/Search.cpp @@ -392,7 +392,7 @@ namespace Search else if (data.last_move() == MOVE_NULL && Ply > 1) static_eval = -data.previous(1)->static_eval; else - static_eval = turn_to_color(Turn) * evaluate(position); + static_eval = turn_to_color(Turn) * data.thread().evaluate(position); } data.static_eval = static_eval; @@ -723,7 +723,7 @@ namespace Search if (tt_hit && tt_static_eval != SCORE_NONE) static_eval = tt_static_eval; else - static_eval = turn_to_color(Turn) * evaluate(position); + static_eval = turn_to_color(Turn) * data.thread().evaluate(position); best_score = static_eval; // Can we use the TT value for a better static evaluation? diff --git a/src/Thread.cpp b/src/Thread.cpp index 6e967c6..6d94b11 100644 --- a/src/Thread.cpp +++ b/src/Thread.cpp @@ -22,7 +22,8 @@ Thread::Thread(int id, ThreadPool& pool) m_pool(pool), m_status(ThreadStatus::STARTING), m_nodes_searched(0), - m_multiPV(UCI::Options::MultiPV) + m_multiPV(UCI::Options::MultiPV), + m_material_table(16192, false) { m_thread = std::thread(&Thread::thread_loop, this); @@ -58,6 +59,7 @@ void Thread::wait() void Thread::clear() { m_histories.clear(); + m_material_table.clear(); } diff --git a/src/Thread.hpp b/src/Thread.hpp index 1c4847e..f292216 100644 --- a/src/Thread.hpp +++ b/src/Thread.hpp @@ -4,6 +4,7 @@ #include "Hash.hpp" #include "MoveOrder.hpp" #include "Search.hpp" +#include "Evaluation.hpp" #include #include #include @@ -42,11 +43,13 @@ class Thread protected: friend class Search::SearchData; friend class ThreadPool; + friend Score Evaluation::evaluation(const Board& board, EvalData& data, Thread& thread); Depth m_seldepth; Search::PvContainer m_pv; Histories m_histories; std::atomic_uint64_t m_nodes_searched; std::vector m_multiPV; + HashTable m_material_table; public: Thread(int id, ThreadPool& pool); @@ -70,6 +73,20 @@ class Thread ThreadPool& pool() const; const Search::Limits& limits() const; const Search::SearchTime& time() const; + + template + Score evaluate(const Position& pos) + { + const Board& board = pos.board(); + Evaluation::EvalData data(board); + + Score score = Evaluation::evaluation(board, data, *this); + + if (OUTPUT) + Evaluation::eval_table(board, data, score); + + return score; + } }; @@ -121,6 +138,8 @@ class ThreadPool void clear(); Thread* get_best_thread() const; + + inline Thread& front() { return *(m_threads.front()); } }; diff --git a/src/UCI.cpp b/src/UCI.cpp index 40261cc..321f3ca 100644 --- a/src/UCI.cpp +++ b/src/UCI.cpp @@ -147,7 +147,7 @@ namespace UCI else if (token == "board") std::cout << pool->position().board() << std::endl; else if (token == "eval") - evaluate(pool->position()); + pool->front().evaluate(pool->position()); else if (token == "bench") bench(stream); else if (token == "test") diff --git a/src/Zobrist.cpp b/src/Zobrist.cpp index d31a7e2..ba9cbe2 100644 --- a/src/Zobrist.cpp +++ b/src/Zobrist.cpp @@ -10,6 +10,7 @@ namespace Zobrist Hash rnd_black_move; Hash rnd_castle_side_turn[NUM_COLORS][NUM_CASTLE_SIDES]; Hash rnd_ep_file[8]; + Hash init_material_hash; } @@ -30,6 +31,12 @@ namespace Zobrist for (int i = 0; i < 8; i++) randoms::rnd_ep_file[i] = rnd.next(); + + // Build the initial material hash key + randoms::init_material_hash = 0; + for (PieceType p : { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }) + for (Turn t : { WHITE, BLACK }) + randoms::init_material_hash ^= randoms::rnd_piece_turn_square[p][t][0]; } @@ -63,4 +70,9 @@ namespace Zobrist // These values have been tested for collisions for all possible Move values return 0x89b4fa525 * move.to_int() + 0xe3b2eb29df24cba7; } + + Hash get_initial_material_hash() + { + return randoms::init_material_hash; + } } diff --git a/src/Zobrist.hpp b/src/Zobrist.hpp index b03b777..573cf49 100644 --- a/src/Zobrist.hpp +++ b/src/Zobrist.hpp @@ -10,6 +10,7 @@ namespace Zobrist extern Hash rnd_black_move; extern Hash rnd_castle_side_turn[NUM_COLORS][NUM_CASTLE_SIDES]; extern Hash rnd_ep_file[8]; + extern Hash init_material_hash; } void build_rnd_hashes(); @@ -19,4 +20,5 @@ namespace Zobrist Hash get_castle_side_turn(CastleSide side, Turn turn); Hash get_ep_file(int file); Hash get_move_hash(Move move); + Hash get_initial_material_hash(); }