add instrumenting support to Bot, returns basic info about algorithm

This commit is contained in:
tali 2023-04-16 19:04:47 -04:00
parent 1784647146
commit 52d339747a
2 changed files with 83 additions and 14 deletions

View File

@ -2,10 +2,10 @@
//! anytime algorithm that will provide a suggestion for the next move. It may be //! anytime algorithm that will provide a suggestion for the next move. It may be
//! repeatedly polled by the `think` method in order to attempt to improve the suggestion. //! repeatedly polled by the `think` method in order to attempt to improve the suggestion.
use core::ops::Range; use alloc::boxed::Box;
use alloc::collections::BinaryHeap; use alloc::collections::BinaryHeap;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::ops::Range;
use mino::matrix::Mat; use mino::matrix::Mat;
use mino::srs::{Piece, Queue}; use mino::srs::{Piece, Queue};
@ -14,18 +14,32 @@ mod trans;
use crate::bot::node::{Node, RawNodePtr}; use crate::bot::node::{Node, RawNodePtr};
use crate::bot::trans::TransTable; use crate::bot::trans::TransTable;
use crate::eval::{features, Weights}; use crate::eval::{features, Features, Weights};
pub(crate) use bumpalo::Bump as Arena; pub(crate) use bumpalo::Bump as Arena;
/// Encompasses an instance of the algorithm. /// Encompasses an instance of the algorithm.
pub struct Bot { pub struct Bot {
iters: u32,
evaluator: Evaluator, evaluator: Evaluator,
trans: TransTable, trans: TransTable,
algorithm: SegmentedAStar, algorithm: SegmentedAStar,
// IMPORTANT: `arena` must occur after `algorithm` so that it is dropped last. // IMPORTANT: `arena` must occur after `algorithm` so that it is dropped last.
arena: Arena, arena: Arena,
iters: u32,
metrics: Option<Box<Metrics>>,
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Metrics {
pub start_features: Features,
pub start_heuristic: i32,
pub end_features: Features,
pub end_heuristic: i32,
pub end_rating: (bool, i32),
pub end_iteration: u32,
// TODO(?) memory usage metrics
// TODO(?) transposition table metrics
// TODO(?) A* metrics
} }
impl Bot { impl Bot {
@ -37,11 +51,12 @@ impl Bot {
let trans = TransTable::new(); let trans = TransTable::new();
let algorithm = SegmentedAStar::new(root); let algorithm = SegmentedAStar::new(root);
Self { Self {
iters: 0,
evaluator, evaluator,
trans, trans,
algorithm, algorithm,
arena, arena,
iters: 0,
metrics: None,
} }
} }
@ -66,12 +81,19 @@ impl Bot {
&self.evaluator, &self.evaluator,
&mut self.iters, &mut self.iters,
); );
if did_update { if did_update {
tracing::debug!( tracing::debug!(
"new suggestion @ {}: {:?}", "new suggestion @ {}: {:?}",
self.iters, self.iters,
self.algorithm.best().unwrap(), self.algorithm.best(),
); );
if let Some(metrics) = self.metrics.as_deref_mut() {
let weights = &self.evaluator.weights;
let start = self.algorithm.best();
metrics.updated_best(weights, start, self.iters);
}
} }
} }
} }
@ -86,7 +108,20 @@ impl Bot {
/// - `think` has not been called enough times to provide an initial suggestion. /// - `think` has not been called enough times to provide an initial suggestion.
/// - there are no valid placements for the initial state /// - there are no valid placements for the initial state
pub fn suggest(&self) -> Option<Piece> { pub fn suggest(&self) -> Option<Piece> {
self.algorithm.best().and_then(|node| node.root_placement()) self.algorithm.best().root_placement()
}
/// Start collecting metrics about the search algorithm. Uses the current suggestion
/// as the "start" of the metrics data.
pub fn start_instrumenting(&mut self) {
let weights = &self.evaluator.weights;
let start = self.algorithm.best();
self.metrics = Some(Metrics::new(weights, start, self.iters).into());
}
/// Returns the current metrics, if enabled by `start_instrumenting`.
pub fn metrics(&self) -> Option<Metrics> {
self.metrics.as_deref().cloned()
} }
} }
@ -127,6 +162,33 @@ impl Evaluator {
} }
} }
impl Metrics {
fn new(weights: &Weights, start: &Node, iters: u32) -> Self {
let features = features(start.matrix(), 0);
let heuristic = features.evaluate(weights);
let rating = start.rating();
Self {
start_features: features,
start_heuristic: heuristic,
end_features: features,
end_heuristic: heuristic,
end_rating: rating,
end_iteration: iters,
}
}
#[cold]
fn updated_best(&mut self, weights: &Weights, node: &Node, iters: u32) {
let features = features(node.matrix(), 0);
let heuristic = features.evaluate(weights);
let rating = node.rating();
self.end_features = features;
self.end_heuristic = heuristic;
self.end_rating = rating;
self.end_iteration = iters;
}
}
// This implements an algorithm that is very similar to A* but has a slight // This implements an algorithm that is very similar to A* but has a slight
// modification. Rather than one big open set, there are separate sets at each depth of // modification. Rather than one big open set, there are separate sets at each depth of
// the search. After picking a node from one open set and expanding its children into the // the search. After picking a node from one open set and expanding its children into the
@ -154,7 +216,7 @@ impl Evaluator {
struct SegmentedAStar { struct SegmentedAStar {
open: Vec<BinaryHeap<AStarNode>>, open: Vec<BinaryHeap<AStarNode>>,
depth: usize, depth: usize,
best: Option<RawNodePtr>, best: RawNodePtr,
} }
impl SegmentedAStar { impl SegmentedAStar {
@ -165,12 +227,12 @@ impl SegmentedAStar {
Self { Self {
open, open,
depth: 0, depth: 0,
best: None, best: root.into(),
} }
} }
fn best(&self) -> Option<&Node> { fn best(&self) -> &Node {
self.best.map(|node| unsafe { node.as_node() }) unsafe { self.best.as_node() }
} }
fn step( fn step(
@ -229,9 +291,10 @@ impl SegmentedAStar {
Ok(work) Ok(work)
} }
#[cold]
fn backup(&mut self, cand: &Node) -> bool { fn backup(&mut self, cand: &Node) -> bool {
if self.best().map_or(true, |best| cand.is_better(best)) { if cand.is_better(self.best()) {
self.best = Some(cand.into()); self.best = cand.into();
true true
} else { } else {
false false

View File

@ -70,10 +70,16 @@ impl Node {
unsafe { self.queue.as_queue() } unsafe { self.queue.as_queue() }
} }
pub fn rating(&self) -> (bool, i32) {
self.rating
}
// TODO: move this function to `bot.algorithm`
pub fn is_better(&self, other: &Node) -> bool { pub fn is_better(&self, other: &Node) -> bool {
self.rating > other.rating self.rating > other.rating
} }
// TODO: move this function to `bot.algorithm`
pub fn is_terminal(&self) -> bool { pub fn is_terminal(&self) -> bool {
self.queue().is_empty() || self.rating.0 self.queue().is_empty() || self.rating.0
} }
@ -146,7 +152,7 @@ impl core::fmt::Debug for Node {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Node {{ ")?; write!(f, "Node {{ ")?;
match self.rating { match self.rating {
(false, h) => write!(f, "heuristic: {}", h), (false, h) => write!(f, "rating: {}", h),
(true, n) => write!(f, "solution: {}", -n), (true, n) => write!(f, "solution: {}", -n),
}?; }?;
if let Some(pc) = self.root_placement() { if let Some(pc) = self.root_placement() {