Skip to content

Commit

Permalink
Introduce material imbalance and material hash tables
Browse files Browse the repository at this point in the history
Bench: 1956204
  • Loading branch information
ruicoelhopedro committed Sep 23, 2022
1 parent 7e847e1 commit 132140b
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 32 deletions.
54 changes: 47 additions & 7 deletions src/Evaluation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "Position.hpp"
#include "Evaluation.hpp"
#include "PieceSquareTables.hpp"
#include "Thread.hpp"
#include <cassert>
#include <stdlib.h>

Expand All @@ -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<MaterialEntry>& 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;
}
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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 })
Expand All @@ -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<false>(data.fields[WHITE].pieces[PAWN], data.fields[BLACK].pieces[PAWN]) << std::endl;
std::cout << " Knights | " << Term<false>(data.fields[WHITE].pieces[KNIGHT], data.fields[BLACK].pieces[KNIGHT]) << std::endl;
Expand Down
54 changes: 39 additions & 15 deletions src/Evaluation.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#pragma once
#include "Types.hpp"
#include "Position.hpp"
#include "Hash.hpp"
#include <iomanip>
#include <array>


class Thread;

namespace Evaluation
{
class Attacks
Expand Down Expand Up @@ -58,6 +61,7 @@ namespace Evaluation
struct EvalFields
{
MixedScore material;
MixedScore imbalance;
MixedScore placement;
MixedScore space;
MixedScore passed;
Expand All @@ -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<MaterialEntry>& table);


Score evaluation(const Board& board, EvalData& data, Thread& thread);


void eval_table(const Board& board, EvalData& data, Score score);
Expand Down Expand Up @@ -129,18 +167,4 @@ namespace Evaluation
out << " ";
return out;
}
}

template<bool OUTPUT>
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;
}
4 changes: 2 additions & 2 deletions src/Hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{}
Expand Down
10 changes: 10 additions & 0 deletions src/PieceSquareTables.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 22 additions & 4 deletions src/Position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,15 @@ 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)
{
auto c = fen.cbegin();

// 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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/Position.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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);
}


Expand All @@ -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);
}


Expand Down Expand Up @@ -607,6 +615,9 @@ class Board
Hash hash() const;


Hash material_hash() const;


Square least_valuable(Bitboard bb) const;


Expand Down
4 changes: 2 additions & 2 deletions src/Search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<false>(position);
static_eval = turn_to_color(Turn) * data.thread().evaluate<false>(position);
}
data.static_eval = static_eval;

Expand Down Expand Up @@ -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<false>(position);
static_eval = turn_to_color(Turn) * data.thread().evaluate<false>(position);
best_score = static_eval;

// Can we use the TT value for a better static evaluation?
Expand Down
4 changes: 3 additions & 1 deletion src/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -58,6 +59,7 @@ void Thread::wait()
void Thread::clear()
{
m_histories.clear();
m_material_table.clear();
}


Expand Down
Loading

0 comments on commit 132140b

Please sign in to comment.