From 41d428e1da82dbe0b3577d855b6b3697b520a80b Mon Sep 17 00:00:00 2001 From: Mathmagician8191 <50558333+Mathmagician8191@users.noreply.github.com> Date: Mon, 29 Jul 2024 18:31:38 +1200 Subject: [PATCH] Oxidation 0.7.2 changes: Improving heuristic for LMR (7 elo) Remove pesto feature (retuning eval for standard chess gains just as much as it does) Increase endgame elephant value (10 elo in variants with elephants) RFP improving (7 elo) Speed up chancellor movegen (15 elo VSTC in variants with chancellors) Depth 4 futility pruning (7 elo) Per phase advanced pawn bonus (4 elo) Lower quiescence search depth (11 elo) Eval tuning on new data (8 elo) PV node LMP (4 elo) Eval tuning (15 elo) Bench: 2395054 --- liberty_chess/src/movegen.rs | 43 ++- liberty_chess_gui/src/main.rs | 6 +- liberty_chess_gui/src/players.rs | 17 +- oxidation/Cargo.toml | 3 +- oxidation/src/bin.rs | 21 +- oxidation/src/evaluate.rs | 100 ++++--- oxidation/src/glue.rs | 9 +- oxidation/src/lib.rs | 63 +---- oxidation/src/parameters.rs | 436 ++++++++++++++++--------------- oxidation/src/pesto.rs | 160 ------------ oxidation/src/search.rs | 96 ++++--- tester/src/lib.rs | 8 +- tester/src/match.rs | 8 +- tester/src/spsa.rs | 15 +- tester/src/tuner.rs | 6 +- ulci/src/client.rs | 19 +- 16 files changed, 424 insertions(+), 586 deletions(-) delete mode 100644 oxidation/src/pesto.rs diff --git a/liberty_chess/src/movegen.rs b/liberty_chess/src/movegen.rs index f4a59f6..dc80c45 100644 --- a/liberty_chess/src/movegen.rs +++ b/liberty_chess/src/movegen.rs @@ -1,7 +1,7 @@ use crate::moves::Move; use crate::{ - Board, BISHOP, CAMEL, CENTAUR, CHAMPION, ELEPHANT, KING, KNIGHT, MANN, OBSTACLE, PAWN, ROOK, - WALL, ZEBRA, + Board, BISHOP, CAMEL, CENTAUR, CHAMPION, CHANCELLOR, ELEPHANT, KING, KNIGHT, MANN, OBSTACLE, + PAWN, ROOK, WALL, ZEBRA, }; impl Board { @@ -70,6 +70,19 @@ impl Board { } } } + CHANCELLOR => { + for k in 0..self.height() { + self.add_if_legal(&mut boards, (i, j), (k, j), &mut skip_legality); + } + for l in 0..self.width() { + self.add_if_legal(&mut boards, (i, j), (i, l), &mut skip_legality); + } + for (k, l) in Self::jump_coords((i, j), 2, 1) { + if k < self.height() && l < self.width() { + self.add_if_legal(&mut boards, (i, j), (k, l), &mut skip_legality); + } + } + } CAMEL => { for (k, l) in Self::jump_coords((i, j), 3, 1) { if k < self.height() && l < self.width() { @@ -238,6 +251,19 @@ impl Board { } } } + CHANCELLOR => { + for k in 0..self.height() { + self.add_if_pseudolegal(captures, quiets, (i, j), (k, j)); + } + for l in 0..self.width() { + self.add_if_pseudolegal(captures, quiets, (i, j), (i, l)); + } + for (k, l) in Self::jump_coords((i, j), 2, 1) { + if k < self.height() && l < self.width() { + self.add_if_pseudolegal(captures, quiets, (i, j), (k, l)); + } + } + } CAMEL => { for (k, l) in Self::jump_coords((i, j), 3, 1) { if k < self.height() && l < self.width() { @@ -402,6 +428,19 @@ impl Board { } } } + CHANCELLOR => { + for k in 0..self.height() { + self.add_if_pseudolegal_qsearch(&mut moves, (i, j), (k, j)); + } + for l in 0..self.width() { + self.add_if_pseudolegal_qsearch(&mut moves, (i, j), (i, l)); + } + for (k, l) in Self::jump_coords((i, j), 2, 1) { + if k < self.height() && l < self.width() { + self.add_if_pseudolegal_qsearch(&mut moves, (i, j), (k, l)); + } + } + } CAMEL => { for (k, l) in Self::jump_coords((i, j), 3, 1) { if k < self.height() && l < self.width() { diff --git a/liberty_chess_gui/src/main.rs b/liberty_chess_gui/src/main.rs index 05fa7a7..647dca9 100644 --- a/liberty_chess_gui/src/main.rs +++ b/liberty_chess_gui/src/main.rs @@ -721,12 +721,8 @@ fn draw_menu(gui: &mut LibertyChessGUI, ctx: &Context, ui: &mut Ui) { } } match player { - PlayerType::BuiltIn(ref mut qdepth, ref mut hash_size) => { + PlayerType::BuiltIn(ref mut hash_size) => { if gui.config.get_advanced() { - ui.horizontal_top(|ui| { - ui.label("Quiescence depth"); - raw_text_edit(ui, size * 2.0, qdepth); - }); ui.horizontal_top(|ui| { ui.label("Hash size (MB)"); raw_text_edit(ui, size * 4.0, hash_size); diff --git a/liberty_chess_gui/src/players.rs b/liberty_chess_gui/src/players.rs index 215c939..4b55741 100644 --- a/liberty_chess_gui/src/players.rs +++ b/liberty_chess_gui/src/players.rs @@ -10,7 +10,7 @@ use liberty_chess::{Board, Gamestate, ALL_PIECES}; use oxidation::glue::process_position; use oxidation::parameters::DEFAULT_PARAMETERS; use oxidation::search::SEARCH_PARAMETERS; -use oxidation::{mvvlva_move, random_move, State, HASH_SIZE, MAX_QDEPTH, QDEPTH, VERSION_NUMBER}; +use oxidation::{mvvlva_move, random_move, State, HASH_SIZE, VERSION_NUMBER}; use rand::{thread_rng, Rng}; use std::collections::HashMap; use std::io::{BufReader, ErrorKind, Write}; @@ -184,8 +184,8 @@ pub struct Limits { pub enum PlayerType { RandomEngine, MVVLVA, - // parameters are qdepth and hash size - BuiltIn(NumericalInput, NumericalInput), + // parameter is hash size + BuiltIn(NumericalInput), External(String), Multiplayer(String, NumericalInput, String), } @@ -195,7 +195,7 @@ impl ToString for PlayerType { match self { Self::RandomEngine => "Random Mover".to_owned(), Self::MVVLVA => "MVVLVA".to_owned(), - Self::BuiltIn(..) => format!("Oxidation v{VERSION_NUMBER}"), + Self::BuiltIn(_) => format!("Oxidation v{VERSION_NUMBER}"), Self::External(_) => "External engine (beta)".to_owned(), Self::Multiplayer(..) => "Connect to server (beta)".to_owned(), } @@ -204,10 +204,7 @@ impl ToString for PlayerType { impl PlayerType { pub fn built_in() -> Self { - Self::BuiltIn( - NumericalInput::new(u16::from(QDEPTH), 0, MAX_QDEPTH), - NumericalInput::new(HASH_SIZE, 0, 1 << 32), - ) + Self::BuiltIn(NumericalInput::new(HASH_SIZE, 0, 1 << 32)) } #[cfg(feature = "clock")] @@ -267,11 +264,10 @@ impl PlayerData { match player { PlayerType::RandomEngine => Ok(Self::RandomEngine), PlayerType::MVVLVA => Ok(Self::MVVLVA), - PlayerType::BuiltIn(qdepth, hash_size) => { + PlayerType::BuiltIn(hash_size) => { let (send_request, recieve_request) = channel(); let (send_result, recieve_result) = channel(); let hash_size = hash_size.get_value(); - let qdepth = qdepth.get_value() as u8; let (send_message, receive_message) = channel(); let ctx = ctx.clone(); spawn(move || { @@ -287,7 +283,6 @@ impl PlayerData { &receive_message, board, searchtime, - qdepth, &mut state, ); ctx.request_repaint(); diff --git a/oxidation/Cargo.toml b/oxidation/Cargo.toml index b807caf..dd21719 100644 --- a/oxidation/Cargo.toml +++ b/oxidation/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxidation" -version = "0.7.1" +version = "0.7.2" authors.workspace = true repository.workspace = true license.workspace = true @@ -26,5 +26,4 @@ ulci = {workspace = true} [features] default = [] -pesto = [] feature_extraction = [] diff --git a/oxidation/src/bin.rs b/oxidation/src/bin.rs index 3e48b92..f48f609 100644 --- a/oxidation/src/bin.rs +++ b/oxidation/src/bin.rs @@ -9,8 +9,7 @@ use oxidation::evaluate::evaluate; use oxidation::parameters::DEFAULT_PARAMETERS; use oxidation::search::SEARCH_PARAMETERS; use oxidation::{ - bench, divide, search, Output, SearchConfig, State, HASH_SIZE, MAX_QDEPTH, MULTI_PV_COUNT, - QDEPTH, QDEPTH_NAME, VERSION_NUMBER, + bench, divide, search, Output, SearchConfig, State, HASH_SIZE, MULTI_PV_COUNT, VERSION_NUMBER, }; use std::collections::{HashMap, HashSet}; use std::io::{stdin, stdout, BufReader}; @@ -48,14 +47,6 @@ const BENCH_POSITIONS: &[(&str, i8)] = &[ fn startup_client(tx: &Sender) { let mut options = HashMap::new(); - options.insert( - QDEPTH_NAME.to_owned(), - UlciOption::Int(IntOption { - default: usize::from(QDEPTH), - min: 0, - max: usize::from(MAX_QDEPTH), - }), - ); options.insert( HASH_NAME.to_owned(), UlciOption::Int(IntOption { @@ -100,7 +91,6 @@ fn startup_client(tx: &Sender) { fn main() { let (tx, rx) = channel(); spawn(move || startup_client(&tx)); - let mut qdepth = QDEPTH; let mut hash_size = HASH_SIZE; let mut pv_lines = MULTI_PV_COUNT; let mut position = get_startpos(); @@ -117,8 +107,7 @@ fn main() { } Message::Go(settings) => { let searchmoves = settings.moves; - let mut settings = - SearchConfig::new_time(&position, &mut qdepth, settings.time, &rx, &mut debug); + let mut settings = SearchConfig::new_time(&position, settings.time, &rx, &mut debug); let pv = search( &mut state, &mut settings, @@ -136,10 +125,6 @@ fn main() { println!("info error not currently searching"); } Message::UpdateOption(name, value) => match &*name { - QDEPTH_NAME => match value { - OptionValue::UpdateInt(value) => qdepth = value as u8, - _ => println!("info error incorrect option type"), - }, HASH_NAME => match value { OptionValue::UpdateInt(value) => { if value != hash_size { @@ -180,7 +165,6 @@ fn main() { &mut state, &mut board, depth, - &mut qdepth, &mut debug, &rx, Output::String(stdout()), @@ -190,7 +174,6 @@ fn main() { &mut state, &mut board, depth, - &mut qdepth, &mut debug, &rx, Output::String(stdout()), diff --git a/oxidation/src/evaluate.rs b/oxidation/src/evaluate.rs index 027ee03..9f13ef0 100644 --- a/oxidation/src/evaluate.rs +++ b/oxidation/src/evaluate.rs @@ -10,13 +10,7 @@ use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; use ulci::Score; #[cfg(not(feature = "feature_extraction"))] -use crate::parameters::{unpack_eg, unpack_mg, PackedParameters}; - -#[cfg(all(not(feature = "feature_extraction"), not(feature = "pesto")))] -use crate::parameters::pack; - -#[cfg(all(feature = "pesto", not(feature = "feature_extraction")))] -use crate::pesto::PSQT; +use crate::parameters::{pack, unpack_eg, unpack_mg, PackedParameters}; /// Extracted evaluation features #[derive(Clone)] @@ -38,7 +32,7 @@ pub struct Features { pub(crate) fn raw( pieces: &Array2D, to_move: bool, - #[cfg(not(feature = "pesto"))] promotion_values: (i32, i32), + promotion_values: (i32, i32), parameters: &PackedParameters, ) -> i32 { let mut value = 0; @@ -59,28 +53,11 @@ pub(crate) fn raw( let mut piece_value = parameters.pieces[piece_type]; let mobility = Board::mobility(pieces, (i, j), piece); piece_value += mobility * parameters.mobility_bonus[piece_type]; - #[cfg(feature = "pesto")] - { - if height == 8 && width == 8 && piece_type < 6 { - let index = if piece > 0 { 7 - i } else { i }; - piece_value += PSQT[piece_type][index][j]; - } else { - let horizontal_distance = min(i, height - 1 - i).min(EDGE_DISTANCE); - let vertical_distance = min(j, width - 1 - j).min(EDGE_DISTANCE); - let index = INDEXING[horizontal_distance * (EDGE_DISTANCE + 1) + vertical_distance]; - if index < EDGE_PARAMETER_COUNT { - piece_value -= parameters.edge_avoidance[piece_type][index]; - } - } - } - #[cfg(not(feature = "pesto"))] - { - let horizontal_distance = min(i, height - 1 - i).min(EDGE_DISTANCE); - let vertical_distance = min(j, width - 1 - j).min(EDGE_DISTANCE); - let index = INDEXING[horizontal_distance * (EDGE_DISTANCE + 1) + vertical_distance]; - if index < EDGE_PARAMETER_COUNT { - piece_value -= parameters.edge_avoidance[piece_type][index]; - } + let horizontal_distance = min(i, height - 1 - i).min(EDGE_DISTANCE); + let vertical_distance = min(j, width - 1 - j).min(EDGE_DISTANCE); + let index = INDEXING[horizontal_distance * (EDGE_DISTANCE + 1) + vertical_distance]; + if index < EDGE_PARAMETER_COUNT { + piece_value -= parameters.edge_avoidance[piece_type][index]; } if pieces.get(block_i, j.wrapping_sub(1)) == enemy_pawn || pieces.get(block_i, j + 1) == enemy_pawn @@ -105,16 +82,15 @@ pub(crate) fn raw( } } // bonus for advanced pawn - #[cfg(not(feature = "pesto"))] - { - let squares_to_go = if piece > 0 { height - 1 - i } else { i } as i32; - if squares_to_go != 0 { - let divisor = - squares_to_go * parameters.pawn_scale_factor + parameters.pawn_scaling_bonus; - let mg_value = promotion_values.0 / divisor; - let eg_value = promotion_values.1 / divisor; - piece_value += pack(mg_value, eg_value); - } + let squares_to_go = if piece > 0 { height - 1 - i } else { i } as i32; + if squares_to_go != 0 { + let mg_divisor = + squares_to_go * parameters.mg_pawn_scale_factor + parameters.mg_pawn_scaling_bonus; + let eg_divisor = + squares_to_go * parameters.eg_pawn_scale_factor + parameters.eg_pawn_scaling_bonus; + let mg_value = promotion_values.0 / mg_divisor; + let eg_value = promotion_values.1 / eg_divisor; + piece_value += pack(mg_value, eg_value); } } value += piece_value * multiplier; @@ -188,10 +164,12 @@ pub fn eval_features< } for (squares_to_go, multiplier) in &features.pawn_list { let multiplier = T::from(*multiplier); - let divisor = - T::from(*squares_to_go) * parameters.pawn_scale_factor + parameters.pawn_scaling_bonus; - middlegame += promotion_values.0 / divisor * multiplier; - endgame += promotion_values.1 / divisor * multiplier; + let mg_divisor = + T::from(*squares_to_go) * parameters.mg_pawn_scale_factor + parameters.mg_pawn_scaling_bonus; + let eg_divisor = + T::from(*squares_to_go) * parameters.eg_pawn_scale_factor + parameters.eg_pawn_scaling_bonus; + middlegame += promotion_values.0 / mg_divisor * multiplier; + endgame += promotion_values.1 / eg_divisor * multiplier; } let threshold = T::from(ENDGAME_THRESHOLD); let material = T::from(features.material); @@ -236,15 +214,28 @@ pub fn gradient( let eg_pawn_attacked_penalty = features.attacked_by_pawn.map(|x| -f64::from(x) * eg_factor); let mg_pawn_defended_bonus = features.defended_by_pawn.map(|x| f64::from(x) * mg_factor); let eg_pawn_defended_bonus = features.defended_by_pawn.map(|x| f64::from(x) * eg_factor); - let mut pawn_scale_factor = 0.0; - let mut pawn_scaling_bonus = 0.0; - let piece_value = promotion_values.0 * mg_factor + promotion_values.1 * eg_factor; + let mut mg_pawn_scale_factor = 0.0; + let mut mg_pawn_scaling_bonus = 0.0; + let mut eg_pawn_scale_factor = 0.0; + let mut eg_pawn_scaling_bonus = 0.0; for (squares, count) in &features.pawn_list { let squares = f64::from(*squares); - let divisor = squares.mul_add(parameters.pawn_scale_factor, parameters.pawn_scaling_bonus); - let scaling_factor = -piece_value * f64::from(*count) / divisor.powi(2); - pawn_scale_factor += scaling_factor * squares; - pawn_scaling_bonus += scaling_factor; + let mg_divisor = squares.mul_add( + parameters.mg_pawn_scale_factor, + parameters.mg_pawn_scaling_bonus, + ); + let eg_divisor = squares.mul_add( + parameters.eg_pawn_scale_factor, + parameters.eg_pawn_scaling_bonus, + ); + let mg_scaling_factor = + -promotion_values.0 * mg_factor * f64::from(*count) / mg_divisor.powi(2); + let eg_scaling_factor = + -promotion_values.0 * eg_factor * f64::from(*count) / eg_divisor.powi(2); + mg_pawn_scale_factor += mg_scaling_factor * squares; + mg_pawn_scaling_bonus += mg_scaling_factor; + eg_pawn_scale_factor += eg_scaling_factor * squares; + eg_pawn_scaling_bonus += eg_scaling_factor; } Parameters { pieces, @@ -260,8 +251,10 @@ pub fn gradient( eg_pawn_attacked_penalty, mg_pawn_defended_bonus, eg_pawn_defended_bonus, - pawn_scale_factor, - pawn_scaling_bonus, + mg_pawn_scale_factor, + mg_pawn_scaling_bonus, + eg_pawn_scale_factor, + eg_pawn_scaling_bonus, } } @@ -353,7 +346,6 @@ pub fn evaluate(state: &State, board: &Board) -> i32 { let score = raw( board.board(), board.to_move(), - #[cfg(not(feature = "pesto"))] state.promotion_values, &state.packed_parameters, ); diff --git a/oxidation/src/glue.rs b/oxidation/src/glue.rs index ad6f5ed..39604a4 100644 --- a/oxidation/src/glue.rs +++ b/oxidation/src/glue.rs @@ -13,20 +13,13 @@ pub fn process_position( receive_message: &Receiver, board: CompressedBoard, searchtime: SearchTime, - mut qdepth: u8, state: &mut State, ) -> Option<()> { let mut position = board.load_from_thread(); state.new_position(&position); let mut debug = false; while receive_message.try_recv().is_ok() {} - let mut config = SearchConfig::new_time( - &position, - &mut qdepth, - searchtime, - receive_message, - &mut debug, - ); + let mut config = SearchConfig::new_time(&position, searchtime, receive_message, &mut debug); let pv = search( state, &mut config, diff --git a/oxidation/src/lib.rs b/oxidation/src/lib.rs index afbf770..5c57e23 100644 --- a/oxidation/src/lib.rs +++ b/oxidation/src/lib.rs @@ -21,7 +21,7 @@ use std::sync::mpsc::{Receiver, Sender, TryRecvError}; use std::time::Instant; use ulci::client::Message; use ulci::server::UlciResult; -use ulci::{AnalysisResult, OptionValue, Score, SearchTime}; +use ulci::{AnalysisResult, Score, SearchTime}; #[cfg(not(feature = "feature_extraction"))] use crate::parameters::PackedParameters; @@ -39,26 +39,14 @@ mod history; mod movepicker; mod tt; -#[cfg(all(feature = "pesto", not(feature = "feature_extraction")))] -mod pesto; - /// The version number of the engine pub const VERSION_NUMBER: &str = env!("CARGO_PKG_VERSION"); -/// Default Quiescence depth -pub const QDEPTH: u8 = 3; -/// Maximum allowed Quiescence depth -pub const MAX_QDEPTH: u16 = 32; /// Default Hash size pub const HASH_SIZE: usize = 64; /// Default Multi-PV lines pub const MULTI_PV_COUNT: u16 = 1; -/// Internal naming thing - do not use -/// -/// Public due to being required in the binary -pub const QDEPTH_NAME: &str = "QDepth"; - const DRAW_SCORE: Score = Score::Centipawn(0); /// The output type to use for analysis results @@ -72,6 +60,7 @@ pub enum Output<'a> { struct StackEntry { movepicker: MovePicker, board: Board, + eval: Option, } impl StackEntry { @@ -79,6 +68,7 @@ impl StackEntry { Self { movepicker: MovePicker::new(), board, + eval: None, } } @@ -173,7 +163,6 @@ pub fn get_promotion_values + From { - qdepth: &'a mut u8, start: Instant, max_depth: u8, max_time: u128, @@ -198,7 +187,6 @@ pub struct SearchConfig<'a> { impl<'a> SearchConfig<'a> { /// Initialise the search config fn new( - qdepth: &'a mut u8, max_depth: u8, max_time: u128, max_nodes: usize, @@ -208,7 +196,6 @@ impl<'a> SearchConfig<'a> { debug: &'a mut bool, ) -> Self { Self { - qdepth, start: Instant::now(), max_depth, max_time, @@ -231,7 +218,6 @@ impl<'a> SearchConfig<'a> { /// Initialise the search config based on the search time pub fn new_time( board: &Board, - qdepth: &'a mut u8, time: SearchTime, rx: &'a Receiver, debug: &'a mut bool, @@ -239,18 +225,9 @@ impl<'a> SearchConfig<'a> { match time { SearchTime::Increment(time, inc) => { let time = time.saturating_sub(100); - let time = time.min(7 * time / 150 + 8 * inc / 13); + let time = time.min(time / 15 + 3 * inc / 4); let time = 1.max(time); - Self::new( - qdepth, - u8::MAX, - time, - usize::MAX, - Score::Loss(0), - false, - rx, - debug, - ) + Self::new(u8::MAX, time, usize::MAX, Score::Loss(0), false, rx, debug) } SearchTime::Asymmetric(wtime, winc, btime, binc) => { let (time, inc) = if board.to_move() { @@ -261,19 +238,9 @@ impl<'a> SearchConfig<'a> { let time = time.saturating_sub(100); let time = time.min(time / 15 + 3 * inc / 4); let time = 1.max(time); - Self::new( - qdepth, - u8::MAX, - time, - usize::MAX, - Score::Loss(0), - false, - rx, - debug, - ) + Self::new(u8::MAX, time, usize::MAX, Score::Loss(0), false, rx, debug) } SearchTime::Infinite => Self::new( - qdepth, u8::MAX, u128::MAX, usize::MAX, @@ -283,7 +250,6 @@ impl<'a> SearchConfig<'a> { debug, ), SearchTime::Other(limits) => Self::new( - qdepth, limits.depth, limits.time, limits.nodes, @@ -293,7 +259,6 @@ impl<'a> SearchConfig<'a> { debug, ), SearchTime::Mate(moves) => Self::new( - qdepth, u8::MAX, u128::MAX, usize::MAX, @@ -336,18 +301,8 @@ impl<'a> SearchConfig<'a> { self.stopped = true; return true; } - Message::UpdateOption(name, value) => { - if name == QDEPTH_NAME { - match value { - OptionValue::UpdateInt(value) => *self.qdepth = value as u8, - OptionValue::SendTrigger - | OptionValue::UpdateBool(_) - | OptionValue::UpdateRange(_) - | OptionValue::UpdateString(_) => { - println!("info error {QDEPTH_NAME} is an integer") - } - } - } + Message::UpdateOption(..) => { + println!("info error cannot change options during search") } Message::IsReady => println!("readyok"), Message::Clock(_) | Message::Info(_) => (), @@ -594,7 +549,6 @@ pub fn bench( state: &mut State, board: &mut Board, depth: u8, - qdepth: &mut u8, debug: &mut bool, rx: &Receiver, out: Output, @@ -603,7 +557,6 @@ pub fn bench( board.skip_checkmate = true; state.new_game(board); let mut settings = SearchConfig::new( - qdepth, depth, u128::MAX, usize::MAX, diff --git a/oxidation/src/parameters.rs b/oxidation/src/parameters.rs index d6e0a46..ebded59 100644 --- a/oxidation/src/parameters.rs +++ b/oxidation/src/parameters.rs @@ -3,165 +3,165 @@ use liberty_chess::{CENTAUR, CHAMPION, ELEPHANT, KING, MANN, OBSTACLE, WALL}; use std::ops::{Add, AddAssign, Div, Mul}; const PIECE_VALUES: [(i32, i32); 18] = [ - (61, 141), // Pawn - (278, 367), // Knight - (323, 299), // Bishop - (443, 512), // Rook - (1047, 964), // Queen - (-505, 812), // King - (853, 1081), // Archbishop - (977, 1147), // Chancellor - (229, 252), // Camel - (195, 199), // Zebra - (208, 339), // Mann - (533, 369), // Nightrider - (549, 1076), // Champion - (544, 1197), // Centaur - (1595, 1635), // Amazon - (753, 552), // Elephant - (18, 44), // Obstacle - (85, 105), // Wall + (71, 132), // Pawn + (286, 350), // Knight + (328, 289), // Bishop + (430, 502), // Rook + (1044, 966), // Queen + (-765, 815), // King + (819, 1033), // Archbishop + (933, 1115), // Chancellor + (249, 241), // Camel + (233, 175), // Zebra + (212, 344), // Mann + (505, 358), // Nightrider + (570, 1043), // Champion + (555, 1198), // Centaur + (1436, 1671), // Amazon + (798, 563), // Elephant + (1, 82), // Obstacle + (118, 94), // Wall ]; const MG_EDGE_AVOIDANCE: [[i32; EDGE_PARAMETER_COUNT]; 18] = [ - [-38, 4, 5, 23, -31, -17, -6, -6, -7], // Pawn - [42, 53, 32, 27, 28, 17, 13, 0, -3], // Knight - [29, 33, 49, 6, 5, -4, 0, -7, 4], // Bishop - [28, 27, 1, 0, 0, -4, 3, -10, -6], // Rook - [23, -4, 0, -7, 23, 4, -3, 0, 1], // Queen - [-32, -49, -17, -1, 34, 19, 48, 10, 32], // King - [57, 9, 15, 25, 20, 3, -1, 2, -3], // Archbishop - [-2, 21, 0, 17, 84, -19, 3, -12, -1], // Chancellor - [-17, 73, 10, 7, 11, 10, -9, -5, -15], // Camel - [37, 0, -2, 29, 79, 2, 17, 57, 3], // Zebra - [7, 34, 10, 73, 14, 0, 0, 17, 16], // Mann - [46, 8, -10, -22, -76, -6, -74, -23, -5], // Nightrider - [65, 38, 14, 9, 0, 1, 9, 18, 13], // Champion - [40, 26, 42, 6, 38, 29, 11, 5, 0], // Centaur - [88, 37, 7, 1, 33, 2, -26, 3, -22], // Amazon - [112, 128, 112, 75, 84, 43, 40, 38, 49], // Elephant + [-89, 2, 3, 22, -29, -14, -2, -2, 0], // Pawn + [29, 50, 35, 32, 28, 12, 14, 6, 3], // Knight + [58, 39, 42, 6, 7, -3, -2, -2, 2], // Bishop + [22, 25, 4, 0, -2, -5, -3, -10, -1], // Rook + [18, 13, 4, 0, 22, -2, -2, -1, 3], // Queen + [-11, -49, -17, -1, 48, 29, 50, 13, 37], // King + [58, 1, 14, 22, 11, 12, 3, -12, 1], // Archbishop + [10, 15, 1, 12, 72, -17, -3, -6, 6], // Chancellor + [-21, 75, -4, 6, 17, 11, -7, 19, 0], // Camel + [0, 32, 12, 23, 82, 10, 19, 64, 11], // Zebra + [74, 68, 21, 43, 0, 25, 0, 18, 24], // Mann + [-9, 1, -13, -22, 59, -23, -40, -12, -1], // Nightrider + [49, 61, 36, 0, 4, 22, 21, 10, 19], // Champion + [40, 22, 33, 26, 36, 26, 14, 5, 2], // Centaur + [53, 32, 9, -3, 13, 8, -5, -17, -5], // Amazon + [207, 171, 138, 63, 120, 49, 12, 11, 22], // Elephant [0, 0, 0, 0, 0, 0, 0, 0, 0], // Obstacle [0, 0, 0, 0, 0, 0, 0, 0, 0], // Wall ]; const EG_EDGE_AVOIDANCE: [[i32; EDGE_PARAMETER_COUNT]; 18] = [ - [141, 8, 20, 12, 1, 8, 3, 7, 6], // Pawn - [51, 9, 21, 23, 15, 11, 17, 5, 11], // Knight - [2, -11, -12, -7, 12, -6, 9, -6, -13], // Bishop - [76, 19, 18, 4, 26, 20, -3, 22, -4], // Rook - [58, 25, 13, 32, 6, 25, 6, 6, -19], // Queen - [132, 83, 56, 41, 20, 22, 3, 14, 0], // King - [298, 204, 127, 80, 134, 39, 61, 54, 5], // Archbishop - [73, 42, 81, -6, -8, 45, 25, 17, -10], // Chancellor - [34, 13, 26, 15, -1, 10, 8, 26, 21], // Camel - [-11, 6, 1, -17, -23, 20, -24, -31, -11], // Zebra - [180, 5, 73, 0, 16, 6, 47, 8, 0], // Mann - [5, 24, -5, -17, 1, 7, 49, 46, 5], // Nightrider - [14, 93, 156, 90, 111, 65, 69, 0, 0], // Champion - [381, 203, 189, 131, 166, 126, 89, 74, 41], // Centaur - [153, 139, 5, -39, -14, 82, -48, 78, -62], // Amazon - [207, 197, 154, 151, 167, 112, 120, 79, 31], // Elephant - [0, 0, 0, 0, 0, 0, 0, 0, 0], // Obstacle - [0, 0, 0, 0, 0, 0, 0, 0, 0], // Wall + [132, 7, 19, 16, 4, 9, 5, 5, 2], // Pawn + [56, 10, 18, 24, 17, 18, 17, 7, 3], // Knight + [-20, -8, -5, -1, 5, 1, 15, -7, -2], // Bishop + [88, 28, 17, 9, 30, 18, -1, 12, -9], // Rook + [61, 10, 12, 16, 5, 22, 6, -3, -19], // Queen + [127, 91, 60, 44, 21, 25, 8, 20, 3], // King + [204, 153, 108, 57, 86, 0, 17, 19, -11], // Archbishop + [32, -6, 58, -4, -45, 13, 0, 6, -30], // Chancellor + [53, 5, 31, 26, -1, 9, 8, 15, 3], // Camel + [37, 19, -2, -13, -3, 11, -20, -38, -6], // Zebra + [32, 85, 104, 4, 35, 15, 23, 15, 0], // Mann + [31, 38, 29, 15, 5, 45, 48, 37, 15], // Nightrider + [124, 138, 124, 161, 182, 38, 2, 17, 0], // Champion + [396, 232, 182, 141, 115, 102, 56, 57, 16], // Centaur + [72, -10, 80, 37, 21, 23, -16, 69, -67], // Amazon + [192, 173, 140, 176, 145, 119, 159, 106, 62], // Elephant + [0, 0, 0, 0, 0, 0, 0, 0, 0], // Obstacle + [0, 0, 0, 0, 0, 0, 0, 0, 0], // Wall ]; const MG_FRIENDLY_PAWN_PENALTY: [i32; 18] = [ 0, // Pawn - 10, // Knight - 2, // Bishop + 11, // Knight + 8, // Bishop 0, // Rook 5, // Queen - 25, // King + 31, // King 7, // Archbishop - 8, // Chancellor + 0, // Chancellor 0, // Camel 0, // Zebra - 8, // Mann + 0, // Mann 0, // Nightrider 0, // Champion 0, // Centaur 0, // Amazon - 5, // Elephant + 20, // Elephant 0, // Obstacle - 18, // Wall + 15, // Wall ]; const EG_FRIENDLY_PAWN_PENALTY: [i32; 18] = [ - 31, // Pawn - 12, // Knight - 19, // Bishop + 37, // Pawn + 8, // Knight + 10, // Bishop 0, // Rook 0, // Queen 0, // King - 25, // Archbishop - 19, // Chancellor - 5, // Camel - 0, // Zebra - 33, // Mann + 0, // Archbishop + 4, // Chancellor + 11, // Camel + 1, // Zebra + 0, // Mann 0, // Nightrider - 4, // Champion + 0, // Champion 0, // Centaur 0, // Amazon - 20, // Elephant - 0, // Obstacle - 0, // Wall + 4, // Elephant + 18, // Obstacle + 13, // Wall ]; const MG_ENEMY_PAWN_PENALTY: [i32; 18] = [ 0, // Pawn - 31, // Knight - 13, // Bishop - -29, // Rook - 4, // Queen - 61, // King - 17, // Archbishop - 11, // Chancellor - -9, // Camel - -32, // Zebra - 32, // Mann - 2, // Nightrider - 3, // Champion - 21, // Centaur - 39, // Amazon - 61, // Elephant - -15, // Obstacle + 25, // Knight + 8, // Bishop + -21, // Rook + 3, // Queen + 71, // King + 21, // Archbishop + 8, // Chancellor + -18, // Camel + -53, // Zebra + 23, // Mann + 31, // Nightrider + 10, // Champion + 18, // Centaur + 16, // Amazon + 71, // Elephant + 0, // Obstacle 0, // Wall ]; const EG_ENEMY_PAWN_PENALTY: [i32; 18] = [ 0, // Pawn - 19, // Knight - 80, // Bishop - 86, // Rook - 51, // Queen - 43, // King - 82, // Archbishop - 63, // Chancellor - 26, // Camel - 37, // Zebra - 92, // Mann - 31, // Nightrider - 81, // Champion - 52, // Centaur - 141, // Amazon + 23, // Knight + 77, // Bishop + 87, // Rook + 61, // Queen + 39, // King + 84, // Archbishop + 89, // Chancellor + 35, // Camel + 55, // Zebra + 106, // Mann + 2, // Nightrider + 97, // Champion + 61, // Centaur + 132, // Amazon 0, // Elephant - 60, // Obstacle + 28, // Obstacle 0, // Wall ]; const MG_MOBILITY_BONUS: [i32; 18] = [ 0, // Pawn 0, // Knight - 4, // Bishop + 3, // Bishop 6, // Rook 3, // Queen 0, // King - 1, // Archbishop + 2, // Archbishop 2, // Chancellor 0, // Camel 0, // Zebra 0, // Mann - 6, // Nightrider + 8, // Nightrider 0, // Champion 0, // Centaur 0, // Amazon @@ -174,111 +174,113 @@ const EG_MOBILITY_BONUS: [i32; 18] = [ 0, // Pawn 0, // Knight 6, // Bishop - 10, // Rook - 10, // Queen + 9, // Rook + 9, // Queen 0, // King - 0, // Archbishop - 13, // Chancellor + 1, // Archbishop + 16, // Chancellor 0, // Camel 0, // Zebra 0, // Mann 0, // Nightrider 0, // Champion 0, // Centaur - 15, // Amazon + 16, // Amazon 0, // Elephant 0, // Obstacle 0, // Wall ]; const MG_PAWN_ATTACKED_PENALTY: [i32; 18] = [ - -4, // Pawn - 50, // Knight - 53, // Bishop - 61, // Rook - 38, // Queen + -18, // Pawn + 52, // Knight + 56, // Bishop + 53, // Rook + 31, // Queen 0, // King - 40, // Archbishop - 49, // Chancellor - 56, // Camel - 22, // Zebra - 68, // Mann - 82, // Nightrider - 34, // Champion - 20, // Centaur - 65, // Amazon - 4, // Elephant - -14, // Obstacle + 30, // Archbishop + 54, // Chancellor + 53, // Camel + 61, // Zebra + 69, // Mann + 52, // Nightrider + 27, // Champion + 22, // Centaur + 45, // Amazon + 28, // Elephant + 0, // Obstacle 22, // Wall ]; const EG_PAWN_ATTACKED_PENALTY: [i32; 18] = [ - -30, // Pawn - 24, // Knight - 50, // Bishop - -3, // Rook - 34, // Queen - 0, // King - -20, // Archbishop - -95, // Chancellor - 48, // Camel - 77, // Zebra - 48, // Mann - -93, // Nightrider - 60, // Champion - 0, // Centaur - 84, // Amazon - 106, // Elephant - 0, // Obstacle - 54, // Wall + -12, // Pawn + 28, // Knight + 45, // Bishop + 49, // Rook + 32, // Queen + 0, // King + -11, // Archbishop + -100, // Chancellor + 69, // Camel + 67, // Zebra + 21, // Mann + -44, // Nightrider + 45, // Champion + -20, // Centaur + -208, // Amazon + 86, // Elephant + 6, // Obstacle + 56, // Wall ]; const MG_PAWN_DEFENDED_BONUS: [i32; 18] = [ - 7, // Pawn - 4, // Knight - 11, // Bishop - -20, // Rook - -4, // Queen - -32, // King - 2, // Archbishop - -2, // Chancellor - 12, // Camel - 35, // Zebra - -31, // Mann - 20, // Nightrider - -3, // Champion - 9, // Centaur - -5, // Amazon - 4, // Elephant - 26, // Obstacle - -13, // Wall + 11, // Pawn + 5, // Knight + 8, // Bishop + -26, // Rook + -9, // Queen + -40, // King + 0, // Archbishop + -1, // Chancellor + 15, // Camel + 24, // Zebra + -14, // Mann + 11, // Nightrider + -5, // Champion + 5, // Centaur + -8, // Amazon + -12, // Elephant + 22, // Obstacle + -10, // Wall ]; const EG_PAWN_DEFENDED_BONUS: [i32; 18] = [ - 4, // Pawn - -5, // Knight - 5, // Bishop - 57, // Rook - 34, // Queen - 22, // King - -16, // Archbishop + 2, // Pawn + -6, // Knight + 4, // Bishop + 55, // Rook + 26, // Queen + 24, // King + -19, // Archbishop -6, // Chancellor - -5, // Camel - -27, // Zebra - 33, // Mann - 70, // Nightrider - -23, // Champion - 27, // Centaur - 165, // Amazon - -4, // Elephant - 9, // Obstacle - 7, // Wall + -12, // Camel + -22, // Zebra + 60, // Mann + 112, // Nightrider + 7, // Champion + 10, // Centaur + 90, // Amazon + 23, // Elephant + -5, // Obstacle + 0, // Wall ]; // advanced pawns get a bonus of numerator/(factor * squares_to_promotion + bonus) times the promotion value pub(crate) const PAWN_SCALING_NUMERATOR: i32 = 20; -const PAWN_SCALING_FACTOR: i32 = 180; -const PAWN_SCALING_BONUS: i32 = -69; +const MG_PAWN_SCALING_FACTOR: i32 = 276; +const MG_PAWN_SCALING_BONUS: i32 = -11; +const EG_PAWN_SCALING_FACTOR: i32 = 146; +const EG_PAWN_SCALING_BONUS: i32 = -56; pub(crate) const TEMPO_BONUS: i32 = 10; @@ -335,10 +337,10 @@ pub(crate) struct PackedParameters { pub(crate) mobility_bonus: [i64; 18], pub(crate) pawn_attacked_penalty: [i64; 18], pub(crate) pawn_defended_bonus: [i64; 18], - #[cfg(not(feature = "pesto"))] - pub(crate) pawn_scale_factor: i32, - #[cfg(not(feature = "pesto"))] - pub(crate) pawn_scaling_bonus: i32, + pub(crate) mg_pawn_scale_factor: i32, + pub(crate) mg_pawn_scaling_bonus: i32, + pub(crate) eg_pawn_scale_factor: i32, + pub(crate) eg_pawn_scaling_bonus: i32, } #[cfg(not(feature = "feature_extraction"))] @@ -380,10 +382,10 @@ impl From> for PackedParameters { mobility_bonus, pawn_attacked_penalty, pawn_defended_bonus, - #[cfg(not(feature = "pesto"))] - pawn_scale_factor: value.pawn_scale_factor, - #[cfg(not(feature = "pesto"))] - pawn_scaling_bonus: value.pawn_scaling_bonus, + mg_pawn_scale_factor: value.mg_pawn_scale_factor, + mg_pawn_scaling_bonus: value.mg_pawn_scaling_bonus, + eg_pawn_scale_factor: value.eg_pawn_scale_factor, + eg_pawn_scaling_bonus: value.eg_pawn_scaling_bonus, } } } @@ -403,8 +405,10 @@ pub const DEFAULT_PARAMETERS: Parameters = Parameters { eg_pawn_attacked_penalty: EG_PAWN_ATTACKED_PENALTY, mg_pawn_defended_bonus: MG_PAWN_DEFENDED_BONUS, eg_pawn_defended_bonus: EG_PAWN_DEFENDED_BONUS, - pawn_scale_factor: PAWN_SCALING_FACTOR, - pawn_scaling_bonus: PAWN_SCALING_BONUS, + mg_pawn_scale_factor: MG_PAWN_SCALING_FACTOR, + mg_pawn_scaling_bonus: MG_PAWN_SCALING_BONUS, + eg_pawn_scale_factor: EG_PAWN_SCALING_FACTOR, + eg_pawn_scaling_bonus: EG_PAWN_SCALING_BONUS, }; /// Parameters for evaluation @@ -437,9 +441,13 @@ pub struct Parameters { /// Endgame bonus for being defended by a pawn pub eg_pawn_defended_bonus: [T; 18], /// Scaling factor for the advanced pawn bonus - pub pawn_scale_factor: T, + pub mg_pawn_scale_factor: T, + /// Scaling factor for the advanced pawn bonus + pub mg_pawn_scaling_bonus: T, + /// Scaling factor for the advanced pawn bonus + pub eg_pawn_scale_factor: T, /// Scaling factor for the advanced pawn bonus - pub pawn_scaling_bonus: T, + pub eg_pawn_scaling_bonus: T, } impl AddAssign for Parameters { @@ -464,8 +472,10 @@ impl AddAssign for Parameters { self.eg_edge[i][j] += rhs.eg_edge[i][j]; } } - self.pawn_scale_factor += rhs.pawn_scale_factor; - self.pawn_scaling_bonus += rhs.pawn_scaling_bonus; + self.mg_pawn_scale_factor += rhs.mg_pawn_scale_factor; + self.mg_pawn_scaling_bonus += rhs.mg_pawn_scaling_bonus; + self.eg_pawn_scale_factor += rhs.eg_pawn_scale_factor; + self.eg_pawn_scaling_bonus += rhs.eg_pawn_scaling_bonus; } } @@ -496,8 +506,10 @@ impl Div for Parameters { eg_pawn_attacked_penalty: self.eg_pawn_attacked_penalty.map(|x| x / rhs), mg_pawn_defended_bonus: self.mg_pawn_defended_bonus.map(|x| x / rhs), eg_pawn_defended_bonus: self.eg_pawn_defended_bonus.map(|x| x / rhs), - pawn_scale_factor: self.pawn_scale_factor / rhs, - pawn_scaling_bonus: self.pawn_scaling_bonus / rhs, + mg_pawn_scale_factor: self.mg_pawn_scale_factor / rhs, + mg_pawn_scaling_bonus: self.mg_pawn_scaling_bonus / rhs, + eg_pawn_scale_factor: self.eg_pawn_scale_factor / rhs, + eg_pawn_scaling_bonus: self.eg_pawn_scaling_bonus / rhs, } } } @@ -524,8 +536,10 @@ impl Div for Parameters { self.eg_edge[i][j] /= rhs.eg_edge[i][j]; } } - self.pawn_scale_factor /= rhs.pawn_scale_factor; - self.pawn_scaling_bonus /= rhs.pawn_scaling_bonus; + self.mg_pawn_scale_factor /= rhs.mg_pawn_scale_factor; + self.mg_pawn_scaling_bonus /= rhs.mg_pawn_scaling_bonus; + self.eg_pawn_scale_factor /= rhs.eg_pawn_scale_factor; + self.eg_pawn_scaling_bonus /= rhs.eg_pawn_scaling_bonus; self } } @@ -548,8 +562,10 @@ impl Mul for Parameters { eg_pawn_attacked_penalty: self.eg_pawn_attacked_penalty.map(|x| x * rhs), mg_pawn_defended_bonus: self.mg_pawn_defended_bonus.map(|x| x * rhs), eg_pawn_defended_bonus: self.eg_pawn_defended_bonus.map(|x| x * rhs), - pawn_scale_factor: self.pawn_scale_factor * rhs, - pawn_scaling_bonus: self.pawn_scaling_bonus * rhs, + mg_pawn_scale_factor: self.mg_pawn_scale_factor * rhs, + mg_pawn_scaling_bonus: self.mg_pawn_scaling_bonus * rhs, + eg_pawn_scale_factor: self.eg_pawn_scale_factor * rhs, + eg_pawn_scaling_bonus: self.eg_pawn_scaling_bonus * rhs, } } } @@ -572,8 +588,10 @@ impl Parameters { eg_pawn_attacked_penalty: self.eg_pawn_attacked_penalty.map(f64::abs), mg_pawn_defended_bonus: self.mg_pawn_defended_bonus.map(f64::abs), eg_pawn_defended_bonus: self.eg_pawn_defended_bonus.map(f64::abs), - pawn_scale_factor: self.pawn_scale_factor.abs(), - pawn_scaling_bonus: self.pawn_scaling_bonus.abs(), + mg_pawn_scale_factor: self.mg_pawn_scale_factor.abs(), + mg_pawn_scaling_bonus: self.mg_pawn_scaling_bonus.abs(), + eg_pawn_scale_factor: self.eg_pawn_scale_factor.abs(), + eg_pawn_scaling_bonus: self.eg_pawn_scaling_bonus.abs(), } } @@ -604,8 +622,10 @@ impl Parameters { eg_pawn_attacked_penalty: self.eg_pawn_attacked_penalty.map(Self::remove_nan), mg_pawn_defended_bonus: self.mg_pawn_defended_bonus.map(Self::remove_nan), eg_pawn_defended_bonus: self.eg_pawn_defended_bonus.map(Self::remove_nan), - pawn_scale_factor: Self::remove_nan(self.pawn_scale_factor), - pawn_scaling_bonus: Self::remove_nan(self.pawn_scaling_bonus), + mg_pawn_scale_factor: Self::remove_nan(self.mg_pawn_scale_factor), + mg_pawn_scaling_bonus: Self::remove_nan(self.mg_pawn_scaling_bonus), + eg_pawn_scale_factor: Self::remove_nan(self.eg_pawn_scale_factor), + eg_pawn_scaling_bonus: Self::remove_nan(self.eg_pawn_scaling_bonus), } } @@ -656,8 +676,10 @@ impl From> for Parameters { eg_pawn_attacked_penalty: value.eg_pawn_attacked_penalty.map(f64::from), mg_pawn_defended_bonus: value.mg_pawn_defended_bonus.map(f64::from), eg_pawn_defended_bonus: value.eg_pawn_defended_bonus.map(f64::from), - pawn_scale_factor: f64::from(value.pawn_scale_factor), - pawn_scaling_bonus: f64::from(value.pawn_scaling_bonus), + mg_pawn_scale_factor: f64::from(value.mg_pawn_scale_factor), + mg_pawn_scaling_bonus: f64::from(value.mg_pawn_scaling_bonus), + eg_pawn_scale_factor: f64::from(value.eg_pawn_scale_factor), + eg_pawn_scaling_bonus: f64::from(value.eg_pawn_scaling_bonus), } } } diff --git a/oxidation/src/pesto.rs b/oxidation/src/pesto.rs deleted file mode 100644 index 25fa6e0..0000000 --- a/oxidation/src/pesto.rs +++ /dev/null @@ -1,160 +0,0 @@ -// Pesto PSQT tables -// For testing how much elo could be gained by specialising for 8x8 boards - -use crate::parameters::pack; - -const MG_PSQT: [[[i32; 8]; 8]; 6] = [ - // pawn - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [98, 134, 61, 95, 68, 126, 34, -11], - [-6, 7, 26, 31, 65, 56, 25, -20], - [-14, 13, 6, 21, 23, 12, 17, -23], - [-27, -2, -5, 12, 17, 6, 10, -25], - [-26, -4, -4, -10, 3, 3, 33, -12], - [-35, -1, -20, -23, -15, 24, 38, -22], - [0, 0, 0, 0, 0, 0, 0, 0], - ], - // knight - [ - [-167, -89, -34, -49, 61, -97, -15, -107], - [-73, -41, 72, 36, 23, 62, 7, -17], - [-47, 60, 37, 65, 84, 129, 73, 44], - [-9, 17, 19, 53, 37, 69, 18, 22], - [-13, 4, 16, 13, 28, 19, 21, -8], - [-23, -9, 12, 10, 19, 17, 25, -16], - [-29, -53, -12, -3, -1, 18, -14, -19], - [-105, -21, -58, -33, -17, -28, -19, -23], - ], - // bishop - [ - [-29, 4, -82, -37, -25, -42, 7, -8], - [-26, 16, -18, -13, 30, 59, 18, -47], - [-16, 37, 43, 40, 35, 50, 37, -2], - [-4, 5, 19, 50, 37, 37, 7, -2], - [-6, 13, 13, 26, 34, 12, 10, 4], - [0, 15, 15, 15, 14, 27, 18, 10], - [4, 15, 16, 0, 7, 21, 33, 1], - [-33, -3, -14, -21, -13, -12, -39, -21], - ], - // rook - [ - [32, 42, 32, 51, 63, 9, 31, 43], - [27, 32, 58, 62, 80, 67, 26, 44], - [-5, 19, 26, 36, 17, 45, 61, 16], - [-24, -11, 7, 26, 24, 35, -8, -20], - [-36, -26, -12, -1, 9, -7, 6, -23], - [-45, -25, -16, -17, 3, 0, -5, -33], - [-44, -16, -20, -9, -1, 11, -6, -71], - [-19, -13, 1, 17, 16, 7, -37, -26], - ], - // queen - [ - [-28, 0, 29, 12, 59, 44, 43, 45], - [-24, -39, -5, 1, -16, 57, 28, 54], - [-13, -17, 7, 8, 29, 56, 47, 57], - [-27, -27, -16, -16, -1, 17, -2, 1], - [-9, -26, -9, -10, -2, -4, 3, -3], - [-14, 2, -11, -2, -5, 2, 14, 5], - [-35, -8, 11, 2, 8, 15, -3, 1], - [-1, -18, -9, 10, -15, -25, -31, -50], - ], - // king - [ - [-65, 23, 16, -15, -56, -34, 2, 13], - [29, -1, -20, -7, -8, -4, -38, -29], - [-9, 24, 2, -16, -20, 6, 22, -22], - [-17, -20, -12, -27, -30, -25, -14, -36], - [-49, -1, -27, -39, -46, -44, -33, -51], - [-14, -14, -22, -46, -44, -30, -15, -27], - [1, 7, -8, -64, -43, -16, 9, 8], - [-15, 36, 12, -54, 8, -28, 24, 14], - ], -]; - -const EG_PSQT: [[[i32; 8]; 8]; 6] = [ - // pawn - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [178, 173, 158, 134, 147, 132, 165, 187], - [94, 100, 85, 67, 56, 53, 82, 84], - [32, 24, 13, 5, -2, 4, 17, 17], - [13, 9, -3, -7, -7, -8, 3, -1], - [4, 7, -6, 1, 0, -5, -1, -8], - [13, 8, 8, 10, 13, 0, 2, -7], - [0, 0, 0, 0, 0, 0, 0, 0], - ], - // knight - [ - [-58, -38, -13, -28, -31, -27, -63, -99], - [-25, -8, -25, -2, -9, -25, -24, -52], - [-24, -20, 10, 9, -1, -9, -19, -41], - [-17, 3, 22, 22, 22, 11, 8, -18], - [-18, -6, 16, 25, 16, 17, 4, -18], - [-23, -3, -1, 15, 10, -3, -20, -22], - [-42, -20, -10, -5, -2, -20, -23, -44], - [-29, -51, -23, -15, -22, -18, -50, -64], - ], - // bishop - [ - [-14, -21, -11, -8, -7, -9, -17, -24], - [-8, -4, 7, -12, -3, -13, -4, -14], - [2, -8, 0, -1, -2, 6, 0, 4], - [-3, 9, 12, 9, 14, 10, 3, 2], - [-6, 3, 13, 19, 7, 10, -3, -9], - [-12, -3, 8, 10, 13, 3, -7, -15], - [-14, -18, -7, -1, 4, -9, -15, -27], - [-23, -9, -23, -5, -9, -16, -5, -17], - ], - // rook - [ - [13, 10, 18, 15, 12, 12, 8, 5], - [11, 13, 13, 11, -3, 3, 8, 3], - [7, 7, 7, 5, 4, -3, -5, -3], - [4, 3, 13, 1, 2, 1, -1, 2], - [3, 5, 8, 4, -5, -6, -8, -11], - [-4, 0, -5, -1, -7, -12, -8, -16], - [-6, -6, 0, 2, -9, -9, -11, -3], - [-9, 2, 3, -1, -5, -13, 4, -20], - ], - // queen - [ - [-9, 22, 22, 27, 27, 19, 10, 20], - [-17, 20, 32, 41, 58, 25, 30, 0], - [-20, 6, 9, 49, 47, 35, 19, 9], - [3, 22, 24, 45, 57, 40, 57, 36], - [-18, 28, 19, 47, 31, 34, 39, 23], - [-16, -27, 15, 6, 9, 17, 10, 5], - [-22, -23, -30, -16, -16, -23, -36, -32], - [-33, -28, -22, -43, -5, -32, -20, -41], - ], - // king - [ - [-74, -35, -18, -18, -11, 15, 4, -17], - [-12, 17, 14, 17, 17, 38, 23, 11], - [10, 17, 23, 15, 20, 45, 44, 13], - [-8, 22, 24, 27, 26, 33, 26, 3], - [-18, -4, 21, 24, 27, 23, 9, -11], - [-19, -3, 11, 21, 23, 16, 7, -9], - [-27, -11, 4, 13, 14, 4, -5, -17], - [-53, -34, -21, -11, -28, -14, -24, -43], - ], -]; - -pub const PSQT: [[[i64; 8]; 8]; 6] = { - let mut result = [[[0; 8]; 8]; 6]; - let mut piece = 0; - while piece < 6 { - let mut i = 0; - while i < 8 { - let mut j = 0; - while j < 8 { - result[piece][i][j] = pack(MG_PSQT[piece][i][j], EG_PSQT[piece][i][j]); - j += 1; - } - i += 1; - } - piece += 1; - } - result -}; diff --git a/oxidation/src/search.rs b/oxidation/src/search.rs index 03db9ac..28b0c1a 100644 --- a/oxidation/src/search.rs +++ b/oxidation/src/search.rs @@ -12,6 +12,7 @@ pub const SEARCH_PARAMETERS: SearchParameters = SearchParameters { lmr_base: 0.42826194, lmr_factor: 0.36211678, lmr_pv_reduction: 0.6459082, + lmr_improving_reduction: 0.5, }; /// Parameters affecting the behaviour of the search @@ -25,6 +26,8 @@ pub struct SearchParameters { pub lmr_factor: f32, /// How much to reduce LMR by in pv nodes pub lmr_pv_reduction: f32, + /// How much to increase LMR by when not improving + pub lmr_improving_reduction: f32, } impl Add for SearchParameters { @@ -35,6 +38,7 @@ impl Add for SearchParameters { lmr_base: self.lmr_base + rhs.lmr_base, lmr_factor: self.lmr_factor + rhs.lmr_factor, lmr_pv_reduction: self.lmr_pv_reduction + rhs.lmr_pv_reduction, + lmr_improving_reduction: self.lmr_improving_reduction + rhs.lmr_improving_reduction, } } } @@ -47,6 +51,7 @@ impl Sub for SearchParameters { lmr_base: self.lmr_base - rhs.lmr_base, lmr_factor: self.lmr_factor - rhs.lmr_factor, lmr_pv_reduction: self.lmr_pv_reduction - rhs.lmr_pv_reduction, + lmr_improving_reduction: self.lmr_improving_reduction - rhs.lmr_improving_reduction, } } } @@ -59,6 +64,7 @@ impl Mul for SearchParameters { lmr_base: self.lmr_base * rhs, lmr_factor: self.lmr_factor * rhs, lmr_pv_reduction: self.lmr_pv_reduction * rhs, + lmr_improving_reduction: self.lmr_improving_reduction * rhs, } } } @@ -73,7 +79,7 @@ fn recaptures( target: (usize, usize), ) -> (Vec, Score) { settings.seldepth = max(settings.seldepth, ply); - let board = &state.stack[ply - 1].board; + let board = &state.stack[ply].board; if board.state() == Gamestate::InProgress { let mut best_score = Score::Centipawn(evaluate(state, board)); if best_score >= beta { @@ -85,16 +91,16 @@ fn recaptures( let mut best_pv = Vec::new(); let mut moves = board.generate_recaptures(target); moves.sort_by_key(|(_, piece)| state.parameters.pieces[usize::from(*piece - 1)].0); - while state.stack.len() < ply + 1 { + while state.stack.len() <= ply + 1 { state .stack - .push(StackEntry::new(state.stack[ply - 1].board.clone())); + .push(StackEntry::new(state.stack[ply].board.clone())); } for (mv, _) in moves { // Safety - the indices are different therefore the references don't alias let position = unsafe { - let board = &*(&state.stack[ply - 1].board as *const Board); - let position = &mut state.stack[ply].board; + let board = &*(&state.stack[ply].board as *const Board); + let position = &mut state.stack[ply + 1].board; position.clone_from(board); position }; @@ -129,8 +135,8 @@ pub fn quiescence( mut alpha: Score, beta: Score, ) -> Option<(Vec, Score)> { - let board = &state.stack[ply - 1].board; settings.seldepth = max(settings.seldepth, ply); + let board = &state.stack[ply].board; if board.state() == Gamestate::InProgress { let hash = board.hash(); let (score, ttmove) = state.table.get(hash, board.moves(), alpha, beta, 0); @@ -164,16 +170,16 @@ pub fn quiescence( state.parameters.pieces[usize::from(*piece - 1)].0 - 100 * state.parameters.pieces[usize::from(*capture - 1)].0 }); - while state.stack.len() < ply + 1 { + while state.stack.len() <= ply + 1 { state .stack - .push(StackEntry::new(state.stack[ply - 1].board.clone())); + .push(StackEntry::new(state.stack[ply].board.clone())); } for (mv, _, _) in moves { // Safety - the indices are different therefore the references don't alias let position = unsafe { - let board = &*(&state.stack[ply - 1].board as *const Board); - let position = &mut state.stack[ply].board; + let board = &*(&state.stack[ply].board as *const Board); + let position = &mut state.stack[ply + 1].board; position.clone_from(board); position }; @@ -213,7 +219,7 @@ fn alpha_beta( nullmove: bool, ) -> Option<(Vec, Score)> { settings.seldepth = max(settings.seldepth, ply); - let board = &state.stack[ply - 1].board; + let board = &state.stack[ply].board; if let Score::Win(movecount) = alpha { let moves = board.moves(); if moves >= movecount { @@ -228,7 +234,7 @@ fn alpha_beta( if board.state() != Gamestate::InProgress { Some((Vec::new(), evaluate_terminal(board))) } else if depth == 0 { - let (pv, score) = quiescence(state, settings, ply, *settings.qdepth, alpha, beta)?; + let (pv, score) = quiescence(state, settings, ply, 1, alpha, beta)?; let tt_flag = if score >= beta { ScoreType::LowerBound } else if score > alpha { @@ -236,7 +242,7 @@ fn alpha_beta( } else { ScoreType::UpperBound }; - let board = &state.stack[ply - 1].board; + let board = &state.stack[ply].board; state.table.store(Entry { hash: board.hash(), depth: 0, @@ -259,21 +265,34 @@ fn alpha_beta( let mut futility_score = None; let movecount = board.moves(); - while state.stack.len() < ply + 1 { + let eval = evaluate(state, board); + + while state.stack.len() <= ply + 1 { state .stack - .push(StackEntry::new(state.stack[ply - 1].board.clone())); + .push(StackEntry::new(state.stack[ply].board.clone())); } - if !pv_node && !in_check { - let board = &state.stack[ply - 1].board; - let eval = evaluate(state, board); + state.stack[ply].eval = if in_check { None } else { Some(eval) }; + let improving = if in_check { + false + } else if ply < 2 { + true + } else if let Some(old_eval) = state.stack[ply - 2].eval { + eval > old_eval + } else { + true + }; + if !pv_node && !in_check { // Reverse futility pruning if depth <= 8 { if let Score::Centipawn(beta_cp) = beta { - let depth = i32::from(depth); - let rfp_margin = 120 * depth - 60; + let mut depth = i32::from(depth); + if improving { + depth -= 1; + } + let rfp_margin = 120 * depth; let rfp_beta = beta_cp + rfp_margin; if eval >= rfp_beta { let score = Score::Centipawn(eval - rfp_margin); @@ -282,11 +301,13 @@ fn alpha_beta( } } + let board = &state.stack[ply].board; + // Null move pruning if !nullmove && depth >= 2 && Score::Centipawn(eval) >= beta && board.has_pieces() { if let Some(nullmove) = board.nullmove() { let null_depth = depth.saturating_sub(3 + depth / 5); - state.stack[ply].board = nullmove; + state.stack[ply + 1].board = nullmove; let score = -null_move_search(state, settings, ply + 1, null_depth, -beta)?; if score >= beta { let score = match score { @@ -306,7 +327,7 @@ fn alpha_beta( } } - if depth <= 3 { + if depth <= 4 { if let Score::Centipawn(alpha_cp) = alpha { let futility_margin = 125 * i32::from(depth); let futility_threshold = alpha_cp - futility_margin; @@ -325,9 +346,8 @@ fn alpha_beta( let mut best_score = Score::Loss(0); let mut move_count = 0; let mut fail_lows: Vec = Vec::new(); - state.stack[ply - 1].movepicker.init(ttmove); - while let Some((mv, is_capture)) = - state.stack[ply - 1].pick_move(&state.history, &state.parameters) + state.stack[ply].movepicker.init(ttmove); + while let Some((mv, is_capture)) = state.stack[ply].pick_move(&state.history, &state.parameters) { // Move loop pruning for quiets - we need to avoid mate first if !is_capture && !matches!(best_score, Score::Loss(_)) { @@ -337,14 +357,14 @@ fn alpha_beta( } // Late move pruning - if !pv_node && depth <= 2 && move_count >= (5 << depth) { + if depth <= 2 && move_count >= (5 << depth) { break; } } // Safety - the indices are different therefore the references don't alias let position = unsafe { - let board = &*(&state.stack[ply - 1].board as *const Board); - let position = &mut state.stack[ply].board; + let board = &*(&state.stack[ply].board as *const Board); + let position = &mut state.stack[ply + 1].board; position.clone_from(board); position }; @@ -358,6 +378,9 @@ fn alpha_beta( if pv_node { reduction -= state.search_parameters.lmr_pv_reduction; } + if !improving { + reduction += state.search_parameters.lmr_improving_reduction; + } // avoid dropping into qsearch (reduction as i8).clamp(0, (depth / 2) as i8) as u8 } else { @@ -403,8 +426,8 @@ fn alpha_beta( }; if score >= beta { if !is_capture { - state.stack[ply - 1].movepicker.store_killer(mv); - let board = &state.stack[ply - 1].board; + state.stack[ply].movepicker.store_killer(mv); + let board = &state.stack[ply].board; for fail_low in fail_lows { state.history.malus( board.to_move(), @@ -532,16 +555,21 @@ pub(crate) fn alpha_beta_root( let mut backup_pv = Vec::new(); let mut move_count = 0; let mut show_output = false; - if state.stack.is_empty() { + while state.stack.len() <= 1 { state.stack.push(StackEntry::new(board.clone())); } + state.stack[0].eval = if board.in_check() { + None + } else { + Some(evaluate(state, board)) + }; for best_move in best_moves { if !excluded_moves.contains(best_move) { if let Some(position) = board.move_if_legal(*best_move) { let node_count = settings.nodes; settings.nodes += 1; move_count += 1; - state.stack[0].board = position; + state.stack[1].board = position; let mut failed_high = false; let (mut pv, score) = if move_count > 1 { // Zero window search to see if raises alpha @@ -635,7 +663,7 @@ pub(crate) fn alpha_beta_root( let node_count = settings.nodes; settings.nodes += 1; move_count += 1; - state.stack[0].board = position; + state.stack[1].board = position; let mut failed_high = false; let (mut pv, score) = if move_count > 1 { // Zero window search to see if raises alpha @@ -750,7 +778,7 @@ pub(crate) fn alpha_beta_root( } else { 0 }; - state.stack[0].board = position; + state.stack[1].board = position; let mut failed_high = false; let (mut pv, score) = if move_count > 1 { // Zero window search to see if raises alpha diff --git a/tester/src/lib.rs b/tester/src/lib.rs index 85c68a3..cec72db 100644 --- a/tester/src/lib.rs +++ b/tester/src/lib.rs @@ -12,7 +12,7 @@ use liberty_chess::Board; use oxidation::evaluate::evaluate; use oxidation::parameters::DEFAULT_PARAMETERS; use oxidation::search::{quiescence, SEARCH_PARAMETERS}; -use oxidation::{random_move, SearchConfig, State, QDEPTH}; +use oxidation::{random_move, SearchConfig, State}; use rand::{thread_rng, Rng}; use std::num::NonZeroUsize; use std::sync::mpsc::channel; @@ -81,10 +81,8 @@ impl StartingPosition { board.friendly_fire = friendly_fire; let mut state = State::new(0, &board, SEARCH_PARAMETERS, DEFAULT_PARAMETERS); let mut debug = false; - let mut qdepth = QDEPTH; let (_tx, rx_2) = channel(); - let mut settings = - SearchConfig::new_time(&board, &mut qdepth, SearchTime::Infinite, &rx_2, &mut debug); + let mut settings = SearchConfig::new_time(&board, SearchTime::Infinite, &rx_2, &mut debug); let mut eval = evaluate(&state, &board); if RANDOM_MOVE_COUNT % 2 == 1 { // Final board is opposite stm, invert score @@ -103,7 +101,7 @@ impl StartingPosition { } // Filter out busted openings state.set_first_stack_entry(&board); - let (_, score) = quiescence(&mut state, &mut settings, 1, QDEPTH, alpha, beta) + let (_, score) = quiescence(&mut state, &mut settings, 0, 1, alpha, beta) .unwrap_or((Vec::new(), Score::Centipawn(eval))); if score > alpha && score < beta { break board; diff --git a/tester/src/match.rs b/tester/src/match.rs index e340c3f..0f24312 100644 --- a/tester/src/match.rs +++ b/tester/src/match.rs @@ -4,7 +4,7 @@ use liberty_chess::threading::CompressedBoard; use liberty_chess::{Board, Gamestate}; use oxidation::parameters::DEFAULT_PARAMETERS; use oxidation::search::{quiescence, SEARCH_PARAMETERS}; -use oxidation::{SearchConfig, State, QDEPTH}; +use oxidation::{SearchConfig, State}; use rand::{thread_rng, Rng}; use std::collections::{HashMap, HashSet}; use std::fs::write; @@ -171,10 +171,8 @@ fn play_game( let mut challenge_tc = CHALLENGE_TIME; let mut state = State::new(0, &board, SEARCH_PARAMETERS, DEFAULT_PARAMETERS); let mut debug = false; - let mut qdepth = QDEPTH; let (_tx, rx_2) = channel(); - let mut settings = - SearchConfig::new_time(&board, &mut qdepth, SearchTime::Infinite, &rx_2, &mut debug); + let mut settings = SearchConfig::new_time(&board, SearchTime::Infinite, &rx_2, &mut debug); while current_board.state() == Gamestate::InProgress { if current_board.to_move() ^ champion_side { challenge_requests @@ -227,8 +225,8 @@ fn play_game( let (pv, _) = quiescence( &mut state, &mut settings, + 0, 1, - QDEPTH, Score::Loss(0), Score::Win(0), ) diff --git a/tester/src/spsa.rs b/tester/src/spsa.rs index bfd2de7..9e5b256 100644 --- a/tester/src/spsa.rs +++ b/tester/src/spsa.rs @@ -4,7 +4,7 @@ use liberty_chess::{Board, Gamestate}; use oxidation::glue::process_position; use oxidation::parameters::DEFAULT_PARAMETERS; use oxidation::search::{SearchParameters, SEARCH_PARAMETERS}; -use oxidation::{State, HASH_SIZE, QDEPTH}; +use oxidation::{State, HASH_SIZE}; use rand::{thread_rng, Rng}; use std::sync::mpsc::{channel, Sender}; use std::time::Instant; @@ -12,6 +12,7 @@ use tester::{get_threadpool, GameResult, POSITIONS, STC}; use ulci::server::UlciResult; use ulci::SearchTime; +const STARTING_ITERATION: u16 = 0; const ITERATION_COUNT: u16 = 2000; const ALPHA: f32 = 0.602; @@ -37,6 +38,7 @@ impl Spsa for SearchParameters { lmr_base: rng.gen_range(-0.06..0.06), lmr_factor: rng.gen_range(-0.04..0.04), lmr_pv_reduction: rng.gen_range(-0.1..0.1), + lmr_improving_reduction: rng.gen_range(-0.1..0.1), } } @@ -55,14 +57,7 @@ fn process_move(state: &mut State, board: &mut Board, search_time: &mut SearchTi let move_time = Instant::now(); let (tx, rx) = channel(); let (_tx_2, rx_2) = channel(); - process_position( - &tx, - &rx_2, - board.send_to_thread(), - *search_time, - QDEPTH, - state, - ); + process_position(&tx, &rx_2, board.send_to_thread(), *search_time, state); while let Ok(result) = rx.recv() { match result { UlciResult::Analysis(results) => { @@ -214,7 +209,7 @@ fn run_match(params1: SearchParameters, params2: SearchParameters) -> (i32, u32) fn main() { let mut params = SEARCH_PARAMETERS; - for k in 0..ITERATION_COUNT { + for k in STARTING_ITERATION..ITERATION_COUNT { println!("Iteration {k}"); let k = f32::from(k); let a_k = 1.0 / (k + 1.0).powf(ALPHA); diff --git a/tester/src/tuner.rs b/tester/src/tuner.rs index a1ad3d7..45da797 100644 --- a/tester/src/tuner.rs +++ b/tester/src/tuner.rs @@ -186,8 +186,10 @@ fn main() { pieces: DEFAULT_PARAMETERS .pieces .map(|(x, y)| (f64::from(x), f64::from(y))), - pawn_scale_factor: f64::from(DEFAULT_PARAMETERS.pawn_scale_factor), - pawn_scaling_bonus: f64::from(DEFAULT_PARAMETERS.pawn_scaling_bonus), + mg_pawn_scale_factor: f64::from(DEFAULT_PARAMETERS.mg_pawn_scale_factor), + mg_pawn_scaling_bonus: f64::from(DEFAULT_PARAMETERS.mg_pawn_scaling_bonus), + eg_pawn_scale_factor: f64::from(DEFAULT_PARAMETERS.eg_pawn_scale_factor), + eg_pawn_scaling_bonus: f64::from(DEFAULT_PARAMETERS.eg_pawn_scaling_bonus), ..Default::default() }; let mut data = Vec::new(); diff --git a/ulci/src/client.rs b/ulci/src/client.rs index b666827..aec02aa 100644 --- a/ulci/src/client.rs +++ b/ulci/src/client.rs @@ -219,7 +219,7 @@ fn position( } else { write(out, &format!("info error invalid position {fen}"))?; // Fatal error, quit the program - return None; + return if !debug { None } else { Some(()) }; } } Some(_) | None => { @@ -242,12 +242,12 @@ fn position( ), )?; // Fatal error, quit the program - return None; + return if !debug { None } else { Some(()) }; } } else { write(out, &format!("info error invalid move {word}"))?; // Fatal error, quit the program - return None; + return if !debug { None } else { Some(()) }; } } } @@ -262,7 +262,12 @@ fn position( .ok() } -fn go(out: &mut impl Write, client: &Sender, mut words: SplitWhitespace) -> Option<()> { +fn go( + out: &mut impl Write, + client: &Sender, + mut words: SplitWhitespace, + debug: bool, +) -> Option<()> { let mut time = SearchTime::Infinite; while let Some(word) = words.next() { match word { @@ -361,7 +366,7 @@ fn go(out: &mut impl Write, client: &Sender, mut words: SplitWhitespace } "searchmoves" => break, _ => { - write(out, "info error unknown go parameter")?; + write(out, format!("info error unknown go parameter {word}"))?; } } } @@ -372,7 +377,7 @@ fn go(out: &mut impl Write, client: &Sender, mut words: SplitWhitespace } else { write(out, "info error invalid move specified")?; // Fatal error, quit the program - return None; + return if !debug { None } else { Some(()) }; } } client @@ -466,7 +471,7 @@ pub fn startup( } Some("setoption") => setoption(&mut out, client, words, info)?, Some("position") => position(&mut out, client, &mut board, words, debug)?, - Some("go") => go(&mut out, client, words)?, + Some("go") => go(&mut out, client, words, debug)?, Some("stop") => client.send(Message::Stop).ok()?, Some("eval") => client.send(Message::Eval).ok()?, Some("ucinewgame") => client.send(Message::NewGame).ok()?,