diff --git a/dataAnalysis/chess/Rchess/R/RcppExports.R b/dataAnalysis/chess/Rchess/R/RcppExports.R index 8c05a31..153c053 100644 --- a/dataAnalysis/chess/Rchess/R/RcppExports.R +++ b/dataAnalysis/chess/Rchess/R/RcppExports.R @@ -6,17 +6,53 @@ HCE <- function(positions) { .Call(`_Rchess_HCE`, positions) } +#' Given a FEN (position) determines if its whites turn +isWhiteTurn <- function(positions) { + .Call(`_Rchess_isWhiteTurn`, positions) +} + +#' Check if current side to move is in check +isCheck <- function(positions) { + .Call(`_Rchess_isCheck`, positions) +} + +#' Check if the current position is a quiet position (no piece is attacked) +isQuiet <- function(positions) { + .Call(`_Rchess_isQuiet`, positions) +} + +#' Check if position is terminal +#' +#' Checks if the position is a terminal position, meaning if the game ended +#' by mate, stale mate or the 50 modes rule. Three-Fold repetition is NOT +#' checked, therefore a seperate game history is required which the board +#' does NOT track. +#' +isTerminal <- function(positions) { + .Call(`_Rchess_isTerminal`, positions) +} + +#' Check if checkmate is possible by material on the board +#' +#' Checks if there is sufficient mating material on the board, meaning if it +#' possible for any side to deliver a check mate. More specifically, it +#' checks if the pieces on the board are KK, KNK or KBK. +#' +isInsufficient <- function(positions) { + .Call(`_Rchess_isInsufficient`, positions) +} + #' Specialized version of `read_cyclic.cpp` taylored to work in conjunction with #' `gmlm_chess()` as data generator to provide random draws from a FEN data set #' with scores filtered to be in in the range `score_min` to `score_max`. #' -data.gen <- function(file, sample_size, score_min = -5.0, score_max = +5.0, quiet = FALSE, min_ply_count = 10L) { - .Call(`_Rchess_data_gen`, file, sample_size, score_min, score_max, quiet, min_ply_count) +data.gen <- function(file, sample_size, score_min = -5.0, score_max = +5.0, quiet = FALSE, min_ply_count = 10L, white_only = TRUE) { + .Call(`_Rchess_data_gen`, file, sample_size, score_min, score_max, quiet, min_ply_count, white_only) } #' Human Crafted Evaluation -eval.psqt <- function(positions, psqt) { - .Call(`_Rchess_eval_psqt`, positions, psqt) +eval.psqt <- function(positions, psqt, pawn_structure = FALSE, eval_rooks = FALSE, eval_king = FALSE) { + .Call(`_Rchess_eval_psqt`, positions, psqt, pawn_structure, eval_rooks, eval_king) } #' Convert a legal FEN string to a 3D binary (integer with 0-1 entries) array diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.cpp b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.cpp index d83fb56..c547e23 100644 --- a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.cpp +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.cpp @@ -946,7 +946,7 @@ Score Board::evalPawns(enum piece color) const { if (color == white) { for (u64 sq = pawns; sq; sq &= sq - 1) { Index i = bitScanLS(sq); - score += pieceSquareTables[pawn][i]; + score += PSQT[pawn][i]; } // Backwards pawns (not isolated but behind all adjacent friendly pawns) u64 backwards = pawns & ~isolated; @@ -963,7 +963,7 @@ Score Board::evalPawns(enum piece color) const { } else { // color == black for (u64 sq = pawns; sq; sq &= sq - 1) { Index i = bitScanLS(sq); - score += pieceSquareTables[pawn][63 - i]; + score += PSQT[pawn][63 - i]; } // Backwards pawns (not isolated but behind all adjacent friendly pawns) u64 backwards = pawns & ~isolated; @@ -988,7 +988,7 @@ Score Board::evalKingSafety(enum piece color) const { ? bitScanLS( _bitBoard[white] & _bitBoard[king]) : bitScanLS(bitFlip(_bitBoard[black] & _bitBoard[king])); - Score score = pieceSquareTables[king][kingSq]; + Score score = PSQT[king][kingSq]; if ((fileIndex(kingSq) < 3) || (4 < fileIndex(kingSq))) { // King is castled // Pawn shields are the least advanced pawns per file @@ -1043,7 +1043,7 @@ Score Board::evalRooks(enum piece color) const { for (u64 sq = rooks; sq; sq &= sq - 1) { Index sqIndex = bitScanLS(sq); // Piece square table (accounts for rook on seventh bonus) - score += pieceSquareTables[rook][sqIndex]; + score += PSQT[rook][sqIndex]; // Add bonuses for semi-open and open files if (bitMask(sqIndex) & openFiles) { @@ -1063,22 +1063,10 @@ Score Board::evalRooks(enum piece color) const { // position fen r1bq1rk1/pp2p1bp/2np4/5B2/nP3P2/N1P2N2/6PP/R1B1QRK1 b - - 0 4 Score Board::evaluate() const { - constexpr Score pstKingEndgame[64] = { // TODO: Proper parameters file, ... - 0, 10, 20, 30, 30, 20, 10, 0, - 10, 20, 30, 40, 40, 30, 20, 10, - 20, 30, 40, 50, 50, 40, 30, 20, - 30, 40, 50, 60, 60, 50, 40, 30, - 30, 40, 50, 60, 60, 50, 40, 30, - 20, 30, 40, 50, 50, 40, 30, 20, - 10, 20, 30, 40, 40, 30, 20, 10, - 0, 10, 20, 30, 30, 20, 10, 0 - }; - constexpr Score maxMaterial = 8 * pieceValues[pawn] + 2 * (pieceValues[rook] + pieceValues[knight] + pieceValues[bishop]) + pieceValues[queen]; - // Start score with material values Score whiteMaterial = evalMaterial(white); Score blackMaterial = evalMaterial(black); @@ -1089,11 +1077,11 @@ Score Board::evaluate() const { for (enum piece type : { queen, bishop, knight }) { // White pieces for (u64 sq = _bitBoard[white] & _bitBoard[type]; sq; sq &= sq - 1) { - score += pieceSquareTables[type][bitScanLS(sq)]; + score += PSQT[type][bitScanLS(sq)]; } // and black pieces for (u64 sq = _bitBoard[black] & _bitBoard[type]; sq; sq &= sq - 1) { - score -= pieceSquareTables[type][63 - bitScanLS(sq)]; + score -= PSQT[type][63 - bitScanLS(sq)]; } } @@ -1107,7 +1095,7 @@ Score Board::evaluate() const { if (blackMaterial <= 1200) { // Endgame: // No king safety, but more king mobility in the center - score += pstKingEndgame[bitScanLS(_bitBoard[white] & _bitBoard[king])]; + score += PSQT[kingEG][bitScanLS(_bitBoard[white] & _bitBoard[king])]; } else { // Middle Game: // King safety weighted by opponents material (The less pieces the enemy @@ -1117,7 +1105,7 @@ Score Board::evaluate() const { } // and the same for the black king with opposite sign if (whiteMaterial <= 1200) { - score -= pstKingEndgame[bitScanLS(_bitBoard[black] & _bitBoard[king])]; + score -= PSQT[kingEG][63 - bitScanLS(_bitBoard[black] & _bitBoard[king])]; } else { score -= (5 * whiteMaterial * evalKingSafety(black)) / (4 * maxMaterial); } diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Move.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Move.h index b8e09b2..a538e84 100644 --- a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Move.h +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Move.h @@ -94,7 +94,7 @@ public: // add 128 to ensure the PST values are positive const Index t = color() == white ? to() : 63 - to(); const Index f = color() == white ? from() : 63 - from(); - const uint32_t pst = pieceSquareTables[piece()][t] - pieceSquareTables[piece()][f] + 128; + const uint32_t pst = PSQT[piece()][t] - PSQT[piece()][f] + 128; return ((static_cast(victim()) * mvv_lva) << (14 + winning * 6)) + pst; } diff --git a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/types.h b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/types.h index 50122dc..ca92594 100644 --- a/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/types.h +++ b/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/types.h @@ -39,7 +39,8 @@ enum piece { bishop = 4, rook = 5, queen = 6, - king = 7 + king = 7, + kingEG = 8 // Lookup index for king end game PSQT }; enum square : Index { @@ -69,10 +70,10 @@ enum location { constexpr Score pieceValues[8] = { 0, 0, // white, black (irrelevant) 100, // pawn - 300, // knight - 300, // bishop - 500, // rook - 900, // queen + 295, // knight + 315, // bishop + 450, // rook + 870, // queen 0 // king (irrelevant, always 2 opposite kings) }; @@ -132,64 +133,76 @@ constexpr u64 kingMoveLookup[64] = { using std::cerr; #endif -// Piece Square tables (from TSCP) -// see: https://www.chessprogramming.org/Simplified_Evaluation_Function -constexpr Score pieceSquareTables[8][64] = { +// Piece SQuare Tables (partially automated tuned tables via supervised +// optimization using stockfish [https://stockfishchess.org/] evaluated positions +// from the lichess database [https://database.lichess.org/]) +// endgame table: https://www.chessprogramming.org/Simplified_Evaluation_Function +// Which is addapted by adding 50. then scaled by 2 / 3 and rounded. +constexpr Score PSQT[9][64] = { { }, { }, // white, black (empty) { // pawn (white) 0, 0, 0, 0, 0, 0, 0, 0, - 5, 10, 15, 20, 20, 15, 10, 5, - 4, 8, 12, 16, 16, 12, 8, 4, - 3, 6, 9, 12, 12, 9, 6, 3, - 2, 4, 6, 8, 8, 6, 4, 2, - 1, 2, 3, -10, -10, 3, 2, 1, - 0, 0, 0, -40, -40, 0, 0, 0, + 109, 82, 89, 25, 25, 89, 82, 109, + 21, 18, -3, 18, 18, -3, 18, 21, + -12, -1, -19, 6, 6, -19, -1, -12, + -25, -15, -22, 9, 9, -22, -15, -25, + -25, -11, -27, -23, -23, -27, -11, -25, + -25, -13, -23, -29, -29, -23, -13, -25, 0, 0, 0, 0, 0, 0, 0, 0 }, { // knight (white) - -10, -10, -10, -10, -10, -10, -10, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, 0, 5, 5, 5, 5, 0, -10, - -10, 0, 5, 10, 10, 5, 0, -10, - -10, 0, 5, 10, 10, 5, 0, -10, - -10, 0, 5, 5, 5, 5, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, -30, -10, -10, -10, -10, -30, -10 }, + -90, -80, -18, 26, 26, -18, -80, -90, + -40, -13, 21, -22, -22, 21, -13, -40, + 6, 2, 32, 38, 38, 32, 2, 6, + -9, -11, 22, 20, 20, 22, -11, -9, + -13, -11, 14, 2, 2, 14, -11, -13, + -25, -10, 2, 3, 3, 2, -10, -25, + -21, -54, -12, -8, -8, -12, -54, -21, + -76, -21, -38, -34, -34, -38, -21, -76 }, { // bishop (white) - -10, -10, -10, -10, -10, -10, -10, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, 0, 5, 5, 5, 5, 0, -10, - -10, 0, 5, 10, 10, 5, 0, -10, - -10, 0, 5, 10, 10, 5, 0, -10, - -10, 0, 5, 5, 5, 5, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, -10, -20, -10, -10, -20, -10, -10 }, + -7, 19, 3, -21, -21, 3, 19, -7, + -15, -5, 6, 40, 40, 6, -5, -15, + 12, 14, 18, 32, 32, 18, 14, 12, + -5, -2, 17, 26, 26, 17, -2, -5, + -19, -2, 2, 8, 8, 2, -2, -19, + 2, 4, 2, 8, 8, 2, 4, 2, + -4, 8, 3, 1, 1, 3, 8, -4, + -31, -13, -7, -20, -20, -7, -13, -31 }, { // rook (white) - 0, 0, 0, 0, 0, 0, 0, 0, - 20, 20, 20, 20, 20, 20, 20, 20, // rook on seventh bonus - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 }, + -5, -2, 23, 40, 40, 23, -2, -5, + 18, 17, 42, 25, 25, 42, 17, 18, + 22, 14, 33, 40, 40, 33, 14, 22, + 21, 16, 20, 28, 28, 20, 16, 21, + -4, -13, -5, 3, 3, -5, -13, -4, + -20, -2, -3, -2, -2, -3, -2, -20, + -11, -13, 0, -6, -6, 0, -13, -11, + -17, -4, 0, 7, 7, 0, -4, -17 }, { // queen (white) - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 }, + -55, -29, 59, 19, 19, 59, -29, -55, + 12, -18, 34, 85, 85, 34, -18, 12, + 33, 17, 31, 34, 34, 31, 17, 33, + 51, 16, 21, 18, 18, 21, 16, 51, + -3, 24, 18, 26, 26, 18, 24, -3, + 11, 14, 24, 2, 2, 24, 14, 11, + 28, 5, 17, 15, 15, 17, 5, 28, + 1, -10, -14, 18, 18, -14, -10, 1 }, { // king middle game (white) - -40, -40, -40, -40, -40, -40, -40, -40, - -40, -40, -40, -40, -40, -40, -40, -40, - -40, -40, -40, -40, -40, -40, -40, -40, - -40, -40, -40, -40, -40, -40, -40, -40, - -40, -40, -40, -40, -40, -40, -40, -40, - -40, -40, -40, -40, -40, -40, -40, -40, - -20, -20, -20, -20, -20, -20, -20, -20, - 0, 20, 40, -20, 0, -20, 40, 20 } + -5, -5, -5, -5, -5, -5, -5, -5, + -5, -5, -5, -5, -5, -5, -5, -5, + -5, -5, -5, -5, -5, -5, -5, -5, + -5, -5, -5, -5, -5, -5, -5, -5, + -5, -5, -5, -5, -5, -5, -5, -5, + -5, -5, -5, -5, -5, -5, -5, -5, + -4, -4, -4, -4, -4, -4, -4, -4, + 24, 13, 3, -28, 2, -14, 15, 1 }, + { // king end game (white) // TODO: self/supervised tuning + 0, 7, 13, 20, 20, 13, 7, 0, + 13, 20, 27, 33, 33, 27, 20, 13, + 13, 27, 47, 53, 53, 47, 27, 13, + 13, 27, 53, 60, 60, 53, 27, 13, + 13, 27, 53, 60, 60, 53, 27, 13, + 13, 27, 47, 53, 53, 47, 27, 13, + 13, 13, 33, 33, 33, 33, 13, 13, + 0, 13, 13, 13, 13, 13, 13, 0 } }; #endif /* INCLUDE_GUARD_TYPES_H */ diff --git a/dataAnalysis/chess/Rchess/src/HCE.cpp b/dataAnalysis/chess/Rchess/src/HCE.cpp new file mode 100644 index 0000000..0dae542 --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/HCE.cpp @@ -0,0 +1,16 @@ +#include +#include + +#include "SchachHoernchen/Move.h" +#include "SchachHoernchen/Board.h" + +//' Human Crafted Evaluation +// [[Rcpp::export(rng = false)]] +Rcpp::NumericVector HCE(const std::vector& positions) { + // Iterate all positions and call the static board evaluation + return Rcpp::NumericVector(positions.begin(), positions.end(), + [](const Board& pos) { + return (double)pos.evaluate() / 100.0; + } + ); +} diff --git a/dataAnalysis/chess/Rchess/src/RcppExports.cpp b/dataAnalysis/chess/Rchess/src/RcppExports.cpp index 5b54f4b..8f1251b 100644 --- a/dataAnalysis/chess/Rchess/src/RcppExports.cpp +++ b/dataAnalysis/chess/Rchess/src/RcppExports.cpp @@ -21,9 +21,59 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// isWhiteTurn +Rcpp::LogicalVector isWhiteTurn(const std::vector& positions); +RcppExport SEXP _Rchess_isWhiteTurn(SEXP positionsSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const std::vector& >::type positions(positionsSEXP); + rcpp_result_gen = Rcpp::wrap(isWhiteTurn(positions)); + return rcpp_result_gen; +END_RCPP +} +// isCheck +Rcpp::LogicalVector isCheck(const std::vector& positions); +RcppExport SEXP _Rchess_isCheck(SEXP positionsSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const std::vector& >::type positions(positionsSEXP); + rcpp_result_gen = Rcpp::wrap(isCheck(positions)); + return rcpp_result_gen; +END_RCPP +} +// isQuiet +Rcpp::LogicalVector isQuiet(const std::vector& positions); +RcppExport SEXP _Rchess_isQuiet(SEXP positionsSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const std::vector& >::type positions(positionsSEXP); + rcpp_result_gen = Rcpp::wrap(isQuiet(positions)); + return rcpp_result_gen; +END_RCPP +} +// isTerminal +Rcpp::LogicalVector isTerminal(const std::vector& positions); +RcppExport SEXP _Rchess_isTerminal(SEXP positionsSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const std::vector& >::type positions(positionsSEXP); + rcpp_result_gen = Rcpp::wrap(isTerminal(positions)); + return rcpp_result_gen; +END_RCPP +} +// isInsufficient +Rcpp::LogicalVector isInsufficient(const std::vector& positions); +RcppExport SEXP _Rchess_isInsufficient(SEXP positionsSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< const std::vector& >::type positions(positionsSEXP); + rcpp_result_gen = Rcpp::wrap(isInsufficient(positions)); + return rcpp_result_gen; +END_RCPP +} // data_gen -Rcpp::CharacterVector data_gen(const std::string& file, const int sample_size, const float score_min, const float score_max, const bool quiet, const int min_ply_count); -RcppExport SEXP _Rchess_data_gen(SEXP fileSEXP, SEXP sample_sizeSEXP, SEXP score_minSEXP, SEXP score_maxSEXP, SEXP quietSEXP, SEXP min_ply_countSEXP) { +Rcpp::CharacterVector data_gen(const std::string& file, const int sample_size, const float score_min, const float score_max, const bool quiet, const int min_ply_count, const bool white_only); +RcppExport SEXP _Rchess_data_gen(SEXP fileSEXP, SEXP sample_sizeSEXP, SEXP score_minSEXP, SEXP score_maxSEXP, SEXP quietSEXP, SEXP min_ply_countSEXP, SEXP white_onlySEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; @@ -33,18 +83,22 @@ BEGIN_RCPP Rcpp::traits::input_parameter< const float >::type score_max(score_maxSEXP); Rcpp::traits::input_parameter< const bool >::type quiet(quietSEXP); Rcpp::traits::input_parameter< const int >::type min_ply_count(min_ply_countSEXP); - rcpp_result_gen = Rcpp::wrap(data_gen(file, sample_size, score_min, score_max, quiet, min_ply_count)); + Rcpp::traits::input_parameter< const bool >::type white_only(white_onlySEXP); + rcpp_result_gen = Rcpp::wrap(data_gen(file, sample_size, score_min, score_max, quiet, min_ply_count, white_only)); return rcpp_result_gen; END_RCPP } // eval_psqt -Rcpp::NumericVector eval_psqt(const std::vector& positions, const std::vector& psqt); -RcppExport SEXP _Rchess_eval_psqt(SEXP positionsSEXP, SEXP psqtSEXP) { +Rcpp::NumericVector eval_psqt(const std::vector& positions, const std::vector& psqt, const bool pawn_structure, const bool eval_rooks, const bool eval_king); +RcppExport SEXP _Rchess_eval_psqt(SEXP positionsSEXP, SEXP psqtSEXP, SEXP pawn_structureSEXP, SEXP eval_rooksSEXP, SEXP eval_kingSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::traits::input_parameter< const std::vector& >::type positions(positionsSEXP); Rcpp::traits::input_parameter< const std::vector& >::type psqt(psqtSEXP); - rcpp_result_gen = Rcpp::wrap(eval_psqt(positions, psqt)); + Rcpp::traits::input_parameter< const bool >::type pawn_structure(pawn_structureSEXP); + Rcpp::traits::input_parameter< const bool >::type eval_rooks(eval_rooksSEXP); + Rcpp::traits::input_parameter< const bool >::type eval_king(eval_kingSEXP); + rcpp_result_gen = Rcpp::wrap(eval_psqt(positions, psqt, pawn_structure, eval_rooks, eval_king)); return rcpp_result_gen; END_RCPP } @@ -196,8 +250,13 @@ END_RCPP static const R_CallMethodDef CallEntries[] = { {"_Rchess_HCE", (DL_FUNC) &_Rchess_HCE, 1}, - {"_Rchess_data_gen", (DL_FUNC) &_Rchess_data_gen, 6}, - {"_Rchess_eval_psqt", (DL_FUNC) &_Rchess_eval_psqt, 2}, + {"_Rchess_isWhiteTurn", (DL_FUNC) &_Rchess_isWhiteTurn, 1}, + {"_Rchess_isCheck", (DL_FUNC) &_Rchess_isCheck, 1}, + {"_Rchess_isQuiet", (DL_FUNC) &_Rchess_isQuiet, 1}, + {"_Rchess_isTerminal", (DL_FUNC) &_Rchess_isTerminal, 1}, + {"_Rchess_isInsufficient", (DL_FUNC) &_Rchess_isInsufficient, 1}, + {"_Rchess_data_gen", (DL_FUNC) &_Rchess_data_gen, 7}, + {"_Rchess_eval_psqt", (DL_FUNC) &_Rchess_eval_psqt, 5}, {"_Rchess_fen2int", (DL_FUNC) &_Rchess_fen2int, 1}, {"_Rchess_read_cyclic", (DL_FUNC) &_Rchess_read_cyclic, 5}, {"_Rchess_sample_move", (DL_FUNC) &_Rchess_sample_move, 1}, diff --git a/dataAnalysis/chess/Rchess/src/board.cpp b/dataAnalysis/chess/Rchess/src/board.cpp new file mode 100644 index 0000000..10b81ea --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/board.cpp @@ -0,0 +1,56 @@ +#include +#include + +#include "SchachHoernchen/Board.h" + +//' Given a FEN (position) determines if its whites turn +// [[Rcpp::export(rng = false)]] +Rcpp::LogicalVector isWhiteTurn(const std::vector& positions) { + // Iterate all positions and call the static board evaluation + return Rcpp::LogicalVector(positions.begin(), positions.end(), + [](const Board& pos) { return pos.isWhiteTurn(); } + ); +} + +//' Check if current side to move is in check +// [[Rcpp::export(rng = false)]] +Rcpp::LogicalVector isCheck(const std::vector& positions) { + return Rcpp::LogicalVector(positions.begin(), positions.end(), + [](const Board& pos) { return pos.isCheck(); } + ); +} + +//' Check if the current position is a quiet position (no piece is attacked) +// [[Rcpp::export(rng = false)]] +Rcpp::LogicalVector isQuiet(const std::vector& positions) { + return Rcpp::LogicalVector(positions.begin(), positions.end(), + [](const Board& pos) { return pos.isQuiet(); } + ); +} + +//' Check if position is terminal +//' +//' Checks if the position is a terminal position, meaning if the game ended +//' by mate, stale mate or the 50 modes rule. Three-Fold repetition is NOT +//' checked, therefore a seperate game history is required which the board +//' does NOT track. +//' +// [[Rcpp::export(rng = false)]] +Rcpp::LogicalVector isTerminal(const std::vector& positions) { + return Rcpp::LogicalVector(positions.begin(), positions.end(), + [](const Board& pos) { return pos.isTerminal(); } + ); +} + +//' Check if checkmate is possible by material on the board +//' +//' Checks if there is sufficient mating material on the board, meaning if it +//' possible for any side to deliver a check mate. More specifically, it +//' checks if the pieces on the board are KK, KNK or KBK. +//' +// [[Rcpp::export(rng = false)]] +Rcpp::LogicalVector isInsufficient(const std::vector& positions) { + return Rcpp::LogicalVector(positions.begin(), positions.end(), + [](const Board& pos) { return pos.isInsufficient(); } + ); +} diff --git a/dataAnalysis/chess/Rchess/src/data_gen.cpp b/dataAnalysis/chess/Rchess/src/data_gen.cpp index dbbc85e..a8dd3c2 100644 --- a/dataAnalysis/chess/Rchess/src/data_gen.cpp +++ b/dataAnalysis/chess/Rchess/src/data_gen.cpp @@ -20,7 +20,8 @@ Rcpp::CharacterVector data_gen( const float score_min = -5.0, const float score_max = +5.0, const bool quiet = false, - const int min_ply_count = 10 + const int min_ply_count = 10, + const bool white_only = true ) { // Check parames if (sample_size < 1) { @@ -103,10 +104,10 @@ Rcpp::CharacterVector data_gen( } // Reject / Filter samples - if (((int)pos.plyCount() < min_ply_count) // Filter early positions - || (pos.sideToMove() == piece::black) // Filter white to move positions - || (score < score_min || score_max <= score) // filter scores out of slice - || (quiet && pos.isQuiet())) // filter quiet positions (iff requested) + if (((int)pos.plyCount() < min_ply_count) // early positions + || (white_only && (pos.sideToMove() == piece::black)) // white to move positions + || (score < score_min || score_max <= score) // scores out of slice + || (quiet && !pos.isQuiet())) // quiet positions { reject_count++; continue; diff --git a/dataAnalysis/chess/Rchess/src/eval_psqt.cpp b/dataAnalysis/chess/Rchess/src/eval_psqt.cpp new file mode 100644 index 0000000..61c72e8 --- /dev/null +++ b/dataAnalysis/chess/Rchess/src/eval_psqt.cpp @@ -0,0 +1,78 @@ +#include +#include + +#include "SchachHoernchen/Move.h" +#include "SchachHoernchen/Board.h" + +//' Human Crafted Evaluation +// [[Rcpp::export(name = "eval.psqt", rng = false)]] +Rcpp::NumericVector eval_psqt( + const std::vector& positions, + const std::vector& psqt, + const bool pawn_structure = false, + const bool eval_rooks = false, + const bool eval_king = false +) { + // validate Piece Square Table count and sizes + if (psqt.size() != 6) { + Rcpp::stop("Expected exactly 6 PSQTs"); + } + for (const auto table : psqt) { + if (table.nrow() != 8 || table.ncol() != 8) { + Rcpp::stop("PSQT table missmatch, all expected to be `8 x 8`"); + } + } + + // create numeric vector by evaluating all positions + return Rcpp::NumericVector(positions.begin(), positions.end(), + [&psqt, pawn_structure, eval_rooks, eval_king]( + const Board& pos + ) { + // Index to color/piece mapping (more robust) + enum piece colorLoopup[2] = { white, black }; + enum piece pieceLookup[6] = { pawn, knight, bishop, rook, queen, king }; + + // Score is the "inner product" of the "one-hot encoded" position + // and the piece square tables (PSQT) + double whiteMaterial = 0.0, blackMaterial = 0.0; + for (int piece = 0; piece < 6; ++piece) { + u64 piece_bb = pos.bb(pieceLookup[piece]); + // First the White (positive) pieces + for (u64 bb = pos.bb(piece::white) & piece_bb; bb; bb &= bb - 1) { + // Get piece on bitboard index (Least Significant Bit) + int index = bitScanLS(bb); + // Transpose to align with PSQT memory layout + index = ((index & 7) << 3) | ((index & 56) >> 3); + whiteMaterial += psqt[piece][index]; + } + // Second the black (negative) pieces (with flipped Ranks) + for (u64 bb = pos.bb(piece::black) & piece_bb; bb; bb &= bb - 1) { + // Get fliped board index + int index = bitScanLS(bb); + // Transpose to align with PSQT memory layout and flip ranks + // convert from whites perspective to blacks persepective + index = ((index & 7) << 3) | (7 - ((index & 56) >> 3)); + blackMaterial += psqt[piece][index]; + } + } + return (whiteMaterial - blackMaterial) / 100.0; + } + ); +} + +/* + +devtools::load_all() +save_point <- sort(list.files( + "~/Work/tensorPredictors/dataAnalysis/chess/", + pattern = "save_point.*\\.Rdata", + full.names = TRUE +), decreasing = TRUE)[[1]] +load(save_point) + +psqt <- Map(function(parts) matrix(rowSums(kronecker(parts[[2]], parts[[1]])), 8, 8), betas) +psqt <- Map(`-`, psqt[1:6], Map(function(table) table[8:1, ], psqt[7:12])) + +eval.psqt("startpos", psqt) + +*/ \ No newline at end of file diff --git a/dataAnalysis/chess/Rchess/src/fen2int.cpp b/dataAnalysis/chess/Rchess/src/fen2int.cpp index 475bb8e..801a8ba 100644 --- a/dataAnalysis/chess/Rchess/src/fen2int.cpp +++ b/dataAnalysis/chess/Rchess/src/fen2int.cpp @@ -15,8 +15,12 @@ Rcpp::IntegerVector fen2int(const std::vector& boards) { auto dims = Rcpp::IntegerVector({ 8, 8, 12, (int)boards.size() }); bitboards.attr("dim") = dims; bitboards.attr("dimnames") = Rcpp::List::create( - Rcpp::Named("rank") = Rcpp::CharacterVector::create("8", "7", "6", "5", "4", "3", "2", "1"), - Rcpp::Named("file") = Rcpp::CharacterVector::create("a", "b", "c", "d", "e", "f", "g", "h"), + Rcpp::Named("rank") = Rcpp::CharacterVector::create( + "8", "7", "6", "5", "4", "3", "2", "1" + ), + Rcpp::Named("file") = Rcpp::CharacterVector::create( + "a", "b", "c", "d", "e", "f", "g", "h" + ), Rcpp::Named("piece") = Rcpp::CharacterVector::create( "P", "N", "B", "R", "Q", "K", // White Pieces (Upper Case) "p", "n", "b", "r", "q", "k" // Black Pieces (Lower Case) @@ -42,6 +46,8 @@ Rcpp::IntegerVector fen2int(const std::vector& boards) { int index = bitScanLS(bb); // Transpose to align with printing as a Chess Board index = ((index & 7) << 3) | ((index & 56) >> 3); + // Flip black to move positions to whites point of view + index ^= pos.isWhiteTurn() ? 0 : 7; bitboards[768 * i + 64 * slice + index] = 1; } } diff --git a/dataAnalysis/chess/Rchess/src/print.cpp b/dataAnalysis/chess/Rchess/src/print.cpp deleted file mode 100644 index 2f445d3..0000000 --- a/dataAnalysis/chess/Rchess/src/print.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// #include - -// #include "SchachHoernchen/utils.h" -// #include "SchachHoernchen/Board.h" -// #include "SchachHoernchen/uci.h" - -// // [[Rcpp::export(name = "print.board", rng = false)]] -// void print_board( -// const Board& board, -// const bool check = true, -// const bool attacked = false, -// const bool pinned = false, -// const bool checkers = false -// ) { -// using Rcpp::Rcout; - -// // Extract some properties -// piece color = board.sideToMove(); -// piece enemy = static_cast(!color); -// u64 empty = ~(board.bb(piece::white) | board.bb(piece::black)); -// u64 cKing = board.bb(king) & board.bb(color); -// // Construct highlight mask -// u64 attackMask = board.attacks(enemy, empty); -// u64 mask = 0; -// mask |= check ? attackMask & board.bb(color) & board.bb(king) : 0; -// mask |= attacked ? attackMask & ~board.bb(color) : 0; -// mask |= pinned ? board.pinned(enemy, cKing, empty) : 0; -// mask |= checkers ? board.checkers(enemy, cKing, empty) : 0; - -// // print the board to console -// Rcout << "FEN: " << board.fen() << '\n'; -// for (Index line = 0; line < 17; line++) { -// if (line % 2) { -// Index rankIndex = line / 2; -// Rcout << static_cast('8' - rankIndex); -// for (Index fileIndex = 0; fileIndex < 8; fileIndex++) { -// Index squareIndex = 8 * rankIndex + fileIndex; -// if (bitMask(squareIndex) & mask) { -// if (board.piece(squareIndex)) { -// if (board.color(squareIndex) == black) { -// // bold + italic + underline + black (blue) -// Rcout << " | \033[1;3;4;94m"; -// } else { -// // bold + italic + underline (+ white) -// Rcout << " | \033[1;3;4m"; -// } -// Rcout << UCI::formatPiece(board.piece(squareIndex)) -// << "\033[0m"; -// } else { -// Rcout << " | ."; -// } -// } else if (board.color(squareIndex) == black) { -// Rcout << " | \033[1m\033[94m" // bold + blue (black) -// << UCI::formatPiece(board.piece(squareIndex)) -// << "\033[0m"; -// } else if (board.color(squareIndex) == white) { -// Rcout << " | \033[1m\033[97m" // bold + white -// << UCI::formatPiece(board.piece(squareIndex)) -// << "\033[0m"; -// } else { -// Rcout << " | "; -// } -// } -// Rcout << " |"; -// } else { -// Rcout << " +---+---+---+---+---+---+---+---+"; -// } -// Rcout << "\033[0K\n"; // clear rest of line (remove potential leftovers) -// } -// Rcout << " a b c d e f g h" << std::endl; -// } diff --git a/dataAnalysis/eeg/eeg.R b/dataAnalysis/eeg/eeg.R index 336a0c8..468c81f 100644 --- a/dataAnalysis/eeg/eeg.R +++ b/dataAnalysis/eeg/eeg.R @@ -1,8 +1,6 @@ -options(keep.source = TRUE, keep.source.pkgs = TRUE) - library(tensorPredictors) -# Load as 3D predictors `X` and flat response `y` +# Load as 3D predictors `X` and flat response `y` and `F = y` with per person dim. 1 x 1 c(X, F, y) %<-% local({ # Load from file ds <- readRDS("eeg_data.rds") @@ -30,42 +28,7 @@ c(X, F, y) %<-% local({ # fit a tensor normal model to the data sample axis 1 indexes persons) fit.gmlm <- gmlm_tensor_normal(X, F, sample.axis = 1L) -# Performa a LOO prediction -y.hat <- sapply(seq_len(dim(X)[1L]), function(i) { - # Fit with i'th observation removes - fit <- gmlm_tensor_normal(X[-i, , ], F[-i, , , drop = FALSE], sample.axis = 1L) - - # Reduce the entire data set - r <- as.vector(mlm(X, fit$betas, modes = 2:3, transpose = TRUE)) - # Fit a logit model on reduced data with i'th observation removed - logit <- glm(y ~ r, family = binomial(link = "logit"), - data = data.frame(y = y[-i], r = r[-i]) - ) - # predict i'th response given i'th reduced observation - y.hat <- predict(logit, newdata = data.frame(r = r[i]), type = "response") - # report progress - cat(sprintf("%3d/%d\n", i, dim(X)[1L])) - - y.hat -}) - -### Classification performance measures -# acc: Accuracy. P(Yhat = Y). Estimated as: (TP+TN)/(P+N). -(acc <- mean(round(y.hat) == y)) # 0.7868852 -# err: Error rate. P(Yhat != Y). Estimated as: (FP+FN)/(P+N). -(err <- mean(round(y.hat) != y)) # 0.2131148 -# fpr: False positive rate. P(Yhat = + | Y = -). aliases: Fallout. -(fpr <- mean((round(y.hat) == 1)[y == 0])) # 0.4 -# tpr: True positive rate. P(Yhat = + | Y = +). aliases: Sensitivity, Recall. -(tpr <- mean((round(y.hat) == 1)[y == 1])) # 0.8961039 -# fnr: False negative rate. P(Yhat = - | Y = +). aliases: Miss. -(fnr <- mean((round(y.hat) == 0)[y == 1])) # 0.1038961 -# tnr: True negative rate. P(Yhat = - | Y = -). -(tnr <- mean((round(y.hat) == 0)[y == 0])) # 0.6 -# auc: Area Under the Curve -(auc <- pROC::roc(y, y.hat, quiet = TRUE)$auc) # 0.838961 - - +# plot the fitted mode wise reductions (for time and sensor axis) with(fit.gmlm, { par.reset <- par(mfrow = c(2, 1)) plot(seq(0, 1, len = 256), betas[[1]], main = "Time", xlab = "Time [s]", ylab = expression(beta[1])) @@ -74,20 +37,83 @@ with(fit.gmlm, { }) -# dimX <- c(4, 3, 5) -# Omegas <- Map(function(p) { -# O <- matrix(rnorm(p^2), p) -# O %*% t(O) -# }, dimX) +#' (2D)^2 PCA preprocessing +#' +#' @param tpc Number of "t"ime "p"rincipal "c"omponents. +#' @param ppc Number of "p"redictor "p"rincipal "c"omponents. +preprocess <- function(X, tpc, ppc) { + # Mode covariances (for predictor and time point modes) + c(Sigma_t, Sigma_p) %<-% mcov(X, sample.axis = 1L) -# # Numerically more stable version of `sum(log(mapply(det, Omegas)) / dimX)` -# # which is itself equivalent to `log(det(Omega)) / prod(nrow(Omega))` where -# # `Omega <- Reduce(kronecker, rev(Omegas))`. + # "predictor" (sensor) and time point principal components + V_t <- svd(Sigma_t, tpc, 0L)$u + V_p <- svd(Sigma_p, ppc, 0L)$u -# Omega <- Reduce(kronecker, rev(Omegas)) -# log(det(Omega)) / prod(nrow(Omega)) + # reduce with mode wise PCs + mlm(X, list(V_t, V_p), modes = 2:3, transposed = TRUE) +} -# (det.Omega <- sum(log(mapply(det, Omegas)) / dimX)) -# sum(mapply(function(Omega) { -# sum(log(eigen(Omega, TRUE, TRUE)$values)) -# }, Omegas) / dimX) + +#' Leave-one-out prediction +#' +#' @param X 3D EEG data (preprocessed or not) +#' @param F binary responce `y` as a 3D tensor, every obs. is a 1 x 1 matrix +loo.predict <- function(X, F) { + sapply(seq_len(dim(X)[1L]), function(i) { + # Fit with i'th observation removes + fit <- gmlm_tensor_normal(X[-i, , ], F[-i, , , drop = FALSE], sample.axis = 1L) + + # Reduce the entire data set + r <- as.vector(mlm(X, fit$betas, modes = 2:3, transpose = TRUE)) + # Fit a logit model on reduced data with i'th observation removed + logit <- glm(y ~ r, family = binomial(link = "logit"), + data = data.frame(y = y[-i], r = r[-i]) + ) + # predict i'th response given i'th reduced observation + y.hat <- predict(logit, newdata = data.frame(r = r[i]), type = "response") + # report progress + cat(sprintf("dim: (%d, %d) - %3d/%d\n", dim(X)[2L], dim(X)[3L], i, dim(X)[1L])) + + y.hat + }) +} + + +### Classification performance measures +# acc: Accuracy. P(Yhat = Y). Estimated as: (TP+TN)/(P+N). +acc <- function(y.true, y.pred) mean(round(y.pred) == y.true) +# err: Error rate. P(Yhat != Y). Estimated as: (FP+FN)/(P+N). +err <- function(y.true, y.pred) mean(round(y.pred) != y.true) +# fpr: False positive rate. P(Yhat = + | Y = -). aliases: Fallout. +fpr <- function(y.true, y.pred) mean((round(y.pred) == 1)[y.true == 0]) +# tpr: True positive rate. P(Yhat = + | Y = +). aliases: Sensitivity, Recall. +tpr <- function(y.true, y.pred) mean((round(y.pred) == 1)[y.true == 1]) +# fnr: False negative rate. P(Yhat = - | Y = +). aliases: Miss. +fnr <- function(y.true, y.pred) mean((round(y.pred) == 0)[y.true == 1]) +# tnr: True negative rate. P(Yhat = - | Y = -). +tnr <- function(y.true, y.pred) mean((round(y.pred) == 0)[y.true == 0]) +# auc: Area Under the Curve +auc <- function(y.true, y.pred) as.numeric(pROC::roc(y.true, y.pred, quiet = TRUE)$auc) +auc.sd <- function(y.true, y.pred) sqrt(pROC::var(pROC::roc(y.true, y.pred, quiet = TRUE))) + + +# perform preprocessed (reduced) and raw (not reduced) leave-one-out prediction +y.hat.3.4 <- loo.predict(preprocess(X, 3, 4), F) +y.hat.15.15 <- loo.predict(preprocess(X, 15, 15), F) +y.hat.20.30 <- loo.predict(preprocess(X, 20, 30), F) +y.hat <- loo.predict(X, F) + +# classification performance measures table by leave-one-out cross-validation +(loo.cv <- apply(cbind(y.hat.3.4, y.hat.15.15, y.hat.20.30, y.hat), 2, function(y.pred) { + sapply(c("acc", "err", "fpr", "tpr", "fnr", "tnr", "auc", "auc.sd"), + function(FUN) { match.fun(FUN)(y, y.pred) }) +})) +#> y.hat.3.4 y.hat.15.15 y.hat.20.30 y.hat +#> acc 0.79508197 0.78688525 0.78688525 0.78688525 +#> err 0.20491803 0.21311475 0.21311475 0.21311475 +#> fpr 0.35555556 0.40000000 0.40000000 0.40000000 +#> tpr 0.88311688 0.89610390 0.89610390 0.89610390 +#> fnr 0.11688312 0.10389610 0.10389610 0.10389610 +#> tnr 0.64444444 0.60000000 0.60000000 0.60000000 +#> auc 0.85108225 0.83838384 0.83924964 0.83896104 +#> auc.sd 0.03584791 0.03760531 0.03751307 0.03754553