Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 18 additions & 18 deletions arche-core/src/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,8 @@ mod tests {
],
[
[182_505, 7_085, 22_788, 535, 0, 287, 0],
[11_884, 914, 3_056, 52, 24, 0, 0],
[15_165, 1_224, 3_823, 3, 0, 0, 39],
[10_070, 755, 2_598, 57, 28, 0, 0],
[12_991, 977, 3_364, 3, 0, 0, 47],
],
"nodes, cutoffs, stores, tainted, tcuts, refused, skipped"
);
Expand Down Expand Up @@ -833,24 +833,24 @@ mod tests {
assert_eq!(
counted,
vec![
("start", 564_019),
("italian", 1_137_689),
("ruy lopez", 378_968),
("kiwipete", 1_625_682),
("perft 4", 2_901_384),
("promotions", 1_643_811),
("middlegame", 494_644),
("sharp middlegame", 449_762),
("start", 810_901),
("italian", 925_606),
("ruy lopez", 568_063),
("kiwipete", 1_717_616),
("perft 4", 2_017_487),
("promotions", 315_447),
("middlegame", 519_737),
("sharp middlegame", 683_004),
("bratko kopec 1", 11_568),
("wac 4", 1_516),
("rook and pawns", 355_432),
("tarrasch", 136_354),
("lucena", 1_403_340),
("philidor", 237_927),
("minor endgame", 156_423),
("queen endgame", 337_681),
("king and pawn", 45_261),
("trebuchet", 35_531),
("rook and pawns", 314_171),
("tarrasch", 186_731),
("lucena", 442_470),
("philidor", 190_861),
("minor endgame", 185_295),
("queen endgame", 360_712),
("king and pawn", 34_240),
("trebuchet", 27_235),
]
);
}
Expand Down
5 changes: 3 additions & 2 deletions arche-core/src/census.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ pub struct Cut {
/// it.
pub index: usize,
pub class: Class,
/// The history score at the cutoff, quiets only and 0 otherwise.
/// The butterfly history's score at the cutoff, quiets only and 0
/// otherwise. The continuation entry the order adds is not in it.
/// Signed: a move tried more often than it cuts sits below zero. Read as
/// a fraction of `history_max`, since the raw number moves with what the
/// search has learned.
Expand All @@ -157,7 +158,7 @@ pub struct Event {
pub searched: usize,
/// None when the loop ran out.
pub cut: Option<Cut>,
/// The largest history score among the generated quiets, clamped at
/// The largest butterfly score among the generated quiets, clamped at
/// zero: what `Cut::history` is read against.
pub history_max: i32,
/// Whether the staged ordering scored the quiet band before the node
Expand Down
38 changes: 31 additions & 7 deletions arche-core/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,12 @@ pub struct SearchConfig {
/// other move, shown by a half depth search with that move excluded,
/// searches the table move a ply deeper.
pub singular_extensions: bool,
/// Whether the search tells the move ordering what it played, so a
/// quiet's history adds the continuation table's entry under the move
/// that reached the node. Off, the table is never written or read and
/// the history is the butterfly table alone. It does nothing with
/// `move_memory` off.
pub continuation_history: bool,
}

/// What to do with a draw tainted score: one stored by a search that read
Expand Down Expand Up @@ -681,7 +687,7 @@ impl SearchConfig {
///
/// `taint` is not among them: it is a policy with four values rather
/// than a switch, and `residuals` already takes it.
pub const SWITCHES: [(&'static str, TurnOff); 14] = [
pub const SWITCHES: [(&'static str, TurnOff); 15] = [
("reverse_futility", |config| config.reverse_futility = false),
("null_move", |config| config.null_move = false),
("adaptive_null_move", |config| {
Expand All @@ -704,6 +710,9 @@ impl SearchConfig {
("singular_extensions", |config| {
config.singular_extensions = false
}),
("continuation_history", |config| {
config.continuation_history = false
}),
];

/// The default with one switch off, or none for a name the table does
Expand Down Expand Up @@ -740,6 +749,7 @@ impl SearchConfig {
move_memory: false,
aspiration: false,
singular_extensions: false,
continuation_history: false,
}
}

Expand Down Expand Up @@ -795,6 +805,7 @@ impl Default for SearchConfig {
move_memory: true,
aspiration: true,
singular_extensions: true,
continuation_history: true,
}
}
}
Expand Down Expand Up @@ -1888,7 +1899,7 @@ impl AlphaBeta {
) {
if let Some(ply) = self.memory_ply() {
self.ordering
.cutoff(self.board.active_color, m, tried, ply, depth);
.cutoff(&self.board, self.board.active_color, m, tried, ply, depth);
}
}

Expand Down Expand Up @@ -2170,7 +2181,7 @@ impl AlphaBeta {
// no memories here: they say nothing about captures or evasions
let Ordered { front, .. } =
self.ordering
.order_split(&self.board, &mut moves, captures, pv_play, None);
.order_split(&self.board, &mut moves, captures, pv_play, None, 0);

for (i, m) in moves.iter().enumerate() {
// two skips the reference does not make. A promotion is exempt
Expand Down Expand Up @@ -2333,6 +2344,7 @@ impl AlphaBeta {
// a zero window: the question is only whether a pass beats beta
if pass && eval >= beta {
let reduction = null_move_reduction(self.config, depth, eval - beta);
self.ordering.passed(self.board.line_ply);
self.board.make_null_move();
let result = self.child_search(
-beta,
Expand Down Expand Up @@ -2400,6 +2412,10 @@ impl AlphaBeta {
!matches!(decision, Decision::Skip),
"the loop searched a move it decided to skip"
);
if self.config.continuation_history {
self.ordering
.played(&self.board, m, self.board.line_ply, depth);
}
if !self.board.make_move(m) {
return Ok(None);
}
Expand Down Expand Up @@ -2778,13 +2794,15 @@ impl AlphaBeta {
&mut moves[front..],
quiets.losing,
ply,
node.depth,
);
} else {
let run = self.ordering.key_quiets(
&self.board,
&mut moves[front..],
quiets.losing,
ply,
node.depth,
);
if run > 1 {
quiets.lazy = Some((front + run, 0));
Expand Down Expand Up @@ -3109,9 +3127,14 @@ impl AlphaBeta {
} else {
self.board.generate_moves_into(&mut moves)
};
let ordered =
self.ordering
.order_split(&self.board, &mut moves, captures, pv_play, node.ply);
let ordered = self.ordering.order_split(
&self.board,
&mut moves,
captures,
pv_play,
node.ply,
node.depth,
);
// `order_split` sorts by `pv_play`, and the search played
// `table_move`, which differ when `is_pseudo_legal` refused the move
let tt_at = if table_move.is_some() {
Expand Down Expand Up @@ -3339,7 +3362,8 @@ impl AlphaBeta {
// debug assertion in `order` holds the table's move at the head
let pv_play = self.transpositions.ordering_play(&self.board);
let mut moves = self.board.generate_moves();
self.ordering.order(&self.board, &mut moves, pv_play, None);
self.ordering
.order(&self.board, &mut moves, pv_play, None, 0);

// the root reduces nothing
for m in &moves {
Expand Down
19 changes: 11 additions & 8 deletions arche-core/src/engine/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3616,8 +3616,8 @@ mod cutoffs {
let color = e.board.active_color;
// the first is taught at another ply with the larger history, so
// only the killer slot makes the second the class
e.ordering.cutoff(color, &cool, &[], 1, 5);
e.ordering.cutoff(color, &killer, &[], 0, 4);
e.ordering.cutoff(&e.board, color, &cool, &[], 1, 5);
e.ordering.cutoff(&e.board, color, &killer, &[], 0, 4);
let moves = e.board.generate_moves();
let (alpha, beta): (Score, Score) = (10, 11);
e.census_event(
Expand Down Expand Up @@ -3674,7 +3674,8 @@ mod cutoffs {
.filter(|m| m.capture.is_none() && m.promote.is_none())
.copied()
.collect();
e.ordering.cutoff(color, &elsewhere, &marked, 1, 4);
e.ordering
.cutoff(&e.board, color, &elsewhere, &marked, 1, 4);
e.census_event(
&node(3, (10, 11), Some(0), Table::Miss, 2, e.nodes),
&moves,
Expand Down Expand Up @@ -3761,7 +3762,8 @@ mod cutoffs {
fn a_held_node_records_no_cutting_move() {
let mut e = engine(SHARP_MIDDLEGAME);
let (taught, _) = quiets(&e);
e.ordering.cutoff(e.board.active_color, &taught, &[], 0, 3);
e.ordering
.cutoff(&e.board, e.board.active_color, &taught, &[], 0, 3);
let moves = e.board.generate_moves();
e.census_event(
&node(
Expand Down Expand Up @@ -3861,8 +3863,8 @@ mod reductions {
let color = e.board.active_color;
// the first is taught at another ply with the larger history, so
// only the killer slot makes the second the class
e.ordering.cutoff(color, &cool, &[], 1, 5);
e.ordering.cutoff(color, &killer, &[], 0, 4);
e.ordering.cutoff(&e.board, color, &cool, &[], 1, 5);
e.ordering.cutoff(&e.board, color, &killer, &[], 0, 4);
let moves = e.board.generate_moves();
let parent_eval = i32::from(crate::eval::eval(&e.board));
let staged = staged(&e, &killer, 5, Some(0));
Expand Down Expand Up @@ -3952,7 +3954,8 @@ mod reductions {
.filter(|q| q.capture.is_none() && q.promote.is_none())
.copied()
.collect();
e.ordering.cutoff(color, &elsewhere, &marked, 1, 4);
e.ordering
.cutoff(&e.board, color, &elsewhere, &marked, 1, 4);
let staged = staged(&e, &m, 5, Some(0));
assert_eq!(staged.features.history, -16);
assert_eq!(staged.features.history_max, 0);
Expand Down Expand Up @@ -4241,7 +4244,7 @@ mod node {
node.answer.searched = 12;
let ordered = e
.ordering
.order_split(&e.board, &mut moves, captures, None, node.ply);
.order_split(&e.board, &mut moves, captures, None, node.ply, node.depth);
let mut quiets = QuietOrder::new(&ordered, node.ply);
let mut rules = late_move::Rules::new(&e.deciding(), &node, None);
let front = ordered.front;
Expand Down
20 changes: 12 additions & 8 deletions arche-core/src/late_move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,10 @@ pub(crate) struct Features {
/// it was searched, is 0.
pub(crate) index: usize,
pub(crate) generated: usize,
/// The history table's score for the move at the decision, signed.
/// The butterfly history's score for the move at the decision, signed.
/// The continuation entry the order adds is not in it.
pub(crate) history: i32,
/// The largest history score among the node's generated quiets,
/// The largest butterfly score among the node's generated quiets,
/// clamped at zero: the denominator `history` is read against.
pub(crate) history_max: i32,
pub(crate) killer: bool,
Expand Down Expand Up @@ -1493,8 +1494,8 @@ mod tests {
// is neither nothing nor the whole. The slot it took at ply zero
// is the killer column, which the node reads here
s.ply = Some(0);
s.ordering.cutoff(color, &quiet, &[], 0, 4);
s.ordering.cutoff(color, &rival, &[], 1, 5);
s.ordering.cutoff(&s.board, color, &quiet, &[], 0, 4);
s.ordering.cutoff(&s.board, color, &rival, &[], 1, 5);
const SEARCHED: usize = 10;
let generated = s.moves.len();
let f = s.features(&quiet, SEARCHED);
Expand Down Expand Up @@ -1726,9 +1727,12 @@ mod tests {
// the check is taught at another ply, so it is no killer at this one
const PLY: usize = 0;
let color = s.board.active_color;
s.ordering.cutoff(color, &checks, &[], PLY + 1, 5);
s.ordering.cutoff(&s.board, color, &checks, &[], PLY + 1, 5);
let mut run = vec![neither[0], promotes, neither[1], checks, neither[2]];
assert_eq!(s.ordering.key_quiets(&s.board, &mut run, 0, PLY), run.len());
assert_eq!(
s.ordering.key_quiets(&s.board, &mut run, 0, PLY, u8::MAX),
run.len()
);
let info = s.board.check_info();
let kept = s
.ordering
Expand Down Expand Up @@ -1920,11 +1924,11 @@ mod tests {
let quiet = play_named(&s.board, "a4a5");
let rival = play_named(&s.board, "a4a6");
let color = s.board.active_color;
s.ordering.cutoff(color, &rival, &[], 1, 5);
s.ordering.cutoff(&s.board, color, &rival, &[], 1, 5);
assert_eq!(s.features(&quiet, 10).history_max, 25);
// taught again and deeper, as a child search between two of the
// node's moves would teach it
s.ordering.cutoff(color, &rival, &[], 1, 8);
s.ordering.cutoff(&s.board, color, &rival, &[], 1, 8);
let taught = s.ordering.history_score(color, &rival);
assert!(taught > 25, "the second cutoff taught nothing: {}", taught);
assert_eq!(s.features(&quiet, 10).history_max, 25);
Expand Down
Loading
Loading