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
|
//! 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
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue