From 369c6d5ed4ec9b1d6ec31a08939f312a1557fcc0 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Wed, 11 Dec 2024 23:46:31 -0500 Subject: [PATCH 01/11] feat: add more operators to `Score` --- engine/src/score.rs | 63 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/engine/src/score.rs b/engine/src/score.rs index ca333fd..0df1848 100644 --- a/engine/src/score.rs +++ b/engine/src/score.rs @@ -4,7 +4,7 @@ * Created Date: Thursday, November 14th 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Tue Dec 10 2024 + * Last Modified: Wed Dec 11 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later @@ -12,7 +12,7 @@ * */ -use std::ops::{Sub, SubAssign}; +use std::ops::{Div, DivAssign, Mul, MulAssign, Shl, Sub, SubAssign}; use std::{ fmt::{self, Display, Formatter}, ops::{Add, AddAssign, Neg}, @@ -120,3 +120,62 @@ impl SubAssign for Score { self.0 -= rhs; } } + +impl Div for Score { + type Output = Score; + fn div(self, rhs: ScoreType) -> Score { + Score(self.0 / rhs) + } +} + +impl Div for Score { + type Output = Score; + fn div(self, rhs: Score) -> Score { + Score(self.0 / rhs.0) + } +} + +impl DivAssign for Score { + fn div_assign(&mut self, rhs: ScoreType) { + self.0 /= rhs; + } +} + +impl DivAssign for Score { + fn div_assign(&mut self, rhs: Score) { + self.0 /= rhs.0; + } +} + +impl Mul for Score { + type Output = Score; + fn mul(self, rhs: ScoreType) -> Score { + Score(self.0 * rhs) + } +} + +impl Mul for Score { + type Output = Score; + fn mul(self, rhs: Score) -> Score { + Score(self.0 * rhs.0) + } +} + +impl MulAssign for Score { + fn mul_assign(&mut self, rhs: ScoreType) { + self.0 *= rhs; + } +} + +impl MulAssign for Score { + fn mul_assign(&mut self, rhs: Score) { + self.0 *= rhs.0; + } +} + +impl Shl for Score { + type Output = Score; + fn shl(self, rhs: u32) -> Score { + Score(self.0 << rhs) + } +} From a7f0608b4f12a23ef46f0ddd8a0f14e90af84ceb Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Wed, 11 Dec 2024 23:47:02 -0500 Subject: [PATCH 02/11] feat: first aspiration window impl attempt --- engine/src/aspiration_window.rs | 71 +++++++++++++++++++++++++++++++++ engine/src/lib.rs | 2 + engine/src/tuneable.rs | 19 +++++++++ 3 files changed, 92 insertions(+) create mode 100644 engine/src/aspiration_window.rs create mode 100644 engine/src/tuneable.rs diff --git a/engine/src/aspiration_window.rs b/engine/src/aspiration_window.rs new file mode 100644 index 0000000..8aeb2fa --- /dev/null +++ b/engine/src/aspiration_window.rs @@ -0,0 +1,71 @@ +use crate::{ + score::{Score, ScoreType}, + tuneable::{INITIAL_ASPIRATION_WINDOW, MIN_ASPIRATION_WINDOW}, +}; + +pub(crate) struct AspirationWindow { + alpha: Score, + beta: Score, + value: Score, + alpha_fails: u32, + beta_fails: u32, +} + +impl AspirationWindow { + pub(crate) fn infinite() -> Self { + Self { + alpha: -Score::INF, + beta: Score::INF, + value: Score::default(), + alpha_fails: 0, + beta_fails: 0, + } + } + + pub(crate) fn alpha(&self) -> Score { + self.alpha + } + + pub(crate) fn beta(&self) -> Score { + self.beta + } + + pub(crate) fn around(score: Score, depth: ScoreType) -> Self { + if score == Score::MATE { + // If the score is mate, we can't use the window as we would expect search results to fluctuate. + // Set it to a full window and search again. + return Self::infinite(); + } else { + Self { + alpha: (score - Self::window_size(depth)).max(-Score::INF), + beta: (score + Self::window_size(depth)).min(Score::INF), + value: score, + alpha_fails: 0, + beta_fails: 0, + } + } + } + + pub(crate) fn widen_down(&mut self, score: Score, depth: ScoreType) { + self.value = score; + let margin = Self::window_size(depth) << (self.alpha_fails + 1); + self.alpha = self.value - margin; + // reset beta to be (alpha + beta / 2) + self.beta = (self.alpha + self.beta) / 2; + // save that this was a fail low + self.alpha_fails += 1; + } + + pub(crate) fn widen_up(&mut self, score: Score, depth: ScoreType) { + self.value = score; + let margin = Self::window_size(depth).0 << (self.beta_fails + 1); + self.beta = self.value + margin; + self.beta_fails += 1; + // Note that we do not alter alpha here, as we are widening the window upwards. + } + + fn window_size(depth: ScoreType) -> Score { + let window = ((INITIAL_ASPIRATION_WINDOW << 3) / depth).max(MIN_ASPIRATION_WINDOW); + Score::new(window) + } +} diff --git a/engine/src/lib.rs b/engine/src/lib.rs index ea38d02..3d1d268 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1,3 +1,4 @@ +pub mod aspiration_window; pub mod defs; pub mod engine; pub mod evaluation; @@ -8,3 +9,4 @@ pub mod score; pub mod search; pub mod search_thread; pub mod ttable; +pub mod tuneable; diff --git a/engine/src/tuneable.rs b/engine/src/tuneable.rs new file mode 100644 index 0000000..28136f1 --- /dev/null +++ b/engine/src/tuneable.rs @@ -0,0 +1,19 @@ +/* + * tuneable.rs + * Part of the byte-knight project + * Created Date: Wednesday, December 11th 2024 + * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) + * ----- + * Last Modified: Wed Dec 11 2024 + * ----- + * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) + * GNU General Public License v3.0 or later + * https://www.gnu.org/licenses/gpl-3.0-standalone.html + * + */ + +use crate::score::ScoreType; + +pub(crate) const INITIAL_ASPIRATION_WINDOW: ScoreType = 25; +pub(crate) const MIN_ASPIRATION_WINDOW: ScoreType = 10; +pub(crate) const MIN_ASPIRATION_DEPTH: ScoreType = 1; From 53dccd7b512f50031871a49fa2a84a191d61a4e9 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Wed, 11 Dec 2024 23:47:17 -0500 Subject: [PATCH 03/11] feat: use aspiration windows in search bench: 1496384 --- engine/src/search.rs | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/engine/src/search.rs b/engine/src/search.rs index 220eff8..d051910 100644 --- a/engine/src/search.rs +++ b/engine/src/search.rs @@ -26,6 +26,7 @@ use itertools::Itertools; use uci_parser::{UciInfo, UciResponse, UciSearchOptions}; use crate::{ + aspiration_window::AspirationWindow, evaluation::Evaluation, history_table::HistoryTable, score::{MoveOrderScoreType, Score, ScoreType}, @@ -229,23 +230,38 @@ impl<'a> Search<'a> { best_result.best_move = Some(*move_list.at(0).unwrap()) } + let mut aspiration_window = AspirationWindow::infinite(); while self.parameters.start_time.elapsed() <= self.parameters.soft_timeout && best_result.depth <= self.parameters.max_depth { - // search the tree, starting at the current depth (starts at 1) - let score = self.negamax( - board, - best_result.depth as ScoreType, - 0, - -Score::INF, - Score::INF, - ); + let mut score: Score; + 'aspiration_window: loop { + // search the tree, starting at the current depth (starts at 1) + score = self.negamax( + board, + best_result.depth as ScoreType, + 0, + aspiration_window.alpha(), + aspiration_window.beta(), + ); + + if score <= aspiration_window.alpha() { + // fail low, widen the window + aspiration_window.widen_down(score, best_result.depth as ScoreType); + } else if score >= aspiration_window.beta() { + // fail high, widen the window + aspiration_window.widen_up(score, best_result.depth as ScoreType); + } else { + // we have a valid score, break the loop + break 'aspiration_window; + } - // check stop conditions - if self.should_stop_searching() { - // we have to stop searching now, use the best result we have - // no score update - break; + // check stop conditions + if self.should_stop_searching() { + // we have to stop searching now, use the best result we have + // no score update + break 'aspiration_window; + } } // update the best result From 2cd57bf3ad6fece8fb98cfed2d906ce8020ef225 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 12 Dec 2024 00:12:41 -0500 Subject: [PATCH 04/11] chore: add alpha/beta constants in `Score` --- engine/src/aspiration_window.rs | 4 ++-- engine/src/score.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/src/aspiration_window.rs b/engine/src/aspiration_window.rs index 8aeb2fa..d5ec9e5 100644 --- a/engine/src/aspiration_window.rs +++ b/engine/src/aspiration_window.rs @@ -14,8 +14,8 @@ pub(crate) struct AspirationWindow { impl AspirationWindow { pub(crate) fn infinite() -> Self { Self { - alpha: -Score::INF, - beta: Score::INF, + alpha: Score::ALPHA, + beta: Score::BETA, value: Score::default(), alpha_fails: 0, beta_fails: 0, diff --git a/engine/src/score.rs b/engine/src/score.rs index 0df1848..48169e8 100644 --- a/engine/src/score.rs +++ b/engine/src/score.rs @@ -29,6 +29,8 @@ impl Score { pub const DRAW: Score = Score(0); pub const MATE: Score = Score(ScoreType::MAX as ScoreType); pub const INF: Score = Score(ScoreType::MAX as ScoreType); + pub const ALPHA: Score = Score(-Score::INF.0); + pub const BETA: Score = Score::INF; // Max/min score for history heuristic // Must be lower then the minimum score for captures in MVV_LVA From 31930ded92e3ad7974883787f0b3268e494d7e6e Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 12 Dec 2024 00:13:10 -0500 Subject: [PATCH 05/11] chore: try to fix aspiration window issues bench: 1496384 --- engine/src/aspiration_window.rs | 18 +++++++++++++----- engine/src/score.rs | 6 +++++- engine/src/search.rs | 9 ++++++--- engine/src/tuneable.rs | 2 +- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/engine/src/aspiration_window.rs b/engine/src/aspiration_window.rs index d5ec9e5..74e55cc 100644 --- a/engine/src/aspiration_window.rs +++ b/engine/src/aspiration_window.rs @@ -1,6 +1,6 @@ use crate::{ score::{Score, ScoreType}, - tuneable::{INITIAL_ASPIRATION_WINDOW, MIN_ASPIRATION_WINDOW}, + tuneable::{INITIAL_ASPIRATION_WINDOW, MIN_ASPIRATION_DEPTH, MIN_ASPIRATION_WINDOW}, }; pub(crate) struct AspirationWindow { @@ -30,15 +30,23 @@ impl AspirationWindow { self.beta } + pub(crate) fn failed_low(&self, score: Score) -> bool { + score != Score::ALPHA && score <= self.alpha + } + + pub(crate) fn failed_high(&self, score: Score) -> bool { + score != Score::BETA && score >= self.beta + } + pub(crate) fn around(score: Score, depth: ScoreType) -> Self { - if score == Score::MATE { + if depth > MIN_ASPIRATION_DEPTH || score.is_mate() { // If the score is mate, we can't use the window as we would expect search results to fluctuate. // Set it to a full window and search again. return Self::infinite(); } else { Self { - alpha: (score - Self::window_size(depth)).max(-Score::INF), - beta: (score + Self::window_size(depth)).min(Score::INF), + alpha: (score - Self::window_size(depth)).max(Score::ALPHA), + beta: (score + Self::window_size(depth)).min(Score::BETA), value: score, alpha_fails: 0, beta_fails: 0, @@ -65,7 +73,7 @@ impl AspirationWindow { } fn window_size(depth: ScoreType) -> Score { - let window = ((INITIAL_ASPIRATION_WINDOW << 3) / depth).max(MIN_ASPIRATION_WINDOW); + let window = ((INITIAL_ASPIRATION_WINDOW << 2) / depth).max(MIN_ASPIRATION_WINDOW); Score::new(window) } } diff --git a/engine/src/score.rs b/engine/src/score.rs index 48169e8..79fc58a 100644 --- a/engine/src/score.rs +++ b/engine/src/score.rs @@ -4,7 +4,7 @@ * Created Date: Thursday, November 14th 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Wed Dec 11 2024 + * Last Modified: Thu Dec 12 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later @@ -43,6 +43,10 @@ impl Score { pub fn clamp(&self, min: ScoreType, max: ScoreType) -> Score { Score(self.0.clamp(min, max)) } + + pub fn is_mate(&self) -> bool { + self.0.abs() >= Score::MATE.0.abs() + } } impl From for UciScore { diff --git a/engine/src/search.rs b/engine/src/search.rs index d051910..afa38de 100644 --- a/engine/src/search.rs +++ b/engine/src/search.rs @@ -230,10 +230,13 @@ impl<'a> Search<'a> { best_result.best_move = Some(*move_list.at(0).unwrap()) } - let mut aspiration_window = AspirationWindow::infinite(); while self.parameters.start_time.elapsed() <= self.parameters.soft_timeout && best_result.depth <= self.parameters.max_depth { + // create an aspiration window around the best result so far + let mut aspiration_window = + AspirationWindow::around(best_result.score, best_result.depth as ScoreType); + let mut score: Score; 'aspiration_window: loop { // search the tree, starting at the current depth (starts at 1) @@ -245,10 +248,10 @@ impl<'a> Search<'a> { aspiration_window.beta(), ); - if score <= aspiration_window.alpha() { + if aspiration_window.failed_low(score) { // fail low, widen the window aspiration_window.widen_down(score, best_result.depth as ScoreType); - } else if score >= aspiration_window.beta() { + } else if aspiration_window.failed_high(score) { // fail high, widen the window aspiration_window.widen_up(score, best_result.depth as ScoreType); } else { diff --git a/engine/src/tuneable.rs b/engine/src/tuneable.rs index 28136f1..954b2a7 100644 --- a/engine/src/tuneable.rs +++ b/engine/src/tuneable.rs @@ -4,7 +4,7 @@ * Created Date: Wednesday, December 11th 2024 * Author: Paul Tsouchlos (DeveloperPaul123) (developer.paul.123@gmail.com) * ----- - * Last Modified: Wed Dec 11 2024 + * Last Modified: Thu Dec 12 2024 * ----- * Copyright (c) 2024 Paul Tsouchlos (DeveloperPaul123) * GNU General Public License v3.0 or later From 8f7c7c2b736db02e38e99221d36a89acb7fa051f Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 12 Dec 2024 10:14:44 -0500 Subject: [PATCH 06/11] chore: add more constants --- engine/src/defs.rs | 2 ++ engine/src/score.rs | 5 ++++- engine/src/search.rs | 9 ++------- engine/src/tuneable.rs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/engine/src/defs.rs b/engine/src/defs.rs index b2b0d91..33a8279 100644 --- a/engine/src/defs.rs +++ b/engine/src/defs.rs @@ -30,3 +30,5 @@ impl About { pub const AUTHORS: &'static str = "Paul T. (DeveloperPaul123)"; pub const BANNER: &'static str = BANNER; } + +pub(crate) const MAX_DEPTH: u8 = 128; diff --git a/engine/src/score.rs b/engine/src/score.rs index 79fc58a..c66d942 100644 --- a/engine/src/score.rs +++ b/engine/src/score.rs @@ -19,6 +19,8 @@ use std::{ }; use uci_parser::UciScore; +use crate::defs::MAX_DEPTH; + pub(crate) type ScoreType = i16; pub(crate) type MoveOrderScoreType = i32; /// Represents a score in centipawns. @@ -28,6 +30,7 @@ pub struct Score(pub ScoreType); impl Score { pub const DRAW: Score = Score(0); pub const MATE: Score = Score(ScoreType::MAX as ScoreType); + pub const MINIMUM_MATE: Score = Score(Score::MATE.0 - MAX_DEPTH as ScoreType); pub const INF: Score = Score(ScoreType::MAX as ScoreType); pub const ALPHA: Score = Score(-Score::INF.0); pub const BETA: Score = Score::INF; @@ -45,7 +48,7 @@ impl Score { } pub fn is_mate(&self) -> bool { - self.0.abs() >= Score::MATE.0.abs() + self.0.abs() >= Score::MINIMUM_MATE.0.abs() } } diff --git a/engine/src/search.rs b/engine/src/search.rs index afa38de..d2ebc29 100644 --- a/engine/src/search.rs +++ b/engine/src/search.rs @@ -26,16 +26,10 @@ use itertools::Itertools; use uci_parser::{UciInfo, UciResponse, UciSearchOptions}; use crate::{ - aspiration_window::AspirationWindow, - evaluation::Evaluation, - history_table::HistoryTable, - score::{MoveOrderScoreType, Score, ScoreType}, - ttable::{self, TranspositionTableEntry}, + aspiration_window::AspirationWindow, defs::MAX_DEPTH, evaluation::Evaluation, history_table::HistoryTable, score::{MoveOrderScoreType, Score, ScoreType}, ttable::{self, TranspositionTableEntry} }; use ttable::TranspositionTable; -const MAX_DEPTH: u8 = 128; - /// Result for a search. #[derive(Clone, Copy, Debug)] pub struct SearchResult { @@ -225,6 +219,7 @@ impl<'a> Search<'a> { // initialize the best result let mut best_result = SearchResult::default(); let mut move_list = MoveList::new(); + self.move_gen.generate_legal_moves(board, &mut move_list); if !move_list.is_empty() { best_result.best_move = Some(*move_list.at(0).unwrap()) diff --git a/engine/src/tuneable.rs b/engine/src/tuneable.rs index 954b2a7..ae97bc5 100644 --- a/engine/src/tuneable.rs +++ b/engine/src/tuneable.rs @@ -14,6 +14,6 @@ use crate::score::ScoreType; -pub(crate) const INITIAL_ASPIRATION_WINDOW: ScoreType = 25; pub(crate) const MIN_ASPIRATION_WINDOW: ScoreType = 10; pub(crate) const MIN_ASPIRATION_DEPTH: ScoreType = 1; +pub(crate) const ASPIRATION_WINDOW: ScoreType = 6; From 1bb584ea1f735e2c4d51b6a41aa07dcb256ae780 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 12 Dec 2024 10:16:41 -0500 Subject: [PATCH 07/11] chore: try to tweak and adjust aspiration windows bench: 1790245 --- engine/src/aspiration_window.rs | 30 ++++++++++++++---------------- engine/src/score.rs | 3 +++ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/engine/src/aspiration_window.rs b/engine/src/aspiration_window.rs index 74e55cc..185a3b2 100644 --- a/engine/src/aspiration_window.rs +++ b/engine/src/aspiration_window.rs @@ -1,12 +1,11 @@ use crate::{ score::{Score, ScoreType}, - tuneable::{INITIAL_ASPIRATION_WINDOW, MIN_ASPIRATION_DEPTH, MIN_ASPIRATION_WINDOW}, + tuneable::{ASPIRATION_WINDOW, MIN_ASPIRATION_DEPTH, MIN_ASPIRATION_WINDOW}, }; pub(crate) struct AspirationWindow { alpha: Score, beta: Score, - value: Score, alpha_fails: u32, beta_fails: u32, } @@ -16,7 +15,6 @@ impl AspirationWindow { Self { alpha: Score::ALPHA, beta: Score::BETA, - value: Score::default(), alpha_fails: 0, beta_fails: 0, } @@ -38,16 +36,18 @@ impl AspirationWindow { score != Score::BETA && score >= self.beta } + /// Create a new [`AspirationWindow`] centered around the given score. pub(crate) fn around(score: Score, depth: ScoreType) -> Self { - if depth > MIN_ASPIRATION_DEPTH || score.is_mate() { + if depth <= MIN_ASPIRATION_DEPTH || score.is_mate() { // If the score is mate, we can't use the window as we would expect search results to fluctuate. // Set it to a full window and search again. + // We also want to do a full search on the first iteration (i.e. depth == 1); return Self::infinite(); } else { + let window = Self::window_size(depth); Self { - alpha: (score - Self::window_size(depth)).max(Score::ALPHA), - beta: (score + Self::window_size(depth)).min(Score::BETA), - value: score, + alpha: (score - window).max(Score::ALPHA), + beta: (score + window).min(Score::BETA), alpha_fails: 0, beta_fails: 0, } @@ -55,25 +55,23 @@ impl AspirationWindow { } pub(crate) fn widen_down(&mut self, score: Score, depth: ScoreType) { - self.value = score; let margin = Self::window_size(depth) << (self.alpha_fails + 1); - self.alpha = self.value - margin; - // reset beta to be (alpha + beta / 2) - self.beta = (self.alpha + self.beta) / 2; + self.alpha = (score - margin).max(Score::ALPHA); // save that this was a fail low self.alpha_fails += 1; } pub(crate) fn widen_up(&mut self, score: Score, depth: ScoreType) { - self.value = score; - let margin = Self::window_size(depth).0 << (self.beta_fails + 1); - self.beta = self.value + margin; - self.beta_fails += 1; // Note that we do not alter alpha here, as we are widening the window upwards. + let margin = Self::window_size(depth) << (self.beta_fails + 1); + let new_beta = (score.0 as i32 + margin.0 as i32).min(Score::BETA.0 as i32); + self.beta = Score::new(new_beta as ScoreType); + // save that this was a fail high + self.beta_fails += 1; } fn window_size(depth: ScoreType) -> Score { - let window = ((INITIAL_ASPIRATION_WINDOW << 2) / depth).max(MIN_ASPIRATION_WINDOW); + let window = (ASPIRATION_WINDOW + (50 / depth)).max(MIN_ASPIRATION_WINDOW); Score::new(window) } } diff --git a/engine/src/score.rs b/engine/src/score.rs index c66d942..21f6616 100644 --- a/engine/src/score.rs +++ b/engine/src/score.rs @@ -30,6 +30,7 @@ pub struct Score(pub ScoreType); impl Score { pub const DRAW: Score = Score(0); pub const MATE: Score = Score(ScoreType::MAX as ScoreType); + /// The minimum mate score. This is the maximum score minus the maximum depth. pub const MINIMUM_MATE: Score = Score(Score::MATE.0 - MAX_DEPTH as ScoreType); pub const INF: Score = Score(ScoreType::MAX as ScoreType); pub const ALPHA: Score = Score(-Score::INF.0); @@ -47,6 +48,8 @@ impl Score { Score(self.0.clamp(min, max)) } + /// Returns true if the score is a mate score. + /// This is the case if the absolute value of the score is greater than or equal to `Score::MINIMUM_MATE`. pub fn is_mate(&self) -> bool { self.0.abs() >= Score::MINIMUM_MATE.0.abs() } From 6d412560e9899391ef872c9586740d1e5864c9ec Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 12 Dec 2024 10:51:06 -0500 Subject: [PATCH 08/11] chore: simplify aspiration windows Reduce confusion with alpha/beta start points and revert to using the INF constant. Also simplified aspiration window widening with just a constant for now. bench: 1583604 --- engine/src/aspiration_window.rs | 29 +++++++++++++++-------------- engine/src/score.rs | 6 ++++-- engine/src/tuneable.rs | 3 +-- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/engine/src/aspiration_window.rs b/engine/src/aspiration_window.rs index 185a3b2..1631c23 100644 --- a/engine/src/aspiration_window.rs +++ b/engine/src/aspiration_window.rs @@ -1,6 +1,6 @@ use crate::{ score::{Score, ScoreType}, - tuneable::{ASPIRATION_WINDOW, MIN_ASPIRATION_DEPTH, MIN_ASPIRATION_WINDOW}, + tuneable::{ASPIRATION_WINDOW, MIN_ASPIRATION_DEPTH}, }; pub(crate) struct AspirationWindow { @@ -13,8 +13,8 @@ pub(crate) struct AspirationWindow { impl AspirationWindow { pub(crate) fn infinite() -> Self { Self { - alpha: Score::ALPHA, - beta: Score::BETA, + alpha: -Score::INF, + beta: Score::INF, alpha_fails: 0, beta_fails: 0, } @@ -29,11 +29,11 @@ impl AspirationWindow { } pub(crate) fn failed_low(&self, score: Score) -> bool { - score != Score::ALPHA && score <= self.alpha + score != -Score::INF && score <= self.alpha } pub(crate) fn failed_high(&self, score: Score) -> bool { - score != Score::BETA && score >= self.beta + score != Score::INF && score >= self.beta } /// Create a new [`AspirationWindow`] centered around the given score. @@ -46,8 +46,8 @@ impl AspirationWindow { } else { let window = Self::window_size(depth); Self { - alpha: (score - window).max(Score::ALPHA), - beta: (score + window).min(Score::BETA), + alpha: (score - window).max(-Score::INF), + beta: (score + window).min(Score::INF), alpha_fails: 0, beta_fails: 0, } @@ -55,23 +55,24 @@ impl AspirationWindow { } pub(crate) fn widen_down(&mut self, score: Score, depth: ScoreType) { - let margin = Self::window_size(depth) << (self.alpha_fails + 1); - self.alpha = (score - margin).max(Score::ALPHA); + // Note that we do not alter beta here, as we are widening the window downwards. + let margin = Self::window_size(depth) + self.alpha_fails as ScoreType * ASPIRATION_WINDOW; + self.alpha = (score - margin).max(-Score::INF); // save that this was a fail low self.alpha_fails += 1; } pub(crate) fn widen_up(&mut self, score: Score, depth: ScoreType) { // Note that we do not alter alpha here, as we are widening the window upwards. - let margin = Self::window_size(depth) << (self.beta_fails + 1); - let new_beta = (score.0 as i32 + margin.0 as i32).min(Score::BETA.0 as i32); + let margin = Self::window_size(depth) + self.beta_fails as ScoreType * ASPIRATION_WINDOW; + let new_beta = (score.0 as i32 + margin.0 as i32).min(Score::INF.0 as i32); self.beta = Score::new(new_beta as ScoreType); // save that this was a fail high self.beta_fails += 1; } - fn window_size(depth: ScoreType) -> Score { - let window = (ASPIRATION_WINDOW + (50 / depth)).max(MIN_ASPIRATION_WINDOW); - Score::new(window) + fn window_size(_depth: ScoreType) -> Score { + // TODO(PT): Scale the window to depth + Score::new(ASPIRATION_WINDOW) } } diff --git a/engine/src/score.rs b/engine/src/score.rs index 21f6616..3bf24dc 100644 --- a/engine/src/score.rs +++ b/engine/src/score.rs @@ -33,8 +33,6 @@ impl Score { /// The minimum mate score. This is the maximum score minus the maximum depth. pub const MINIMUM_MATE: Score = Score(Score::MATE.0 - MAX_DEPTH as ScoreType); pub const INF: Score = Score(ScoreType::MAX as ScoreType); - pub const ALPHA: Score = Score(-Score::INF.0); - pub const BETA: Score = Score::INF; // Max/min score for history heuristic // Must be lower then the minimum score for captures in MVV_LVA @@ -53,6 +51,10 @@ impl Score { pub fn is_mate(&self) -> bool { self.0.abs() >= Score::MINIMUM_MATE.0.abs() } + + pub fn pow(&self, exp: u32) -> Score { + Score(self.0.pow(exp)) + } } impl From for UciScore { diff --git a/engine/src/tuneable.rs b/engine/src/tuneable.rs index ae97bc5..fd5fea8 100644 --- a/engine/src/tuneable.rs +++ b/engine/src/tuneable.rs @@ -14,6 +14,5 @@ use crate::score::ScoreType; -pub(crate) const MIN_ASPIRATION_WINDOW: ScoreType = 10; pub(crate) const MIN_ASPIRATION_DEPTH: ScoreType = 1; -pub(crate) const ASPIRATION_WINDOW: ScoreType = 6; +pub(crate) const ASPIRATION_WINDOW: ScoreType = 50; From ec8d16f5ff51cf001ebfb7618a38c0ca9d076997 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 12 Dec 2024 10:51:47 -0500 Subject: [PATCH 09/11] fix: do not continue searching if search was canceled Break out of the ID loop if search was cancelled during aspiration loop. bench: 1583604 --- engine/src/search.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/search.rs b/engine/src/search.rs index d2ebc29..1d9c2cc 100644 --- a/engine/src/search.rs +++ b/engine/src/search.rs @@ -225,7 +225,7 @@ impl<'a> Search<'a> { best_result.best_move = Some(*move_list.at(0).unwrap()) } - while self.parameters.start_time.elapsed() <= self.parameters.soft_timeout + 'deepening: while self.parameters.start_time.elapsed() <= self.parameters.soft_timeout && best_result.depth <= self.parameters.max_depth { // create an aspiration window around the best result so far @@ -258,7 +258,7 @@ impl<'a> Search<'a> { if self.should_stop_searching() { // we have to stop searching now, use the best result we have // no score update - break 'aspiration_window; + break 'deepening; } } From 651d77d5e099be429473c9c561a6173f20ce047a Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 12 Dec 2024 11:16:07 -0500 Subject: [PATCH 10/11] chore: auto format bench: 1583604 --- engine/src/search.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/engine/src/search.rs b/engine/src/search.rs index 1d9c2cc..ee243d3 100644 --- a/engine/src/search.rs +++ b/engine/src/search.rs @@ -26,7 +26,12 @@ use itertools::Itertools; use uci_parser::{UciInfo, UciResponse, UciSearchOptions}; use crate::{ - aspiration_window::AspirationWindow, defs::MAX_DEPTH, evaluation::Evaluation, history_table::HistoryTable, score::{MoveOrderScoreType, Score, ScoreType}, ttable::{self, TranspositionTableEntry} + aspiration_window::AspirationWindow, + defs::MAX_DEPTH, + evaluation::Evaluation, + history_table::HistoryTable, + score::{MoveOrderScoreType, Score, ScoreType}, + ttable::{self, TranspositionTableEntry}, }; use ttable::TranspositionTable; From 7fcc427fbced462d0b346595ceaee637b14f7c25 Mon Sep 17 00:00:00 2001 From: Paul Tsouchlos Date: Thu, 12 Dec 2024 11:16:51 -0500 Subject: [PATCH 11/11] chore: address clippy lints bench: 1583604 --- engine/src/aspiration_window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/aspiration_window.rs b/engine/src/aspiration_window.rs index 1631c23..984e6cd 100644 --- a/engine/src/aspiration_window.rs +++ b/engine/src/aspiration_window.rs @@ -42,7 +42,7 @@ impl AspirationWindow { // If the score is mate, we can't use the window as we would expect search results to fluctuate. // Set it to a full window and search again. // We also want to do a full search on the first iteration (i.e. depth == 1); - return Self::infinite(); + Self::infinite() } else { let window = Self::window_size(depth); Self {