From 26a6625682e3ef18a02ae7d6ef357eec7f410cc2 Mon Sep 17 00:00:00 2001 From: primenumber Date: Tue, 30 Apr 2024 00:05:28 +0900 Subject: [PATCH 1/6] Add new benchmark: solve_inner_parallel --- src/engine/endgame/test.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/engine/endgame/test.rs b/src/engine/endgame/test.rs index edbedde..0337d69 100644 --- a/src/engine/endgame/test.rs +++ b/src/engine/endgame/test.rs @@ -1,6 +1,7 @@ extern crate test; use super::*; use crate::setup::*; +use rayon::prelude::*; use std::fs::File; use std::io::{BufRead, BufReader}; use test::Bencher; @@ -59,3 +60,21 @@ fn bench_solve_inner(b: &mut Bencher) { } }); } + +#[bench] +fn bench_solve_inner_parallel(b: &mut Bencher) { + let solve_obj = setup_default(); + let dataset = load_stress_test_set(); + + b.iter(|| { + dataset.par_iter().for_each(|&(board, _desired)| { + let mut obj = solve_obj.clone(); + let (_res, _stat) = solve_inner( + &mut obj, + board, + (-(BOARD_SIZE as i8), BOARD_SIZE as i8), + false, + ); + }); + }); +} From 778691d3934c2b18c57b533f96bdb7d3d699723e Mon Sep 17 00:00:00 2001 From: primenumber Date: Tue, 30 Apr 2024 06:21:00 +0900 Subject: [PATCH 2/6] Generalize Evaluator --- src/book.rs | 11 ++++++++--- src/engine/endgame.rs | 39 +++++++++++++++++++++++++++--------- src/engine/eval.rs | 20 ++++++++++++------- src/engine/midgame.rs | 17 ++++++++-------- src/engine/search.rs | 46 ++++++++++++++++++++++++++++++++----------- src/engine/think.rs | 27 ++++++++++++++++++++----- src/main.rs | 8 +++++++- src/play.rs | 8 ++++++-- src/remote.rs | 7 +++++-- src/setup.rs | 4 ++-- src/train.rs | 2 +- 11 files changed, 138 insertions(+), 51 deletions(-) diff --git a/src/book.rs b/src/book.rs index 6c53cf2..8bfb8d0 100644 --- a/src/book.rs +++ b/src/book.rs @@ -169,7 +169,12 @@ impl Book { } } -fn search(board: Board, think_time_limit: u128, solve_obj: &mut SolveObj, sub_solver: &Arc) -> Hand { +fn search( + board: Board, + think_time_limit: u128, + solve_obj: &mut SolveObj, + sub_solver: &Arc, +) -> Hand { solve_obj.cache_gen += 1; if board.empty().count_ones() <= 18 { let mut solve_obj = solve_obj.clone(); @@ -205,10 +210,10 @@ fn gen_opening(rng: &mut SmallRng) -> (Board, Vec) { (board, hands) } -fn play_with_book( +fn play_with_book( book: Arc>, think_time_limit: u128, - solve_obj: &mut SolveObj, + solve_obj: &mut SolveObj, rng: &mut SmallRng, sub_solver: &Arc, ) { diff --git a/src/engine/endgame.rs b/src/engine/endgame.rs index 05b6fa6..d0448a1 100644 --- a/src/engine/endgame.rs +++ b/src/engine/endgame.rs @@ -2,13 +2,14 @@ mod test; use crate::engine::bits::*; use crate::engine::board::*; +use crate::engine::eval::*; use crate::engine::hand::*; use crate::engine::search::*; use crate::engine::table::*; use arrayvec::ArrayVec; use std::cmp::max; -fn near_leaf(solve_obj: &mut SolveObj, board: Board) -> (i8, SolveStat) { +fn near_leaf(solve_obj: &mut SolveObj, board: Board) -> (i8, SolveStat) { let (score, node_count) = solve_obj.last_cache.solve_last(board); ( score, @@ -19,7 +20,12 @@ fn near_leaf(solve_obj: &mut SolveObj, board: Board) -> (i8, SolveStat) { ) } -fn naive(solve_obj: &mut SolveObj, board: Board, (mut alpha, beta): (i8, i8), passed: bool) -> (i8, SolveStat) { +fn naive( + solve_obj: &mut SolveObj, + board: Board, + (mut alpha, beta): (i8, i8), + passed: bool, +) -> (i8, SolveStat) { let mut pass = true; let mut res = -(BOARD_SIZE as i8); let mut stat = SolveStat::one(); @@ -45,7 +51,12 @@ fn naive(solve_obj: &mut SolveObj, board: Board, (mut alpha, beta): (i8, i8), pa (res, stat) } -fn static_order(solve_obj: &mut SolveObj, board: Board, (mut alpha, beta): (i8, i8), passed: bool) -> (i8, SolveStat) { +fn static_order( + solve_obj: &mut SolveObj, + board: Board, + (mut alpha, beta): (i8, i8), + passed: bool, +) -> (i8, SolveStat) { let mut pass = true; let mut res = -(BOARD_SIZE as i8); let mut stat = SolveStat::one(); @@ -84,7 +95,12 @@ fn static_order(solve_obj: &mut SolveObj, board: Board, (mut alpha, beta): (i8, (res, stat) } -fn negascout_impl(solve_obj: &mut SolveObj, next: Board, (alpha, beta): (i8, i8), is_first: bool) -> (i8, SolveStat) { +fn negascout_impl( + solve_obj: &mut SolveObj, + next: Board, + (alpha, beta): (i8, i8), + is_first: bool, +) -> (i8, SolveStat) { if is_first { solve_inner(solve_obj, next, (-beta, -alpha), false) } else { @@ -102,7 +118,12 @@ fn negascout_impl(solve_obj: &mut SolveObj, next: Board, (alpha, beta): (i8, i8) } } -fn fastest_first(solve_obj: &mut SolveObj, board: Board, (mut alpha, beta): (i8, i8), passed: bool) -> (i8, SolveStat) { +fn fastest_first( + solve_obj: &mut SolveObj, + board: Board, + (mut alpha, beta): (i8, i8), + passed: bool, +) -> (i8, SolveStat) { const MAX_FFS_NEXT: usize = 20; let mut nexts = ArrayVec::<_, MAX_FFS_NEXT>::new(); for (next, _pos) in board.next_iter() { @@ -132,8 +153,8 @@ fn fastest_first(solve_obj: &mut SolveObj, board: Board, (mut alpha, beta): (i8, (res, stat) } -fn move_ordering_by_eval( - solve_obj: &mut SolveObj, +fn move_ordering_by_eval( + solve_obj: &mut SolveObj, board: Board, (mut alpha, beta): (i8, i8), passed: bool, @@ -167,8 +188,8 @@ fn move_ordering_by_eval( (res, best, stat) } -pub fn solve_inner( - solve_obj: &mut SolveObj, +pub fn solve_inner( + solve_obj: &mut SolveObj, board: Board, (mut alpha, mut beta): (i8, i8), passed: bool, diff --git a/src/engine/eval.rs b/src/engine/eval.rs index bc2b795..ec962b1 100644 --- a/src/engine/eval.rs +++ b/src/engine/eval.rs @@ -431,7 +431,11 @@ impl IndicesVectorizer { // configuable const INDICES_VECTORIZER_PACK_SIZE: usize = 8; -pub struct Evaluator { +pub trait Evaluator: Send + Sync { + fn eval(&self, board: Board) -> i16; +} + +pub struct PatternLinearEvaluator { stones_range: RangeInclusive, params: Vec, vectorizer: IndicesVectorizer, @@ -449,13 +453,13 @@ pub const SCALE: i16 = 256; pub const EVAL_SCORE_MAX: i16 = BOARD_SIZE as i16 * SCALE; pub const EVAL_SCORE_MIN: i16 = -EVAL_SCORE_MAX; -impl Evaluator { - pub fn load(path: &Path) -> Option { +impl PatternLinearEvaluator { + pub fn load(path: &Path) -> Option { let folded = FoldedEvaluator::load(path)?; Some(Self::from_folded(&folded)) } - fn from_folded(folded: &FoldedEvaluator) -> Evaluator { + fn from_folded(folded: &FoldedEvaluator) -> Self { let params: Vec<_> = folded .weights .iter() @@ -469,7 +473,7 @@ impl Evaluator { let vectorizer = IndicesVectorizer::::new(¶ms.first().unwrap().patterns); - Evaluator { + Self { stones_range: folded.config.stones_range.clone(), params, vectorizer, @@ -569,8 +573,10 @@ impl Evaluator { score += self.lookup_patterns(param, vidx); score } +} - pub fn eval(&self, board: Board) -> i16 { +impl Evaluator for PatternLinearEvaluator { + fn eval(&self, board: Board) -> i16 { Self::smooth_val(self.eval_impl(board)) } } @@ -582,7 +588,7 @@ mod tests { #[test] fn test_smooth() { for raw in -60000..=60000 { - let smoothed = Evaluator::smooth_val(raw); + let smoothed = PatternLinearEvaluator::smooth_val(raw); assert!(smoothed > EVAL_SCORE_MIN); assert!(smoothed < EVAL_SCORE_MAX); } diff --git a/src/engine/midgame.rs b/src/engine/midgame.rs index 4997085..22ed98c 100644 --- a/src/engine/midgame.rs +++ b/src/engine/midgame.rs @@ -1,6 +1,7 @@ use crate::engine::bits::*; use crate::engine::board::*; use crate::engine::endgame::*; +use crate::engine::eval::*; use crate::engine::hand::*; use crate::engine::search::*; use crate::engine::table::*; @@ -10,15 +11,15 @@ use std::sync::atomic::AtomicBool; use std::sync::Arc; use std::thread; -struct ABDADAContext { - solve_obj: SolveObj, +struct ABDADAContext { + solve_obj: SolveObj, cs_hash: Arc>, finished: Arc, stats: SolveStat, } -fn simplified_abdada_body( - ctx: &mut ABDADAContext, +fn simplified_abdada_body( + ctx: &mut ABDADAContext, board: Board, (mut alpha, beta): (i8, i8), passed: bool, @@ -88,8 +89,8 @@ fn simplified_abdada_body( Some((res, best)) } -fn simplified_abdada_intro( - ctx: &mut ABDADAContext, +fn simplified_abdada_intro( + ctx: &mut ABDADAContext, board: Board, (mut alpha, mut beta): (i8, i8), passed: bool, @@ -141,8 +142,8 @@ fn defer_search(board: Board, cs_hash: &Arc>) -> bool { cs_hash.contains(&board) } -pub fn simplified_abdada( - solve_obj: &mut SolveObj, +pub fn simplified_abdada( + solve_obj: &mut SolveObj, board: Board, (alpha, beta): (i8, i8), passed: bool, diff --git a/src/engine/search.rs b/src/engine/search.rs index 47ae682..9b76ea0 100644 --- a/src/engine/search.rs +++ b/src/engine/search.rs @@ -45,24 +45,36 @@ pub struct SearchParams { pub static_ordering_limit: i8, } -#[derive(Clone)] -pub struct SolveObj { +pub struct SolveObj { pub res_cache: Arc, pub eval_cache: Arc, - pub evaluator: Arc, + pub evaluator: Arc, pub last_cache: Arc, pub params: SearchParams, pub cache_gen: u32, } -impl SolveObj { +impl Clone for SolveObj { + fn clone(&self) -> Self { + SolveObj:: { + res_cache: self.res_cache.clone(), + eval_cache: self.eval_cache.clone(), + evaluator: self.evaluator.clone(), + last_cache: self.last_cache.clone(), + params: self.params.clone(), + cache_gen: self.cache_gen.clone(), + } + } +} + +impl SolveObj { pub fn new( res_cache: Arc, eval_cache: Arc, - evaluator: Arc, + evaluator: Arc, params: SearchParams, cache_gen: u32, - ) -> SolveObj { + ) -> SolveObj { SolveObj { res_cache, eval_cache, @@ -177,7 +189,11 @@ pub fn make_lookup_result(res_cache: Option, (alpha, beta): (&mut i8, } } -pub fn lookup_table(solve_obj: &mut SolveObj, board: Board, (alpha, beta): (&mut i8, &mut i8)) -> CacheLookupResult { +pub fn lookup_table( + solve_obj: &mut SolveObj, + board: Board, + (alpha, beta): (&mut i8, &mut i8), +) -> CacheLookupResult { let res_cache = solve_obj.res_cache.get(board); make_lookup_result(res_cache, (alpha, beta)) } @@ -211,7 +227,11 @@ fn calc_max_depth(rem: i8) -> i8 { max_depth } -pub fn move_ordering_impl(solve_obj: &mut SolveObj, board: Board, _old_best: Option) -> Vec<(Hand, Board)> { +pub fn move_ordering_impl( + solve_obj: &mut SolveObj, + board: Board, + _old_best: Option, +) -> Vec<(Hand, Board)> { const MAX_NEXT_COUNT: usize = 32; let mut nexts = ArrayVec::<_, MAX_NEXT_COUNT>::new(); for (next, pos) in board.next_iter() { @@ -267,8 +287,8 @@ pub fn move_ordering_impl(solve_obj: &mut SolveObj, board: Board, _old_best: Opt } } -pub fn solve( - solve_obj: &mut SolveObj, +pub fn solve( + solve_obj: &mut SolveObj, _worker_urls: &[String], board: Board, (alpha, beta): (i8, i8), @@ -278,7 +298,11 @@ pub fn solve( simplified_abdada(solve_obj, board, (alpha, beta), passed, depth) } -pub fn solve_with_move(board: Board, solve_obj: &mut SolveObj, _sub_solver: &Arc) -> Hand { +pub fn solve_with_move( + board: Board, + solve_obj: &mut SolveObj, + _sub_solver: &Arc, +) -> Hand { if let Some(best) = simplified_abdada( solve_obj, board, diff --git a/src/engine/think.rs b/src/engine/think.rs index 49fb6a4..e15b671 100644 --- a/src/engine/think.rs +++ b/src/engine/think.rs @@ -19,18 +19,29 @@ impl Timer { } } -#[derive(Clone)] -pub struct Searcher { - pub evaluator: Arc, +pub struct Searcher { + pub evaluator: Arc, pub cache: Arc, pub timer: Option, pub node_count: usize, pub cache_gen: u32, } +impl Clone for Searcher { + fn clone(&self) -> Self { + Searcher:: { + evaluator: self.evaluator.clone(), + cache: self.cache.clone(), + timer: self.timer.clone(), + node_count: self.node_count.clone(), + cache_gen: self.cache_gen.clone(), + } + } +} + pub const DEPTH_SCALE: i32 = 256; -impl Searcher { +impl Searcher { fn think_naive( &mut self, board: Board, @@ -323,7 +334,13 @@ impl Searcher { } } -pub fn think_parallel(searcher: &Searcher, board: Board, alpha: i16, beta: i16, passed: bool) -> (i16, Hand, i8) { +pub fn think_parallel( + searcher: &Searcher, + board: Board, + alpha: i16, + beta: i16, + passed: bool, +) -> (i16, Hand, i8) { thread::scope(|s| { let mut handles = Vec::new(); for i in 0..num_cpus::get() { diff --git a/src/main.rs b/src/main.rs index 002ec68..73a6b58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ mod train; use crate::book::*; use crate::compression::*; use crate::engine::board::*; +use crate::engine::eval::*; use crate::engine::search::*; use crate::play::*; use crate::remote::*; @@ -65,7 +66,12 @@ struct Stat { correct: bool, } -fn solve_ffo(name: &str, index: &mut usize, solve_obj: &mut SolveObj, workers: &[String]) -> Vec { +fn solve_ffo( + name: &str, + index: &mut usize, + solve_obj: &mut SolveObj, + workers: &[String], +) -> Vec { let file = File::open(name).unwrap(); let reader = BufReader::new(file); let mut total_nodes = 0; diff --git a/src/play.rs b/src/play.rs index 06bdfe1..6f758d6 100644 --- a/src/play.rs +++ b/src/play.rs @@ -137,7 +137,11 @@ pub fn self_play(matches: &ArgMatches) -> Board { board.board } -fn self_play_worker(mut solve_obj: SolveObj, sub_solver: Arc, initial_record: &[Hand]) -> (String, i8) { +fn self_play_worker( + mut solve_obj: SolveObj, + sub_solver: Arc, + initial_record: &[Hand], +) -> (String, i8) { use std::fmt::Write; let mut board = BoardWithColor::initial_state(); let mut record_str = String::new(); @@ -256,7 +260,7 @@ macro_rules! parse_input { pub fn codingame(_matches: &ArgMatches) -> Result<(), Box> { let res_cache = Arc::new(ResCacheTable::new(512, 4096)); let eval_cache = Arc::new(EvalCacheTable::new(512, 4096)); - let evaluator = Arc::new(Evaluator::load(Path::new("table-220710")).unwrap()); + let evaluator = Arc::new(PatternLinearEvaluator::load(Path::new("table-220710")).unwrap()); let search_params = SearchParams { reduce: false, parallel_depth_limit: 16, diff --git a/src/remote.rs b/src/remote.rs index 45eda4b..bf84cb6 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -15,7 +15,10 @@ use std::path::Path; use std::sync::Arc; use tokio::net::TcpListener; -async fn worker_impl(solve_obj: SolveObj, req: Request) -> Result>, hyper::Error> { +async fn worker_impl( + solve_obj: SolveObj, + req: Request, +) -> Result>, hyper::Error> { match req.method() { &Method::POST => {} method => { @@ -51,7 +54,7 @@ async fn worker_body() -> Result<(), Box> { ffs_ordering_limit: 6, static_ordering_limit: 5, }; - let evaluator = Arc::new(Evaluator::load(Path::new("table-220710")).unwrap()); + let evaluator = Arc::new(PatternLinearEvaluator::load(Path::new("table-220710")).unwrap()); let res_cache = Arc::new(ResCacheTable::new(256, 65536)); let eval_cache = Arc::new(EvalCacheTable::new(256, 65536)); let solve_obj = SolveObj::new(res_cache, eval_cache, evaluator, search_params, 0); diff --git a/src/setup.rs b/src/setup.rs index a66be26..4ffc2f5 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -4,10 +4,10 @@ use crate::engine::table::*; use std::path::Path; use std::sync::Arc; -pub fn setup_default() -> SolveObj { +pub fn setup_default() -> SolveObj { let res_cache = Arc::new(ResCacheTable::new(2048, 16384)); let eval_cache = Arc::new(EvalCacheTable::new(2048, 16384)); - let evaluator = Arc::new(Evaluator::load(Path::new("table-220710")).unwrap()); + let evaluator = Arc::new(PatternLinearEvaluator::load(Path::new("table-220710")).unwrap()); let search_params = SearchParams { reduce: false, parallel_depth_limit: 16, diff --git a/src/train.rs b/src/train.rs index e04ed50..8734c03 100644 --- a/src/train.rs +++ b/src/train.rs @@ -447,7 +447,7 @@ pub fn eval_stats(matches: &ArgMatches) -> Option<()> { let dataset: Vec<_> = dataset.into_iter().take(8192).collect(); eprintln!("Computing..."); - let evaluator = Arc::new(Evaluator::load(Path::new("table-single"))?); + let evaluator = Arc::new(PatternLinearEvaluator::load(Path::new("table-single"))?); let depth_max = 8; let scores: Vec<_> = dataset .par_iter() From 818f707d2b8b2934849b0513c3445ad90537afb4 Mon Sep 17 00:00:00 2001 From: primenumber Date: Tue, 30 Apr 2024 22:59:56 +0900 Subject: [PATCH 3/6] Split eval and pattern_eval --- src/book.rs | 8 +- src/engine.rs | 1 + src/engine/eval.rs | 592 +---------------------------------- src/engine/pattern_eval.rs | 617 +++++++++++++++++++++++++++++++++++++ src/engine/search.rs | 12 +- src/engine/think.rs | 40 ++- src/main.rs | 1 + src/play.rs | 37 ++- src/remote.rs | 1 + src/setup.rs | 2 +- src/train.rs | 11 +- 11 files changed, 698 insertions(+), 624 deletions(-) create mode 100644 src/engine/pattern_eval.rs diff --git a/src/book.rs b/src/book.rs index 8bfb8d0..1f8d748 100644 --- a/src/book.rs +++ b/src/book.rs @@ -192,7 +192,13 @@ fn search( node_count: 0, cache_gen: solve_obj.cache_gen, }; - let (_score, hand, _depth) = think_parallel(&searcher, board, EVAL_SCORE_MIN, EVAL_SCORE_MAX, false); + let (_score, hand, _depth) = think_parallel( + &searcher, + board, + solve_obj.evaluator.score_min(), + solve_obj.evaluator.score_max(), + false, + ); hand } } diff --git a/src/engine.rs b/src/engine.rs index 9c9e931..2323d56 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -5,6 +5,7 @@ pub mod eval; pub mod hand; pub mod last_cache; pub mod midgame; +pub mod pattern_eval; pub mod search; pub mod table; pub mod think; diff --git a/src/engine/eval.rs b/src/engine/eval.rs index ec962b1..19e6374 100644 --- a/src/engine/eval.rs +++ b/src/engine/eval.rs @@ -1,596 +1,10 @@ #[cfg(test)] mod test; -use crate::engine::bits::*; use crate::engine::board::*; -#[cfg(target_feature = "avx2")] -use core::arch::x86_64::*; -use std::fs::File; -use std::io::Read; -use std::mem; -use std::ops::RangeInclusive; -use std::path::Path; -use std::simd::prelude::*; -use yaml_rust::yaml; - -struct EvaluatorConfig { - masks: Vec, - stones_range: RangeInclusive, -} - -struct EvaluatorPattern { - mask: u64, - offset: usize, - pattern_count: usize, -} - -impl EvaluatorConfig { - fn from_file(config_path: &Path) -> Option { - let mut config_file = File::open(config_path).ok()?; - let mut config_string = String::new(); - config_file.read_to_string(&mut config_string).ok()?; - let config_objs = yaml::YamlLoader::load_from_str(&config_string).ok()?; - let config = &config_objs[0]; - let masks = config["masks"] - .as_vec()? - .iter() - .map(|e| u64::from_str_radix(e.as_str().unwrap(), 2).unwrap()) - .collect(); - let stones_range_yaml = &config["stone_counts"]; - let from = stones_range_yaml["from"].as_i64()? as i8; - let to = stones_range_yaml["to"].as_i64()? as i8; - let stones_range = from..=to; - Some(EvaluatorConfig { - masks, - stones_range, - }) - } - - fn pattern_info(&self) -> (Vec, usize, usize) { - let mut patterns = Vec::new(); - let mut offset = 0; - for &mask in self.masks.iter() { - let mask_size = popcnt(mask); - let pattern_count = pow3(mask_size); - patterns.push(EvaluatorPattern { - mask, - offset, - pattern_count, - }); - offset += pattern_count; - } - (patterns, offset, NON_PATTERN_SCORES) - } -} - -struct PatternWeightTable { - mask: u64, - weights: Vec, -} - -struct WeightTable { - pattern_tables: Vec, - p_mobility_score: i16, - o_mobility_score: i16, - parity_score: i16, - constant_score: i16, -} - -struct FoldedEvaluator { - config: EvaluatorConfig, - weights: Vec, -} - -impl FoldedEvaluator { - fn load_weight(path: &Path, length: usize) -> Option> { - let mut value_file = File::open(path).ok()?; - let mut buf = vec![0u8; length * 8]; - value_file.read_exact(&mut buf).ok()?; - let mut v = Vec::with_capacity(length); - for i in 0usize..length { - let mut ary: [u8; 8] = Default::default(); - ary.copy_from_slice(&buf[(8 * i)..(8 * (i + 1))]); - let raw_weight = unsafe { mem::transmute::<[u8; 8], f64>(ary) }; - v.push( - (SCALE as f64 * raw_weight) - .max(SCALE as f64 * -64.0) - .min(SCALE as f64 * 64.0) - .round() as i16, - ); - } - Some(v) - } - - fn smooth_weight(weights: &[Vec], length: usize, window_size: i8) -> Vec> { - assert_eq!(window_size % 2, 1); - let half_window = window_size / 2; - let mut result = vec![vec![0; length]; weights.len()]; - for (i, w) in result.iter_mut().enumerate() { - for (k, e) in w.iter_mut().enumerate() { - for d in -half_window..=half_window { - let j = i as isize + d as isize; - let j = if j < 0 { 0 } else { j as usize }; - let j = if j >= weights.len() { - weights.len() - 1 - } else { - j - }; - *e += weights[j][k]; - } - *e /= window_size as i16; - } - } - result - } - - fn load_all_weights(table_path: &Path, config: &EvaluatorConfig, length: usize) -> Vec> { - let mut weights = Vec::new(); - for num in config.stones_range.clone() { - let path = table_path.join(format!("value{}", num)); - weights.push(Self::load_weight(&path, length).unwrap()); - } - weights - } - - fn unpack_weights(weights: &[i16], patterns: &[EvaluatorPattern], non_patterns_offset: usize) -> WeightTable { - let mut pattern_tables = Vec::new(); - for pattern in patterns { - pattern_tables.push(PatternWeightTable { - mask: pattern.mask, - weights: weights[pattern.offset..(pattern.offset + pattern.pattern_count)].to_vec(), - }); - } - let p_mobility_score = weights[non_patterns_offset]; - let o_mobility_score = weights[non_patterns_offset + 1]; - let parity_score = weights[non_patterns_offset + 2]; - let constant_score = weights[non_patterns_offset + 3]; - WeightTable { - pattern_tables, - p_mobility_score, - o_mobility_score, - parity_score, - constant_score, - } - } - - fn load(path: &Path) -> Option { - let config_path = path.join("config.yaml"); - let config = EvaluatorConfig::from_file(&config_path)?; - let (patterns, pettern_weights_length, non_patterns_count) = config.pattern_info(); - let packed_weights = Self::load_all_weights(path, &config, pettern_weights_length + non_patterns_count); - let smoothed_packed_weights = Self::smooth_weight( - &packed_weights, - pettern_weights_length + non_patterns_count, - 1, - ); - let weights = smoothed_packed_weights - .iter() - .map(|packed| Self::unpack_weights(packed, &patterns, pettern_weights_length)) - .collect(); - Some(FoldedEvaluator { config, weights }) - } -} - -// interprete base-2 number as base-3 number -// base_2_to_3(x) := radix_parse(radix_fmt(x, 2), 3) -const fn base_2_to_3(mut x: usize) -> usize { - let mut base3 = 0; - let mut pow3 = 1; - while x > 0 { - base3 += (x % 2) * pow3; - pow3 *= 3; - x /= 2; - } - base3 -} - -const NON_PATTERN_SCORES: usize = 4; - -const BASE_2_TO_3_TABLE_BITS: usize = 13; -const BASE_2_TO_3: [usize; 1 << BASE_2_TO_3_TABLE_BITS] = { - let mut table = [0usize; 1 << BASE_2_TO_3_TABLE_BITS]; - let mut i = 0; - while i < table.len() { - table[i] = base_2_to_3(i); - i += 1; - } - table -}; - -// x = R^rot M ^ mirror -struct SquareGroup { - rot: u8, - mirror: u8, -} - -impl SquareGroup { - #[allow(dead_code)] - fn compose(&self, rhs: &Self) -> Self { - let rot = if self.mirror != 0 { - (4 + self.rot - rhs.rot) % 4 - } else { - (self.rot + rhs.rot) % 4 - }; - Self { - rot, - mirror: (self.mirror + rhs.mirror) % 2, - } - } - - fn apply(&self, mut bits: u64) -> u64 { - if self.mirror != 0 { - bits = flip_diag(bits); - } - for _ in 0..self.rot { - bits = rot90(bits); - } - bits - } - - fn all_elements() -> Vec { - let mut res = Vec::new(); - for rot in 0..4 { - for mirror in 0..2 { - res.push(Self { rot, mirror }); - } - } - res - } -} - -struct PatternPermutation { - perms: Vec<(u64, Vec)>, -} - -impl PatternPermutation { - fn permute_indices_base2(mask: u64) -> PatternPermutation { - let pattern_size = mask.count_ones(); - let table_size = (1 << pattern_size) as usize; - PatternPermutation { - perms: SquareGroup::all_elements() - .iter() - .map(|op| { - let pattern = op.apply(mask); - let permutation = (0..table_size) - .map(|pidx| { - let bits = (pidx as u64).pdep(mask); - op.apply(bits).pext(pattern) as usize - }) - .collect(); - (pattern, permutation) - }) - .collect(), - } - } - - fn permute_indices(mask: u64) -> PatternPermutation { - let pattern_size = mask.count_ones(); - PatternPermutation { - perms: Self::permute_indices_base2(mask) - .perms - .iter() - .map(|(pattern, perm_base2)| { - let mut perm_base3 = vec![0; pow3(pattern_size as i8)]; - for pidx in 0..(1 << pattern_size) { - let remain = ((1 << pattern_size) - 1) ^ pidx; - let mut oidx = (1 << pattern_size) - 1; - while oidx as isize >= 0 { - oidx &= remain; - let orig_index = BASE_2_TO_3[pidx] + BASE_2_TO_3[oidx] * 2; - let t_pidx = perm_base2[pidx]; - let t_oidx = perm_base2[oidx]; - let index = BASE_2_TO_3[t_pidx] + BASE_2_TO_3[t_oidx] * 2; - perm_base3[index] = orig_index; - oidx = oidx.wrapping_sub(1); - } - } - (*pattern, perm_base3) - }) - .collect(), - } - } - - fn expand_weights_by_d4(&self, weights: &[i16]) -> Vec { - self.perms - .iter() - .map(|(pattern, perm_base3)| { - let mut permuted_weights = vec![0; perm_base3.len()]; - for (i, &p) in perm_base3.iter().enumerate() { - permuted_weights[i] = weights[p]; - } - PatternWeightTable { - mask: *pattern, - weights: permuted_weights, - } - }) - .collect() - } - - fn expand_weights_with_compaction(&self, weights: &[i16]) -> Vec { - let mut result: Vec = Vec::new(); - for table in self.expand_weights_by_d4(weights) { - if let Some(ref_table) = result - .iter_mut() - .find(|ref_table| ref_table.mask == table.mask) - { - for (w, &e) in ref_table.weights.iter_mut().zip(table.weights.iter()) { - *w += e; - } - } else { - result.push(table); - } - } - result - } -} - -struct Parameters { - pattern_weights: Vec, - patterns: Vec, - offsets: Vec, - p_mobility_score: i16, - o_mobility_score: i16, - parity_score: i16, - constant_score: i16, -} - -impl Parameters { - fn new(table: &WeightTable) -> Option { - let mut v: Vec = Vec::new(); - for table in table.pattern_tables.iter() { - let perm = PatternPermutation::permute_indices(table.mask); - v.extend(perm.expand_weights_with_compaction(&table.weights)); - } - let mut pattern_weights = Vec::new(); - let mut patterns = Vec::new(); - let mut offsets = Vec::new(); - let mut offset = 0; - for table in v { - offsets.push(offset as u32); - offset += table.weights.len(); - pattern_weights.extend(table.weights); - patterns.push(table.mask); - } - // padding for vectorization - pattern_weights.push(0); - while offsets.len() % 16 != 0 { - offsets.push(offset as u32); - } - Some(Parameters { - pattern_weights, - patterns, - offsets, - p_mobility_score: table.p_mobility_score, - o_mobility_score: table.o_mobility_score, - parity_score: table.parity_score, - constant_score: table.constant_score, - }) - } - - fn fold_parity(&mut self, stone_count: i8) { - if stone_count % 2 == 1 { - self.constant_score += self.parity_score; - } - self.parity_score = 0; - } -} - -// not configuable -const INDICES_VECTORIZER_VECTOR_ELEMENTS: usize = 16; -const INDICES_VECTORIZER_VECTOR_COUNT: usize = 3; -const INDICES_VECTORIZER_LEN: usize = INDICES_VECTORIZER_VECTOR_COUNT * INDICES_VECTORIZER_VECTOR_ELEMENTS; - -struct IndicesVectorizer { - indices: Vec, -} - -impl IndicesVectorizer { - fn new(patterns: &[u64]) -> Self { - let chunk_count = (64 + W - 1) / W; - let mut indices = Vec::new(); - for chunk_idx in 0..chunk_count { - let shift = chunk_idx * W; - for bits in 0..(1 << W) { - let chunk_bits = bits << shift; - for &pattern in patterns { - let index = BASE_2_TO_3[chunk_bits.pext(pattern) as usize]; - indices.push(index as u16); - } - while indices.len() % INDICES_VECTORIZER_VECTOR_ELEMENTS != 0 { - indices.push(0); - } - } - } - Self { indices } - } - - fn feature_indices(&self, board: Board) -> [u16x16; INDICES_VECTORIZER_VECTOR_COUNT] { - let mut indices = [Simd::splat(0); INDICES_VECTORIZER_VECTOR_COUNT]; - let mask: u64 = (1 << W) - 1; - let chunk_count = (64 + W - 1) / W; - for chunk_idx in 0..chunk_count { - let pidx = ((board.player >> (chunk_idx * W)) & mask) as usize; - let oidx = ((board.opponent >> (chunk_idx * W)) & mask) as usize; - let offset_base_p = INDICES_VECTORIZER_LEN * (pidx + (1 << W) * chunk_idx); - let offset_base_o = INDICES_VECTORIZER_LEN * (oidx + (1 << W) * chunk_idx); - for (vidx, idx) in indices.iter_mut().enumerate() { - let vp = Simd::from_slice(unsafe { - self.indices - .get_unchecked((offset_base_p + 16 * vidx)..(offset_base_p + 16 * (vidx + 1))) - }); - let vo = Simd::from_slice(unsafe { - self.indices - .get_unchecked((offset_base_o + 16 * vidx)..(offset_base_o + 16 * (vidx + 1))) - }); - *idx += vp + vo + vo; - } - } - indices - } -} - -// configuable -const INDICES_VECTORIZER_PACK_SIZE: usize = 8; pub trait Evaluator: Send + Sync { fn eval(&self, board: Board) -> i16; -} - -pub struct PatternLinearEvaluator { - stones_range: RangeInclusive, - params: Vec, - vectorizer: IndicesVectorizer, -} - -fn pow3(x: i8) -> usize { - if x == 0 { - 1 - } else { - 3 * pow3(x - 1) - } -} - -pub const SCALE: i16 = 256; -pub const EVAL_SCORE_MAX: i16 = BOARD_SIZE as i16 * SCALE; -pub const EVAL_SCORE_MIN: i16 = -EVAL_SCORE_MAX; - -impl PatternLinearEvaluator { - pub fn load(path: &Path) -> Option { - let folded = FoldedEvaluator::load(path)?; - Some(Self::from_folded(&folded)) - } - - fn from_folded(folded: &FoldedEvaluator) -> Self { - let params: Vec<_> = folded - .weights - .iter() - .enumerate() - .map(|(i, table)| { - let mut param = Parameters::new(table).unwrap(); - param.fold_parity(i as i8 + folded.config.stones_range.start()); - param - }) - .collect(); - - let vectorizer = IndicesVectorizer::::new(¶ms.first().unwrap().patterns); - - Self { - stones_range: folded.config.stones_range.clone(), - params, - vectorizer, - } - } - - fn smooth_val(raw_score: i32) -> i16 { - let scale32 = SCALE as i32; - (if raw_score > 63 * scale32 { - (BOARD_SIZE as i32) * scale32 - scale32 * scale32 / (raw_score - 62 * scale32) - } else if raw_score < -63 * scale32 { - -(BOARD_SIZE as i32) * scale32 - scale32 * scale32 / (raw_score + 62 * scale32) - } else { - raw_score - }) as i16 - } - - #[cfg(target_feature = "avx2")] - fn lookup_patterns(&self, param: &Parameters, vidx: [u16x16; 3]) -> i32 { - unsafe { - unsafe fn unpack_idx(idx: __m256i) -> (__m256i, __m256i) { - const MM_PERM_ACBD: i32 = 0b11011000; - let permed = _mm256_permute4x64_epi64(idx, MM_PERM_ACBD); - let lo = _mm256_unpacklo_epi16(permed, _mm256_setzero_si256()); - let hi = _mm256_unpackhi_epi16(permed, _mm256_setzero_si256()); - (lo, hi) - } - let (idxh0, idxh1) = unpack_idx((*vidx.get_unchecked(0)).into()); - let (idxh2, idxh3) = unpack_idx((*vidx.get_unchecked(1)).into()); - let (idxh4, idxh5) = unpack_idx((*vidx.get_unchecked(2)).into()); - unsafe fn gather_weight(param: &Parameters, idx: __m256i, start: usize) -> __m256i { - let offset = _mm256_add_epi32( - idx, - _mm256_loadu_si256(param.offsets.get_unchecked(start) as *const u32 as *const __m256i), - ); - _mm256_i32gather_epi32(param.pattern_weights.as_ptr() as *const i32, offset, 2) - } - let vw0 = gather_weight(param, idxh0, 0); - let vw1 = gather_weight(param, idxh1, 8); - let vw2 = gather_weight(param, idxh2, 16); - let vw3 = gather_weight(param, idxh3, 24); - let vw4 = gather_weight(param, idxh4, 32); - let vw5 = gather_weight(param, idxh5, 40); - unsafe fn vector_sum( - v0: __m256i, - v1: __m256i, - v2: __m256i, - v3: __m256i, - v4: __m256i, - v5: __m256i, - ) -> u16x16 { - let sum0 = _mm256_blend_epi16(v0, _mm256_slli_epi32(v1, 16), 0b10101010); - let sum1 = _mm256_blend_epi16(v2, _mm256_slli_epi32(v3, 16), 0b10101010); - let sum2 = _mm256_blend_epi16(v4, _mm256_slli_epi32(v5, 16), 0b10101010); - _mm256_add_epi16(_mm256_add_epi16(sum0, sum1), sum2).into() - } - vector_sum(vw0, vw1, vw2, vw3, vw4, vw5).reduce_sum() as i16 as i32 - } - } - - #[cfg(not(target_feature = "avx2"))] - fn lookup_patterns(&self, param: &Parameters, vidx: [u16x16; 3]) -> i32 { - let mut sum = 0i32; - for (i, idx_vec) in vidx.iter().enumerate() { - for (j, idx) in idx_vec.as_array().iter().enumerate() { - unsafe { - let offset = *idx as u32 - + *param - .offsets - .get_unchecked(i * INDICES_VECTORIZER_VECTOR_ELEMENTS + j); - sum += *param.pattern_weights.get_unchecked(offset as usize) as i32; - } - } - } - sum - } - - fn eval_impl(&self, board: Board) -> i32 { - let rem: usize = popcnt(board.empty()) as usize; - let stones = (BOARD_SIZE - rem) - .max(*self.stones_range.start() as usize) - .min(*self.stones_range.end() as usize); - let param_index = stones - *self.stones_range.start() as usize; - let param = unsafe { self.params.get_unchecked(param_index) }; - - // non-pattern scores - let mut score = param.constant_score; - // Since parity_score s absorbed into constant_score, parity_score can be assumed to be 0 - // here. - // score += param.parity_score; - score += param.p_mobility_score * popcnt(board.mobility_bits()) as i16; - score += param.o_mobility_score * popcnt(board.pass_unchecked().mobility_bits()) as i16; - let mut score = score as i32; - - // pattern-based scores - let vidx = self.vectorizer.feature_indices(board); - score += self.lookup_patterns(param, vidx); - score - } -} - -impl Evaluator for PatternLinearEvaluator { - fn eval(&self, board: Board) -> i16 { - Self::smooth_val(self.eval_impl(board)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_smooth() { - for raw in -60000..=60000 { - let smoothed = PatternLinearEvaluator::smooth_val(raw); - assert!(smoothed > EVAL_SCORE_MIN); - assert!(smoothed < EVAL_SCORE_MAX); - } - } + fn score_scale(&self) -> i16; + fn score_max(&self) -> i16; + fn score_min(&self) -> i16; } diff --git a/src/engine/pattern_eval.rs b/src/engine/pattern_eval.rs new file mode 100644 index 0000000..91131b7 --- /dev/null +++ b/src/engine/pattern_eval.rs @@ -0,0 +1,617 @@ +#[cfg(test)] +mod test; +use crate::engine::bits::*; +use crate::engine::board::*; +use crate::engine::eval::*; +#[cfg(target_feature = "avx2")] +use core::arch::x86_64::*; +use std::fs::File; +use std::io::Read; +use std::mem; +use std::ops::RangeInclusive; +use std::path::Path; +use std::simd::prelude::*; +use yaml_rust::yaml; + +struct EvaluatorConfig { + masks: Vec, + stones_range: RangeInclusive, +} + +struct EvaluatorPattern { + mask: u64, + offset: usize, + pattern_count: usize, +} + +impl EvaluatorConfig { + fn from_file(config_path: &Path) -> Option { + let mut config_file = File::open(config_path).ok()?; + let mut config_string = String::new(); + config_file.read_to_string(&mut config_string).ok()?; + let config_objs = yaml::YamlLoader::load_from_str(&config_string).ok()?; + let config = &config_objs[0]; + let masks = config["masks"] + .as_vec()? + .iter() + .map(|e| u64::from_str_radix(e.as_str().unwrap(), 2).unwrap()) + .collect(); + let stones_range_yaml = &config["stone_counts"]; + let from = stones_range_yaml["from"].as_i64()? as i8; + let to = stones_range_yaml["to"].as_i64()? as i8; + let stones_range = from..=to; + Some(EvaluatorConfig { + masks, + stones_range, + }) + } + + fn pattern_info(&self) -> (Vec, usize, usize) { + let mut patterns = Vec::new(); + let mut offset = 0; + for &mask in self.masks.iter() { + let mask_size = popcnt(mask); + let pattern_count = pow3(mask_size); + patterns.push(EvaluatorPattern { + mask, + offset, + pattern_count, + }); + offset += pattern_count; + } + (patterns, offset, NON_PATTERN_SCORES) + } +} + +struct PatternWeightTable { + mask: u64, + weights: Vec, +} + +struct WeightTable { + pattern_tables: Vec, + p_mobility_score: i16, + o_mobility_score: i16, + parity_score: i16, + constant_score: i16, +} + +struct FoldedEvaluator { + config: EvaluatorConfig, + weights: Vec, +} + +impl FoldedEvaluator { + fn load_weight(path: &Path, length: usize) -> Option> { + let mut value_file = File::open(path).ok()?; + let mut buf = vec![0u8; length * 8]; + value_file.read_exact(&mut buf).ok()?; + let mut v = Vec::with_capacity(length); + for i in 0usize..length { + let mut ary: [u8; 8] = Default::default(); + ary.copy_from_slice(&buf[(8 * i)..(8 * (i + 1))]); + let raw_weight = unsafe { mem::transmute::<[u8; 8], f64>(ary) }; + v.push( + (SCALE as f64 * raw_weight) + .max(SCALE as f64 * -64.0) + .min(SCALE as f64 * 64.0) + .round() as i16, + ); + } + Some(v) + } + + fn smooth_weight(weights: &[Vec], length: usize, window_size: i8) -> Vec> { + assert_eq!(window_size % 2, 1); + let half_window = window_size / 2; + let mut result = vec![vec![0; length]; weights.len()]; + for (i, w) in result.iter_mut().enumerate() { + for (k, e) in w.iter_mut().enumerate() { + for d in -half_window..=half_window { + let j = i as isize + d as isize; + let j = if j < 0 { 0 } else { j as usize }; + let j = if j >= weights.len() { + weights.len() - 1 + } else { + j + }; + *e += weights[j][k]; + } + *e /= window_size as i16; + } + } + result + } + + fn load_all_weights(table_path: &Path, config: &EvaluatorConfig, length: usize) -> Vec> { + let mut weights = Vec::new(); + for num in config.stones_range.clone() { + let path = table_path.join(format!("value{}", num)); + weights.push(Self::load_weight(&path, length).unwrap()); + } + weights + } + + fn unpack_weights(weights: &[i16], patterns: &[EvaluatorPattern], non_patterns_offset: usize) -> WeightTable { + let mut pattern_tables = Vec::new(); + for pattern in patterns { + pattern_tables.push(PatternWeightTable { + mask: pattern.mask, + weights: weights[pattern.offset..(pattern.offset + pattern.pattern_count)].to_vec(), + }); + } + let p_mobility_score = weights[non_patterns_offset]; + let o_mobility_score = weights[non_patterns_offset + 1]; + let parity_score = weights[non_patterns_offset + 2]; + let constant_score = weights[non_patterns_offset + 3]; + WeightTable { + pattern_tables, + p_mobility_score, + o_mobility_score, + parity_score, + constant_score, + } + } + + fn load(path: &Path) -> Option { + let config_path = path.join("config.yaml"); + let config = EvaluatorConfig::from_file(&config_path)?; + let (patterns, pettern_weights_length, non_patterns_count) = config.pattern_info(); + let packed_weights = Self::load_all_weights(path, &config, pettern_weights_length + non_patterns_count); + let smoothed_packed_weights = Self::smooth_weight( + &packed_weights, + pettern_weights_length + non_patterns_count, + 1, + ); + let weights = smoothed_packed_weights + .iter() + .map(|packed| Self::unpack_weights(packed, &patterns, pettern_weights_length)) + .collect(); + Some(FoldedEvaluator { config, weights }) + } +} + +// interprete base-2 number as base-3 number +// base_2_to_3(x) := radix_parse(radix_fmt(x, 2), 3) +const fn base_2_to_3(mut x: usize) -> usize { + let mut base3 = 0; + let mut pow3 = 1; + while x > 0 { + base3 += (x % 2) * pow3; + pow3 *= 3; + x /= 2; + } + base3 +} + +const NON_PATTERN_SCORES: usize = 4; + +const BASE_2_TO_3_TABLE_BITS: usize = 13; +const BASE_2_TO_3: [usize; 1 << BASE_2_TO_3_TABLE_BITS] = { + let mut table = [0usize; 1 << BASE_2_TO_3_TABLE_BITS]; + let mut i = 0; + while i < table.len() { + table[i] = base_2_to_3(i); + i += 1; + } + table +}; + +// x = R^rot M ^ mirror +struct SquareGroup { + rot: u8, + mirror: u8, +} + +impl SquareGroup { + #[allow(dead_code)] + fn compose(&self, rhs: &Self) -> Self { + let rot = if self.mirror != 0 { + (4 + self.rot - rhs.rot) % 4 + } else { + (self.rot + rhs.rot) % 4 + }; + Self { + rot, + mirror: (self.mirror + rhs.mirror) % 2, + } + } + + fn apply(&self, mut bits: u64) -> u64 { + if self.mirror != 0 { + bits = flip_diag(bits); + } + for _ in 0..self.rot { + bits = rot90(bits); + } + bits + } + + fn all_elements() -> Vec { + let mut res = Vec::new(); + for rot in 0..4 { + for mirror in 0..2 { + res.push(Self { rot, mirror }); + } + } + res + } +} + +struct PatternPermutation { + perms: Vec<(u64, Vec)>, +} + +impl PatternPermutation { + fn permute_indices_base2(mask: u64) -> PatternPermutation { + let pattern_size = mask.count_ones(); + let table_size = (1 << pattern_size) as usize; + PatternPermutation { + perms: SquareGroup::all_elements() + .iter() + .map(|op| { + let pattern = op.apply(mask); + let permutation = (0..table_size) + .map(|pidx| { + let bits = (pidx as u64).pdep(mask); + op.apply(bits).pext(pattern) as usize + }) + .collect(); + (pattern, permutation) + }) + .collect(), + } + } + + fn permute_indices(mask: u64) -> PatternPermutation { + let pattern_size = mask.count_ones(); + PatternPermutation { + perms: Self::permute_indices_base2(mask) + .perms + .iter() + .map(|(pattern, perm_base2)| { + let mut perm_base3 = vec![0; pow3(pattern_size as i8)]; + for pidx in 0..(1 << pattern_size) { + let remain = ((1 << pattern_size) - 1) ^ pidx; + let mut oidx = (1 << pattern_size) - 1; + while oidx as isize >= 0 { + oidx &= remain; + let orig_index = BASE_2_TO_3[pidx] + BASE_2_TO_3[oidx] * 2; + let t_pidx = perm_base2[pidx]; + let t_oidx = perm_base2[oidx]; + let index = BASE_2_TO_3[t_pidx] + BASE_2_TO_3[t_oidx] * 2; + perm_base3[index] = orig_index; + oidx = oidx.wrapping_sub(1); + } + } + (*pattern, perm_base3) + }) + .collect(), + } + } + + fn expand_weights_by_d4(&self, weights: &[i16]) -> Vec { + self.perms + .iter() + .map(|(pattern, perm_base3)| { + let mut permuted_weights = vec![0; perm_base3.len()]; + for (i, &p) in perm_base3.iter().enumerate() { + permuted_weights[i] = weights[p]; + } + PatternWeightTable { + mask: *pattern, + weights: permuted_weights, + } + }) + .collect() + } + + fn expand_weights_with_compaction(&self, weights: &[i16]) -> Vec { + let mut result: Vec = Vec::new(); + for table in self.expand_weights_by_d4(weights) { + if let Some(ref_table) = result + .iter_mut() + .find(|ref_table| ref_table.mask == table.mask) + { + for (w, &e) in ref_table.weights.iter_mut().zip(table.weights.iter()) { + *w += e; + } + } else { + result.push(table); + } + } + result + } +} + +struct Parameters { + pattern_weights: Vec, + patterns: Vec, + offsets: Vec, + p_mobility_score: i16, + o_mobility_score: i16, + parity_score: i16, + constant_score: i16, +} + +impl Parameters { + fn new(table: &WeightTable) -> Option { + let mut v: Vec = Vec::new(); + for table in table.pattern_tables.iter() { + let perm = PatternPermutation::permute_indices(table.mask); + v.extend(perm.expand_weights_with_compaction(&table.weights)); + } + let mut pattern_weights = Vec::new(); + let mut patterns = Vec::new(); + let mut offsets = Vec::new(); + let mut offset = 0; + for table in v { + offsets.push(offset as u32); + offset += table.weights.len(); + pattern_weights.extend(table.weights); + patterns.push(table.mask); + } + // padding for vectorization + pattern_weights.push(0); + while offsets.len() % 16 != 0 { + offsets.push(offset as u32); + } + Some(Parameters { + pattern_weights, + patterns, + offsets, + p_mobility_score: table.p_mobility_score, + o_mobility_score: table.o_mobility_score, + parity_score: table.parity_score, + constant_score: table.constant_score, + }) + } + + fn fold_parity(&mut self, stone_count: i8) { + if stone_count % 2 == 1 { + self.constant_score += self.parity_score; + } + self.parity_score = 0; + } +} + +// not configuable +const INDICES_VECTORIZER_VECTOR_ELEMENTS: usize = 16; +const INDICES_VECTORIZER_VECTOR_COUNT: usize = 3; +const INDICES_VECTORIZER_LEN: usize = INDICES_VECTORIZER_VECTOR_COUNT * INDICES_VECTORIZER_VECTOR_ELEMENTS; + +struct IndicesVectorizer { + indices: Vec, +} + +impl IndicesVectorizer { + fn new(patterns: &[u64]) -> Self { + let chunk_count = (64 + W - 1) / W; + let mut indices = Vec::new(); + for chunk_idx in 0..chunk_count { + let shift = chunk_idx * W; + for bits in 0..(1 << W) { + let chunk_bits = bits << shift; + for &pattern in patterns { + let index = BASE_2_TO_3[chunk_bits.pext(pattern) as usize]; + indices.push(index as u16); + } + while indices.len() % INDICES_VECTORIZER_VECTOR_ELEMENTS != 0 { + indices.push(0); + } + } + } + Self { indices } + } + + fn feature_indices(&self, board: Board) -> [u16x16; INDICES_VECTORIZER_VECTOR_COUNT] { + let mut indices = [Simd::splat(0); INDICES_VECTORIZER_VECTOR_COUNT]; + let mask: u64 = (1 << W) - 1; + let chunk_count = (64 + W - 1) / W; + for chunk_idx in 0..chunk_count { + let pidx = ((board.player >> (chunk_idx * W)) & mask) as usize; + let oidx = ((board.opponent >> (chunk_idx * W)) & mask) as usize; + let offset_base_p = INDICES_VECTORIZER_LEN * (pidx + (1 << W) * chunk_idx); + let offset_base_o = INDICES_VECTORIZER_LEN * (oidx + (1 << W) * chunk_idx); + for (vidx, idx) in indices.iter_mut().enumerate() { + let vp = Simd::from_slice(unsafe { + self.indices + .get_unchecked((offset_base_p + 16 * vidx)..(offset_base_p + 16 * (vidx + 1))) + }); + let vo = Simd::from_slice(unsafe { + self.indices + .get_unchecked((offset_base_o + 16 * vidx)..(offset_base_o + 16 * (vidx + 1))) + }); + *idx += vp + vo + vo; + } + } + indices + } +} + +// configuable +const INDICES_VECTORIZER_PACK_SIZE: usize = 8; + +pub struct PatternLinearEvaluator { + stones_range: RangeInclusive, + params: Vec, + vectorizer: IndicesVectorizer, +} + +fn pow3(x: i8) -> usize { + if x == 0 { + 1 + } else { + 3 * pow3(x - 1) + } +} + +const SCALE: i16 = 256; +const EVAL_SCORE_MAX: i16 = BOARD_SIZE as i16 * SCALE; +const EVAL_SCORE_MIN: i16 = -EVAL_SCORE_MAX; + +impl PatternLinearEvaluator { + pub fn load(path: &Path) -> Option { + let folded = FoldedEvaluator::load(path)?; + Some(Self::from_folded(&folded)) + } + + fn from_folded(folded: &FoldedEvaluator) -> Self { + let params: Vec<_> = folded + .weights + .iter() + .enumerate() + .map(|(i, table)| { + let mut param = Parameters::new(table).unwrap(); + param.fold_parity(i as i8 + folded.config.stones_range.start()); + param + }) + .collect(); + + let vectorizer = IndicesVectorizer::::new(¶ms.first().unwrap().patterns); + + Self { + stones_range: folded.config.stones_range.clone(), + params, + vectorizer, + } + } + + fn smooth_val(raw_score: i32) -> i16 { + let scale32 = SCALE as i32; + (if raw_score > 63 * scale32 { + (BOARD_SIZE as i32) * scale32 - scale32 * scale32 / (raw_score - 62 * scale32) + } else if raw_score < -63 * scale32 { + -(BOARD_SIZE as i32) * scale32 - scale32 * scale32 / (raw_score + 62 * scale32) + } else { + raw_score + }) as i16 + } + + #[cfg(target_feature = "avx2")] + fn lookup_patterns(&self, param: &Parameters, vidx: [u16x16; 3]) -> i32 { + unsafe { + unsafe fn unpack_idx(idx: __m256i) -> (__m256i, __m256i) { + const MM_PERM_ACBD: i32 = 0b11011000; + let permed = _mm256_permute4x64_epi64(idx, MM_PERM_ACBD); + let lo = _mm256_unpacklo_epi16(permed, _mm256_setzero_si256()); + let hi = _mm256_unpackhi_epi16(permed, _mm256_setzero_si256()); + (lo, hi) + } + let (idxh0, idxh1) = unpack_idx((*vidx.get_unchecked(0)).into()); + let (idxh2, idxh3) = unpack_idx((*vidx.get_unchecked(1)).into()); + let (idxh4, idxh5) = unpack_idx((*vidx.get_unchecked(2)).into()); + unsafe fn gather_weight(param: &Parameters, idx: __m256i, start: usize) -> __m256i { + let offset = _mm256_add_epi32( + idx, + _mm256_loadu_si256(param.offsets.get_unchecked(start) as *const u32 as *const __m256i), + ); + _mm256_i32gather_epi32(param.pattern_weights.as_ptr() as *const i32, offset, 2) + } + let vw0 = gather_weight(param, idxh0, 0); + let vw1 = gather_weight(param, idxh1, 8); + let vw2 = gather_weight(param, idxh2, 16); + let vw3 = gather_weight(param, idxh3, 24); + let vw4 = gather_weight(param, idxh4, 32); + let vw5 = gather_weight(param, idxh5, 40); + unsafe fn vector_sum( + v0: __m256i, + v1: __m256i, + v2: __m256i, + v3: __m256i, + v4: __m256i, + v5: __m256i, + ) -> u16x16 { + let sum0 = _mm256_blend_epi16(v0, _mm256_slli_epi32(v1, 16), 0b10101010); + let sum1 = _mm256_blend_epi16(v2, _mm256_slli_epi32(v3, 16), 0b10101010); + let sum2 = _mm256_blend_epi16(v4, _mm256_slli_epi32(v5, 16), 0b10101010); + _mm256_add_epi16(_mm256_add_epi16(sum0, sum1), sum2).into() + } + vector_sum(vw0, vw1, vw2, vw3, vw4, vw5).reduce_sum() as i16 as i32 + } + } + + #[cfg(not(target_feature = "avx2"))] + fn lookup_patterns(&self, param: &Parameters, vidx: [u16x16; 3]) -> i32 { + let mut sum = 0i32; + for (i, idx_vec) in vidx.iter().enumerate() { + for (j, idx) in idx_vec.as_array().iter().enumerate() { + unsafe { + let offset = *idx as u32 + + *param + .offsets + .get_unchecked(i * INDICES_VECTORIZER_VECTOR_ELEMENTS + j); + sum += *param.pattern_weights.get_unchecked(offset as usize) as i32; + } + } + } + sum + } + + fn eval_impl(&self, board: Board) -> i32 { + let rem: usize = popcnt(board.empty()) as usize; + let stones = (BOARD_SIZE - rem) + .max(*self.stones_range.start() as usize) + .min(*self.stones_range.end() as usize); + let param_index = stones - *self.stones_range.start() as usize; + let param = unsafe { self.params.get_unchecked(param_index) }; + + // non-pattern scores + let mut score = param.constant_score; + // Since parity_score s absorbed into constant_score, parity_score can be assumed to be 0 + // here. + // score += param.parity_score; + score += param.p_mobility_score * popcnt(board.mobility_bits()) as i16; + score += param.o_mobility_score * popcnt(board.pass_unchecked().mobility_bits()) as i16; + let mut score = score as i32; + + // pattern-based scores + let vidx = self.vectorizer.feature_indices(board); + score += self.lookup_patterns(param, vidx); + score + } + + fn score_scale() -> i16 { + SCALE + } + + fn score_min() -> i16 { + EVAL_SCORE_MIN + } + + fn score_max() -> i16 { + EVAL_SCORE_MAX + } +} + +impl Evaluator for PatternLinearEvaluator { + fn eval(&self, board: Board) -> i16 { + Self::smooth_val(self.eval_impl(board)) + } + + fn score_scale(&self) -> i16 { + Self::score_scale() + } + + fn score_min(&self) -> i16 { + Self::score_min() + } + + fn score_max(&self) -> i16 { + Self::score_max() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_smooth() { + for raw in -60000..=60000 { + let smoothed = PatternLinearEvaluator::smooth_val(raw); + assert!(smoothed > PatternLinearEvaluator::score_min()); + assert!(smoothed < PatternLinearEvaluator::score_max()); + } + } +} diff --git a/src/engine/search.rs b/src/engine/search.rs index 9b76ea0..331b9e4 100644 --- a/src/engine/search.rs +++ b/src/engine/search.rs @@ -247,11 +247,11 @@ pub fn move_ordering_impl( for &(_score, pos, next) in nexts.iter() { let mobility_score = popcnt(next.mobility_bits()) as i16; let bonus = if rem < 18 { - mobility_score * SCALE * 1 + mobility_score * solve_obj.evaluator.score_scale() * 1 } else if rem < 22 { - mobility_score * SCALE / 2 + mobility_score * solve_obj.evaluator.score_scale() / 2 } else { - mobility_score * SCALE / 4 + mobility_score * solve_obj.evaluator.score_scale() / 4 }; let mut searcher = Searcher { evaluator: solve_obj.evaluator.clone(), @@ -263,8 +263,8 @@ pub fn move_ordering_impl( let score = searcher .think( next, - EVAL_SCORE_MIN, - EVAL_SCORE_MAX, + solve_obj.evaluator.score_min(), + solve_obj.evaluator.score_max(), false, think_depth as i32 * DEPTH_SCALE, ) @@ -279,7 +279,7 @@ pub fn move_ordering_impl( let score_min = nexts[0].0; nexts .into_iter() - .filter(|e| e.0 < score_min + 16 * SCALE) + .filter(|e| e.0 < score_min + 16 * solve_obj.evaluator.score_scale()) .map(|e| (e.1, e.2)) .collect() } else { diff --git a/src/engine/think.rs b/src/engine/think.rs index e15b671..014788c 100644 --- a/src/engine/think.rs +++ b/src/engine/think.rs @@ -50,7 +50,7 @@ impl Searcher { passed: bool, depth: i32, ) -> Option<(i16, Hand)> { - let mut res = EVAL_SCORE_MIN - 1; + let mut res = self.evaluator.score_min() - 1; let mut best = None; for (i, (next, pos)) in board.next_iter().enumerate() { if i == 0 { @@ -87,7 +87,10 @@ impl Searcher { } if best == None { if passed { - return Some(((board.score() as i16) * SCALE, Hand::Pass)); + return Some(( + (board.score() as i16) * self.evaluator.score_scale(), + Hand::Pass, + )); } else { return Some(( -self @@ -113,7 +116,7 @@ impl Searcher { for (next, pos) in board.next_iter() { let use_eval_depth = 4; let bonus = if Some(pos) == old_best { - -16 * SCALE + -16 * self.evaluator.score_scale() } else { 0 }; @@ -125,13 +128,13 @@ impl Searcher { v.push(( next, pos, - bonus + weighted_mobility(&next) as i16 * SCALE + eval_score as i16, + bonus + weighted_mobility(&next) as i16 * self.evaluator.score_scale() + eval_score as i16, eval_score, 0, )); } v.sort_by(|a, b| (a.2, a.3, a.4).cmp(&(b.2, b.3, b.4))); - let mut res = EVAL_SCORE_MIN; + let mut res = self.evaluator.score_min(); let mut best = None; for (i, &(next, pos, _, eval_score, _)) in v.iter().enumerate() { if i == 0 { @@ -140,11 +143,11 @@ impl Searcher { .0; best = Some(pos); } else { - let reduce = if -eval_score < alpha - 8 * SCALE { + let reduce = if -eval_score < alpha - 8 * self.evaluator.score_scale() { 4 * DEPTH_SCALE - } else if -eval_score < alpha - 5 * SCALE { + } else if -eval_score < alpha - 5 * self.evaluator.score_scale() { 3 * DEPTH_SCALE - } else if -eval_score < alpha - 3 * SCALE { + } else if -eval_score < alpha - 3 * self.evaluator.score_scale() { 2 * DEPTH_SCALE } else { DEPTH_SCALE @@ -174,7 +177,10 @@ impl Searcher { } if v.is_empty() { if passed { - return Some(((board.score() as i16) * SCALE, Hand::Pass)); + return Some(( + (board.score() as i16) * self.evaluator.score_scale(), + Hand::Pass, + )); } else { return Some(( -self @@ -217,13 +223,17 @@ impl Searcher { if entry.depth >= depth { (entry.lower, entry.upper, entry.best) } else { - (EVAL_SCORE_MIN, EVAL_SCORE_MAX, entry.best) + ( + self.evaluator.score_min(), + self.evaluator.score_max(), + entry.best, + ) } } - None => (EVAL_SCORE_MIN, EVAL_SCORE_MAX, None), + None => (self.evaluator.score_min(), self.evaluator.score_max(), None), } } else { - (EVAL_SCORE_MIN, EVAL_SCORE_MAX, None) + (self.evaluator.score_min(), self.evaluator.score_max(), None) }; let new_alpha = alpha.max(lower); let new_beta = beta.min(upper); @@ -237,9 +247,9 @@ impl Searcher { let (res, best) = self.think_impl(board, new_alpha, new_beta, passed, old_best, depth)?; if depth >= min_cache_depth { let range = if res <= new_alpha { - (EVAL_SCORE_MIN, res) + (self.evaluator.score_min(), res) } else if res >= new_beta { - (res, EVAL_SCORE_MAX) + (res, self.evaluator.score_max()) } else { (res, res) }; @@ -271,7 +281,7 @@ impl Searcher { return Some((score, b)); } - let mut current_score = EVAL_SCORE_MIN - 1; + let mut current_score = self.evaluator.score_min() - 1; let mut current_hand = None; let mut pass = true; for (next, hand) in board.next_iter() { diff --git a/src/main.rs b/src/main.rs index 73a6b58..1750971 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![feature(const_option)] #![feature(portable_simd)] +#![feature(iterator_try_collect)] #![feature(test)] mod book; mod compression; diff --git a/src/play.rs b/src/play.rs index 6f758d6..4c067d3 100644 --- a/src/play.rs +++ b/src/play.rs @@ -3,6 +3,7 @@ use crate::engine::bits::*; use crate::engine::board::*; use crate::engine::eval::*; use crate::engine::hand::*; +use crate::engine::pattern_eval::*; use crate::engine::search::*; use crate::engine::table::*; use crate::engine::think::*; @@ -57,9 +58,15 @@ pub fn play(matches: &ArgMatches) -> Board { node_count: 0, cache_gen: solve_obj.cache_gen, }; - let (score, best, depth) = - searcher.iterative_think(board.board, EVAL_SCORE_MIN, EVAL_SCORE_MAX, false, 3, 0); - let scaled_score = score as f64 / SCALE as f64; + let (score, best, depth) = searcher.iterative_think( + board.board, + searcher.evaluator.score_min(), + searcher.evaluator.score_max(), + false, + 3, + 0, + ); + let scaled_score = score as f64 / solve_obj.evaluator.score_scale() as f64; eprintln!("Estimated result: {}, Depth: {}", scaled_score, depth); best } else { @@ -109,8 +116,14 @@ pub fn self_play(matches: &ArgMatches) -> Board { node_count: 0, cache_gen: solve_obj.cache_gen, }; - let (score, best, depth) = - searcher.iterative_think(board.board, EVAL_SCORE_MIN, EVAL_SCORE_MAX, false, 3, 0); + let (score, best, depth) = searcher.iterative_think( + board.board, + searcher.evaluator.score_min(), + searcher.evaluator.score_max(), + false, + 3, + 0, + ); let secs = start.elapsed().as_secs_f64(); let nps = (searcher.node_count as f64 / secs) as u64; eprintln!( @@ -172,8 +185,14 @@ fn self_play_worker( node_count: 0, cache_gen: solve_obj.cache_gen, }; - let (_score, best, _depth) = - searcher.iterative_think(board.board, EVAL_SCORE_MIN, EVAL_SCORE_MAX, false, 3, 0); + let (_score, best, _depth) = searcher.iterative_think( + board.board, + searcher.evaluator.score_min(), + searcher.evaluator.score_max(), + false, + 3, + 0, + ); best } else { let mut obj = solve_obj.clone(); @@ -341,8 +360,8 @@ pub fn codingame(_matches: &ArgMatches) -> Result<(), Box let (score, best, depth) = think_parallel( &searcher, board.board, - EVAL_SCORE_MIN, - EVAL_SCORE_MAX, + searcher.evaluator.score_min(), + searcher.evaluator.score_max(), false, ); eprintln!( diff --git a/src/remote.rs b/src/remote.rs index bf84cb6..be15b05 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -1,6 +1,7 @@ use crate::engine::board::*; use crate::engine::endgame::*; use crate::engine::eval::*; +use crate::engine::pattern_eval::*; use crate::engine::search::*; use crate::engine::table::*; use clap::ArgMatches; diff --git a/src/setup.rs b/src/setup.rs index 4ffc2f5..4ad7338 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,4 +1,4 @@ -use crate::engine::eval::*; +use crate::engine::pattern_eval::*; use crate::engine::search::*; use crate::engine::table::*; use std::path::Path; diff --git a/src/train.rs b/src/train.rs index 8734c03..704e4fa 100644 --- a/src/train.rs +++ b/src/train.rs @@ -2,6 +2,7 @@ use crate::engine::bits::*; use crate::engine::board::*; use crate::engine::eval::*; use crate::engine::hand::*; +use crate::engine::pattern_eval::*; use crate::engine::table::*; use crate::engine::think::*; use crate::sparse_mat::*; @@ -464,8 +465,8 @@ pub fn eval_stats(matches: &ArgMatches) -> Option<()> { for depth in 1..=depth_max { if let Some((evaluated, _)) = searcher.think( board, - EVAL_SCORE_MIN, - EVAL_SCORE_MAX, + evaluator.score_min(), + evaluator.score_max(), false, depth as i32 * DEPTH_SCALE, ) { @@ -502,7 +503,11 @@ pub fn eval_stats(matches: &ArgMatches) -> Option<()> { for idx in 1..=15 { let ratio = 1.0 - 0.7f32.powi(idx); let index = (total as f32 * ratio) as usize; - println!("{} {}", ratio, vd[index] as f32 / SCALE as f32); + println!( + "{} {}", + ratio, + vd[index] as f32 / evaluator.score_scale() as f32 + ); } } } From a789296d708cbf1fe266c539975eb241e75836c4 Mon Sep 17 00:00:00 2001 From: primenumber Date: Tue, 30 Apr 2024 23:49:42 +0900 Subject: [PATCH 4/6] Fix cfg --- src/engine/bits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/bits.rs b/src/engine/bits.rs index 09c685e..c92f3c7 100644 --- a/src/engine/bits.rs +++ b/src/engine/bits.rs @@ -33,7 +33,7 @@ impl BitManip for u64 { x } - #[cfg(target_feature = "bmi1")] + #[cfg(target_feature = "bmi2")] fn pdep(&self, mask: u64) -> u64 { unsafe { _pdep_u64(*self, mask) } } From 85131d68c5defd39c7cef539ae9b2432fc016811 Mon Sep 17 00:00:00 2001 From: primenumber Date: Sun, 5 May 2024 08:10:07 +0900 Subject: [PATCH 5/6] Return node_count from think_parallel --- src/book.rs | 2 +- src/engine/think.rs | 16 ++++++++++++---- src/play.rs | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/book.rs b/src/book.rs index 1f8d748..9e27545 100644 --- a/src/book.rs +++ b/src/book.rs @@ -192,7 +192,7 @@ fn search( node_count: 0, cache_gen: solve_obj.cache_gen, }; - let (_score, hand, _depth) = think_parallel( + let (_score, hand, _depth, _node_count) = think_parallel( &searcher, board, solve_obj.evaluator.score_min(), diff --git a/src/engine/think.rs b/src/engine/think.rs index 014788c..cebb4ac 100644 --- a/src/engine/think.rs +++ b/src/engine/think.rs @@ -350,26 +350,34 @@ pub fn think_parallel( alpha: i16, beta: i16, passed: bool, -) -> (i16, Hand, i8) { +) -> (i16, Hand, i8, usize) { thread::scope(|s| { let mut handles = Vec::new(); for i in 0..num_cpus::get() { handles.push(s.spawn(move || { let mut ctx = searcher.clone(); - ctx.iterative_think(board, alpha, beta, passed, 3, i) + let (score, hand, depth) = ctx.iterative_think(board, alpha, beta, passed, 3, i); + (score, hand, depth, ctx.node_count) })); } let mut result_depth = 0; let mut result_score = None; let mut result_hand = None; + let mut result_node_count = 0; for h in handles { - let (tscore, thand, tdepth) = h.join().unwrap(); + let (tscore, thand, tdepth, node_count) = h.join().unwrap(); + result_node_count += node_count; if tdepth > result_depth { result_depth = tdepth; result_score = Some(tscore); result_hand = Some(thand); } } - (result_score.unwrap(), result_hand.unwrap(), result_depth) + ( + result_score.unwrap(), + result_hand.unwrap(), + result_depth, + result_node_count, + ) }) } diff --git a/src/play.rs b/src/play.rs index 4c067d3..795151d 100644 --- a/src/play.rs +++ b/src/play.rs @@ -357,7 +357,7 @@ pub fn codingame(_matches: &ArgMatches) -> Result<(), Box node_count: 0, cache_gen: solve_obj.cache_gen, }; - let (score, best, depth) = think_parallel( + let (score, best, depth, node_count) = think_parallel( &searcher, board.board, searcher.evaluator.score_min(), @@ -366,7 +366,7 @@ pub fn codingame(_matches: &ArgMatches) -> Result<(), Box ); eprintln!( "Estimated result: {}, Depth: {}, Nodes: {}", - score, depth, searcher.node_count + score, depth, node_count ); best } else { From 23fc8615b0e52860130d89fb2400a5d03c0e7a88 Mon Sep 17 00:00:00 2001 From: primenumber Date: Thu, 2 May 2024 10:20:50 +0900 Subject: [PATCH 6/6] Fix benchmark --- src/engine/eval.rs | 2 -- src/engine/{eval => pattern_eval}/test.rs | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) rename src/engine/{eval => pattern_eval}/test.rs (82%) diff --git a/src/engine/eval.rs b/src/engine/eval.rs index 19e6374..d06b733 100644 --- a/src/engine/eval.rs +++ b/src/engine/eval.rs @@ -1,5 +1,3 @@ -#[cfg(test)] -mod test; use crate::engine::board::*; pub trait Evaluator: Send + Sync { diff --git a/src/engine/eval/test.rs b/src/engine/pattern_eval/test.rs similarity index 82% rename from src/engine/eval/test.rs rename to src/engine/pattern_eval/test.rs index 8bbab2e..a289ef2 100644 --- a/src/engine/eval/test.rs +++ b/src/engine/pattern_eval/test.rs @@ -1,7 +1,7 @@ extern crate test; use super::*; -use crate::setup::*; use std::io::{BufRead, BufReader}; +use std::sync::Arc; use test::Bencher; fn load_stress_test_set() -> Vec<(Board, i8)> { @@ -26,13 +26,13 @@ fn load_stress_test_set() -> Vec<(Board, i8)> { #[bench] fn bench_eval(b: &mut Bencher) { - let solve_obj = setup_default(); + let evaluator = Arc::new(PatternLinearEvaluator::load(Path::new("table-220710")).unwrap()); let dataset = load_stress_test_set(); b.iter(|| { dataset .iter() - .map(|(board, _)| solve_obj.evaluator.eval(*board) as i32) + .map(|(board, _)| evaluator.eval(*board) as i32) .sum::() }); }