small rewrite and addition of by_depth + by_depth_test

This commit is contained in:
Simon Gardling 2025-02-25 15:03:25 -05:00
parent f60de28c58
commit a95b9e08c1
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
5 changed files with 129 additions and 66 deletions

7
Cargo.lock generated
View File

@ -334,6 +334,12 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nohash-hasher"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]] [[package]]
name = "num" name = "num"
version = "0.4.3" version = "0.4.3"
@ -436,6 +442,7 @@ dependencies = [
"either", "either",
"indicatif", "indicatif",
"lazy_static", "lazy_static",
"nohash-hasher",
"num", "num",
"rand", "rand",
"static_assertions", "static_assertions",

View File

@ -33,6 +33,7 @@ const_fn = "0.4"
either = "1.13" either = "1.13"
indicatif = "0.17" indicatif = "0.17"
lazy_static = "1.5" lazy_static = "1.5"
nohash-hasher = "0.2.0"
num = "0.4" num = "0.4"
rand = "0.9" rand = "0.9"
static_assertions = "1.1" static_assertions = "1.1"

View File

@ -12,8 +12,8 @@ pub struct ComplexAgent {
#[allow(dead_code)] #[allow(dead_code)]
impl ComplexAgent { impl ComplexAgent {
pub const fn new(color: Piece) -> Self { pub const fn new(color: Piece) -> Self {
const MAX_DEPTH: usize = 8; const MAX_DEPTH: usize = 5;
const NON_LAZY_DEPTH: usize = 8; const NON_LAZY_DEPTH: usize = 3;
Self { Self {
color, color,
future_moves: FutureMoves::new(color, MAX_DEPTH, NON_LAZY_DEPTH), future_moves: FutureMoves::new(color, MAX_DEPTH, NON_LAZY_DEPTH),

View File

@ -3,7 +3,7 @@ use crate::{
repr::{Board, Piece, Winner}, repr::{Board, Piece, Winner},
}; };
use indicatif::{ProgressIterator, ProgressStyle}; use indicatif::{ProgressIterator, ProgressStyle};
use std::collections::HashMap; use std::{collections::HashMap, hash::BuildHasherDefault};
pub struct FutureMoves { pub struct FutureMoves {
/// Arena containing all [`Move`] /// Arena containing all [`Move`]
@ -49,7 +49,7 @@ impl FutureMoves {
// we want to select all nodes that don't have children, or are lazy (need to maybe be regenerated) // we want to select all nodes that don't have children, or are lazy (need to maybe be regenerated)
.filter(|&idx| { .filter(|&idx| {
let got = &self.arena[idx]; let got = &self.arena[idx];
got.lazy_children || got.children.is_empty() got.is_lazy || got.children.is_empty()
}) })
.filter(|&idx| self.is_connected_to_root(idx)) // put here so this will not extend needlessly before prunes .filter(|&idx| self.is_connected_to_root(idx)) // put here so this will not extend needlessly before prunes
.collect(); .collect();
@ -65,14 +65,14 @@ impl FutureMoves {
.unwrap(), .unwrap(),
) )
.flat_map(|node_idx| { .flat_map(|node_idx| {
self.generate_children( if (self.arena[node_idx].is_lazy
node_idx, && self.depth_of(node_idx) + 1 > self.lazy_expire)
if self.arena[node_idx].lazy_children { || !self.arena[node_idx].is_lazy
self.depth_of(node_idx) {
} else { self.generate_children(node_idx, i > self.lazy_expire)
i } else {
} > self.lazy_expire, None
) }
}) })
.flatten() .flatten()
.collect(); .collect();
@ -126,7 +126,6 @@ impl FutureMoves {
j, j,
new_board, new_board,
new_color, new_color,
lazy_children,
self.agent_color, self.agent_color,
Some(parent_idx), Some(parent_idx),
) )
@ -134,7 +133,7 @@ impl FutureMoves {
.collect(); .collect();
// keep the TOP_K children of their magnitude // keep the TOP_K children of their magnitude
const TOP_K_CHILDREN: usize = 10; const TOP_K_CHILDREN: usize = 3;
let start_idx = self.arena.len(); let start_idx = self.arena.len();
self.arena.extend(new); self.arena.extend(new);
@ -142,13 +141,14 @@ impl FutureMoves {
let new_indices = start_idx..self.arena.len(); let new_indices = start_idx..self.arena.len();
self.arena[parent_idx].children.extend(new_indices.clone()); self.arena[parent_idx].children.extend(new_indices.clone());
let mut parent_copy = self.arena[parent_idx].clone();
parent_copy.sort_children(self.arena.as_mut_slice());
self.arena[parent_idx] = parent_copy;
if lazy_children && new_indices.clone().count() > TOP_K_CHILDREN { if lazy_children && new_indices.clone().count() > TOP_K_CHILDREN {
let mut parent_copy = self.arena[parent_idx].clone();
parent_copy.sort_children(self.arena.as_mut_slice());
self.arena[parent_idx] = parent_copy;
for i in new_indices.clone().skip(TOP_K_CHILDREN) { for i in new_indices.clone().skip(TOP_K_CHILDREN) {
self.arena[i].lazy_children = true; self.arena[i].is_lazy = true;
} }
} }
@ -167,13 +167,12 @@ impl FutureMoves {
depth - 1 depth - 1
} }
/// Compute `Move.value`, propegating upwards from the furthest out Moves fn by_depth(&self, indexes: impl Iterator<Item = usize>) -> Vec<(usize, Vec<usize>)> {
/// in the Arena. let mut by_depth: HashMap<
fn compute_values(&mut self, indexes: impl Iterator<Item = usize>) { usize,
// PERF! pre-organize all indexes based on what depth they're at Vec<usize>,
// previously, I did a lookup map based on if a node was visited, still resulted in a full BuildHasherDefault<nohash_hasher::NoHashHasher<usize>>,
// O(n) iteration each depth > = HashMap::with_hasher(BuildHasherDefault::default());
let mut by_depth: HashMap<usize, Vec<usize>> = HashMap::new();
for idx in indexes { for idx in indexes {
let depth = self.depth_of(idx); let depth = self.depth_of(idx);
if let Some(got) = by_depth.get_mut(&depth) { if let Some(got) = by_depth.get_mut(&depth) {
@ -182,36 +181,42 @@ impl FutureMoves {
by_depth.insert(depth, vec![idx]); by_depth.insert(depth, vec![idx]);
} }
} }
let mut by_depth_vec: Vec<(usize, Vec<usize>)> = by_depth.into_iter().collect(); let mut by_depth_vec: Vec<(usize, Vec<usize>)> = by_depth.into_iter().collect();
by_depth_vec.sort_by_key(|x| x.0); by_depth_vec.sort_by_key(|x| x.0);
by_depth_vec
}
for (depth, nodes) in by_depth_vec { /// Compute `Move.value`, propegating upwards from the furthest out Moves
/// in the Arena.
fn compute_values(&mut self, indexes: impl Iterator<Item = usize>) {
// PERF! pre-organize all indexes based on what depth they're at
// previously, I did a lookup map based on if a node was visited, still resulted in a full
// O(n) iteration each depth
let by_depth_vec = self.by_depth(indexes);
// reversed so we build up the value of the closest (in time) moves from the future
for (depth, nodes) in by_depth_vec.into_iter().rev() {
for idx in nodes { for idx in nodes {
// TODO! impl dynamic sorting based on children's states, maybe it propegates // TODO! impl dynamic sorting based on children's states, maybe it propegates
// upwards using the `parent` field // upwards using the `parent` field
// SAFETY! the sort_by_key function should not modify anything
unsafe { &mut (*(self as *mut Self)) }.arena[idx] // let mut parent_copy = self.arena[idx].clone();
.children // parent_copy.sort_children(self.arena.as_mut_slice());
// negative because we want the largest value in the first index // self.arena[idx] = parent_copy;
// abs so we get the most extreme solutions
// but base on `.value` for recursive behavior
.sort_by_key(|&x| -self.arena[x].value.abs());
let children_value = self.arena[idx] let children_value = self.arena[idx]
.children .children
.iter() .iter()
.rev() // rev then reverse so we get an index starting from the back .map(|&child| self.arena[child].value.expect("child has no value??"))
.enumerate()
// since children are sorted by value, we should weight the first one more
.map(|(i, &child)| self.arena[child].value * ((i + 1) as i128))
.sum::<i128>(); .sum::<i128>();
// previously we used `depth` and divided `self_value` by it, idk if this is worth it // we use `depth` and divided `self_value` by it, idk if this is worth it
// we should really setup some sort of ELO rating for each commit, playing them against // we should really setup some sort of ELO rating for each commit, playing them against
// each other or something, could be cool to benchmark these more subjective things, not // each other or something, could be cool to benchmark these more subjective things, not
// just performance // just performance (cycles/time wise)
self.arena[idx].value = self.arena[idx].self_value as i128 + children_value; self.arena[idx].value = Some(
(self.arena[idx].self_value as i128 + children_value) / (depth + 1) as i128,
);
} }
} }
} }
@ -260,7 +265,6 @@ impl FutureMoves {
0, // dummy 0, // dummy
board, board,
!self.agent_color, !self.agent_color,
false,
self.agent_color, self.agent_color,
None, None,
)); ));
@ -347,7 +351,7 @@ impl FutureMoves {
let mut children = self.arena[root].children.clone(); let mut children = self.arena[root].children.clone();
children.sort_by_key(|&i| -self.arena[i].value); children.sort_by_key(|&i| -self.arena[i].value.expect("child has no value"));
let start_len = ((children.len()) as f32 * (1.0 - BOTTOM_PERC)) as usize; let start_len = ((children.len()) as f32 * (1.0 - BOTTOM_PERC)) as usize;
let drained = children.drain(start_len..); let drained = children.drain(start_len..);
println!("{}", drained.len()); println!("{}", drained.len());
@ -439,10 +443,10 @@ mod tests {
winner: Winner::None, winner: Winner::None,
parent: None, parent: None,
children: vec![1, 3, 4], children: vec![1, 3, 4],
value: 0, value: None,
self_value: 0, self_value: 0,
color: Piece::Black, color: Piece::Black,
lazy_children: false, is_lazy: false,
}); });
futm.update_root_idx_raw(0); futm.update_root_idx_raw(0);
@ -453,7 +457,6 @@ mod tests {
0, 0,
Board::new(), Board::new(),
Piece::White, Piece::White,
false,
Piece::Black, Piece::Black,
Some(0), Some(0),
)); ));
@ -464,7 +467,6 @@ mod tests {
1234, 1234,
Board::new(), Board::new(),
Piece::White, Piece::White,
false,
Piece::Black, Piece::Black,
None, None,
)); ));
@ -474,7 +476,6 @@ mod tests {
0, 0,
Board::new(), Board::new(),
Piece::White, Piece::White,
false,
Piece::Black, Piece::Black,
Some(0), Some(0),
)); ));
@ -484,7 +485,6 @@ mod tests {
0, 0,
Board::new(), Board::new(),
Piece::White, Piece::White,
false,
Piece::Black, Piece::Black,
Some(0), Some(0),
)); ));
@ -510,7 +510,6 @@ mod tests {
0, 0,
Board::new().starting_pos(), Board::new().starting_pos(),
Piece::Black, Piece::Black,
false,
Piece::Black, Piece::Black,
None, None,
)); ));
@ -546,10 +545,10 @@ mod tests {
winner: Winner::None, winner: Winner::None,
parent: None, parent: None,
children: vec![1], children: vec![1],
value: 0, value: None,
self_value: 0, self_value: 0,
color: Piece::Black, color: Piece::Black,
lazy_children: false, is_lazy: false,
}); });
futm.update_root_idx_raw(0); futm.update_root_idx_raw(0);
@ -560,7 +559,6 @@ mod tests {
0, 0,
Board::new(), Board::new(),
Piece::White, Piece::White,
false,
Piece::Black, Piece::Black,
Some(0), Some(0),
)); ));
@ -573,7 +571,6 @@ mod tests {
1234, 1234,
Board::new(), Board::new(),
Piece::White, Piece::White,
false,
Piece::Black, Piece::Black,
None, None,
)); ));
@ -583,7 +580,6 @@ mod tests {
0, 0,
Board::new(), Board::new(),
Piece::White, Piece::White,
false,
Piece::Black, Piece::Black,
Some(0), Some(0),
)); ));
@ -595,11 +591,68 @@ mod tests {
0, 0,
Board::new(), Board::new(),
Piece::White, Piece::White,
false,
Piece::Black, Piece::Black,
Some(0), Some(0),
)); ));
assert_eq!(futm.depth_of(3), 2); assert_eq!(futm.depth_of(3), 2);
} }
#[test]
fn by_depth_test() {
let mut futm = FutureMoves::new(Piece::Black, 0, 0);
futm.arena.push(Move {
i: 0,
j: 0,
board: Board::new(),
winner: Winner::None,
parent: None,
children: vec![1],
value: None,
self_value: 0,
color: Piece::Black,
is_lazy: false,
});
futm.update_root_idx_raw(0);
// child 1
futm.arena.push(Move::new(
0,
0,
Board::new(),
Piece::White,
Piece::Black,
Some(0),
));
futm.arena[1].parent = Some(0);
futm.arena[1].children = vec![3];
// dummy
futm.arena.push(Move::new(
1234,
1234,
Board::new(),
Piece::White,
Piece::Black,
None,
));
futm.arena.push(Move::new(
0,
0,
Board::new(),
Piece::White,
Piece::Black,
Some(0),
));
futm.arena[3].parent = Some(1);
assert_eq!(
futm.by_depth(0..futm.arena.len()),
vec![(0, vec![0, 2]), (1, vec![1]), (2, vec![3])]
);
}
} }

