add instrumenting support to Bot, returns basic info about algorithm
This commit is contained in:
parent
1784647146
commit
52d339747a
|
@ -2,10 +2,10 @@
|
|||
//! 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.
|
||||
|
||||
use core::ops::Range;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BinaryHeap;
|
||||
use alloc::vec::Vec;
|
||||
use core::ops::Range;
|
||||
use mino::matrix::Mat;
|
||||
use mino::srs::{Piece, Queue};
|
||||
|
||||
|
@ -14,18 +14,32 @@ mod trans;
|
|||
|
||||
use crate::bot::node::{Node, RawNodePtr};
|
||||
use crate::bot::trans::TransTable;
|
||||
use crate::eval::{features, Weights};
|
||||
use crate::eval::{features, Features, Weights};
|
||||
|
||||
pub(crate) use bumpalo::Bump as Arena;
|
||||
|
||||
/// Encompasses an instance of the algorithm.
|
||||
pub struct Bot {
|
||||
iters: u32,
|
||||
evaluator: Evaluator,
|
||||
trans: TransTable,
|
||||
algorithm: SegmentedAStar,
|
||||
// IMPORTANT: `arena` must occur after `algorithm` so that it is dropped last.
|
||||
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 {
|
||||
|
@ -37,11 +51,12 @@ impl Bot {
|
|||
let trans = TransTable::new();
|
||||
let algorithm = SegmentedAStar::new(root);
|
||||
Self {
|
||||
iters: 0,
|
||||
evaluator,
|
||||
trans,
|
||||
algorithm,
|
||||
arena,
|
||||
iters: 0,
|
||||
metrics: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,12 +81,19 @@ impl Bot {
|
|||
&self.evaluator,
|
||||
&mut self.iters,
|
||||
);
|
||||
|
||||
if did_update {
|
||||
tracing::debug!(
|
||||
"new suggestion @ {}: {:?}",
|
||||
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.
|
||||
/// - there are no valid placements for the initial state
|
||||
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
|
||||
// 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
|
||||
|
@ -154,7 +216,7 @@ impl Evaluator {
|
|||
struct SegmentedAStar {
|
||||
open: Vec<BinaryHeap<AStarNode>>,
|
||||
depth: usize,
|
||||
best: Option<RawNodePtr>,
|
||||
best: RawNodePtr,
|
||||
}
|
||||
|
||||
impl SegmentedAStar {
|
||||
|
@ -165,12 +227,12 @@ impl SegmentedAStar {
|
|||
Self {
|
||||
open,
|
||||
depth: 0,
|
||||
best: None,
|
||||
best: root.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn best(&self) -> Option<&Node> {
|
||||
self.best.map(|node| unsafe { node.as_node() })
|
||||
fn best(&self) -> &Node {
|
||||
unsafe { self.best.as_node() }
|
||||
}
|
||||
|
||||
fn step(
|
||||
|
@ -229,9 +291,10 @@ impl SegmentedAStar {
|
|||
Ok(work)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn backup(&mut self, cand: &Node) -> bool {
|
||||
if self.best().map_or(true, |best| cand.is_better(best)) {
|
||||
self.best = Some(cand.into());
|
||||
if cand.is_better(self.best()) {
|
||||
self.best = cand.into();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
|
@ -70,10 +70,16 @@ impl Node {
|
|||
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 {
|
||||
self.rating > other.rating
|
||||
}
|
||||
|
||||
// TODO: move this function to `bot.algorithm`
|
||||
pub fn is_terminal(&self) -> bool {
|
||||
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 {
|
||||
write!(f, "Node {{ ")?;
|
||||
match self.rating {
|
||||
(false, h) => write!(f, "heuristic: {}", h),
|
||||
(false, h) => write!(f, "rating: {}", h),
|
||||
(true, n) => write!(f, "solution: {}", -n),
|
||||
}?;
|
||||
if let Some(pc) = self.root_placement() {
|
||||
|
|
Loading…
Reference in New Issue