feat: uci options, quiet position detector

This commit is contained in:
dogeystamp 2024-12-28 15:59:27 -05:00
parent fc8eab4d4b
commit ede46552fe
No known key found for this signature in database
3 changed files with 98 additions and 9 deletions

View File

@ -165,6 +165,8 @@ pub struct MsgBestmove {
pub pv: Vec<Move>,
/// Evaluation of the position
pub eval: SearchEval,
/// Extra information (displayed as `info string`).
pub info: Vec<String>,
}
/// Interface messages that may be received by main's channel.

View File

@ -51,8 +51,9 @@ macro_rules! ignore {
/// UCI engine metadata query.
fn cmd_uci() -> String {
let str = "id name chess_inator\n\
id author dogeystamp\n\
uciok";
id author dogeystamp\n\
option name NNUETrainInfo type check default false\n\
uciok";
str.into()
}
@ -123,7 +124,6 @@ fn cmd_go(mut tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) {
};
}
while let Some(token) = tokens.next() {
match token {
"wtime" => {
@ -207,6 +207,50 @@ fn cmd_eval(mut _tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) {
println!("- total: {}", res.total_eval);
}
fn match_true_false(s: &str) -> Option<bool> {
match s {
"true" => Some(true),
"false" => Some(false),
_ => None,
}
}
/// Set engine options via UCI.
fn cmd_setoption(mut tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) {
while let Some(token) = tokens.next() {
fn get_val(mut tokens: std::str::SplitWhitespace<'_>) -> Option<String> {
if let Some("value") = tokens.next() {
if let Some(value) = tokens.next() {
return Some(value.to_string());
}
}
None
}
match token {
"name" => {
if let Some(name) = tokens.next() {
match name {
"NNUETrainInfo" => {
if let Some(value) = get_val(tokens) {
if let Some(value) = match_true_false(&value) {
state.config.nnue_train_info = value;
}
}
}
_ => {
println!("info string Unknown option: {}", name)
}
}
}
}
_ => ignore!(),
}
break;
}
}
/// Root UCI parser.
fn cmd_root(mut tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) {
while let Some(token) = tokens.next() {
@ -242,6 +286,9 @@ fn cmd_root(mut tokens: std::str::SplitWhitespace<'_>, state: &mut MainState) {
state.tx_engine.send(MsgToEngine::Stop).unwrap();
}
}
"setoption" => {
cmd_setoption(tokens, state);
}
// non-standard command.
"eval" => {
cmd_eval(tokens, state);
@ -274,6 +321,10 @@ fn outp_bestmove(bestmove: MsgBestmove) {
panic!("info string ERROR: stopped search")
}
}
for line in bestmove.info {
println!("info string {line}");
}
match chosen {
Some(mv) => println!("bestmove {}", mv.to_uci_algebraic()),
None => println!("bestmove 0000"),
@ -315,8 +366,21 @@ fn task_engine(tx_main: Sender<MsgToMain>, rx_engine: Receiver<MsgToEngine>) {
state.config = msg_box.config;
state.time_lims = msg_box.time_lims;
let (pv, eval) = best_line(&mut board, &mut state);
let mut info: Vec<String> = Vec::new();
if state.config.nnue_train_info {
let is_quiet = chess_inator::search::is_quiescent_position(&board, eval);
let is_quiet = if is_quiet {"quiet"} else {"non-quiet"};
info.push(format!("NNUETrainInfo {}", is_quiet))
}
tx_main
.send(MsgToMain::Bestmove(MsgBestmove { pv, eval }))
.send(MsgToMain::Bestmove(MsgBestmove {
pv,
eval,
info,
}))
.unwrap();
}
MsgToEngine::Stop => {}

View File

@ -128,6 +128,8 @@ pub struct SearchConfig {
pub enable_trans_table: bool,
/// Transposition table size (2^n where this is n)
pub transposition_size: usize,
/// Print machine-readable information about the position during NNUE training data generation.
pub nnue_train_info: bool,
}
impl Default for SearchConfig {
@ -139,6 +141,7 @@ impl Default for SearchConfig {
contempt: 0,
enable_trans_table: true,
transposition_size: 24,
nnue_train_info: false,
}
}
}
@ -450,11 +453,7 @@ impl TimeLimits {
/// Make time limits based on wtime, btime (but color-independent).
///
/// Also takes in eval metrics, for instance to avoid wasting too much time in the opening.
pub fn from_ourtime_theirtime(
ourtime_ms: u64,
_theirtime_ms: u64,
eval: EvalMetrics,
) -> Self {
pub fn from_ourtime_theirtime(ourtime_ms: u64, _theirtime_ms: u64, eval: EvalMetrics) -> Self {
// hard timeout (max)
let mut hard_ms = 100_000;
// soft timeout (default max)
@ -550,3 +549,27 @@ pub fn best_move(board: &mut Board, engine_state: &mut EngineState) -> Option<Mo
let (line, _eval) = best_line(board, engine_state);
line.last().copied()
}
/// Utility for NNUE training set generation to determine if a position is quiet or not.
///
/// Our definition of "quiet" is that there are no checks, and the static and quiescence search
/// evaluations are similar. (See https://arxiv.org/html/2412.17948v1.)
///
/// It is the caller's responsibility to get the search evaluation and pass it to this function.
pub fn is_quiescent_position(board: &Board, eval: SearchEval) -> bool {
// max centipawn value difference to call "similar"
const THRESHOLD: EvalInt = 170;
if board.is_check(board.turn) {
return false;
}
if matches!(eval, SearchEval::Checkmate(_)) {
return false;
}
// white perspective
let abs_eval = EvalInt::from(eval) * EvalInt::from(board.turn.sign());
(board.eval() - EvalInt::from(abs_eval)).abs() <= THRESHOLD.abs()
}