View File

@ -23,7 +23,7 @@ pub struct Move {
pub children: Vec<usize>, pub children: Vec<usize>,
/// Value of this move (including children) /// Value of this move (including children)
pub value: i128, pub value: Option<i128>,
/// What is the inherit value of this move (not including children) /// What is the inherit value of this move (not including children)
pub self_value: i64, pub self_value: i64,
@ -31,8 +31,8 @@ pub struct Move {
/// Which color made a move on this move? /// Which color made a move on this move?
pub color: Piece, pub color: Piece,
/// Should the children of this move be lazily generated? /// Was this child lazily created? (it will have delayed child generation)
pub lazy_children: bool, pub is_lazy: bool,
} }
lazy_static! { lazy_static! {
@ -45,7 +45,6 @@ impl Move {
j: usize, j: usize,
board: Board, board: Board,
color: Piece, color: Piece,
lazy_children: bool,
agent_color: Piece, agent_color: Piece,
parent: Option<usize>, parent: Option<usize>,
) -> Self { ) -> Self {
@ -56,13 +55,13 @@ impl Move {
winner: board.game_winner(), winner: board.game_winner(),
parent, parent,
children: Vec::new(), children: Vec::new(),
value: 0, value: None,
color, color,
lazy_children, is_lazy: false,
self_value: 0, self_value: 0,
}; };
m.self_value = m.compute_self_value(agent_color); m.self_value = m.compute_self_value(agent_color);
m.value = m.self_value as i128; m.value = Some(m.self_value as i128);
m m
} }
@ -79,11 +78,14 @@ impl Move {
} else if self.winner == Winner::Player(agent_color) { } else if self.winner == Winner::Player(agent_color) {
// results in a win for the agent // results in a win for the agent
return i64::MAX - 1; return i64::MAX - 1;
} else if self.winner == Winner::Tie {
// idk what a Tie should be valued?
return 0;
} }
// else if self.winner == Winner::Tie {
// // idk what a Tie should be valued?
// return 0;
// }
// I guess ignore Ties here, don't give them an explicit value,
// because even in the case of ties, we want to have a higher score
BVM.board_value(&self.board, agent_color) BVM.board_value(&self.board, agent_color)
} }