Skip to content

Commit

Permalink
remove static exchange evaluation (see)
Browse files Browse the repository at this point in the history
Rank Name                          Elo     +/-   Games    Wins  Losses   Draws   Points   Score    Draw
   0 dev                            12       5    9000    2642    2324    4034   4659.0   51.8%   44.8%
   1 Frozenight-6.0                  2       9    3000     808     790    1402   1509.0   50.3%   46.7%
   2 Marvin-6.2                    -18       9    3000     722     873    1405   1424.5   47.5%   46.8%
   3 Halogen-11.0                  -21      10    3000     794     979    1227   1407.5   46.9%   40.9%
  • Loading branch information
brunocodutra committed Nov 15, 2024
1 parent 5ac58d2 commit e1af234
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 129 deletions.
57 changes: 1 addition & 56 deletions lib/chess/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,45 +435,6 @@ impl Position {
moves.into_iter()
}

/// Finds the least valued captor of the piece on a square.
#[inline(always)]
pub fn exchange(&self, sq: Square) -> Option<Move> {
use Role::*;

let turn = self.turn();
let king = self.king(turn);
let occupied = self.occupied();

if !self.material(!turn).contains(sq) {
return None;
} else if !self.is_check() || self.checkers() == sq.bitboard() {
let unpinned = self.material(turn) & (!self.pinned() | Bitboard::segment(sq, king));

for role in [Pawn, Knight, Bishop, Rook, Queen] {
let candidates = unpinned & self.board.by_role(role);
if !candidates.is_empty() {
let piece = Piece::new(role, turn);
for wc in candidates & piece.flip().targets(sq) {
if matches!(role, Pawn | Knight)
|| Bitboard::segment(sq, wc).intersection(occupied).is_empty()
{
let moves = MoveSet::capture(piece, wc, sq.bitboard());
return moves.into_iter().next();
}
}
}
}
}

if Piece::new(King, turn).targets(king).contains(sq)
&& !self.is_threatened(sq, !turn, occupied.without(king))
{
return Some(Move::capture(king, sq, None));
}

None
}

