diff --git a/libcmbr/src/cmbr/mod.rs b/libcmbr/src/cmbr/mod.rs index 4cdc0b8..929ea77 100644 --- a/libcmbr/src/cmbr/mod.rs +++ b/libcmbr/src/cmbr/mod.rs @@ -12,4 +12,4 @@ impl CmbrFile { pub fn serialize(&self) -> Vec { return bitcode::serialize(&self).unwrap(); } -} \ No newline at end of file +} diff --git a/libcmbr/src/cmbr/pgntocmbr.rs b/libcmbr/src/cmbr/pgntocmbr.rs index 2ce8b08..96ac374 100644 --- a/libcmbr/src/cmbr/pgntocmbr.rs +++ b/libcmbr/src/cmbr/pgntocmbr.rs @@ -81,7 +81,7 @@ impl CmbrFile { convertor: &mut SanToCmbrMvConvertor, is_compressed: bool, ) -> Result> { - debug_assert!(is_compressed == false); + debug_assert!(!is_compressed); let mut file = CmbrFile::new(is_compressed); let mut board = Chess::new(); @@ -97,7 +97,7 @@ impl CmbrFile { let len = ast.len(); - (0..len).into_iter().for_each(|game_i| { + (0..len).for_each(|game_i| { if game_i % 1000 == 0 || game_i == len { print!("{}\r", game_i as f64 / len as f64 * 100.0); let _ = std::io::stdout().flush(); @@ -126,7 +126,7 @@ impl CmbrFile { // SAFE: Safe Token::TagString(v) => unsafe { - let _ = cmbr_game.headers.push(( + cmbr_game.headers.push(( from_utf8_unchecked(current_key).to_owned(), from_utf8_unchecked(v).to_owned(), )); @@ -144,7 +144,7 @@ impl CmbrFile { variation_pointers.insert(0, 0); for (id, variation) in variations_iter { - if variation.0.len() == 0 { + if variation.0.is_empty() { eprintln!("[WARN] Empty variation on game N{game_i}. Skipping game"); break; } @@ -198,8 +198,7 @@ impl CmbrFile { Token::NAG(n) => { let mut nag_numeral = // SAFE: Safe - u32::from_str_radix(unsafe { from_utf8_unchecked(*n) }, 10) - .unwrap(); + (unsafe { from_utf8_unchecked(n) }).parse::().unwrap(); nag_numeral <<= 8; nag_numeral |= 0b00001000; @@ -237,7 +236,7 @@ impl CmbrFile { } Token::MoveAnnotation(an) => cmbr_variation.moves.push( - (((MOVE_ANNOTATION_TO_NAG[an] as u32) << 8) as u32 | 0b10000000) + (((MOVE_ANNOTATION_TO_NAG[an] as u32) << 8) | 0b10000000) .into(), ), diff --git a/libcmbr/src/cmbr/santocmbrmv.rs b/libcmbr/src/cmbr/santocmbrmv.rs index fefae19..986da7b 100644 --- a/libcmbr/src/cmbr/santocmbrmv.rs +++ b/libcmbr/src/cmbr/santocmbrmv.rs @@ -98,8 +98,8 @@ impl SanToCmbrMvConvertor { promotion, } => Self::shakmaty_move_to_cmbr( role, - &from, - &to, + from, + to, &capture.is_some(), promotion, &san.suffix, @@ -134,8 +134,8 @@ impl SanToCmbrMvConvertor { shakmaty::Move::EnPassant { from, to } => Self::shakmaty_move_to_cmbr( &Role::Pawn, - &from, - &to, + from, + to, &true, &None, &san.suffix, diff --git a/libcmbr/src/cmbr/structs.rs b/libcmbr/src/cmbr/structs.rs index 511832b..69f34d8 100644 --- a/libcmbr/src/cmbr/structs.rs +++ b/libcmbr/src/cmbr/structs.rs @@ -13,7 +13,7 @@ def_enum! ( FlagCapture => 1 << 2, FlagNag => 1 << 3, // If this flag is set, the first 8 bits of the CMBR are replaced with a NAG index (https://w.wiki/AWUT) - FlagPromotesBishop => (1 << 6) | 0b000000, + FlagPromotesBishop => (1 << 6), FlagPromotesKnight => (1 << 6) | 0b010000, FlagPromotesRook => (1 << 6) | 0b100000, FlagPromotesQueen => (1 << 6) | 0b110000, @@ -63,7 +63,7 @@ pub struct CmbrFile { /// A Struct denoting the structure of a game represented in CMBR #[cfg_attr(feature = "bitcode", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct CmbrGame { pub headers: Vec<(String, String)>, /// Possible values: 'w', 'b', 'd', 'u'. @@ -122,4 +122,4 @@ impl CmbrVariation { comments: Vec::new(), }; } -} \ No newline at end of file +} diff --git a/libcmbr/src/cmbr/tests.rs b/libcmbr/src/cmbr/tests.rs index 5b19143..496bce4 100644 --- a/libcmbr/src/cmbr/tests.rs +++ b/libcmbr/src/cmbr/tests.rs @@ -48,16 +48,11 @@ mod cmbr_tests { let mut board = Chess::new(); for token in variation.0 { - match token { - PgnToken::Token(t) => match t { - Token::Move(san) => { - let cmbr = convertor.san_to_cmbr(&mut board, san).unwrap(); - cmbrs.push(cmbr); - } - _ => {} - }, - - _ => {} + if let PgnToken::Token(t) = token { + if let Token::Move(san) = t { + let cmbr = convertor.san_to_cmbr(&mut board, san).unwrap(); + cmbrs.push(cmbr); + } } } } @@ -77,57 +72,58 @@ mod cmbr_tests { assert_eq!(expected_vec, cmbrs); } - - #[cfg(feature = "benchmark")] - #[bench] - fn bench_san_cmbr(b: &mut Bencher) { - let file_path = get_project_root().unwrap().join("data/twic1544.pgn"); - let file = File::open(file_path.clone()); - - if file.is_err() { - panic!( - "[ERROR] {}. File path: {:?}", - file.err().unwrap(), - file_path - ); - } - - // SAFE: Safe - let file = unsafe { file.unwrap_unchecked() }; - let mmap = unsafe { Mmap::map(&file) }; - if mmap.is_err() { - panic!("[ERROR] {}", mmap.err().unwrap()); - } - - let mut mmap = mmap.unwrap(); - let ast = pgn::parse_pgn(&mut mmap); - let mut convertor = SanToCmbrMvConvertor::new(/* 128MB */ 128 * 1024 * 1024); - - b.iter(|| { - 'game: for game in &ast { - // This clone is fucking it up - for (_, variation) in (&game).0 .1.clone() { - let mut board = Chess::new(); - - for token in variation.0 { - match token { - PgnToken::Token(t) => match t { - Token::Move(san) => { - let cmbr = convertor.san_to_cmbr(&mut board, san); - - if cmbr.is_err() { - continue 'game; - } - } - _ => {} - }, - - _ => {} - } - } - } - } - }); - } + // FIXME: bench_san_cmbr is broken + // #[cfg(feature = "benchmark")] + // #[bench] + // fn bench_san_cmbr(b: &mut Bencher) { + // let file_path = get_project_root().unwrap().join("data/twic1544.pgn"); + // let file = File::open(file_path.clone()); + + // if file.is_err() { + // panic!( + // "[ERROR] {}. File path: {:?}", + // file.err().unwrap(), + // file_path + // ); + // } + + // // SAFE: Safe + // let file = unsafe { file.unwrap_unchecked() }; + // let mmap = unsafe { Mmap::map(&file) }; + + // if mmap.is_err() { + // panic!("[ERROR] {}", mmap.err().unwrap()); + // } + + // let mut mmap = mmap.unwrap(); + // let ast = pgn::parse_pgn(&mut mmap); + // let mut convertor = SanToCmbrMvConvertor::new(/* 128MB */ 128 * 1024 * 1024); + + // b.iter(|| { + // 'game: for game in &ast { + // // This clone is fucking it up + // for (_, variation) in (&game).0 .1.clone() { + // let mut board = Chess::new(); + + // for token in variation.0 { + // match token { + // PgnToken::Token(t) => match t { + // Token::Move(san) => { + // let cmbr = convertor.san_to_cmbr(&mut board, san); + + // if cmbr.is_err() { + // continue 'game; + // } + // } + // _ => {} + // }, + + // _ => {} + // } + // } + // } + // } + // }); + // } } diff --git a/libcmbr/src/lib.rs b/libcmbr/src/lib.rs index 17f7a46..922093a 100644 --- a/libcmbr/src/lib.rs +++ b/libcmbr/src/lib.rs @@ -1,5 +1,5 @@ -#![allow(non_upper_case_globals)] -#![feature(test, map_try_insert)] +#![allow(non_upper_case_globals, clippy::needless_return)] +#![feature(test, map_try_insert, stmt_expr_attributes)] use cfg_if::cfg_if; diff --git a/libcmbr/src/pgn/ast.rs b/libcmbr/src/pgn/ast.rs index 09c018b..038a18a 100644 --- a/libcmbr/src/pgn/ast.rs +++ b/libcmbr/src/pgn/ast.rs @@ -26,7 +26,7 @@ pub struct PgnVariation<'a>(pub Vec>); #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct PgnGame<'a> { pub global_tokens: Vec>, - pub variations: LiteMap> + pub variations: LiteMap>, } /// Builds an ast (represented as `a Vec`) from the inputted Token list @@ -44,7 +44,7 @@ pub fn build_pgn_ast<'a>(tokens: &mut VecDeque>) -> Vec> { value.variations.insert(0, PgnVariation::default()); } - while tokens.len() != 0 { + while !tokens.is_empty() { next_token( tokens, &mut tree, @@ -64,7 +64,7 @@ macro_rules! push_token { $tree .get_mut($game_number as usize) .unwrap() - .variations + .variations .get_mut($variation_depth) .unwrap() .0 @@ -95,14 +95,14 @@ fn next_token<'a>( Token::TagSymbol(_) | Token::TagString(_) => tree .get_mut(*game_number as usize) .unwrap() - .global_tokens + .global_tokens .push(token), Token::NullMove(_) => {} Token::EscapeComment(_) => { /* NOTE: IDK what to do with this */ } Token::Result(_) => { tree.get_mut(*game_number as usize) .unwrap() - .global_tokens + .global_tokens .push(token); *game_number += 1; @@ -114,7 +114,9 @@ fn next_token<'a>( let value = &mut tree.get_unchecked_mut(*game_number as usize); value.global_tokens = Vec::new(); - value.variations.insert(variation_depth, PgnVariation::default()); + value + .variations + .insert(variation_depth, PgnVariation::default()); } } Token::StartVariation(_) => { @@ -132,7 +134,9 @@ fn next_token<'a>( // SAFE: Safe unsafe { let value = &mut tree.get_unchecked_mut(*game_number as usize); - value.variations.insert(new_variation_depth, PgnVariation::default()); + value + .variations + .insert(new_variation_depth, PgnVariation::default()); } next_token( @@ -148,7 +152,7 @@ fn next_token<'a>( } } - if tokens.len() != 0 { + if !tokens.is_empty() { next_token( tokens, tree, diff --git a/libcmbr/src/position/mod.rs b/libcmbr/src/position/mod.rs index 1a06c5f..ecf97f1 100644 --- a/libcmbr/src/position/mod.rs +++ b/libcmbr/src/position/mod.rs @@ -1,105 +1,305 @@ -use shakmaty::{Chess, Position, Square}; use bit_vec::BitVec; +use phf::phf_map; +use shakmaty::{Bitboard, Chess, Color, Position}; -pub type PytorchPosition = Vec; +pub type CompressedPosition = Vec; -pub trait PytorchPositionConvertable { - fn to_pytorch_position(s: &T) -> PytorchPosition; - fn from_pytorch_position(position: PytorchPosition) -> T; +pub trait CompressedPositionConvertable { + fn to_pytorch_position(s: &T) -> CompressedPosition; + fn from_pytorch_position(position: &mut CompressedPosition) -> String; } -impl PytorchPositionConvertable for Chess { - fn to_pytorch_position(s: &Chess) -> PytorchPosition { - let mut position = [0u8; 1024]; - let mut position_i = 0; - - let occupancy = s.board().occupied().0; - let occupancy_bytes = occupancy.to_ne_bytes(); - for byte in occupancy_bytes { - position[position_i] = byte; - position_i += 1; - } +impl CompressedPositionConvertable for Chess { + fn to_pytorch_position(s: &Chess) -> CompressedPosition { + let mut encoded = CompressedPosition::new(); + + let occupied = s + .board() + .occupied() + .0 + .to_ne_bytes() + .into_iter() + .collect::>(); + + occupied.iter().for_each(|byte| encoded.push(*byte)); + + let piece_lookup = phf_map! { + 'K' => 0, 'Q' => 1, 'R' => 2, 'B' => 3, 'N' => 4, 'P' => 5, + 'k' => 6, 'q' => 7, 'r' => 8, 'b' => 9, 'n' => 10, 'p' => 11u8, + }; - let mut pieces_bitvec = BitVec::from_elem(1024, false); - let mut pieces_bitvec_i = 0; + // FIXME: Don't generate a fen. Performance issue + let fen = format!("{}", s.board().board_fen(shakmaty::Bitboard(0))); - for square in 0..64u8 { - // SAFE: Safe - let square: Square = unsafe { std::mem::transmute(square) }; - let piece_at = s.board().piece_at(square); + let mut bitvec = BitVec::with_capacity(1024); - if piece_at.is_none() { + for char in fen.chars() { + if char == ' ' { + break; + } + if char == '/' || !char.is_alphabetic() { continue; } - // SAFE: Safe - let piece_at = unsafe { piece_at.unwrap_unchecked() }; - let piece_bits = (piece_at.color as u8) << 3 | (piece_at.role as u8); - let bits_to_append_n = 8 - piece_bits.trailing_zeros(); - - for j in 0..bits_to_append_n { - pieces_bitvec.set(pieces_bitvec_i, (piece_bits & (1 << j)) != 0); - pieces_bitvec_i += 1; + let lookup = piece_lookup[&char]; + // FIXME: This is also probably bad + for i in 0..4 { + bitvec.push(lookup & (1 << i) != 0); + // bitvec.set(bitvec_i + i, lookup & (1 << i) != 0); } } - let bitvec_bytes = &pieces_bitvec.to_bytes()[0..(pieces_bitvec_i + (8 - (pieces_bitvec_i % 8))) / 8]; - for byte in bitvec_bytes { - position[position_i] = *byte; - position_i += 1; - } + bitvec.push(s.turn() == Color::White); - let can_castle_wk = s.castles().rook(shakmaty::Color::White, shakmaty::CastlingSide::KingSide).is_some(); - let can_castle_bk = s.castles().rook(shakmaty::Color::Black, shakmaty::CastlingSide::KingSide).is_some(); - let can_castle_wq = s.castles().rook(shakmaty::Color::White, shakmaty::CastlingSide::QueenSide).is_some(); - let can_castle_bq = s.castles().rook(shakmaty::Color::Black, shakmaty::CastlingSide::QueenSide).is_some(); + let can_castle_wk = s + .castles() + .rook(shakmaty::Color::White, shakmaty::CastlingSide::KingSide) + .is_some(); + let can_castle_bk = s + .castles() + .rook(shakmaty::Color::Black, shakmaty::CastlingSide::KingSide) + .is_some(); + let can_castle_wq = s + .castles() + .rook(shakmaty::Color::White, shakmaty::CastlingSide::QueenSide) + .is_some(); + let can_castle_bq = s + .castles() + .rook(shakmaty::Color::Black, shakmaty::CastlingSide::QueenSide) + .is_some(); - let mut castle_en_passant_mask = BitVec::from_elem(16, false); - castle_en_passant_mask.set(0, can_castle_wk); - castle_en_passant_mask.set(1, can_castle_bk); - castle_en_passant_mask.set(2, can_castle_wq); - castle_en_passant_mask.set(3, can_castle_bq); + bitvec.push(can_castle_wk); + bitvec.push(can_castle_wq); + bitvec.push(can_castle_bk); + bitvec.push(can_castle_bq); - let ep_square = s.ep_square(shakmaty::EnPassantMode::Legal); - let ep_bits = if ep_square.is_none() { - 0b11111111 + let square = s.ep_square(shakmaty::EnPassantMode::Legal); + let square_bits = if square.is_some() { + square.unwrap() as u8 } else { - ep_square.unwrap() as u8 + u8::MAX }; - for j in 0..8 { - castle_en_passant_mask.set(j + 4, ep_bits & (1 << j) != 0); + for i in 0..8 { + bitvec.push(square_bits & (1 << i) != 0); } - let castle_en_passant_mask = castle_en_passant_mask.to_bytes(); + // let bits_to_bytes = (bitvec_i + 7) / 8; + let bitvec_bytes = bitvec.to_bytes(); - for byte in castle_en_passant_mask { - position[position_i] = byte; - position_i += 1; - } + bitvec_bytes.iter().for_each(|byte| encoded.push(*byte)); - let position_truncated = position[0..position_i].to_vec(); - return position_truncated; + return encoded; } - fn from_pytorch_position(_position: PytorchPosition) -> Self { - todo!(); + fn from_pytorch_position(position: &mut CompressedPosition) -> String { + let piece_lookup = phf_map! { + 0u8 => 'K', 1u8 => 'Q', 2u8 => 'R', 3u8 => 'B', 4u8 => 'N', 5u8 => 'P', + 6u8 => 'k', 7u8 => 'q', 8u8 => 'r', 9u8 => 'b', 10u8 => 'n', 11u8 => 'p', + }; + + let occupied = Bitboard(u64::from_ne_bytes(position[0..8].try_into().unwrap())); + let number_of_pieces = occupied.0.count_ones(); + + position.drain(0..8); + + let bitvec = BitVec::from_bytes(&position[0..position.len()]); + + let mut fen = String::with_capacity(64); + + let mut read_piece_count = 0; + let mut empty = 0; + let mut bitvec_i = 0; + + // FIXME: Optimize this to only loop through occupied squares + for rank in (0..8).rev() { + for file in 0..8 { + 'occupancy_check: { + let i = rank * 8 + file; + let bit = occupied.0 & (1 << i) != 0; + + if !bit { + empty += 1; + + break 'occupancy_check; + } else if empty != 0 { + let empty_as_char = (empty + b'0') as char; + fen.push(empty_as_char); + + empty = 0; + } + + // SAFE: Safe + let piece = unsafe { + (bitvec.get_unchecked(read_piece_count * 4 + 0) as u8) << 0 + | (bitvec.get_unchecked(read_piece_count * 4 + 1) as u8) << 1 + | (bitvec.get_unchecked(read_piece_count * 4 + 2) as u8) << 2 + | (bitvec.get_unchecked(read_piece_count * 4 + 3) as u8) << 3 + }; + + read_piece_count += 1; + + println!("{:08b} | read_piece_count: {read_piece_count} | number_of_pieces {number_of_pieces}", piece); + + let piece_char = piece_lookup[&piece]; + fen.push(piece_char); + } + + if empty == 8 || (file == 7 && empty != 0) { + let empty_as_char = (empty + b'0') as char; + fen.push(empty_as_char); + + empty = 0; + } + + if file == 7 { + empty = 0; + fen.push('/'); + } + } + } + + debug_assert!(read_piece_count == number_of_pieces as usize); + fen.pop(); + + bitvec_i += read_piece_count * 4; + + let turn = if bitvec.get(bitvec_i) == Some(true) { + " w " + } else { + " b " + }; + + fen.push_str(turn); + + bitvec_i += 1; + + let can_castle_wk = if bitvec.get(bitvec_i + 0).unwrap() { + "K" + } else { + "" + }; + let can_castle_wq = if bitvec.get(bitvec_i + 1).unwrap() { + "Q" + } else { + "" + }; + let can_castle_bk = if bitvec.get(bitvec_i + 2).unwrap() { + "k" + } else { + "" + }; + let can_castle_bq = if bitvec.get(bitvec_i + 3).unwrap() { + "q" + } else { + "" + }; + + if can_castle_wk.is_empty() + && can_castle_wq.is_empty() + && can_castle_bk.is_empty() + && can_castle_bq.is_empty() + { + fen.pop(); + } + + fen.push_str(can_castle_wk); + fen.push_str(can_castle_wq); + fen.push_str(can_castle_bk); + fen.push_str(can_castle_bq); + + bitvec_i += 4; + + let en_passant_bits = unsafe { + (bitvec.get_unchecked(bitvec_i + 0) as u8) << 0 + | (bitvec.get(bitvec_i + 1).unwrap() as u8) << 1 + | (bitvec.get(bitvec_i + 2).unwrap() as u8) << 2 + | (bitvec.get(bitvec_i + 3).unwrap() as u8) << 3 + | (bitvec.get(bitvec_i + 4).unwrap() as u8) << 4 + | (bitvec.get(bitvec_i + 5).unwrap() as u8) << 5 + | (bitvec.get(bitvec_i + 6).unwrap() as u8) << 6 + }; + + let en_pessant_squre = if en_passant_bits == u8::MAX & !(1 << 7) { + " -" + } else { + &format!( + " {}{}", + ((en_passant_bits % 8) + b'a') as char, + ((en_passant_bits / 8) + b'1') as char + ) + }; + + fen.push_str(en_pessant_squre); + + return fen; } } #[cfg(test)] mod tests { - use shakmaty::{fen::Fen, Chess, FromSetup}; use super::*; + use shakmaty::{fen::Fen, Chess, FromSetup}; + + #[cfg(feature = "benchmark")] + extern crate test; + #[cfg(feature = "benchmark")] + use test::Bencher; #[test] - pub fn test_to_pytorch_position() { - let starting_fen = Fen::from_ascii(b"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap(); - let starting_setup = starting_fen.as_setup(); - let starting = Chess::from_setup(starting_setup.clone(), shakmaty::CastlingMode::Standard).unwrap(); + pub fn test_to_and_from_pytorch_position() { + let fens_to_test: &[&[u8]] = &[ + b"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -", + b"rnbqkb1r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKB1R w KQkq -", + b"5k2/1pp1nb2/1r1p4/1P6/2P3R1/6P1/P1B5/b1K5 w -", + b"1rbqk2r/p1p1pnbp/1p1p2p1/n5PQ/R3pP1P/1PP5/3PB3/1NB1K1NR w Kk -", + b"1rbqk2r/p1pppnbp/2n2pp1/Pp5Q/4PP1P/1P6/2PPB1P1/RNB1K1NR w KQk b6", + b"r1b1k2r/1n1q1p1p/p1p1p1p1/1p1pP1b1/1N1P1P1P/P1P1Q1P1/1P1nN1B1/R1B1K2R b KQkq -", + ]; + + for fen_str in fens_to_test { + let fen = Fen::from_ascii(fen_str).unwrap(); + let setup = fen.as_setup(); + let position = + Chess::from_setup(setup.clone(), shakmaty::CastlingMode::Standard).unwrap(); + + let mut pytorch_position = Chess::to_pytorch_position(&position); + let pytorch_fen = Chess::from_pytorch_position(&mut pytorch_position); - let pytorch_position = Chess::to_pytorch_position(&starting); + println!( + "Pytorch length: {} | Fen length: {} | Absolute diff: {} | Percent change: {:.2}%", + pytorch_position.len(), + fen_str.len(), + pytorch_position.len().abs_diff(fen_str.len()), + (1.0 - (fen_str.len() as f64) / (pytorch_position.len() as f64)) * 100.0 + ); - println!("{pytorch_position:?} {}", pytorch_position.len()); + assert_eq!(&pytorch_fen, std::str::from_utf8(fen_str).unwrap()); + } + } + + #[cfg(feature = "benchmark")] + #[bench] + fn bench_to_pytorch_position(b: &mut Bencher) { + let fens_to_test: &[&[u8]] = &[ + b"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -", + b"rnbqkb1r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKB1R w KQkq -", + b"5k2/1pp1nb2/1r1p4/1P6/2P3R1/6P1/P1B5/b1K5 w -", + b"1rbqk2r/p1p1pnbp/1p1p2p1/n5PQ/R3pP1P/1PP5/3PB3/1NB1K1NR w Kk -", + b"1rbqk2r/p1pppnbp/2n2pp1/Pp5Q/4PP1P/1P6/2PPB1P1/RNB1K1NR w KQk b6", + b"r1b1k2r/1n1q1p1p/p1p1p1p1/1p1pP1b1/1N1P1P1P/P1P1Q1P1/1P1nN1B1/R1B1K2R b KQkq -", + ]; + + for fen_str in fens_to_test { + let fen = Fen::from_ascii(fen_str).unwrap(); + let setup = fen.as_setup(); + let position = + Chess::from_setup(setup.clone(), shakmaty::CastlingMode::Standard).unwrap(); + + b.iter(|| { + let pytorch_position = Chess::to_pytorch_position(&position); + }); + + } } -} \ No newline at end of file +}