analysis: provide "nodes" and "nps" UCI info

This commit is contained in:
dece 2020-06-15 02:33:08 +02:00
parent 685039d98a
commit a426e2f777
3 changed files with 80 additions and 29 deletions

View file

@ -1,8 +1,8 @@
//! Analysis functions. //! Analysis functions.
use std::sync::{Arc, atomic, mpsc}; use std::sync::{Arc, atomic, mpsc};
use std::time::Instant;
use crate::board;
use crate::engine; use crate::engine;
use crate::movement::Move; use crate::movement::Move;
use crate::node::Node; use crate::node::Node;
@ -19,6 +19,9 @@ pub struct Analyzer {
node: Node, node: Node,
engine_tx: mpsc::Sender<engine::Cmd>, engine_tx: mpsc::Sender<engine::Cmd>,
max_depth: u32, max_depth: u32,
nps_time: Instant,
num_nodes: u64,
num_nodes_in_second: u64,
} }
/// Analysis parameters. /// Analysis parameters.
@ -31,16 +34,40 @@ pub struct AnalysisParams {
pub black_inc: i32, pub black_inc: i32,
} }
/// Analysis info to report.
#[derive(Debug, Clone)]
pub enum AnalysisInfo {
Nodes(u64),
Nps(u64),
CurrentMove(Move),
}
impl Analyzer { impl Analyzer {
/// Create a new worker to analyze from `node`. /// Create a new worker to analyze from `node`.
pub fn new(node: Node, engine_tx: mpsc::Sender<engine::Cmd>) -> Analyzer { pub fn new(node: Node, engine_tx: mpsc::Sender<engine::Cmd>) -> Analyzer {
Analyzer { debug: false, node, engine_tx, max_depth: 1 } Analyzer {
debug: false,
node,
engine_tx,
max_depth: 1,
nps_time: Instant::now(),
num_nodes: 0,
num_nodes_in_second: 0,
}
} }
fn log(&self, message: String) { fn log(&self, message: String) {
self.engine_tx.send(engine::Cmd::Log(message)).unwrap(); self.engine_tx.send(engine::Cmd::Log(message)).unwrap();
} }
fn report_info(&self, infos: Vec<AnalysisInfo>) {
self.engine_tx.send(engine::Cmd::WorkerInfo(infos)).unwrap();
}
fn report_best_move(&self, m: Option<Move>) {
self.engine_tx.send(engine::Cmd::WorkerBestMove(m)).unwrap();
}
/// Analyse best moves for the node. /// Analyse best moves for the node.
/// ///
/// - `args`: parameters provided for this analysis. /// - `args`: parameters provided for this analysis.
@ -60,8 +87,9 @@ impl Analyzer {
self.log(format!("Legal moves: {}", notation::move_list_to_string(&moves))); self.log(format!("Legal moves: {}", notation::move_list_to_string(&moves)));
} }
self.max_depth = 3; self.nps_time = Instant::now();
let (max_score, best_move) = self.negamax(&self.node, MIN_F32, MAX_F32, 0); self.max_depth = 4;
let (max_score, best_move) = self.negamax(&self.node.clone(), MIN_F32, MAX_F32, 0);
if best_move.is_some() { if best_move.is_some() {
let log_str = format!( let log_str = format!(
@ -69,34 +97,50 @@ impl Analyzer {
notation::move_to_string(&best_move.unwrap()), max_score notation::move_to_string(&best_move.unwrap()), max_score
); );
self.log(log_str); self.log(log_str);
self.engine_tx.send(engine::Cmd::TmpBestMove(best_move)).unwrap(); self.report_best_move(best_move);
} else { } else {
// If no best move could be found, checkmate is unavoidable; send the first legal move. // If no best move could be found, checkmate is unavoidable; send the first legal move.
self.log("Checkmate is unavoidable.".to_string()); self.log("Checkmate is unavoidable.".to_string());
let moves = rules::get_player_moves(&self.node.board, &self.node.game_state, true); let moves = rules::get_player_moves(&self.node.board, &self.node.game_state, true);
let m = if moves.len() > 0 { Some(moves[0]) } else { None }; let m = if moves.len() > 0 { Some(moves[0]) } else { None };
self.engine_tx.send(engine::Cmd::TmpBestMove(m)).unwrap(); self.report_best_move(m);
} }
} }
fn negamax( fn negamax(
&self, &mut self,
node: &Node, node: &Node,
alpha: f32, alpha: f32,
beta: f32, beta: f32,
depth: u32, depth: u32,
) -> (f32, Option<Move>) { ) -> (f32, Option<Move>) {
// Increment number of nodes for stats.
self.num_nodes += 1;
self.num_nodes_in_second += 1;
// If we reached max depth, evaluate the node and stop searching.
if depth == self.max_depth { if depth == self.max_depth {
let stats = node.compute_stats(); let stats = node.compute_stats();
let ev = evaluate(&stats); let ev = evaluate(&stats);
return (ev, None) return (ev, None)
} }
// Here's a good time to get some stats!
if self.nps_time.elapsed().as_millis() >= 1000 {
self.report_info(vec![
AnalysisInfo::Nodes(self.num_nodes),
AnalysisInfo::Nps(self.num_nodes_in_second),
]);
self.num_nodes_in_second = 0;
self.nps_time = Instant::now();
}
// Get negamax for playable moves.
let moves = node.get_player_moves(true); let moves = node.get_player_moves(true);
let mut alpha = alpha; let mut alpha = alpha;
let mut best_score = MIN_F32; let mut best_score = MIN_F32;
let mut best_move = None; let mut best_move = None;
for m in moves { for m in moves {
self.log(format!("negamax: depth {} move {}...", depth, notation::move_to_string(&m)));
let mut sub_node = node.clone(); let mut sub_node = node.clone();
sub_node.apply_move(&m); sub_node.apply_move(&m);
let result = self.negamax(&sub_node, -beta, -alpha, depth + 1); let result = self.negamax(&sub_node, -beta, -alpha, depth + 1);
@ -104,14 +148,11 @@ impl Analyzer {
if score > best_score { if score > best_score {
best_score = score; best_score = score;
best_move = Some(m); best_move = Some(m);
self.log(format!("negamax: depth {} new best score {}.", depth, best_score));
} }
if best_score > alpha { if best_score > alpha {
alpha = best_score; alpha = best_score;
self.log(format!("negamax: depth {} new alpha {}.", depth, alpha));
} }
if alpha >= beta { if alpha >= beta {
self.log(format!("negamax: depth {} alpha above beta {}, cut.", depth, beta));
break break
} }
} }

View file

@ -47,30 +47,33 @@ enum Mode {
#[derive(Debug)] #[derive(Debug)]
pub enum Cmd { pub enum Cmd {
// Commands that can be received by the engine. // Commands that can be received by the engine.
UciChannel(mpsc::Sender<Cmd>), // Provide a sender to UCI to start receiving commands.
UciDebug(bool), // UCI "debug" command. /// Provide a sender to UCI to start receiving commands.
UciPosition(Vec<uci::PositionArgs>), // UCI "position" command. UciChannel(mpsc::Sender<Cmd>),
UciGo(Vec<uci::GoArgs>), // UCI "go" command. /// UCI "debug" command.
Stop, // Stop working ASAP. UciDebug(bool),
TmpBestMove(Option<Move>), // Send best move found by analysis worker (TEMPORARY). /// UCI "position" command.
WorkerInfo(Vec<Info>), // Informations from a worker. UciPosition(Vec<uci::PositionArgs>),
/// UCI "go" command.
UciGo(Vec<uci::GoArgs>),
/// Stop working ASAP.
Stop,
/// Informations from a worker.
WorkerInfo(Vec<analysis::AnalysisInfo>),
/// Send best move found by analysis worker.
WorkerBestMove(Option<Move>),
// Commands that can be sent by the engine. // Commands that can be sent by the engine.
/// Ask for a string to be logged or printed. /// Ask for a string to be logged or printed.
/// ///
/// Note that workers can send this command to engine, expecting /// Note that workers can send this command to engine, expecting
/// the message to be forwarded to whatever can log. /// the message to be forwarded to whatever can log.
Log(String), Log(String),
/// Report ongoing analysis information.
Info(Vec<analysis::AnalysisInfo>),
/// Report found best move. /// Report found best move.
BestMove(Option<Move>), BestMove(Option<Move>),
/// Report ongoing analysis information.
Info(Vec<Info>),
}
/// Information to be transmitted back to whatever is listening.
#[derive(Debug, Clone)]
pub enum Info {
CurrentMove(Move),
} }
/// General engine implementation. /// General engine implementation.
@ -120,7 +123,7 @@ impl Engine {
// Workers commands. // Workers commands.
Cmd::Log(s) => self.reply(Cmd::Log(s.to_string())), Cmd::Log(s) => self.reply(Cmd::Log(s.to_string())),
Cmd::WorkerInfo(infos) => self.reply(Cmd::Info(infos.to_vec())), Cmd::WorkerInfo(infos) => self.reply(Cmd::Info(infos.to_vec())),
Cmd::TmpBestMove(m) => self.reply(Cmd::BestMove(*m)), Cmd::WorkerBestMove(m) => self.reply(Cmd::BestMove(*m)),
_ => eprintln!("Not an engine input command: {:?}", cmd), _ => eprintln!("Not an engine input command: {:?}", cmd),
} }
} }

View file

@ -5,6 +5,7 @@ use std::io::{self, Write};
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use crate::analysis::AnalysisInfo;
use crate::engine; use crate::engine;
use crate::movement::Move; use crate::movement::Move;
use crate::notation; use crate::notation;
@ -259,11 +260,17 @@ impl Uci {
} }
/// Send engine analysis information. /// Send engine analysis information.
fn send_infos(&mut self, infos: &Vec<engine::Info>) { fn send_infos(&mut self, infos: &Vec<AnalysisInfo>) {
let mut s = "info".to_string(); let mut s = "info".to_string();
for i in infos { for i in infos {
match i { match i {
engine::Info::CurrentMove(m) => { AnalysisInfo::Nodes(n) => {
s.push_str(&format!(" nodes {}", n));
}
AnalysisInfo::Nps(n) => {
s.push_str(&format!(" nps {}", n));
}
AnalysisInfo::CurrentMove(m) => {
s.push_str(&format!(" currmove {}", notation::move_to_string(m))); s.push_str(&format!(" currmove {}", notation::move_to_string(m)));
} }
} }