/// Play a [`Move`].
#[inline(always)]
pub fn play(&mut self, m: Move) -> (Role, Option<(Role, Square)>) {
Expand Down Expand Up @@ -678,7 +639,7 @@ impl FromStr for Position {
#[cfg(test)]
mod tests {
use super::*;
use std::{cmp::Reverse, fmt::Debug, hash::DefaultHasher};
use std::{fmt::Debug, hash::DefaultHasher};
use test_strategy::proptest;

#[proptest]
Expand Down Expand Up @@ -760,22 +721,6 @@ mod tests {
}
}

#[proptest]
fn exchange_finds_captor_of_least_value(
#[filter(#pos.outcome().is_none())] pos: Position,
#[map(|s: Selector| s.select(#pos.material(!#pos.turn())))] sq: Square,
) {
assert_eq!(
pos.exchange(sq)
.map(|m| (pos.board.role_on(m.whence()), m.promotion(),)),
pos.moves()
.filter(|m| m.whither().contains(sq))
.flatten()
.map(|m| (pos.board.role_on(m.whence()), m.promotion()))
.min_by_key(|&(r, p)| (r, Reverse(p)))
);
}

#[proptest]
fn captures_reduce_material(
#[filter(#pos.moves().any(|ms| ms.is_capture()))] mut pos: Position,
Expand Down
49 changes: 1 addition & 48 deletions lib/nnue/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::nnue::{Accumulator, Feature, Material, Positional, Value};
use crate::util::Integer;
use arrayvec::ArrayVec;
use derive_more::{Debug, Deref, Display};
use std::{ops::Range, str::FromStr};
use std::str::FromStr;

#[cfg(test)]
use proptest::prelude::*;
Expand Down Expand Up @@ -55,39 +55,6 @@ impl<T: Accumulator> Evaluator<T> {
value.saturate()
}

/// The Static Exchange Evaluation ([SEE]) algorithm.
///
/// [SEE]: https://www.chessprogramming.org/Static_Exchange_Evaluation
pub fn see(&mut self, sq: Square, bounds: Range<Value>) -> Value {
let (mut alpha, mut beta) = (bounds.start, bounds.end);

loop {
alpha = alpha.max(self.evaluate());

if alpha >= beta {
break beta;
}

let Some(m) = self.exchange(sq) else {
break alpha;
};

self.play(m);

beta = beta.min(-self.evaluate());

if alpha >= beta {
break alpha;
}

let Some(m) = self.exchange(sq) else {
break beta;
};

self.play(m);
}
}

/// Play a [null-move].
///
/// [null-move]: https://www.chessprogramming.org/Null_Move
Expand Down Expand Up @@ -176,20 +143,6 @@ mod tests {
use std::fmt::Debug;
use test_strategy::proptest;

#[proptest]
fn see_returns_value_within_bounds(pos: Position, sq: Square, r: Range<Value>) {
let (a, b) = (r.start, r.end);
assert!((a..=b).contains(&Evaluator::<Positional>::new(pos).see(sq, r)));
}

#[proptest]
fn see_returns_beta_if_alpha_is_not_smaller(pos: Position, sq: Square, r: Range<Value>) {
assert_eq!(
Evaluator::<Positional>::new(pos).see(sq, r.end..r.start),
r.start
);
}

#[proptest]
fn play_updates_evaluator(
#[filter(#e.outcome().is_none())] mut e: Evaluator,
Expand Down
37 changes: 12 additions & 25 deletions lib/search/engine.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::nnue::{Evaluator, Value};
use crate::search::*;
use crate::util::{Assume, Counter, Integer, Timer, Trigger};
use crate::{chess::Move, search::*};
use arrayvec::ArrayVec;
use std::{cell::RefCell, ops::Range, time::Duration};

Expand Down Expand Up @@ -76,16 +76,8 @@ impl Engine {
/// An implementation of [null move pruning].
///
/// [null move pruning]: https://www.chessprogramming.org/Null_Move_Pruning
fn nmp(
&self,
pos: &Evaluator,
guess: Score,
beta: Score,
depth: Depth,
ply: Ply,
) -> Option<Depth> {
let turn = pos.turn();
if guess >= beta && pos.pieces(turn).len() > 1 {
fn nmp(&self, guess: Score, beta: Score, depth: Depth, ply: Ply) -> Option<Depth> {
if guess >= beta {
Some(depth - 2 - (depth - ply) / 4)
} else {
None
Expand All @@ -95,15 +87,8 @@ impl Engine {
/// An implementation of [late move pruning].
///
/// [late move pruning]: https://www.chessprogramming.org/Late_Move_Reductions
fn lmp(
&self,
next: &Evaluator,
m: Move,
alpha: Value,
depth: Depth,
ply: Ply,
) -> Option<Depth> {
let r = match (alpha + next.clone().see(m.whither(), -(alpha - 15)..-(alpha - 101))).get() {
fn lmp(&self, guess: Value, alpha: Value, depth: Depth, ply: Ply) -> Option<Depth> {
let r = match (alpha - guess).get() {
..=15 => return None,
16..=50 => 1,
51..=100 => 2,
Expand Down Expand Up @@ -202,8 +187,8 @@ impl Engine {

if alpha >= beta || ply >= Ply::MAX {
return Ok(Pv::new(score, None));
} else if !is_pv && !pos.is_check() {
if let Some(d) = self.nmp(pos, score, beta, depth, ply) {
} else if !is_pv && !pos.is_check() && pos.pieces(pos.turn()).len() > 1 {
if let Some(d) = self.nmp(score, beta, depth, ply) {
let mut next = pos.clone();
next.pass();
if d <= ply || -self.nw(&next, -beta + 1, d, ply + 1, ctrl)? >= beta {
Expand All @@ -224,7 +209,7 @@ impl Engine {
} else if Self::KILLERS.with_borrow(|ks| ks.contains(ply, pos.turn(), m)) {
(m, Value::new(25))
} else if m.is_quiet() {
(m, Value::new(0))
(m, Value::lower())
} else {
let mut next = pos.material();
let material = next.evaluate();
Expand Down Expand Up @@ -259,8 +244,9 @@ impl Engine {
next.play(m);

self.tt.prefetch(next.zobrist());
if gain <= 0 && !pos.is_check() && !next.is_check() {
if let Some(d) = self.lmp(&next, m, alpha.saturate(), depth, ply) {
if gain < 0 && !pos.is_check() && !next.is_check() {
let guess = -next.evaluate();
if let Some(d) = self.lmp(guess, alpha.saturate(), depth, ply) {
if d <= ply || -self.nw(&next, -alpha, d, ply + 1, ctrl)? <= alpha {
#[cfg(not(test))]
// The late move pruning heuristic is not exact.
Expand Down Expand Up @@ -370,6 +356,7 @@ impl Engine {
#[cfg(test)]
mod tests {
use super::*;
use crate::chess::Move;
use proptest::{prop_assume, sample::Selector};
use std::time::Instant;
use test_strategy::proptest;
Expand Down

0 comments on commit e1af234

Please sign in to comment.