early bench
cargo devel CI / cargo CI (push) Successful in 1m36s Details

This commit is contained in:
Christoph J. Scherr 2024-04-04 22:46:25 +02:00
parent 2ae53bc464
commit 15edd58d3b
Signed by: PlexSheep
GPG Key ID: 7CDD0B14851A08EF
7 changed files with 129 additions and 29 deletions

View File

@ -18,8 +18,7 @@ where
SL: Solver<'wl, WL>, SL: Solver<'wl, WL>,
SL: 'wl, SL: 'wl,
{ {
fn build(wordlist: &'wl WL, solver: SL) -> crate::error::WResult<Self> { fn build(wordlist: &'wl WL, solver: SL, builder: GameBuilder<'wl, WL>) -> crate::error::WResult<Self> {
let builder: GameBuilder<_> = Game::builder(wordlist);
Ok(Self { Ok(Self {
wordlist, wordlist,
solver, solver,

View File

@ -1,4 +1,4 @@
use std::fmt::Debug; use std::fmt::{Debug, Display};
use libpt::log::debug; use libpt::log::debug;
@ -14,6 +14,9 @@ use report::*;
#[cfg(feature = "builtin")] #[cfg(feature = "builtin")]
pub mod builtin; pub mod builtin;
/// Default amount of games to play for a [Benchmark]
pub const DEFAULT_N: usize = 50;
pub trait Benchmark<'wl, WL, SL>: Clone + Sized + Debug pub trait Benchmark<'wl, WL, SL>: Clone + Sized + Debug
where where
WL: WordList, WL: WordList,
@ -21,7 +24,11 @@ where
SL: Solver<'wl, WL>, SL: Solver<'wl, WL>,
SL: 'wl, SL: 'wl,
{ {
fn build(wordlist: &'wl WL, solver: SL) -> WResult<Self>; fn build(
wordlist: &'wl WL,
solver: SL,
builder: GameBuilder<'wl, WL>,
) -> crate::error::WResult<Self>;
fn builder(&'wl self) -> &'wl GameBuilder<'wl, WL>; fn builder(&'wl self) -> &'wl GameBuilder<'wl, WL>;
fn make_game(&'wl self) -> WResult<Game<'wl, WL>> { fn make_game(&'wl self) -> WResult<Game<'wl, WL>> {
Ok(self.builder().build()?) Ok(self.builder().build()?)
@ -30,16 +37,21 @@ where
fn play(&'wl self) -> WResult<GuessResponse> { fn play(&'wl self) -> WResult<GuessResponse> {
self.solver().play(&mut self.make_game()?) self.solver().play(&mut self.make_game()?)
} }
// TODO: add some interface to get reports while the benchmark runs
// TODO: make the benchmark optionally multithreaded
fn bench(&'wl self, n: usize) -> WResult<Report> { fn bench(&'wl self, n: usize) -> WResult<Report> {
// PERF: it would be better to make this multithreaded // PERF: it would be better to make this multithreaded
let part = n / 20; let part = match n / 20 {
0 => 19,
other => other,
};
let mut report = Report::new(); let mut report = Report::new();
for i in 0..n { for i in 0..n {
// TODO: limit execution time for the following, perhaps async
report.add(self.play()?); report.add(self.play()?);
if i % part == part - 1 { if i % part == part - 1 {
debug!("{}", report); // TODO: add the report to the struct so that users can poll it to print the status
// TODO: update the report in the struct
} }
} }

View File

@ -1,11 +1,18 @@
use chrono::{self, NaiveDateTime, NaiveTime, TimeDelta}; use chrono::{self, Duration, NaiveDateTime, NaiveTime, TimeDelta};
use libpt::log::debug;
use core::panic;
use std::fmt::Display; use std::fmt::Display;
use std::ops::Div;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::game::response::GuessResponse; use crate::game::response::GuessResponse;
pub const WEIGHTING_SCORE: f64 = 0.9;
pub const WEIGHTING_TIME: f64 = 0.1;
pub const WEIGHTING_WIN: f64 = 0.0;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Report { pub struct Report {
data: Vec<GuessResponse>, data: Vec<GuessResponse>,
@ -34,12 +41,68 @@ impl Report {
self.data.len() self.data.len()
} }
pub fn win_ratio(&self) -> usize { pub fn total_wins(&self) -> usize {
todo!() let mut wins: usize = 0;
self.data.iter().for_each(|d| {
if d.won() {
wins += 1;
}
});
wins
} }
pub fn avg_score(&self) -> usize { pub fn avg_win(&self) -> f64 {
todo!() self.total_wins() as f64 / self.n() as f64
}
pub fn total_score(&self) -> usize {
let mut score: usize = 0;
self.data.iter().for_each(|d| score += d.step());
score
}
pub fn avg_score(&self) -> f64 {
self.total_score() as f64 / self.n() as f64
}
pub fn avg_time(&self) -> Option<TimeDelta> {
let av = self.benchtime()? / self.n() as i32;
Some(av)
}
pub fn rating(&self) -> Option<f64> {
assert_eq!(WEIGHTING_WIN + WEIGHTING_SCORE + WEIGHTING_TIME, 1.0);
debug!(
"partial rating - score: {}",
WEIGHTING_SCORE * self.avg_score()
);
debug!(
"partial rating - win: {}",
WEIGHTING_WIN * (1.0 - self.avg_win())
);
// FIXME: this is not normalized, which can lead to negative score
debug!(
"partial rating - time: {}",
WEIGHTING_TIME
* (1.0
- (1_000_000_000.0
/ self
.avg_time()?
.num_nanoseconds()
.expect("took so many ns per game that an integer overflow occured")
as f64))
);
let r = WEIGHTING_SCORE * self.avg_score()
+ WEIGHTING_WIN * (1.0 - self.avg_win())
+ WEIGHTING_TIME
* (1.0
- (1_000_000_000.0
/ self
.avg_time()?
.num_nanoseconds()
.expect("took so many ns per game that an integer overflow occured")
as f64));
Some(r)
} }
/// finalize the record /// finalize the record
@ -58,16 +121,33 @@ impl Report {
pub fn finished(&self) -> bool { pub fn finished(&self) -> bool {
self.finished self.finished
} }
pub fn benchtime(&self) -> Option<TimeDelta> {
self.benchtime
}
} }
impl Display for Report { impl Display for Report {
/// Implement the [Display] trait
///
/// # Panics
///
/// This will panic if the [Report] is not finished
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.finished {
panic!("can only display finished reports");
}
write!( write!(
f, f,
"n: {}, win_ratio: {}, avg_score: {}", "n: {}, win_ratio: {:.2}%, avg_score: {:.4} steps until finish, avgerage time per game: {}μs, \n\
rating: {:.4}, full time until completion: {}ms
",
self.n(), self.n(),
self.win_ratio(), self.avg_win() * 100.0,
self.avg_score() self.avg_score(),
self.avg_time().unwrap().num_microseconds().expect("overflow when converting to micrseconds"),
self.rating().unwrap(),
self.benchtime().unwrap().num_milliseconds()
) )
} }
} }

View File

@ -2,10 +2,14 @@
// #![warn(missing_docs)] // #![warn(missing_docs)]
#![warn(missing_debug_implementations)] #![warn(missing_debug_implementations)]
use std::thread::sleep_ms;
use clap::Parser; use clap::Parser;
use libpt::log::*; use libpt::log::*;
use wordle_analyzer::solve::BuiltinSolverNames; use wordle_analyzer::bench::builtin::BuiltinBenchmark;
use wordle_analyzer::bench::{Benchmark, DEFAULT_N};
use wordle_analyzer::solve::{BuiltinSolverNames, Solver};
use wordle_analyzer::wlist::builtin::BuiltinWList; use wordle_analyzer::wlist::builtin::BuiltinWList;
use wordle_analyzer::{self, game}; use wordle_analyzer::{self, game};
@ -28,26 +32,31 @@ struct Cli {
/// which solver to use /// which solver to use
#[arg(short, long, default_value_t = BuiltinSolverNames::default())] #[arg(short, long, default_value_t = BuiltinSolverNames::default())]
solver: BuiltinSolverNames, solver: BuiltinSolverNames,
/// how many games to play for the benchmark
#[arg(short, long, default_value_t = DEFAULT_N)]
n: usize,
} }
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
if cli.verbose { if cli.verbose {
Logger::build_mini(Some(Level::TRACE))?; Logger::build_mini(Some(Level::DEBUG))?;
} else { } else {
Logger::build_mini(Some(Level::INFO))?; Logger::build_mini(Some(Level::INFO))?;
} }
debug!("dumping CLI: {:#?}", cli); trace!("dumping CLI: {:#?}", cli);
let wl = BuiltinWList::default(); let wl = BuiltinWList::default();
let _builder = game::Game::builder(&wl) let builder = game::Game::builder(&wl)
.length(cli.length) .length(cli.length)
.max_steps(cli.max_steps) .max_steps(cli.max_steps)
.precompute(cli.precompute); .precompute(cli.precompute);
let _solver = cli.solver.to_solver(&wl); let solver = cli.solver.to_solver(&wl);
let _bench = cli.solver.to_solver(&wl); let bench = BuiltinBenchmark::build(&wl, solver, builder)?;
trace!("{bench:#?}");
let report = bench.bench(cli.n)?;
todo!(); println!("{report}");
Ok(()) Ok(())
} }

View File

@ -2,7 +2,7 @@ use crate::error::*;
use crate::wlist::word::{ManyWordDatas, ManyWordsRef, Word, WordData}; use crate::wlist::word::{ManyWordDatas, ManyWordsRef, Word, WordData};
use crate::wlist::WordList; use crate::wlist::WordList;
use libpt::log::debug; use libpt::log::{debug, trace};
pub mod response; pub mod response;
use response::GuessResponse; use response::GuessResponse;
@ -56,7 +56,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
length, length,
precompute, precompute,
max_steps, max_steps,
step: 1, step: 0,
solution, solution,
wordlist: wlist, wordlist: wlist,
finished: false, finished: false,
@ -204,7 +204,7 @@ impl<'wl, WL: WordList> GameBuilder<'wl, WL> {
/// build a [`Game`] with the stored configuration /// build a [`Game`] with the stored configuration
pub fn build(&'wl self) -> GameResult<Game<'wl, WL>> { pub fn build(&'wl self) -> GameResult<Game<'wl, WL>> {
debug!("{:#?}", self); trace!("{:#?}", self);
let game: Game<WL> = let game: Game<WL> =
Game::build(self.length, self.precompute, self.max_steps, self.wordlist)?; Game::build(self.length, self.precompute, self.max_steps, self.wordlist)?;
Ok(game) Ok(game)

View File

@ -1,4 +1,4 @@
use libpt::log::{debug, info}; use libpt::log::{debug, info, trace};
use crate::wlist::word::{ManyWordDatas, Word}; use crate::wlist::word::{ManyWordDatas, Word};
use crate::wlist::WordList; use crate::wlist::WordList;
@ -29,7 +29,7 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
} }
} }
} }
debug!("other chars: {:?}", other_chars); trace!("other chars: {:?}", other_chars);
let matches: ManyWordDatas = game let matches: ManyWordDatas = game
.wordlist() .wordlist()
.get_words_matching(pattern) .get_words_matching(pattern)

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::fmt::write; use std::fmt::write;
use std::hash::Hash; use std::hash::Hash;
use libpt::log::debug; use libpt::log::{debug, trace};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -61,7 +61,7 @@ impl WordMap {
let len = self.len(); let len = self.len();
let mut c: f64 = l_under_sigmoid * (0.5 + self.n_common() as f64 / len as f64); let mut c: f64 = l_under_sigmoid * (0.5 + self.n_common() as f64 / len as f64);
c *= 1e-7; c *= 1e-7;
debug!(threshold = c); trace!(threshold = c);
c c
} }
pub fn inner(&self) -> &HashMap<Word, Frequency> { pub fn inner(&self) -> &HashMap<Word, Frequency> {