/* This file is part of ctak. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ctak. If not, see . */ #include "nn.h" #include "weights_5.h" #include "weights_6.h" // =================================================================== // Helpers // =================================================================== #define RELU(x) ((x) = ((x) < 0) ? 0 : (x)) static float fast_tanh(const float x) { // tanh via a degree-11 odd polynomial, least-squares fit of tanh on [-3, 3] // (max absolute error ~4e-3), saturating to +-1 outside that interval. if (x > 3.0f) return 1.0f; if (x < -3.0f) return -1.0f; const float x2 = x * x; return x * (0.997136032f + x2 * (-0.305215124f + x2 * (0.0832466371f + x2 * (-0.0139947924f + x2 * (0.00123106314f + x2 * -4.29748644e-05f))))); } // =================================================================== // Input encoding // =================================================================== inline uint8_t cell_idx_at(const tak_state_p state, const uint8_t loc) { const uint8_t d = state->celldat[loc]; if ((d & NUM_MASK) == 0) return 0; // empty return (uint8_t)(1u + 3u * (state->colours[loc] & 1u) + (d & STONE_MASK)); } inline void nn_cell_idxs(uint8_t *out, tak_state_p state) { const unsigned int n_cells = state->board_size * state->board_size; for (unsigned int loc = 0; loc < n_cells; loc++) out[loc] = cell_idx_at(state, loc); } inline void nn_prepare_head(float out_head[3], tak_state_p state) { const float inv_max_flats = (state->board_size == 6) ? (1.0f / 30.0f) : (1.0f / 21.0f); out_head[0] = (state->ply & 1) ? 1 : -1; out_head[1] = (float)(state->white_count & 127) * inv_max_flats; out_head[2] = (float)(state->black_count & 127) * inv_max_flats; } void nn_prepare_input(float *cur_board, tak_state_p state, const float embedding[NN_N_EMB]) { const unsigned int n_cells = state->board_size * state->board_size; nn_prepare_head(cur_board, state); uint8_t idxs[36]; nn_cell_idxs(idxs, state); for (unsigned int loc = 0; loc < n_cells; loc++) cur_board[3 + loc] = embedding[idxs[loc]]; } // =================================================================== // Implementation of small neural networks // =================================================================== float eval_net(const float *board, const unsigned int n_inp, const float *w, const float *biases, const float *logit_w, const float logit_b, const unsigned int n_dense) { float dense1[FIVE_DENSE_NUM]; // FIVE_DENSE_NUM == SIX_DENSE_NUM float z = logit_b; for (unsigned int d1 = 0; d1 < n_dense; d1++) { float h = biases[d1]; const float *wd = w + d1 * n_inp; for (unsigned int fl = 0; fl < n_inp; fl++) h += board[fl] * wd[fl]; RELU(h); dense1[d1] = h; } for (unsigned int d1 = 0; d1 < n_dense; d1++) z += dense1[d1] * logit_w[d1]; return fast_tanh(z); } // =================================================================== // Interfaces to the two built-in evaluators // =================================================================== float five_nn_fn(tak_state_p state) { static float cur_board[FIVE_INP_NUM] NN_ALIGNED; nn_prepare_input(cur_board, state, five_embedding); return eval_net(cur_board, FIVE_INP_NUM, &five_dense_weights[0][0], five_dense_biases, five_logit_weights, five_logit_bias, FIVE_DENSE_NUM); } float six_nn_fn(tak_state_p state) { static float cur_board[SIX_INP_NUM] NN_ALIGNED; nn_prepare_input(cur_board, state, six_embedding); return eval_net(cur_board, SIX_INP_NUM, &six_dense_weights[0][0], six_dense_biases, six_logit_weights, six_logit_bias, SIX_DENSE_NUM); }