Skip to content

Commit

Permalink
Add pawn cache (#282)
Browse files Browse the repository at this point in the history
bench 7142602
  • Loading branch information
sroelants authored Aug 14, 2024
1 parent 7cb8020 commit 7e1a5d9
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 35 deletions.
5 changes: 4 additions & 1 deletion simbelmyne/src/cli/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::time::Duration;

use uci::time_control::TimeControl;

use crate::evaluate::pawn_cache::PawnCache;
use crate::history_tables::History;
use crate::position::Position;
use crate::transpositions::TTable;
Expand Down Expand Up @@ -81,19 +82,21 @@ pub fn run_bench() {
println!(
"{} nodes {} nps",
total_nodes,
total_nodes / total_time.as_secs(),
1_000_000_000 * total_nodes / total_time.as_nanos() as u64,
);
}

pub fn run_single(fen: &str, depth: usize) -> BenchResult {
let board = fen.parse().unwrap();
let position = Position::new(board);
let mut tt = TTable::with_capacity(16);
let mut pc = PawnCache::with_capacity(2);
let (mut tc, _handle) = TimeController::new(TimeControl::Depth(depth), board);
let mut history = History::new();

let search = position.search::<NO_DEBUG>(
&mut tt,
&mut pc,
&mut history,
&mut tc,
);
Expand Down
107 changes: 88 additions & 19 deletions simbelmyne/src/evaluate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ pub mod tuner;
pub mod pawn_structure;
pub mod pretty_print;
pub mod terms;
pub mod pawn_cache;
pub mod util;
mod piece_square_tables;

use crate::history_tables::history::HistoryIndex;
use crate::s;
use crate::zobrist::ZHash;

use chess::bitboard::Bitboard;
use chess::board::Board;
Expand All @@ -46,6 +48,8 @@ use chess::square::Square;
use chess::piece::PieceType;
use chess::piece::Color;
use params::TEMPO_BONUS;
use pawn_cache::PawnCache;
use pawn_cache::PawnCacheEntry;
use self::terms::*;
use self::pawn_structure::PawnStructure;
pub use util::*;
Expand Down Expand Up @@ -152,16 +156,41 @@ impl Eval {
pub fn new(board: &Board) -> Self {
let mut eval = Self::default();

// Walk through all the pieces on the board, and add update the Score
// counter for each one.
for (sq_idx, piece) in board.piece_list.into_iter().enumerate() {
if let Some(piece) = piece {
let square = Square::from(sq_idx);

eval.add(piece, square, board);
let sq = Square::from(sq_idx);
eval.game_phase += Self::phase_value(piece);
eval.material += material(piece, None);
eval.psqt += psqt(piece, sq, None);
}
}

eval.pawn_structure = PawnStructure::new(board);
eval.pawn_shield = pawn_shield::<WHITE>(board, None);
eval.pawn_shield -= pawn_shield::<BLACK>(board, None);
eval.pawn_storm = pawn_storm::<WHITE>(board, None);
eval.pawn_storm -= pawn_storm::<BLACK>(board, None);
eval.passers_friendly_king = passers_friendly_king::<WHITE>(board, &eval.pawn_structure, None);
eval.passers_friendly_king -= passers_friendly_king::<BLACK>(board, &eval.pawn_structure, None);
eval.passers_enemy_king = passers_enemy_king::<WHITE>(board, &eval.pawn_structure, None);
eval.passers_enemy_king -= passers_enemy_king::<BLACK>(board, &eval.pawn_structure, None);
eval.knight_outposts = knight_outposts::<WHITE>(board, &eval.pawn_structure, None);
eval.knight_outposts -= knight_outposts::<BLACK>(board, &eval.pawn_structure, None);
eval.bishop_outposts = bishop_outposts::<WHITE>(board, &eval.pawn_structure, None);
eval.bishop_outposts -= bishop_outposts::<BLACK>(board, &eval.pawn_structure, None);
eval.bishop_pair = bishop_pair::<WHITE>(board, None);
eval.bishop_pair -= bishop_pair::<BLACK>(board, None);
eval.rook_open_file = rook_open_file::<WHITE>(board, &eval.pawn_structure, None);
eval.rook_open_file -= rook_open_file::<BLACK>(board, &eval.pawn_structure, None);
eval.rook_semiopen_file = rook_semiopen_file::<WHITE>(board, &eval.pawn_structure, None);
eval.rook_semiopen_file -= rook_semiopen_file::<BLACK>(board, &eval.pawn_structure, None);
eval.queen_open_file = queen_open_file::<WHITE>(board, &eval.pawn_structure, None);
eval.queen_open_file -= queen_open_file::<BLACK>(board, &eval.pawn_structure, None);
eval.queen_semiopen_file = queen_semiopen_file::<WHITE>(board, &eval.pawn_structure, None);
eval.queen_semiopen_file -= queen_semiopen_file::<BLACK>(board, &eval.pawn_structure, None);
eval.major_on_seventh = major_on_seventh::<WHITE>(board, None);
eval.major_on_seventh -= major_on_seventh::<BLACK>(board, None);

eval
}

Expand Down Expand Up @@ -219,7 +248,13 @@ impl Eval {
if board.current.is_white() { score } else { -score }
}

pub fn play_move(&self, idx: HistoryIndex, board: &Board) -> Self {
pub fn play_move(
&self,
idx: HistoryIndex,
board: &Board,
pawn_hash: ZHash,
pawn_cache: &mut PawnCache
) -> Self {
let mut new_score = *self;
let HistoryIndex { moved, captured, mv } = idx;
let us = moved.color();
Expand All @@ -230,70 +265,104 @@ impl Eval {

// Remove any captured pieces
if let Some(captured) = captured {
new_score.remove(captured, mv.get_capture_sq(), &board);
new_score.remove(captured, mv.get_capture_sq(), &board, pawn_hash, pawn_cache);
}

// Update the moved piece
if idx.mv.is_promotion() {
new_score.remove(moved, mv.src(), &board);
new_score.remove(moved, mv.src(), &board, pawn_hash, pawn_cache);

let promo_piece = Piece::new(mv.get_promo_type().unwrap(), us);
new_score.add(promo_piece, mv.tgt(), &board);
new_score.add(promo_piece, mv.tgt(), &board, pawn_hash, pawn_cache);
} else {
new_score.update(moved, mv.src(), mv.tgt(), &board);
new_score.update(moved, mv.src(), mv.tgt(), &board, pawn_hash, pawn_cache);
}

if mv.is_castle() {
let ctype = CastleType::from_move(mv).unwrap();
let rook_move = ctype.rook_move();
let rook = Piece::new(PieceType::Rook, us);
new_score.update(rook, rook_move.src(), rook_move.tgt(), &board);
new_score.update(rook, rook_move.src(), rook_move.tgt(), &board, pawn_hash, pawn_cache);
}

new_score
}

/// Update the Eval by adding a piece to it
pub fn add(&mut self, piece: Piece, sq: Square, board: &Board) {
pub fn add(
&mut self,
piece: Piece,
sq: Square,
board: &Board,
pawn_hash: ZHash,
pawn_cache: &mut PawnCache
) {
self.game_phase += Self::phase_value(piece);
self.material += material(piece, None);
self.psqt += psqt(piece, sq, None);
self.update_incremental_terms(piece, board);
self.update_incremental_terms(piece, board, pawn_hash, pawn_cache);
}

/// Update the score by removing a piece from it
pub fn remove(&mut self, piece: Piece, sq: Square, board: &Board) {
pub fn remove(
&mut self,
piece: Piece,
sq: Square,
board: &Board,
pawn_hash: ZHash,
pawn_cache: &mut PawnCache
) {
self.game_phase -= Self::phase_value(piece);
self.material -= material(piece, None);
self.psqt -= psqt(piece, sq, None);
self.update_incremental_terms(piece, board);
self.update_incremental_terms(piece, board, pawn_hash, pawn_cache);
}

/// Update the score by moving a piece from one square to another
///
/// Slightly more efficient helper that does less work than calling
/// `Eval::remove` and `Eval::add` in succession.
pub fn update(&mut self, piece: Piece, from: Square, to: Square, board: &Board) {
pub fn update(
&mut self,
piece: Piece,
from: Square,
to: Square,
board: &Board,
pawn_hash: ZHash,
pawn_cache: &mut PawnCache
) {
let from_psqt = psqt(piece, from, None);
let to_psqt = psqt(piece, to, None);
self.psqt -= from_psqt;
self.psqt += to_psqt;
self.update_incremental_terms(piece, board)
self.update_incremental_terms(piece, board, pawn_hash, pawn_cache);
}

/// Update the incremental eval terms, according to piece that moved.
///
/// This tries to save as much work as possible, by only recomputing eval
/// terms that depend on the moved piece. No need to update rook-related
/// terms when a bishop has moved.
fn update_incremental_terms(&mut self, piece: Piece, board: &Board) {
fn update_incremental_terms(
&mut self,
piece: Piece,
board: &Board,
pawn_hash: ZHash,
pawn_cache: &mut PawnCache
) {
use PieceType::*;

match piece.piece_type() {
// Pawn moves require almost _all_ terms, save a couple, to be
// updated.
Pawn => {
self.pawn_structure = PawnStructure::new(board);
self.pawn_structure = if let Some(entry) = pawn_cache.probe(pawn_hash) {
entry.into()
} else {
let pawn_structure = PawnStructure::new(board);
pawn_cache.insert(PawnCacheEntry::new(pawn_hash, pawn_structure));
pawn_structure
};

self.pawn_shield = pawn_shield::<WHITE>(board, None);
self.pawn_shield -= pawn_shield::<BLACK>(board, None);
Expand Down
73 changes: 73 additions & 0 deletions simbelmyne/src/evaluate/pawn_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use chess::bitboard::Bitboard;
use std::mem::size_of;

use crate::transpositions::ZKey;
use crate::zobrist::ZHash;

use super::{pawn_structure::PawnStructure, S};

#[derive(Copy, Clone, Debug, Default)]
pub struct PawnCacheEntry {
pub hash: ZHash,
pub score: S,
pub passers: [Bitboard; 2],
pub semi_opens: [Bitboard; 2],
pub outposts: [Bitboard; 2]
}

impl PawnCacheEntry {
pub fn new(hash: ZHash, pawn_structure: PawnStructure) -> Self {
Self {
hash,
score: pawn_structure.score(),
passers: pawn_structure.passed_pawns,
semi_opens: pawn_structure.semi_open_files,
outposts: pawn_structure.outposts,
}
}
}

pub struct PawnCache {
table: Vec<PawnCacheEntry>,
size: usize,
}

impl PawnCache {
/// Create a new table with the requested capacity in megabytes
pub fn with_capacity(mb_size: usize) -> PawnCache {
// The number of enties in the TT
let size = (mb_size << 20) / size_of::<PawnCacheEntry>();
let mut table = Vec::with_capacity(size);
table.resize_with(size, PawnCacheEntry::default);

PawnCache { table, size }
}

pub fn insert(&mut self, entry: PawnCacheEntry) {
let key: ZKey = ZKey::from_hash(entry.hash, self.size);
let existing = self.table[key.0];
self.table[key.0] = entry;
}

// Check whether the hash appears in the transposition table, and return it
// if so.
//
pub fn probe(&self, hash: ZHash) -> Option<PawnCacheEntry> {
let key = ZKey::from_hash(hash, self.size);

self.table.get(key.0)
.filter(|entry| entry.hash == hash)
.copied()
}
}

impl From<PawnCacheEntry> for PawnStructure {
fn from(value: PawnCacheEntry) -> Self {
Self {
score: value.score,
passed_pawns: value.passers,
semi_open_files: value.semi_opens,
outposts: value.outposts,
}
}
}
17 changes: 6 additions & 11 deletions simbelmyne/src/evaluate/pawn_structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ const BLACK: bool = false;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct PawnStructure {
/// The score associated with the pawn structure
score: S,
pub score: S,

/// Passed pawn bitboards for White and Black
passed_pawns: [Bitboard; Color::COUNT],
pub passed_pawns: [Bitboard; Color::COUNT],

/// Semi-open file bitboards for White and Black
semi_open_files: [Bitboard; Color::COUNT],
pub semi_open_files: [Bitboard; Color::COUNT],

/// Outpost squares
/// Squares that can't be attacked (easily) by opponent pawns, and are
/// defended by one of our pawns
outposts: [Bitboard; Color::COUNT],
pub outposts: [Bitboard; Color::COUNT],
}

impl PawnStructure {
Expand All @@ -37,13 +37,8 @@ impl PawnStructure {
let black_pawns = board.pawns(Black);

// Pawns attacks
let white_left_attacks = white_pawns.forward_left::<WHITE>();
let white_right_attacks = white_pawns.forward_right::<WHITE>();
let white_attacks = white_left_attacks | white_right_attacks;

let black_left_attacks = black_pawns.forward_left::<BLACK>();
let black_right_attacks = black_pawns.forward_right::<BLACK>();
let black_attacks = black_left_attacks | black_right_attacks;
let white_attacks = board.pawn_attacks(White);
let black_attacks = board.pawn_attacks(Black);

// Passed pawns
let white_passers = white_pawns
Expand Down
3 changes: 3 additions & 0 deletions simbelmyne/src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
//!
use std::io::IsTerminal;
use std::time::Duration;
use crate::evaluate::pawn_cache::PawnCache;
use crate::evaluate::ScoreExt;
use crate::history_tables::pv::PVTable;
use crate::history_tables::History;
Expand Down Expand Up @@ -108,6 +109,7 @@ impl Position {
pub fn search<const DEBUG: bool>(
&self,
tt: &mut TTable,
pawn_cache: &mut PawnCache,
history: &mut History,
tc: &mut TimeController,
) -> SearchReport {
Expand Down Expand Up @@ -139,6 +141,7 @@ impl Position {
search.depth,
latest_report.score,
tt,
pawn_cache,
&mut pv,
&mut search
);
Expand Down
3 changes: 3 additions & 0 deletions simbelmyne/src/search/aspiration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//! The hope, as always in these things, is that the score is stable enough that
//! re-searches are minimal, and the time we save in the best-case scenario
//! more than compensates for the odd re-search.
use crate::evaluate::pawn_cache::PawnCache;
use crate::evaluate::Eval;
use crate::history_tables::pv::PVTable;
use crate::position::Position;
Expand All @@ -31,6 +32,7 @@ impl Position {
depth: usize,
guess: Score,
tt: &mut TTable,
pawn_cache: &mut PawnCache,
pv: &mut PVTable,
search: &mut Search,
) -> Score {
Expand All @@ -51,6 +53,7 @@ impl Position {
alpha,
beta,
tt,
pawn_cache,
pv,
search,
Eval::new(&self.board),
Expand Down
Loading

0 comments on commit 7e1a5d9

Please sign in to comment.