mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 20:57:10 +00:00
Merge commit '43b2d65d7275b11fd47c7225f8a0d19afbab4cd1' into cluster
This is the last commit before there are more conflicts.
This commit is contained in:
@@ -19,22 +19,22 @@ jobs:
|
||||
working-directory: Stockfish/src
|
||||
run: make -j build debug=yes
|
||||
|
||||
- name: Checkout fast-chess repo
|
||||
- name: Checkout fastchess repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Disservin/fast-chess
|
||||
path: fast-chess
|
||||
ref: d54af1910d5479c669dc731f1f54f9108a251951
|
||||
repository: Disservin/fastchess
|
||||
path: fastchess
|
||||
ref: 894616028492ae6114835195f14a899f6fa237d3
|
||||
persist-credentials: false
|
||||
|
||||
- name: fast-chess build
|
||||
working-directory: fast-chess
|
||||
- name: fastchess build
|
||||
working-directory: fastchess
|
||||
run: make -j
|
||||
|
||||
- name: Run games
|
||||
working-directory: fast-chess
|
||||
working-directory: fastchess
|
||||
run: |
|
||||
./fast-chess -rounds 4 -games 2 -repeat -concurrency 4 -openings file=app/tests/data/openings.epd format=epd order=random -srand $RANDOM\
|
||||
./fastchess -rounds 4 -games 2 -repeat -concurrency 4 -openings file=app/tests/data/openings.epd format=epd order=random -srand $RANDOM\
|
||||
-engine name=sf1 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\
|
||||
-engine name=sf2 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\
|
||||
-ratinginterval 1 -report penta=true -each proto=uci tc=4+0.04 -log file=fast.log | tee fast.out
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
repository: vondele/matetrack
|
||||
path: matetrack
|
||||
ref: 814160f82e6428ed2f6522dc06c2a6fa539cd413
|
||||
ref: 4f8a80860ed8f3607f05a9195df8b40203bdc360
|
||||
persist-credentials: false
|
||||
|
||||
- name: matetrack install deps
|
||||
@@ -50,5 +50,22 @@ jobs:
|
||||
- name: Run matetrack
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout.out
|
||||
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout.out
|
||||
! grep "issues were detected" matecheckout.out > /dev/null
|
||||
|
||||
- name: Run matetrack with --syzygy50MoveRule false
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
grep 5men cursed.epd > cursed5.epd
|
||||
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --syzygy50MoveRule false | tee matecheckcursed.out
|
||||
! grep "issues were detected" matecheckcursed.out > /dev/null
|
||||
|
||||
- name: Verify mate and TB win count for matecheckcursed.out
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
mates=$(grep "Found mates:" matecheckcursed.out | awk '{print $3}')
|
||||
tbwins=$(grep "Found TB wins:" matecheckcursed.out | awk '{print $4}')
|
||||
if [ $(($mates + $tbwins)) -ne 32 ]; then
|
||||
echo "Sum of mates and TB wins is not 32 in matecheckcursed.out" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -125,10 +125,12 @@ jundery
|
||||
Justin Blanchard (UncombedCoconut)
|
||||
Kelly Wilson
|
||||
Ken Takusagawa
|
||||
Kenneth Lee (kennethlee33)
|
||||
Kian E (KJE-98)
|
||||
kinderchocolate
|
||||
Kiran Panditrao (Krgp)
|
||||
Kojirion
|
||||
Krisztián Peőcz
|
||||
Krystian Kuzniarek (kuzkry)
|
||||
Leonardo Ljubičić (ICCF World Champion)
|
||||
Leonid Pechenik (lp--)
|
||||
|
||||
+5
-5
@@ -22,6 +22,7 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
@@ -268,11 +269,10 @@ inline int popcount(Bitboard b) {
|
||||
|
||||
#ifndef USE_POPCNT
|
||||
|
||||
union {
|
||||
Bitboard bb;
|
||||
uint16_t u[4];
|
||||
} v = {b};
|
||||
return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]];
|
||||
std::uint16_t indices[4];
|
||||
std::memcpy(indices, &b, sizeof(b));
|
||||
return PopCnt16[indices[0]] + PopCnt16[indices[1]] + PopCnt16[indices[2]]
|
||||
+ PopCnt16[indices[3]];
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
|
||||
+73
-48
@@ -59,57 +59,83 @@ Engine::Engine(std::optional<std::string> path) :
|
||||
NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) {
|
||||
pos.set(StartFEN, false, &states->back());
|
||||
|
||||
options["Debug Log File"] << Option("", [](const Option& o) {
|
||||
start_logger(o);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
options["NumaPolicy"] << Option("auto", [this](const Option& o) {
|
||||
set_numa_config_from_option(o);
|
||||
return numa_config_information_as_string() + "\n"
|
||||
+ thread_allocation_information_as_string();
|
||||
});
|
||||
options.add( //
|
||||
"Debug Log File", Option("", [](const Option& o) {
|
||||
start_logger(o);
|
||||
return std::nullopt;
|
||||
}));
|
||||
|
||||
options["Threads"] << Option(1, 1, 1024, [this](const Option&) {
|
||||
resize_threads();
|
||||
return thread_allocation_information_as_string();
|
||||
});
|
||||
options.add( //
|
||||
"NumaPolicy", Option("auto", [this](const Option& o) {
|
||||
set_numa_config_from_option(o);
|
||||
return numa_config_information_as_string() + "\n"
|
||||
+ thread_allocation_information_as_string();
|
||||
}));
|
||||
|
||||
options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) {
|
||||
set_tt_size(o);
|
||||
return std::nullopt;
|
||||
});
|
||||
options.add( //
|
||||
"Threads", Option(1, 1, 1024, [this](const Option&) {
|
||||
resize_threads();
|
||||
return thread_allocation_information_as_string();
|
||||
}));
|
||||
|
||||
options["Clear Hash"] << Option([this](const Option&) {
|
||||
search_clear();
|
||||
return std::nullopt;
|
||||
});
|
||||
options["Ponder"] << Option(false);
|
||||
options["MultiPV"] << Option(1, 1, MAX_MOVES);
|
||||
options["Skill Level"] << Option(20, 0, 20);
|
||||
options["Move Overhead"] << Option(10, 0, 5000);
|
||||
options["nodestime"] << Option(0, 0, 10000);
|
||||
options["UCI_Chess960"] << Option(false);
|
||||
options["UCI_LimitStrength"] << Option(false);
|
||||
options["UCI_Elo"] << Option(Stockfish::Search::Skill::LowestElo,
|
||||
Stockfish::Search::Skill::LowestElo,
|
||||
Stockfish::Search::Skill::HighestElo);
|
||||
options["UCI_ShowWDL"] << Option(false);
|
||||
options["SyzygyPath"] << Option("", [](const Option& o) {
|
||||
Tablebases::init(o);
|
||||
return std::nullopt;
|
||||
});
|
||||
options["SyzygyProbeDepth"] << Option(1, 1, 100);
|
||||
options["Syzygy50MoveRule"] << Option(true);
|
||||
options["SyzygyProbeLimit"] << Option(7, 0, 7);
|
||||
options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option& o) {
|
||||
load_big_network(o);
|
||||
return std::nullopt;
|
||||
});
|
||||
options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) {
|
||||
load_small_network(o);
|
||||
return std::nullopt;
|
||||
});
|
||||
options.add( //
|
||||
"Hash", Option(16, 1, MaxHashMB, [this](const Option& o) {
|
||||
set_tt_size(o);
|
||||
return std::nullopt;
|
||||
}));
|
||||
|
||||
options.add( //
|
||||
"Clear Hash", Option([this](const Option&) {
|
||||
search_clear();
|
||||
return std::nullopt;
|
||||
}));
|
||||
|
||||
options.add( //
|
||||
"Ponder", Option(false));
|
||||
|
||||
options.add( //
|
||||
"MultiPV", Option(1, 1, MAX_MOVES));
|
||||
|
||||
options.add("Skill Level", Option(20, 0, 20));
|
||||
|
||||
options.add("Move Overhead", Option(10, 0, 5000));
|
||||
|
||||
options.add("nodestime", Option(0, 0, 10000));
|
||||
|
||||
options.add("UCI_Chess960", Option(false));
|
||||
|
||||
options.add("UCI_LimitStrength", Option(false));
|
||||
|
||||
options.add("UCI_Elo",
|
||||
Option(Stockfish::Search::Skill::LowestElo, Stockfish::Search::Skill::LowestElo,
|
||||
Stockfish::Search::Skill::HighestElo));
|
||||
|
||||
options.add("UCI_ShowWDL", Option(false));
|
||||
|
||||
options.add( //
|
||||
"SyzygyPath", Option("", [](const Option& o) {
|
||||
Tablebases::init(o);
|
||||
return std::nullopt;
|
||||
}));
|
||||
|
||||
options.add("SyzygyProbeDepth", Option(1, 1, 100));
|
||||
|
||||
options.add("Syzygy50MoveRule", Option(true));
|
||||
|
||||
options.add("SyzygyProbeLimit", Option(7, 0, 7));
|
||||
|
||||
options.add( //
|
||||
"EvalFile", Option(EvalFileDefaultNameBig, [this](const Option& o) {
|
||||
load_big_network(o);
|
||||
return std::nullopt;
|
||||
}));
|
||||
|
||||
options.add( //
|
||||
"EvalFileSmall", Option(EvalFileDefaultNameSmall, [this](const Option& o) {
|
||||
load_small_network(o);
|
||||
return std::nullopt;
|
||||
}));
|
||||
|
||||
load_networks();
|
||||
resize_threads();
|
||||
@@ -343,5 +369,4 @@ std::string Engine::thread_allocation_information_as_string() const {
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
|
||||
// Blend optimism and eval with nnue complexity
|
||||
int nnueComplexity = std::abs(psqt - positional);
|
||||
optimism += optimism * nnueComplexity / 468;
|
||||
nnue -= nnue * nnueComplexity / (smallNet ? 20233 : 17879);
|
||||
nnue -= nnue * nnueComplexity / 18000;
|
||||
|
||||
int material = 535 * pos.count<PAWN>() + pos.non_pawn_material();
|
||||
int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777;
|
||||
|
||||
+5
-10
@@ -54,10 +54,6 @@ inline int pawn_structure_index(const Position& pos) {
|
||||
return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1);
|
||||
}
|
||||
|
||||
inline int major_piece_index(const Position& pos) {
|
||||
return pos.major_piece_key() & (CORRECTION_HISTORY_SIZE - 1);
|
||||
}
|
||||
|
||||
inline int minor_piece_index(const Position& pos) {
|
||||
return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1);
|
||||
}
|
||||
@@ -75,7 +71,7 @@ inline int non_pawn_index(const Position& pos) {
|
||||
template<typename T, int D>
|
||||
class StatsEntry {
|
||||
|
||||
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
|
||||
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
|
||||
static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
|
||||
|
||||
T entry;
|
||||
@@ -136,18 +132,17 @@ using PawnHistory = Stats<std::int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUAR
|
||||
// see https://www.chessprogramming.org/Static_Evaluation_Correction_History
|
||||
enum CorrHistType {
|
||||
Pawn, // By color and pawn structure
|
||||
Major, // By color and positions of major pieces (Queen, Rook) and King
|
||||
Minor, // By color and positions of minor pieces (Knight, Bishop) and King
|
||||
NonPawn, // By color and non-pawn material positions
|
||||
Minor, // By color and positions of minor pieces (Knight, Bishop)
|
||||
NonPawn, // By non-pawn material positions and color
|
||||
PieceTo, // By [piece][to] move
|
||||
Continuation, // Combined history of move pairs
|
||||
};
|
||||
|
||||
namespace Detail {
|
||||
|
||||
template<CorrHistType _>
|
||||
template<CorrHistType>
|
||||
struct CorrHistTypedef {
|
||||
using type = Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>;
|
||||
using type = Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, CORRECTION_HISTORY_SIZE, COLOR_NB>;
|
||||
};
|
||||
|
||||
template<>
|
||||
|
||||
+8
-6
@@ -120,11 +120,8 @@ void sync_cout_start();
|
||||
void sync_cout_end();
|
||||
|
||||
// True if and only if the binary is compiled on a little-endian machine
|
||||
static inline const union {
|
||||
uint32_t i;
|
||||
char c[4];
|
||||
} Le = {0x01020304};
|
||||
static inline const bool IsLittleEndian = (Le.c[0] == 4);
|
||||
static inline const std::uint16_t Le = 1;
|
||||
static inline const bool IsLittleEndian = *reinterpret_cast<const char*>(&Le) == 1;
|
||||
|
||||
|
||||
template<typename T, std::size_t MaxSize>
|
||||
@@ -158,6 +155,10 @@ struct MultiArrayHelper<T, Size> {
|
||||
using ChildType = T;
|
||||
};
|
||||
|
||||
template<typename To, typename From>
|
||||
constexpr bool is_strictly_assignable_v =
|
||||
std::is_assignable_v<To&, From> && (std::is_same_v<To, From> || !std::is_convertible_v<From, To>);
|
||||
|
||||
}
|
||||
|
||||
// MultiArray is a generic N-dimensional array.
|
||||
@@ -215,7 +216,8 @@ class MultiArray {
|
||||
|
||||
template<typename U>
|
||||
void fill(const U& v) {
|
||||
static_assert(std::is_assignable_v<T, U>, "Cannot assign fill value to entry type");
|
||||
static_assert(Detail::is_strictly_assignable_v<T, U>,
|
||||
"Cannot assign fill value to entry type");
|
||||
for (auto& ele : data_)
|
||||
{
|
||||
if constexpr (sizeof...(Sizes) == 0)
|
||||
|
||||
@@ -102,7 +102,7 @@ static void affine_transform_non_ssse3(std::int32_t* output,
|
||||
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
|
||||
sum = vpadalq_s16(sum, product);
|
||||
}
|
||||
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
|
||||
output[i] = Simd::neon_m128_reduce_add_epi32(sum);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -38,99 +38,41 @@
|
||||
namespace Stockfish::Eval::NNUE::Layers {
|
||||
|
||||
#if (USE_SSSE3 | (USE_NEON >= 8))
|
||||
static constexpr int lsb_index64[64] = {
|
||||
0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42,
|
||||
21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43,
|
||||
31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63};
|
||||
|
||||
constexpr int constexpr_lsb(uint64_t bb) {
|
||||
assert(bb != 0);
|
||||
constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL;
|
||||
return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58];
|
||||
}
|
||||
|
||||
alignas(CacheLineSize) static constexpr struct OffsetIndices {
|
||||
|
||||
#if (USE_SSE41)
|
||||
alignas(CacheLineSize) static constexpr std::uint8_t
|
||||
std::uint8_t offset_indices[256][8];
|
||||
#else
|
||||
alignas(CacheLineSize) static constexpr std::uint16_t
|
||||
std::uint16_t offset_indices[256][8];
|
||||
#endif
|
||||
lookup_indices[256][8] = {
|
||||
{0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 0, 0, 0, 0, 0, 0}, {2, 0, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 0},
|
||||
{1, 2, 0, 0, 0, 0, 0, 0}, {0, 1, 2, 0, 0, 0, 0, 0}, {3, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 3, 0, 0, 0, 0, 0, 0}, {1, 3, 0, 0, 0, 0, 0, 0}, {0, 1, 3, 0, 0, 0, 0, 0},
|
||||
{2, 3, 0, 0, 0, 0, 0, 0}, {0, 2, 3, 0, 0, 0, 0, 0}, {1, 2, 3, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 3, 0, 0, 0, 0}, {4, 0, 0, 0, 0, 0, 0, 0}, {0, 4, 0, 0, 0, 0, 0, 0},
|
||||
{1, 4, 0, 0, 0, 0, 0, 0}, {0, 1, 4, 0, 0, 0, 0, 0}, {2, 4, 0, 0, 0, 0, 0, 0},
|
||||
{0, 2, 4, 0, 0, 0, 0, 0}, {1, 2, 4, 0, 0, 0, 0, 0}, {0, 1, 2, 4, 0, 0, 0, 0},
|
||||
{3, 4, 0, 0, 0, 0, 0, 0}, {0, 3, 4, 0, 0, 0, 0, 0}, {1, 3, 4, 0, 0, 0, 0, 0},
|
||||
{0, 1, 3, 4, 0, 0, 0, 0}, {2, 3, 4, 0, 0, 0, 0, 0}, {0, 2, 3, 4, 0, 0, 0, 0},
|
||||
{1, 2, 3, 4, 0, 0, 0, 0}, {0, 1, 2, 3, 4, 0, 0, 0}, {5, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 5, 0, 0, 0, 0, 0, 0}, {1, 5, 0, 0, 0, 0, 0, 0}, {0, 1, 5, 0, 0, 0, 0, 0},
|
||||
{2, 5, 0, 0, 0, 0, 0, 0}, {0, 2, 5, 0, 0, 0, 0, 0}, {1, 2, 5, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 5, 0, 0, 0, 0}, {3, 5, 0, 0, 0, 0, 0, 0}, {0, 3, 5, 0, 0, 0, 0, 0},
|
||||
{1, 3, 5, 0, 0, 0, 0, 0}, {0, 1, 3, 5, 0, 0, 0, 0}, {2, 3, 5, 0, 0, 0, 0, 0},
|
||||
{0, 2, 3, 5, 0, 0, 0, 0}, {1, 2, 3, 5, 0, 0, 0, 0}, {0, 1, 2, 3, 5, 0, 0, 0},
|
||||
{4, 5, 0, 0, 0, 0, 0, 0}, {0, 4, 5, 0, 0, 0, 0, 0}, {1, 4, 5, 0, 0, 0, 0, 0},
|
||||
{0, 1, 4, 5, 0, 0, 0, 0}, {2, 4, 5, 0, 0, 0, 0, 0}, {0, 2, 4, 5, 0, 0, 0, 0},
|
||||
{1, 2, 4, 5, 0, 0, 0, 0}, {0, 1, 2, 4, 5, 0, 0, 0}, {3, 4, 5, 0, 0, 0, 0, 0},
|
||||
{0, 3, 4, 5, 0, 0, 0, 0}, {1, 3, 4, 5, 0, 0, 0, 0}, {0, 1, 3, 4, 5, 0, 0, 0},
|
||||
{2, 3, 4, 5, 0, 0, 0, 0}, {0, 2, 3, 4, 5, 0, 0, 0}, {1, 2, 3, 4, 5, 0, 0, 0},
|
||||
{0, 1, 2, 3, 4, 5, 0, 0}, {6, 0, 0, 0, 0, 0, 0, 0}, {0, 6, 0, 0, 0, 0, 0, 0},
|
||||
{1, 6, 0, 0, 0, 0, 0, 0}, {0, 1, 6, 0, 0, 0, 0, 0}, {2, 6, 0, 0, 0, 0, 0, 0},
|
||||
{0, 2, 6, 0, 0, 0, 0, 0}, {1, 2, 6, 0, 0, 0, 0, 0}, {0, 1, 2, 6, 0, 0, 0, 0},
|
||||
{3, 6, 0, 0, 0, 0, 0, 0}, {0, 3, 6, 0, 0, 0, 0, 0}, {1, 3, 6, 0, 0, 0, 0, 0},
|
||||
{0, 1, 3, 6, 0, 0, 0, 0}, {2, 3, 6, 0, 0, 0, 0, 0}, {0, 2, 3, 6, 0, 0, 0, 0},
|
||||
{1, 2, 3, 6, 0, 0, 0, 0}, {0, 1, 2, 3, 6, 0, 0, 0}, {4, 6, 0, 0, 0, 0, 0, 0},
|
||||
{0, 4, 6, 0, 0, 0, 0, 0}, {1, 4, 6, 0, 0, 0, 0, 0}, {0, 1, 4, 6, 0, 0, 0, 0},
|
||||
{2, 4, 6, 0, 0, 0, 0, 0}, {0, 2, 4, 6, 0, 0, 0, 0}, {1, 2, 4, 6, 0, 0, 0, 0},
|
||||
{0, 1, 2, 4, 6, 0, 0, 0}, {3, 4, 6, 0, 0, 0, 0, 0}, {0, 3, 4, 6, 0, 0, 0, 0},
|
||||
{1, 3, 4, 6, 0, 0, 0, 0}, {0, 1, 3, 4, 6, 0, 0, 0}, {2, 3, 4, 6, 0, 0, 0, 0},
|
||||
{0, 2, 3, 4, 6, 0, 0, 0}, {1, 2, 3, 4, 6, 0, 0, 0}, {0, 1, 2, 3, 4, 6, 0, 0},
|
||||
{5, 6, 0, 0, 0, 0, 0, 0}, {0, 5, 6, 0, 0, 0, 0, 0}, {1, 5, 6, 0, 0, 0, 0, 0},
|
||||
{0, 1, 5, 6, 0, 0, 0, 0}, {2, 5, 6, 0, 0, 0, 0, 0}, {0, 2, 5, 6, 0, 0, 0, 0},
|
||||
{1, 2, 5, 6, 0, 0, 0, 0}, {0, 1, 2, 5, 6, 0, 0, 0}, {3, 5, 6, 0, 0, 0, 0, 0},
|
||||
{0, 3, 5, 6, 0, 0, 0, 0}, {1, 3, 5, 6, 0, 0, 0, 0}, {0, 1, 3, 5, 6, 0, 0, 0},
|
||||
{2, 3, 5, 6, 0, 0, 0, 0}, {0, 2, 3, 5, 6, 0, 0, 0}, {1, 2, 3, 5, 6, 0, 0, 0},
|
||||
{0, 1, 2, 3, 5, 6, 0, 0}, {4, 5, 6, 0, 0, 0, 0, 0}, {0, 4, 5, 6, 0, 0, 0, 0},
|
||||
{1, 4, 5, 6, 0, 0, 0, 0}, {0, 1, 4, 5, 6, 0, 0, 0}, {2, 4, 5, 6, 0, 0, 0, 0},
|
||||
{0, 2, 4, 5, 6, 0, 0, 0}, {1, 2, 4, 5, 6, 0, 0, 0}, {0, 1, 2, 4, 5, 6, 0, 0},
|
||||
{3, 4, 5, 6, 0, 0, 0, 0}, {0, 3, 4, 5, 6, 0, 0, 0}, {1, 3, 4, 5, 6, 0, 0, 0},
|
||||
{0, 1, 3, 4, 5, 6, 0, 0}, {2, 3, 4, 5, 6, 0, 0, 0}, {0, 2, 3, 4, 5, 6, 0, 0},
|
||||
{1, 2, 3, 4, 5, 6, 0, 0}, {0, 1, 2, 3, 4, 5, 6, 0}, {7, 0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 7, 0, 0, 0, 0, 0, 0}, {1, 7, 0, 0, 0, 0, 0, 0}, {0, 1, 7, 0, 0, 0, 0, 0},
|
||||
{2, 7, 0, 0, 0, 0, 0, 0}, {0, 2, 7, 0, 0, 0, 0, 0}, {1, 2, 7, 0, 0, 0, 0, 0},
|
||||
{0, 1, 2, 7, 0, 0, 0, 0}, {3, 7, 0, 0, 0, 0, 0, 0}, {0, 3, 7, 0, 0, 0, 0, 0},
|
||||
{1, 3, 7, 0, 0, 0, 0, 0}, {0, 1, 3, 7, 0, 0, 0, 0}, {2, 3, 7, 0, 0, 0, 0, 0},
|
||||
{0, 2, 3, 7, 0, 0, 0, 0}, {1, 2, 3, 7, 0, 0, 0, 0}, {0, 1, 2, 3, 7, 0, 0, 0},
|
||||
{4, 7, 0, 0, 0, 0, 0, 0}, {0, 4, 7, 0, 0, 0, 0, 0}, {1, 4, 7, 0, 0, 0, 0, 0},
|
||||
{0, 1, 4, 7, 0, 0, 0, 0}, {2, 4, 7, 0, 0, 0, 0, 0}, {0, 2, 4, 7, 0, 0, 0, 0},
|
||||
{1, 2, 4, 7, 0, 0, 0, 0}, {0, 1, 2, 4, 7, 0, 0, 0}, {3, 4, 7, 0, 0, 0, 0, 0},
|
||||
{0, 3, 4, 7, 0, 0, 0, 0}, {1, 3, 4, 7, 0, 0, 0, 0}, {0, 1, 3, 4, 7, 0, 0, 0},
|
||||
{2, 3, 4, 7, 0, 0, 0, 0}, {0, 2, 3, 4, 7, 0, 0, 0}, {1, 2, 3, 4, 7, 0, 0, 0},
|
||||
{0, 1, 2, 3, 4, 7, 0, 0}, {5, 7, 0, 0, 0, 0, 0, 0}, {0, 5, 7, 0, 0, 0, 0, 0},
|
||||
{1, 5, 7, 0, 0, 0, 0, 0}, {0, 1, 5, 7, 0, 0, 0, 0}, {2, 5, 7, 0, 0, 0, 0, 0},
|
||||
{0, 2, 5, 7, 0, 0, 0, 0}, {1, 2, 5, 7, 0, 0, 0, 0}, {0, 1, 2, 5, 7, 0, 0, 0},
|
||||
{3, 5, 7, 0, 0, 0, 0, 0}, {0, 3, 5, 7, 0, 0, 0, 0}, {1, 3, 5, 7, 0, 0, 0, 0},
|
||||
{0, 1, 3, 5, 7, 0, 0, 0}, {2, 3, 5, 7, 0, 0, 0, 0}, {0, 2, 3, 5, 7, 0, 0, 0},
|
||||
{1, 2, 3, 5, 7, 0, 0, 0}, {0, 1, 2, 3, 5, 7, 0, 0}, {4, 5, 7, 0, 0, 0, 0, 0},
|
||||
{0, 4, 5, 7, 0, 0, 0, 0}, {1, 4, 5, 7, 0, 0, 0, 0}, {0, 1, 4, 5, 7, 0, 0, 0},
|
||||
{2, 4, 5, 7, 0, 0, 0, 0}, {0, 2, 4, 5, 7, 0, 0, 0}, {1, 2, 4, 5, 7, 0, 0, 0},
|
||||
{0, 1, 2, 4, 5, 7, 0, 0}, {3, 4, 5, 7, 0, 0, 0, 0}, {0, 3, 4, 5, 7, 0, 0, 0},
|
||||
{1, 3, 4, 5, 7, 0, 0, 0}, {0, 1, 3, 4, 5, 7, 0, 0}, {2, 3, 4, 5, 7, 0, 0, 0},
|
||||
{0, 2, 3, 4, 5, 7, 0, 0}, {1, 2, 3, 4, 5, 7, 0, 0}, {0, 1, 2, 3, 4, 5, 7, 0},
|
||||
{6, 7, 0, 0, 0, 0, 0, 0}, {0, 6, 7, 0, 0, 0, 0, 0}, {1, 6, 7, 0, 0, 0, 0, 0},
|
||||
{0, 1, 6, 7, 0, 0, 0, 0}, {2, 6, 7, 0, 0, 0, 0, 0}, {0, 2, 6, 7, 0, 0, 0, 0},
|
||||
{1, 2, 6, 7, 0, 0, 0, 0}, {0, 1, 2, 6, 7, 0, 0, 0}, {3, 6, 7, 0, 0, 0, 0, 0},
|
||||
{0, 3, 6, 7, 0, 0, 0, 0}, {1, 3, 6, 7, 0, 0, 0, 0}, {0, 1, 3, 6, 7, 0, 0, 0},
|
||||
{2, 3, 6, 7, 0, 0, 0, 0}, {0, 2, 3, 6, 7, 0, 0, 0}, {1, 2, 3, 6, 7, 0, 0, 0},
|
||||
{0, 1, 2, 3, 6, 7, 0, 0}, {4, 6, 7, 0, 0, 0, 0, 0}, {0, 4, 6, 7, 0, 0, 0, 0},
|
||||
{1, 4, 6, 7, 0, 0, 0, 0}, {0, 1, 4, 6, 7, 0, 0, 0}, {2, 4, 6, 7, 0, 0, 0, 0},
|
||||
{0, 2, 4, 6, 7, 0, 0, 0}, {1, 2, 4, 6, 7, 0, 0, 0}, {0, 1, 2, 4, 6, 7, 0, 0},
|
||||
{3, 4, 6, 7, 0, 0, 0, 0}, {0, 3, 4, 6, 7, 0, 0, 0}, {1, 3, 4, 6, 7, 0, 0, 0},
|
||||
{0, 1, 3, 4, 6, 7, 0, 0}, {2, 3, 4, 6, 7, 0, 0, 0}, {0, 2, 3, 4, 6, 7, 0, 0},
|
||||
{1, 2, 3, 4, 6, 7, 0, 0}, {0, 1, 2, 3, 4, 6, 7, 0}, {5, 6, 7, 0, 0, 0, 0, 0},
|
||||
{0, 5, 6, 7, 0, 0, 0, 0}, {1, 5, 6, 7, 0, 0, 0, 0}, {0, 1, 5, 6, 7, 0, 0, 0},
|
||||
{2, 5, 6, 7, 0, 0, 0, 0}, {0, 2, 5, 6, 7, 0, 0, 0}, {1, 2, 5, 6, 7, 0, 0, 0},
|
||||
{0, 1, 2, 5, 6, 7, 0, 0}, {3, 5, 6, 7, 0, 0, 0, 0}, {0, 3, 5, 6, 7, 0, 0, 0},
|
||||
{1, 3, 5, 6, 7, 0, 0, 0}, {0, 1, 3, 5, 6, 7, 0, 0}, {2, 3, 5, 6, 7, 0, 0, 0},
|
||||
{0, 2, 3, 5, 6, 7, 0, 0}, {1, 2, 3, 5, 6, 7, 0, 0}, {0, 1, 2, 3, 5, 6, 7, 0},
|
||||
{4, 5, 6, 7, 0, 0, 0, 0}, {0, 4, 5, 6, 7, 0, 0, 0}, {1, 4, 5, 6, 7, 0, 0, 0},
|
||||
{0, 1, 4, 5, 6, 7, 0, 0}, {2, 4, 5, 6, 7, 0, 0, 0}, {0, 2, 4, 5, 6, 7, 0, 0},
|
||||
{1, 2, 4, 5, 6, 7, 0, 0}, {0, 1, 2, 4, 5, 6, 7, 0}, {3, 4, 5, 6, 7, 0, 0, 0},
|
||||
{0, 3, 4, 5, 6, 7, 0, 0}, {1, 3, 4, 5, 6, 7, 0, 0}, {0, 1, 3, 4, 5, 6, 7, 0},
|
||||
{2, 3, 4, 5, 6, 7, 0, 0}, {0, 2, 3, 4, 5, 6, 7, 0}, {1, 2, 3, 4, 5, 6, 7, 0},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7}};
|
||||
|
||||
constexpr OffsetIndices() :
|
||||
offset_indices() {
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
std::uint64_t j = i, k = 0;
|
||||
while (j)
|
||||
{
|
||||
offset_indices[i][k++] = constexpr_lsb(j);
|
||||
j &= j - 1;
|
||||
}
|
||||
while (k < 8)
|
||||
offset_indices[i][k++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} Lookup;
|
||||
|
||||
// Find indices of nonzero numbers in an int32_t array
|
||||
template<const IndexType InputDimensions>
|
||||
@@ -196,9 +138,9 @@ void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_ou
|
||||
}
|
||||
for (IndexType j = 0; j < OutputsPerChunk; ++j)
|
||||
{
|
||||
const auto lookup = (nnz >> (j * 8)) & 0xFF;
|
||||
const auto offsets =
|
||||
vec128_load(reinterpret_cast<const vec128_t*>(&lookup_indices[lookup]));
|
||||
const unsigned lookup = (nnz >> (j * 8)) & 0xFF;
|
||||
const vec128_t offsets =
|
||||
vec128_load(reinterpret_cast<const vec128_t*>(&Lookup.offset_indices[lookup]));
|
||||
vec128_storeu(reinterpret_cast<vec128_t*>(out + count), vec128_add(base, offsets));
|
||||
count += popcount(lookup);
|
||||
base = vec128_add(base, increment);
|
||||
|
||||
@@ -280,12 +280,6 @@ void Network<Arch, Transformer>::verify(std::string
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
void Network<Arch, Transformer>::hint_common_access(
|
||||
const Position& pos, AccumulatorCaches::Cache<FTDimensions>* cache) const {
|
||||
featureTransformer->hint_common_access(pos, cache);
|
||||
}
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
NnueEvalTrace
|
||||
Network<Arch, Transformer>::trace_evaluate(const Position& pos,
|
||||
|
||||
@@ -67,9 +67,6 @@ class Network {
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const;
|
||||
|
||||
|
||||
void hint_common_access(const Position& pos,
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const;
|
||||
|
||||
void verify(std::string evalfilePath, const std::function<void(std::string_view)>&) const;
|
||||
NnueEvalTrace trace_evaluate(const Position& pos,
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const;
|
||||
|
||||
+236
-205
@@ -26,6 +26,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iosfwd>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "../position.h"
|
||||
@@ -40,6 +41,11 @@ using BiasType = std::int16_t;
|
||||
using WeightType = std::int16_t;
|
||||
using PSQTWeightType = std::int32_t;
|
||||
|
||||
enum IncUpdateDirection {
|
||||
FORWARD,
|
||||
BACKWARDS
|
||||
};
|
||||
|
||||
// If vector instructions are enabled, we update and refresh the
|
||||
// accumulator tile by tile such that each tile fits in the CPU's
|
||||
// vector registers.
|
||||
@@ -143,6 +149,46 @@ using psqt_vec_t = int32x4_t;
|
||||
|
||||
#endif
|
||||
|
||||
// Returns the inverse of a permutation
|
||||
template<std::size_t Len>
|
||||
constexpr std::array<std::size_t, Len>
|
||||
invert_permutation(const std::array<std::size_t, Len>& order) {
|
||||
std::array<std::size_t, Len> inverse{};
|
||||
for (std::size_t i = 0; i < order.size(); i++)
|
||||
inverse[order[i]] = i;
|
||||
return inverse;
|
||||
}
|
||||
|
||||
// Divide a byte region of size TotalSize to chunks of size
|
||||
// BlockSize, and permute the blocks by a given order
|
||||
template<std::size_t BlockSize, typename T, std::size_t N, std::size_t OrderSize>
|
||||
void permute(T (&data)[N], const std::array<std::size_t, OrderSize>& order) {
|
||||
constexpr std::size_t TotalSize = N * sizeof(T);
|
||||
|
||||
static_assert(TotalSize % (BlockSize * OrderSize) == 0,
|
||||
"ChunkSize * OrderSize must perfectly divide TotalSize");
|
||||
|
||||
constexpr std::size_t ProcessChunkSize = BlockSize * OrderSize;
|
||||
|
||||
std::array<std::byte, ProcessChunkSize> buffer{};
|
||||
|
||||
std::byte* const bytes = reinterpret_cast<std::byte*>(data);
|
||||
|
||||
for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize)
|
||||
{
|
||||
std::byte* const values = &bytes[i];
|
||||
|
||||
for (std::size_t j = 0; j < OrderSize; j++)
|
||||
{
|
||||
auto* const buffer_chunk = &buffer[j * BlockSize];
|
||||
auto* const value_chunk = &values[order[j] * BlockSize];
|
||||
|
||||
std::copy(value_chunk, value_chunk + BlockSize, buffer_chunk);
|
||||
}
|
||||
|
||||
std::copy(std::begin(buffer), std::end(buffer), values);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute optimal SIMD register count for feature transformer accumulation.
|
||||
template<IndexType TransformedFeatureWidth, IndexType HalfDimensions>
|
||||
@@ -206,6 +252,7 @@ class FeatureTransformer {
|
||||
|
||||
// Number of output dimensions for one side
|
||||
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
|
||||
static constexpr bool Big = TransformedFeatureDimensions == TransformedFeatureDimensionsBig;
|
||||
|
||||
private:
|
||||
using Tiling = SIMDTiling<TransformedFeatureDimensions, HalfDimensions>;
|
||||
@@ -221,62 +268,42 @@ class FeatureTransformer {
|
||||
// Size of forward propagation buffer
|
||||
static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType);
|
||||
|
||||
// Store the order by which 128-bit blocks of a 1024-bit data must
|
||||
// be permuted so that calling packus on adjacent vectors of 16-bit
|
||||
// integers loaded from the data results in the pre-permutation order
|
||||
static constexpr auto PackusEpi16Order = []() -> std::array<std::size_t, 8> {
|
||||
#if defined(USE_AVX512)
|
||||
// _mm512_packus_epi16 after permutation:
|
||||
// | 0 | 2 | 4 | 6 | // Vector 0
|
||||
// | 1 | 3 | 5 | 7 | // Vector 1
|
||||
// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | // Packed Result
|
||||
return {0, 2, 4, 6, 1, 3, 5, 7};
|
||||
#elif defined(USE_AVX2)
|
||||
// _mm256_packus_epi16 after permutation:
|
||||
// | 0 | 2 | | 4 | 6 | // Vector 0, 2
|
||||
// | 1 | 3 | | 5 | 7 | // Vector 1, 3
|
||||
// | 0 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | // Packed Result
|
||||
return {0, 2, 1, 3, 4, 6, 5, 7};
|
||||
#else
|
||||
return {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
#endif
|
||||
}();
|
||||
|
||||
static constexpr auto InversePackusEpi16Order = invert_permutation(PackusEpi16Order);
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t get_hash_value() {
|
||||
return FeatureSet::HashValue ^ (OutputDimensions * 2);
|
||||
}
|
||||
|
||||
static constexpr void order_packs([[maybe_unused]] uint64_t* v) {
|
||||
#if defined(USE_AVX512) // _mm512_packs_epi16 ordering
|
||||
uint64_t tmp0 = v[2], tmp1 = v[3];
|
||||
v[2] = v[8], v[3] = v[9];
|
||||
v[8] = v[4], v[9] = v[5];
|
||||
v[4] = tmp0, v[5] = tmp1;
|
||||
tmp0 = v[6], tmp1 = v[7];
|
||||
v[6] = v[10], v[7] = v[11];
|
||||
v[10] = v[12], v[11] = v[13];
|
||||
v[12] = tmp0, v[13] = tmp1;
|
||||
#elif defined(USE_AVX2) // _mm256_packs_epi16 ordering
|
||||
std::swap(v[2], v[4]);
|
||||
std::swap(v[3], v[5]);
|
||||
#endif
|
||||
void permute_weights() {
|
||||
permute<16>(biases, PackusEpi16Order);
|
||||
permute<16>(weights, PackusEpi16Order);
|
||||
}
|
||||
|
||||
static constexpr void inverse_order_packs([[maybe_unused]] uint64_t* v) {
|
||||
#if defined(USE_AVX512) // Inverse _mm512_packs_epi16 ordering
|
||||
uint64_t tmp0 = v[2], tmp1 = v[3];
|
||||
v[2] = v[4], v[3] = v[5];
|
||||
v[4] = v[8], v[5] = v[9];
|
||||
v[8] = tmp0, v[9] = tmp1;
|
||||
tmp0 = v[6], tmp1 = v[7];
|
||||
v[6] = v[12], v[7] = v[13];
|
||||
v[12] = v[10], v[13] = v[11];
|
||||
v[10] = tmp0, v[11] = tmp1;
|
||||
#elif defined(USE_AVX2) // Inverse _mm256_packs_epi16 ordering
|
||||
std::swap(v[2], v[4]);
|
||||
std::swap(v[3], v[5]);
|
||||
#endif
|
||||
}
|
||||
|
||||
void permute_weights([[maybe_unused]] void (*order_fn)(uint64_t*)) {
|
||||
#if defined(USE_AVX2)
|
||||
#if defined(USE_AVX512)
|
||||
constexpr IndexType di = 16;
|
||||
#else
|
||||
constexpr IndexType di = 8;
|
||||
#endif
|
||||
uint64_t* b = reinterpret_cast<uint64_t*>(&biases[0]);
|
||||
for (IndexType i = 0; i < HalfDimensions * sizeof(BiasType) / sizeof(uint64_t); i += di)
|
||||
order_fn(&b[i]);
|
||||
|
||||
for (IndexType j = 0; j < InputDimensions; ++j)
|
||||
{
|
||||
uint64_t* w = reinterpret_cast<uint64_t*>(&weights[j * HalfDimensions]);
|
||||
for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(uint64_t);
|
||||
i += di)
|
||||
order_fn(&w[i]);
|
||||
}
|
||||
#endif
|
||||
void unpermute_weights() {
|
||||
permute<16>(biases, InversePackusEpi16Order);
|
||||
permute<16>(weights, InversePackusEpi16Order);
|
||||
}
|
||||
|
||||
inline void scale_weights(bool read) {
|
||||
@@ -298,7 +325,7 @@ class FeatureTransformer {
|
||||
read_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
|
||||
read_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
|
||||
|
||||
permute_weights(inverse_order_packs);
|
||||
permute_weights();
|
||||
scale_weights(true);
|
||||
return !stream.fail();
|
||||
}
|
||||
@@ -306,14 +333,14 @@ class FeatureTransformer {
|
||||
// Write network parameters
|
||||
bool write_parameters(std::ostream& stream) {
|
||||
|
||||
permute_weights(order_packs);
|
||||
unpermute_weights();
|
||||
scale_weights(false);
|
||||
|
||||
write_leb_128<BiasType>(stream, biases, HalfDimensions);
|
||||
write_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
|
||||
write_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
|
||||
|
||||
permute_weights(inverse_order_packs);
|
||||
permute_weights();
|
||||
scale_weights(true);
|
||||
return !stream.fail();
|
||||
}
|
||||
@@ -445,102 +472,121 @@ class FeatureTransformer {
|
||||
return psqt;
|
||||
} // end of function transform()
|
||||
|
||||
void hint_common_access(const Position& pos,
|
||||
AccumulatorCaches::Cache<HalfDimensions>* cache) const {
|
||||
update_accumulator<WHITE>(pos, cache);
|
||||
update_accumulator<BLACK>(pos, cache);
|
||||
}
|
||||
|
||||
private:
|
||||
template<Color Perspective>
|
||||
StateInfo* try_find_computed_accumulator(const Position& pos) const {
|
||||
// Look for a usable accumulator of an earlier position. We keep track
|
||||
// of the estimated gain in terms of features to be added/subtracted.
|
||||
StateInfo* st = pos.state();
|
||||
int gain = FeatureSet::refresh_cost(pos);
|
||||
while (st->previous && !(st->*accPtr).computed[Perspective])
|
||||
{
|
||||
// This governs when a full feature refresh is needed and how many
|
||||
// updates are better than just one full refresh.
|
||||
if (FeatureSet::requires_refresh(st, Perspective)
|
||||
|| (gain -= FeatureSet::update_cost(st) + 1) < 0)
|
||||
break;
|
||||
st = st->previous;
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
// Computes the accumulator of the next position.
|
||||
template<Color Perspective>
|
||||
void update_accumulator_incremental(const Position& pos, StateInfo* computed) const {
|
||||
// Given a computed accumulator, computes the accumulator of another position.
|
||||
template<Color Perspective, IncUpdateDirection Direction = FORWARD>
|
||||
void update_accumulator_incremental(const Square ksq,
|
||||
StateInfo* target_state,
|
||||
const StateInfo* computed) const {
|
||||
[[maybe_unused]] constexpr bool Forward = Direction == FORWARD;
|
||||
[[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS;
|
||||
assert((computed->*accPtr).computed[Perspective]);
|
||||
assert(computed->next != nullptr);
|
||||
|
||||
#ifdef VECTOR
|
||||
// Gcc-10.2 unnecessarily spills AVX2 registers if this array
|
||||
// is defined in the VECTOR code below, once in each branch.
|
||||
vec_t acc[Tiling::NumRegs];
|
||||
psqt_vec_t psqt[Tiling::NumPsqtRegs];
|
||||
#endif
|
||||
StateInfo* next = Forward ? computed->next : computed->previous;
|
||||
|
||||
const Square ksq = pos.square<KING>(Perspective);
|
||||
assert(next != nullptr);
|
||||
assert(!(next->*accPtr).computed[Perspective]);
|
||||
|
||||
// The size must be enough to contain the largest possible update.
|
||||
// That might depend on the feature set and generally relies on the
|
||||
// feature set's update cost calculation to be correct and never allow
|
||||
// updates with more added/removed features than MaxActiveDimensions.
|
||||
// In this case, the maximum size of both feature addition and removal
|
||||
// is 2, since we are incrementally updating one move at a time.
|
||||
FeatureSet::IndexList removed, added;
|
||||
FeatureSet::append_changed_indices<Perspective>(ksq, computed->next->dirtyPiece, removed,
|
||||
added);
|
||||
if constexpr (Forward)
|
||||
FeatureSet::append_changed_indices<Perspective>(ksq, next->dirtyPiece, removed, added);
|
||||
else
|
||||
FeatureSet::append_changed_indices<Perspective>(ksq, computed->dirtyPiece, added,
|
||||
removed);
|
||||
|
||||
StateInfo* next = computed->next;
|
||||
assert(!(next->*accPtr).computed[Perspective]);
|
||||
if (removed.size() == 0 && added.size() == 0)
|
||||
{
|
||||
std::memcpy((next->*accPtr).accumulation[Perspective],
|
||||
(computed->*accPtr).accumulation[Perspective],
|
||||
HalfDimensions * sizeof(BiasType));
|
||||
std::memcpy((next->*accPtr).psqtAccumulation[Perspective],
|
||||
(computed->*accPtr).psqtAccumulation[Perspective],
|
||||
PSQTBuckets * sizeof(PSQTWeightType));
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(added.size() == 1 || added.size() == 2);
|
||||
assert(removed.size() == 1 || removed.size() == 2);
|
||||
if (Forward)
|
||||
assert(added.size() <= removed.size());
|
||||
else
|
||||
assert(removed.size() <= added.size());
|
||||
|
||||
#ifdef VECTOR
|
||||
if ((removed.size() == 1 || removed.size() == 2) && added.size() == 1)
|
||||
{
|
||||
auto* accIn =
|
||||
reinterpret_cast<const vec_t*>(&(computed->*accPtr).accumulation[Perspective][0]);
|
||||
auto* accOut = reinterpret_cast<vec_t*>(&(next->*accPtr).accumulation[Perspective][0]);
|
||||
|
||||
const IndexType offsetA0 = HalfDimensions * added[0];
|
||||
auto* columnA0 = reinterpret_cast<const vec_t*>(&weights[offsetA0]);
|
||||
const IndexType offsetR0 = HalfDimensions * removed[0];
|
||||
auto* columnR0 = reinterpret_cast<const vec_t*>(&weights[offsetR0]);
|
||||
const IndexType offsetA = HalfDimensions * added[0];
|
||||
auto* columnA = reinterpret_cast<const vec_t*>(&weights[offsetA]);
|
||||
|
||||
if (removed.size() == 1)
|
||||
if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1))
|
||||
{
|
||||
assert(added.size() == 1 && removed.size() == 1);
|
||||
for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i)
|
||||
accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA[i]);
|
||||
accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]);
|
||||
}
|
||||
else
|
||||
else if (Forward && added.size() == 1)
|
||||
{
|
||||
assert(removed.size() == 2);
|
||||
const IndexType offsetR1 = HalfDimensions * removed[1];
|
||||
auto* columnR1 = reinterpret_cast<const vec_t*>(&weights[offsetR1]);
|
||||
|
||||
for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i)
|
||||
accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA[i]),
|
||||
accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]),
|
||||
vec_add_16(columnR0[i], columnR1[i]));
|
||||
}
|
||||
else if (Backwards && removed.size() == 1)
|
||||
{
|
||||
assert(added.size() == 2);
|
||||
const IndexType offsetA1 = HalfDimensions * added[1];
|
||||
auto* columnA1 = reinterpret_cast<const vec_t*>(&weights[offsetA1]);
|
||||
|
||||
for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i)
|
||||
accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]),
|
||||
vec_sub_16(columnA1[i], columnR0[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(added.size() == 2 && removed.size() == 2);
|
||||
const IndexType offsetA1 = HalfDimensions * added[1];
|
||||
auto* columnA1 = reinterpret_cast<const vec_t*>(&weights[offsetA1]);
|
||||
const IndexType offsetR1 = HalfDimensions * removed[1];
|
||||
auto* columnR1 = reinterpret_cast<const vec_t*>(&weights[offsetR1]);
|
||||
|
||||
for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i)
|
||||
accOut[i] =
|
||||
vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]),
|
||||
vec_add_16(columnR0[i], columnR1[i])));
|
||||
}
|
||||
|
||||
auto* accPsqtIn = reinterpret_cast<const psqt_vec_t*>(
|
||||
&(computed->*accPtr).psqtAccumulation[Perspective][0]);
|
||||
auto* accPsqtOut =
|
||||
reinterpret_cast<psqt_vec_t*>(&(next->*accPtr).psqtAccumulation[Perspective][0]);
|
||||
|
||||
const IndexType offsetPsqtA0 = PSQTBuckets * added[0];
|
||||
auto* columnPsqtA0 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtA0]);
|
||||
const IndexType offsetPsqtR0 = PSQTBuckets * removed[0];
|
||||
auto* columnPsqtR0 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtR0]);
|
||||
const IndexType offsetPsqtA = PSQTBuckets * added[0];
|
||||
auto* columnPsqtA = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtA]);
|
||||
|
||||
if (removed.size() == 1)
|
||||
if ((Forward && removed.size() == 1)
|
||||
|| (Backwards && added.size() == 1)) // added.size() == removed.size() == 1
|
||||
{
|
||||
for (std::size_t i = 0;
|
||||
i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i)
|
||||
accPsqtOut[i] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]),
|
||||
columnPsqtA[i]);
|
||||
columnPsqtA0[i]);
|
||||
}
|
||||
else
|
||||
else if (Forward && added.size() == 1)
|
||||
{
|
||||
const IndexType offsetPsqtR1 = PSQTBuckets * removed[1];
|
||||
auto* columnPsqtR1 =
|
||||
@@ -549,117 +595,78 @@ class FeatureTransformer {
|
||||
for (std::size_t i = 0;
|
||||
i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i)
|
||||
accPsqtOut[i] =
|
||||
vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA[i]),
|
||||
vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]),
|
||||
vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (IndexType i = 0; i < HalfDimensions / Tiling::TileHeight; ++i)
|
||||
else if (Backwards && removed.size() == 1)
|
||||
{
|
||||
// Load accumulator
|
||||
auto* accTileIn = reinterpret_cast<const vec_t*>(
|
||||
&(computed->*accPtr).accumulation[Perspective][i * Tiling::TileHeight]);
|
||||
for (IndexType j = 0; j < Tiling::NumRegs; ++j)
|
||||
acc[j] = vec_load(&accTileIn[j]);
|
||||
const IndexType offsetPsqtA1 = PSQTBuckets * added[1];
|
||||
auto* columnPsqtA1 =
|
||||
reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtA1]);
|
||||
|
||||
// Difference calculation for the deactivated features
|
||||
for (const auto index : removed)
|
||||
{
|
||||
const IndexType offset = HalfDimensions * index + i * Tiling::TileHeight;
|
||||
auto* column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
||||
for (IndexType j = 0; j < Tiling::NumRegs; ++j)
|
||||
acc[j] = vec_sub_16(acc[j], column[j]);
|
||||
}
|
||||
|
||||
// Difference calculation for the activated features
|
||||
for (const auto index : added)
|
||||
{
|
||||
const IndexType offset = HalfDimensions * index + i * Tiling::TileHeight;
|
||||
auto* column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
||||
for (IndexType j = 0; j < Tiling::NumRegs; ++j)
|
||||
acc[j] = vec_add_16(acc[j], column[j]);
|
||||
}
|
||||
|
||||
// Store accumulator
|
||||
auto* accTileOut = reinterpret_cast<vec_t*>(
|
||||
&(next->*accPtr).accumulation[Perspective][i * Tiling::TileHeight]);
|
||||
for (IndexType j = 0; j < Tiling::NumRegs; ++j)
|
||||
vec_store(&accTileOut[j], acc[j]);
|
||||
for (std::size_t i = 0;
|
||||
i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i)
|
||||
accPsqtOut[i] =
|
||||
vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]),
|
||||
vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i]));
|
||||
}
|
||||
|
||||
for (IndexType i = 0; i < PSQTBuckets / Tiling::PsqtTileHeight; ++i)
|
||||
else
|
||||
{
|
||||
// Load accumulator
|
||||
auto* accTilePsqtIn = reinterpret_cast<const psqt_vec_t*>(
|
||||
&(computed->*accPtr).psqtAccumulation[Perspective][i * Tiling::PsqtTileHeight]);
|
||||
for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j)
|
||||
psqt[j] = vec_load_psqt(&accTilePsqtIn[j]);
|
||||
const IndexType offsetPsqtA1 = PSQTBuckets * added[1];
|
||||
auto* columnPsqtA1 =
|
||||
reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtA1]);
|
||||
const IndexType offsetPsqtR1 = PSQTBuckets * removed[1];
|
||||
auto* columnPsqtR1 =
|
||||
reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtR1]);
|
||||
|
||||
// Difference calculation for the deactivated features
|
||||
for (const auto index : removed)
|
||||
{
|
||||
const IndexType offset = PSQTBuckets * index + i * Tiling::PsqtTileHeight;
|
||||
auto* columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
||||
for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j)
|
||||
psqt[j] = vec_sub_psqt_32(psqt[j], columnPsqt[j]);
|
||||
}
|
||||
|
||||
// Difference calculation for the activated features
|
||||
for (const auto index : added)
|
||||
{
|
||||
const IndexType offset = PSQTBuckets * index + i * Tiling::PsqtTileHeight;
|
||||
auto* columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
||||
for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j)
|
||||
psqt[j] = vec_add_psqt_32(psqt[j], columnPsqt[j]);
|
||||
}
|
||||
|
||||
// Store accumulator
|
||||
auto* accTilePsqtOut = reinterpret_cast<psqt_vec_t*>(
|
||||
&(next->*accPtr).psqtAccumulation[Perspective][i * Tiling::PsqtTileHeight]);
|
||||
for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j)
|
||||
vec_store_psqt(&accTilePsqtOut[j], psqt[j]);
|
||||
for (std::size_t i = 0;
|
||||
i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i)
|
||||
accPsqtOut[i] = vec_add_psqt_32(
|
||||
accPsqtIn[i],
|
||||
vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]),
|
||||
vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])));
|
||||
}
|
||||
}
|
||||
#else
|
||||
std::memcpy((next->*accPtr).accumulation[Perspective],
|
||||
(computed->*accPtr).accumulation[Perspective],
|
||||
HalfDimensions * sizeof(BiasType));
|
||||
std::memcpy((next->*accPtr).psqtAccumulation[Perspective],
|
||||
(computed->*accPtr).psqtAccumulation[Perspective],
|
||||
PSQTBuckets * sizeof(PSQTWeightType));
|
||||
std::memcpy((next->*accPtr).accumulation[Perspective],
|
||||
(computed->*accPtr).accumulation[Perspective],
|
||||
HalfDimensions * sizeof(BiasType));
|
||||
std::memcpy((next->*accPtr).psqtAccumulation[Perspective],
|
||||
(computed->*accPtr).psqtAccumulation[Perspective],
|
||||
PSQTBuckets * sizeof(PSQTWeightType));
|
||||
|
||||
// Difference calculation for the deactivated features
|
||||
for (const auto index : removed)
|
||||
{
|
||||
const IndexType offset = HalfDimensions * index;
|
||||
for (IndexType i = 0; i < HalfDimensions; ++i)
|
||||
(next->*accPtr).accumulation[Perspective][i] -= weights[offset + i];
|
||||
// Difference calculation for the deactivated features
|
||||
for (const auto index : removed)
|
||||
{
|
||||
const IndexType offset = HalfDimensions * index;
|
||||
for (IndexType i = 0; i < HalfDimensions; ++i)
|
||||
(next->*accPtr).accumulation[Perspective][i] -= weights[offset + i];
|
||||
|
||||
for (std::size_t i = 0; i < PSQTBuckets; ++i)
|
||||
(next->*accPtr).psqtAccumulation[Perspective][i] -=
|
||||
psqtWeights[index * PSQTBuckets + i];
|
||||
}
|
||||
for (std::size_t i = 0; i < PSQTBuckets; ++i)
|
||||
(next->*accPtr).psqtAccumulation[Perspective][i] -=
|
||||
psqtWeights[index * PSQTBuckets + i];
|
||||
}
|
||||
|
||||
// Difference calculation for the activated features
|
||||
for (const auto index : added)
|
||||
{
|
||||
const IndexType offset = HalfDimensions * index;
|
||||
for (IndexType i = 0; i < HalfDimensions; ++i)
|
||||
(next->*accPtr).accumulation[Perspective][i] += weights[offset + i];
|
||||
// Difference calculation for the activated features
|
||||
for (const auto index : added)
|
||||
{
|
||||
const IndexType offset = HalfDimensions * index;
|
||||
for (IndexType i = 0; i < HalfDimensions; ++i)
|
||||
(next->*accPtr).accumulation[Perspective][i] += weights[offset + i];
|
||||
|
||||
for (std::size_t i = 0; i < PSQTBuckets; ++i)
|
||||
(next->*accPtr).psqtAccumulation[Perspective][i] +=
|
||||
psqtWeights[index * PSQTBuckets + i];
|
||||
}
|
||||
for (std::size_t i = 0; i < PSQTBuckets; ++i)
|
||||
(next->*accPtr).psqtAccumulation[Perspective][i] +=
|
||||
psqtWeights[index * PSQTBuckets + i];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
(next->*accPtr).computed[Perspective] = true;
|
||||
|
||||
if (next != pos.state())
|
||||
update_accumulator_incremental<Perspective>(pos, next);
|
||||
if (next != target_state)
|
||||
update_accumulator_incremental<Perspective, Direction>(ksq, target_state, next);
|
||||
}
|
||||
|
||||
|
||||
template<Color Perspective>
|
||||
void update_accumulator_refresh_cache(const Position& pos,
|
||||
AccumulatorCaches::Cache<HalfDimensions>* cache) const {
|
||||
@@ -823,16 +830,40 @@ class FeatureTransformer {
|
||||
template<Color Perspective>
|
||||
void update_accumulator(const Position& pos,
|
||||
AccumulatorCaches::Cache<HalfDimensions>* cache) const {
|
||||
if ((pos.state()->*accPtr).computed[Perspective])
|
||||
return;
|
||||
StateInfo* oldest = try_find_computed_accumulator<Perspective>(pos);
|
||||
StateInfo* st = pos.state();
|
||||
if ((st->*accPtr).computed[Perspective])
|
||||
return; // nothing to do
|
||||
|
||||
if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state())
|
||||
// Start from the oldest computed accumulator, update all the
|
||||
// accumulators up to the current position.
|
||||
update_accumulator_incremental<Perspective>(pos, oldest);
|
||||
else
|
||||
update_accumulator_refresh_cache<Perspective>(pos, cache);
|
||||
[[maybe_unused]] // only used when !Big
|
||||
int gain = FeatureSet::refresh_cost(pos);
|
||||
// Look for a usable already computed accumulator of an earlier position.
|
||||
// When computing the small accumulator, we keep track of the estimated gain in
|
||||
// terms of features to be added/subtracted.
|
||||
// When computing the big accumulator, we expect to be able to reuse any
|
||||
// accumulators, so we always try to do an incremental update.
|
||||
do
|
||||
{
|
||||
if (FeatureSet::requires_refresh(st, Perspective)
|
||||
|| (!Big && (gain -= FeatureSet::update_cost(st) < 0)) || !st->previous
|
||||
|| st->previous->next != st)
|
||||
{
|
||||
// compute accumulator from scratch for this position
|
||||
update_accumulator_refresh_cache<Perspective>(pos, cache);
|
||||
if (Big && st != pos.state())
|
||||
// when computing a big accumulator from scratch we can use it to
|
||||
// efficiently compute the accumulator backwards, until we get to a king
|
||||
// move. We expect that we will need these accumulators later anyway, so
|
||||
// computing them now will save some work.
|
||||
update_accumulator_incremental<Perspective, BACKWARDS>(
|
||||
pos.square<KING>(Perspective), st, pos.state());
|
||||
return;
|
||||
}
|
||||
st = st->previous;
|
||||
} while (!(st->*accPtr).computed[Perspective]);
|
||||
|
||||
// Start from the oldest computed accumulator, update all the
|
||||
// accumulators up to the current position.
|
||||
update_accumulator_incremental<Perspective>(pos.square<KING>(Perspective), pos.state(), st);
|
||||
}
|
||||
|
||||
template<IndexType Size>
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
#include "../evaluate.h"
|
||||
#include "../position.h"
|
||||
#include "../types.h"
|
||||
#include "../uci.h"
|
||||
@@ -43,15 +42,6 @@ namespace Stockfish::Eval::NNUE {
|
||||
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
|
||||
|
||||
|
||||
void hint_common_parent_position(const Position& pos,
|
||||
const Networks& networks,
|
||||
AccumulatorCaches& caches) {
|
||||
if (Eval::use_smallnet(pos))
|
||||
networks.small.hint_common_access(pos, &caches.small);
|
||||
else
|
||||
networks.big.hint_common_access(pos, &caches.big);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Converts a Value into (centi)pawns and writes it in a buffer.
|
||||
// The buffer must have capacity for at least 5 chars.
|
||||
|
||||
@@ -54,9 +54,6 @@ struct Networks;
|
||||
struct AccumulatorCaches;
|
||||
|
||||
std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches);
|
||||
void hint_common_parent_position(const Position& pos,
|
||||
const Networks& networks,
|
||||
AccumulatorCaches& caches);
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
} // namespace Stockfish
|
||||
|
||||
+26
-47
@@ -119,6 +119,9 @@ void Position::init() {
|
||||
for (Piece pc : Pieces)
|
||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||
Zobrist::psq[pc][s] = rng.rand<Key>();
|
||||
// pawns on these squares will promote
|
||||
std::fill_n(Zobrist::psq[W_PAWN] + SQ_A8, 8, 0);
|
||||
std::fill_n(Zobrist::psq[B_PAWN], 8, 0);
|
||||
|
||||
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||
Zobrist::enpassant[f] = rng.rand<Key>();
|
||||
@@ -335,7 +338,7 @@ void Position::set_check_info() const {
|
||||
void Position::set_state() const {
|
||||
|
||||
st->key = st->materialKey = 0;
|
||||
st->majorPieceKey = st->minorPieceKey = 0;
|
||||
st->minorPieceKey = 0;
|
||||
st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0;
|
||||
st->pawnKey = Zobrist::noPawns;
|
||||
st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO;
|
||||
@@ -360,18 +363,9 @@ void Position::set_state() const {
|
||||
{
|
||||
st->nonPawnMaterial[color_of(pc)] += PieceValue[pc];
|
||||
|
||||
if (type_of(pc) >= ROOK)
|
||||
st->majorPieceKey ^= Zobrist::psq[pc][s];
|
||||
|
||||
else
|
||||
if (type_of(pc) <= BISHOP)
|
||||
st->minorPieceKey ^= Zobrist::psq[pc][s];
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
st->majorPieceKey ^= Zobrist::psq[pc][s];
|
||||
st->minorPieceKey ^= Zobrist::psq[pc][s];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +379,7 @@ void Position::set_state() const {
|
||||
|
||||
for (Piece pc : Pieces)
|
||||
for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
|
||||
st->materialKey ^= Zobrist::psq[pc][cnt];
|
||||
st->materialKey ^= Zobrist::psq[pc][8 + cnt];
|
||||
}
|
||||
|
||||
|
||||
@@ -742,7 +736,6 @@ void Position::do_move(Move m,
|
||||
do_castling<true>(us, from, to, rfrom, rto);
|
||||
|
||||
k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
|
||||
st->majorPieceKey ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
|
||||
st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
|
||||
captured = NO_PIECE;
|
||||
}
|
||||
@@ -773,10 +766,7 @@ void Position::do_move(Move m,
|
||||
st->nonPawnMaterial[them] -= PieceValue[captured];
|
||||
st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq];
|
||||
|
||||
if (type_of(captured) >= ROOK)
|
||||
st->majorPieceKey ^= Zobrist::psq[captured][capsq];
|
||||
|
||||
else
|
||||
if (type_of(captured) <= BISHOP)
|
||||
st->minorPieceKey ^= Zobrist::psq[captured][capsq];
|
||||
}
|
||||
|
||||
@@ -789,7 +779,7 @@ void Position::do_move(Move m,
|
||||
remove_piece(capsq);
|
||||
|
||||
k ^= Zobrist::psq[captured][capsq];
|
||||
st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]];
|
||||
st->materialKey ^= Zobrist::psq[captured][8 + pieceCount[captured]];
|
||||
|
||||
// Reset rule 50 counter
|
||||
st->rule50 = 0;
|
||||
@@ -853,15 +843,12 @@ void Position::do_move(Move m,
|
||||
dp.dirty_num++;
|
||||
|
||||
// Update hash keys
|
||||
k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
|
||||
st->pawnKey ^= Zobrist::psq[pc][to];
|
||||
st->materialKey ^=
|
||||
Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]];
|
||||
// Zobrist::psq[pc][to] is zero, so we don't need to clear it
|
||||
k ^= Zobrist::psq[promotion][to];
|
||||
st->materialKey ^= Zobrist::psq[promotion][8 + pieceCount[promotion] - 1]
|
||||
^ Zobrist::psq[pc][8 + pieceCount[pc]];
|
||||
|
||||
if (promotionType >= ROOK)
|
||||
st->majorPieceKey ^= Zobrist::psq[promotion][to];
|
||||
|
||||
else
|
||||
if (promotionType <= BISHOP)
|
||||
st->minorPieceKey ^= Zobrist::psq[promotion][to];
|
||||
|
||||
// Update material
|
||||
@@ -879,16 +866,7 @@ void Position::do_move(Move m,
|
||||
{
|
||||
st->nonPawnKey[us] ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
||||
|
||||
if (type_of(pc) == KING)
|
||||
{
|
||||
st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
||||
st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
||||
}
|
||||
|
||||
else if (type_of(pc) >= ROOK)
|
||||
st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
||||
|
||||
else
|
||||
if (type_of(pc) <= BISHOP)
|
||||
st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
||||
}
|
||||
|
||||
@@ -1039,11 +1017,6 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) {
|
||||
st->next = &newSt;
|
||||
st = &newSt;
|
||||
|
||||
st->dirtyPiece.dirty_num = 0;
|
||||
st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator()
|
||||
st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] =
|
||||
st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false;
|
||||
|
||||
if (st->epSquare != SQ_NONE)
|
||||
{
|
||||
st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
|
||||
@@ -1051,9 +1024,13 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) {
|
||||
}
|
||||
|
||||
st->key ^= Zobrist::side;
|
||||
++st->rule50;
|
||||
prefetch(tt.first_entry(key()));
|
||||
|
||||
st->dirtyPiece.dirty_num = 0;
|
||||
st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator()
|
||||
st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] =
|
||||
st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false;
|
||||
|
||||
st->pliesFromNull = 0;
|
||||
|
||||
sideToMove = ~sideToMove;
|
||||
@@ -1163,8 +1140,9 @@ bool Position::see_ge(Move m, int threshold) const {
|
||||
|
||||
else if ((bb = stmAttackers & pieces(QUEEN)))
|
||||
{
|
||||
if ((swap = QueenValue - swap) < res)
|
||||
break;
|
||||
swap = QueenValue - swap;
|
||||
// implies that the previous recapture was done by a higher rated piece than a Queen (King is excluded)
|
||||
assert(swap >= res);
|
||||
occupied ^= least_significant_square_bb(bb);
|
||||
|
||||
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
|
||||
@@ -1187,11 +1165,12 @@ bool Position::is_draw(int ply) const {
|
||||
if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
|
||||
return true;
|
||||
|
||||
// Return a draw score if a position repeats once earlier but strictly
|
||||
// after the root, or repeats twice before or at the root.
|
||||
return st->repetition && st->repetition < ply;
|
||||
return is_repetition(ply);
|
||||
}
|
||||
|
||||
// Return a draw score if a position repeats once earlier but strictly
|
||||
// after the root, or repeats twice before or at the root.
|
||||
bool Position::is_repetition(int ply) const { return st->repetition && st->repetition < ply; }
|
||||
|
||||
// Tests whether there has been at least one repetition
|
||||
// of positions since the last capture or pawn move.
|
||||
|
||||
+2
-5
@@ -43,7 +43,6 @@ struct StateInfo {
|
||||
// Copied when making a move
|
||||
Key materialKey;
|
||||
Key pawnKey;
|
||||
Key majorPieceKey;
|
||||
Key minorPieceKey;
|
||||
Key nonPawnKey[COLOR_NB];
|
||||
Value nonPawnMaterial[COLOR_NB];
|
||||
@@ -64,9 +63,9 @@ struct StateInfo {
|
||||
int repetition;
|
||||
|
||||
// Used by NNUE
|
||||
DirtyPiece dirtyPiece;
|
||||
Eval::NNUE::Accumulator<Eval::NNUE::TransformedFeatureDimensionsBig> accumulatorBig;
|
||||
Eval::NNUE::Accumulator<Eval::NNUE::TransformedFeatureDimensionsSmall> accumulatorSmall;
|
||||
DirtyPiece dirtyPiece;
|
||||
};
|
||||
|
||||
|
||||
@@ -154,7 +153,6 @@ class Position {
|
||||
Key key() const;
|
||||
Key material_key() const;
|
||||
Key pawn_key() const;
|
||||
Key major_piece_key() const;
|
||||
Key minor_piece_key() const;
|
||||
Key non_pawn_key(Color c) const;
|
||||
|
||||
@@ -163,6 +161,7 @@ class Position {
|
||||
int game_ply() const;
|
||||
bool is_chess960() const;
|
||||
bool is_draw(int ply) const;
|
||||
bool is_repetition(int ply) const;
|
||||
bool upcoming_repetition(int ply) const;
|
||||
bool has_repeated() const;
|
||||
int rule50_count() const;
|
||||
@@ -304,8 +303,6 @@ inline Key Position::pawn_key() const { return st->pawnKey; }
|
||||
|
||||
inline Key Position::material_key() const { return st->materialKey; }
|
||||
|
||||
inline Key Position::major_piece_key() const { return st->majorPieceKey; }
|
||||
|
||||
inline Key Position::minor_piece_key() const { return st->minorPieceKey; }
|
||||
|
||||
inline Key Position::non_pawn_key(Color c) const { return st->nonPawnKey[c]; }
|
||||
|
||||
+210
-216
@@ -42,7 +42,6 @@
|
||||
#include "nnue/network.h"
|
||||
#include "nnue/nnue_accumulator.h"
|
||||
#include "nnue/nnue_common.h"
|
||||
#include "nnue/nnue_misc.h"
|
||||
#include "position.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
#include "thread.h"
|
||||
@@ -65,6 +64,12 @@ using namespace Search;
|
||||
|
||||
namespace {
|
||||
|
||||
// (*Scalers):
|
||||
// The values with Scaler asterisks have proven non-linear scaling.
|
||||
// They are optimized to time controls of 180 + 1.8 and longer,
|
||||
// so changing them or adding conditions that are similar requires
|
||||
// tests at these types of time controls.
|
||||
|
||||
// Futility margin
|
||||
Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) {
|
||||
Value futilityMult = 112 - 26 * noTtCutNode;
|
||||
@@ -78,32 +83,47 @@ constexpr int futility_move_count(bool improving, Depth depth) {
|
||||
return (3 + depth * depth) / (2 - improving);
|
||||
}
|
||||
|
||||
int correction_value(const Worker& w, const Position& pos, const Stack* ss) {
|
||||
int correction_value(const Worker& w, const Position& pos, const Stack* const ss) {
|
||||
const Color us = pos.side_to_move();
|
||||
const auto m = (ss - 1)->currentMove;
|
||||
const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)];
|
||||
const auto macv = w.majorPieceCorrectionHistory[us][major_piece_index(pos)];
|
||||
const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)];
|
||||
const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)];
|
||||
const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)];
|
||||
const auto pcv = w.pawnCorrectionHistory[pawn_structure_index<Correction>(pos)][us];
|
||||
const auto micv = w.minorPieceCorrectionHistory[minor_piece_index(pos)][us];
|
||||
const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][non_pawn_index<WHITE>(pos)][us];
|
||||
const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][non_pawn_index<BLACK>(pos)][us];
|
||||
const auto cntcv =
|
||||
m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]
|
||||
: 0;
|
||||
|
||||
return (6922 * pcv + 3837 * macv + 6238 * micv + 7490 * (wnpcv + bnpcv) + 6270 * cntcv);
|
||||
return 6995 * pcv + 6593 * micv + 7753 * (wnpcv + bnpcv) + 6049 * cntcv;
|
||||
}
|
||||
|
||||
// Add correctionHistory value to raw staticEval and guarantee evaluation
|
||||
// does not hit the tablebase range.
|
||||
Value to_corrected_static_eval(Value v, const int cv) {
|
||||
Value to_corrected_static_eval(const Value v, const int cv) {
|
||||
return std::clamp(v + cv / 131072, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
||||
}
|
||||
|
||||
// History and stats update bonus, based on depth
|
||||
int stat_bonus(Depth d) { return std::min(154 * d - 102, 1661); }
|
||||
void update_correction_history(const Position& pos,
|
||||
Stack* const ss,
|
||||
Search::Worker& workerThread,
|
||||
const int bonus) {
|
||||
const Move m = (ss - 1)->currentMove;
|
||||
const Color us = pos.side_to_move();
|
||||
|
||||
// History and stats update malus, based on depth
|
||||
int stat_malus(Depth d) { return std::min(831 * d - 269, 2666); }
|
||||
static constexpr int nonPawnWeight = 165;
|
||||
|
||||
workerThread.pawnCorrectionHistory[pawn_structure_index<Correction>(pos)][us]
|
||||
<< bonus * 109 / 128;
|
||||
workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 141 / 128;
|
||||
workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index<WHITE>(pos)][us]
|
||||
<< bonus * nonPawnWeight / 128;
|
||||
workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index<BLACK>(pos)][us]
|
||||
<< bonus * nonPawnWeight / 128;
|
||||
|
||||
if (m.is_ok())
|
||||
(*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]
|
||||
<< bonus * 138 / 128;
|
||||
}
|
||||
|
||||
// Add a small random component to draw evaluations to avoid 3-fold blindness
|
||||
Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); }
|
||||
@@ -121,7 +141,8 @@ void update_all_stats(const Position& pos,
|
||||
ValueList<Move, 32>& quietsSearched,
|
||||
ValueList<Move, 32>& capturesSearched,
|
||||
Depth depth,
|
||||
bool isTTMove);
|
||||
bool isTTMove,
|
||||
int moveCount);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -321,7 +342,7 @@ void Search::Worker::iterative_deepening() {
|
||||
|
||||
int searchAgainCounter = 0;
|
||||
|
||||
lowPlyHistory.fill(97);
|
||||
lowPlyHistory.fill(95);
|
||||
|
||||
// Iterative deepening loop until requested to stop or the target depth is reached
|
||||
while (++rootDepth < MAX_PLY && !threads.stop
|
||||
@@ -357,13 +378,13 @@ void Search::Worker::iterative_deepening() {
|
||||
selDepth = 0;
|
||||
|
||||
// Reset aspiration window starting size
|
||||
delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 12991;
|
||||
delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13000;
|
||||
Value avg = rootMoves[pvIdx].averageScore;
|
||||
alpha = std::max(avg - delta, -VALUE_INFINITE);
|
||||
beta = std::min(avg + delta, VALUE_INFINITE);
|
||||
|
||||
// Adjust optimism based on root move's averageScore (~4 Elo)
|
||||
optimism[us] = 141 * avg / (std::abs(avg) + 83);
|
||||
// Adjust optimism based on root move's averageScore
|
||||
optimism[us] = 138 * avg / (std::abs(avg) + 81);
|
||||
optimism[~us] = -optimism[us];
|
||||
|
||||
// Start with a small aspiration window and, in the case of a fail
|
||||
@@ -553,12 +574,11 @@ void Search::Worker::iterative_deepening() {
|
||||
|
||||
// Reset histories, usually before a new game
|
||||
void Search::Worker::clear() {
|
||||
mainHistory.fill(63);
|
||||
lowPlyHistory.fill(108);
|
||||
captureHistory.fill(-631);
|
||||
pawnHistory.fill(-1210);
|
||||
pawnCorrectionHistory.fill(0);
|
||||
majorPieceCorrectionHistory.fill(0);
|
||||
mainHistory.fill(65);
|
||||
lowPlyHistory.fill(107);
|
||||
captureHistory.fill(-655);
|
||||
pawnHistory.fill(-1215);
|
||||
pawnCorrectionHistory.fill(4);
|
||||
minorPieceCorrectionHistory.fill(0);
|
||||
nonPawnCorrectionHistory[WHITE].fill(0);
|
||||
nonPawnCorrectionHistory[BLACK].fill(0);
|
||||
@@ -571,10 +591,10 @@ void Search::Worker::clear() {
|
||||
for (StatsType c : {NoCaptures, Captures})
|
||||
for (auto& to : continuationHistory[inCheck][c])
|
||||
for (auto& h : to)
|
||||
h.fill(-479);
|
||||
h.fill(-493);
|
||||
|
||||
for (size_t i = 1; i < reductions.size(); ++i)
|
||||
reductions[i] = int(2143 / 100.0 * std::log(i));
|
||||
reductions[i] = int(2937 / 128.0 * std::log(i));
|
||||
|
||||
refreshTable.clear(networks[numaAccessToken]);
|
||||
}
|
||||
@@ -622,8 +642,8 @@ Value Search::Worker::search(
|
||||
Value bestValue, value, eval, maxValue, probCutBeta;
|
||||
bool givesCheck, improving, priorCapture, opponentWorsening;
|
||||
bool capture, ttCapture;
|
||||
int priorReduction = ss->reduction;
|
||||
ss->reduction = 0;
|
||||
int priorReduction = (ss - 1)->reduction;
|
||||
(ss - 1)->reduction = 0;
|
||||
Piece movedPiece;
|
||||
|
||||
ValueList<Move, 32> capturesSearched;
|
||||
@@ -693,20 +713,21 @@ Value Search::Worker::search(
|
||||
if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta)
|
||||
&& is_valid(ttData.value) // Can happen when !ttHit or when access race in probe()
|
||||
&& (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))
|
||||
&& (cutNode == (ttData.value >= beta) || depth > 9))
|
||||
&& (cutNode == (ttData.value >= beta) || depth > 5))
|
||||
{
|
||||
// If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo)
|
||||
// If ttMove is quiet, update move sorting heuristics on TT hit
|
||||
if (ttData.move && ttData.value >= beta)
|
||||
{
|
||||
// Bonus for a quiet ttMove that fails high (~2 Elo)
|
||||
// Bonus for a quiet ttMove that fails high
|
||||
if (!ttCapture)
|
||||
update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 746 / 1024);
|
||||
update_quiet_histories(pos, ss, *this, ttData.move,
|
||||
std::min(117600 * depth - 71344, 1244992) / 1024);
|
||||
|
||||
// Extra penalty for early quiet moves of
|
||||
// the previous ply (~1 Elo on STC, ~2 Elo on LTC)
|
||||
if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture)
|
||||
// Extra penalty for early quiet moves of the previous ply
|
||||
if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture)
|
||||
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
|
||||
-stat_malus(depth + 1) * 1042 / 1024);
|
||||
-std::min(779788 * (depth + 1) - 271806, 2958308)
|
||||
/ 1024);
|
||||
}
|
||||
|
||||
// Partial workaround for the graph history interaction problem
|
||||
@@ -782,8 +803,6 @@ Value Search::Worker::search(
|
||||
else if (excludedMove)
|
||||
{
|
||||
// Providing the hint that this node's accumulator will be used often
|
||||
// brings significant Elo gain (~13 Elo).
|
||||
Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable);
|
||||
unadjustedStaticEval = eval = ss->staticEval;
|
||||
}
|
||||
else if (ss->ttHit)
|
||||
@@ -792,12 +811,10 @@ Value Search::Worker::search(
|
||||
unadjustedStaticEval = ttData.eval;
|
||||
if (!is_valid(unadjustedStaticEval))
|
||||
unadjustedStaticEval = evaluate(pos);
|
||||
else if (PvNode)
|
||||
Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable);
|
||||
|
||||
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue);
|
||||
|
||||
// ttValue can be used as a better position evaluation (~7 Elo)
|
||||
// ttValue can be used as a better position evaluation
|
||||
if (is_valid(ttData.value)
|
||||
&& (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER)))
|
||||
eval = ttData.value;
|
||||
@@ -813,14 +830,14 @@ Value Search::Worker::search(
|
||||
tt.generation());
|
||||
}
|
||||
|
||||
// Use static evaluation difference to improve quiet move ordering (~9 Elo)
|
||||
// Use static evaluation difference to improve quiet move ordering
|
||||
if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture)
|
||||
{
|
||||
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1881, 1413) + 616;
|
||||
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1151 / 1024;
|
||||
int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1906, 1450) + 638;
|
||||
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1136 / 1024;
|
||||
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
|
||||
thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq]
|
||||
<< bonus * 1107 / 1024;
|
||||
<< bonus * 1195 / 1024;
|
||||
}
|
||||
|
||||
// Set up the improving flag, which is true if current static evaluation is
|
||||
@@ -829,39 +846,37 @@ Value Search::Worker::search(
|
||||
// false otherwise. The improving flag is used in various pruning heuristics.
|
||||
improving = ss->staticEval > (ss - 2)->staticEval;
|
||||
|
||||
opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2;
|
||||
opponentWorsening = ss->staticEval > -(ss - 1)->staticEval;
|
||||
|
||||
if (priorReduction >= 3 && ss->staticEval + (ss - 1)->staticEval < 0)
|
||||
{
|
||||
if (priorReduction >= 3 && !opponentWorsening)
|
||||
depth++;
|
||||
}
|
||||
if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 200)
|
||||
depth--;
|
||||
|
||||
// Step 7. Razoring (~1 Elo)
|
||||
// Step 7. Razoring
|
||||
// If eval is really low, skip search entirely and return the qsearch value.
|
||||
// For PvNodes, we must have a guard against mates being returned.
|
||||
if (!PvNode && eval < alpha - 462 - 297 * depth * depth)
|
||||
if (!PvNode && eval < alpha - 446 - 303 * depth * depth)
|
||||
return qsearch<NonPV>(pos, ss, alpha, beta);
|
||||
|
||||
// Step 8. Futility pruning: child node (~40 Elo)
|
||||
// Step 8. Futility pruning: child node
|
||||
// The depth condition is important for mate finding.
|
||||
if (!ss->ttPv && depth < 14
|
||||
&& eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening)
|
||||
- (ss - 1)->statScore / 310 + 40 - std::abs(correctionValue) / 131072
|
||||
- (ss - 1)->statScore / 326 + 37 - std::abs(correctionValue) / 132821
|
||||
>= beta
|
||||
&& eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval))
|
||||
return beta + (eval - beta) / 3;
|
||||
|
||||
improving |= ss->staticEval >= beta + 97;
|
||||
|
||||
// Step 9. Null move search with verification search (~35 Elo)
|
||||
// Step 9. Null move search with verification search
|
||||
if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta
|
||||
&& ss->staticEval >= beta - 20 * depth + 440 && !excludedMove && pos.non_pawn_material(us)
|
||||
&& ss->ply >= thisThread->nmpMinPly && !is_loss(beta))
|
||||
&& ss->staticEval >= beta - 21 * depth + 455 - 60 * improving && !excludedMove
|
||||
&& pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta))
|
||||
{
|
||||
assert(eval - beta >= 0);
|
||||
|
||||
// Null move dynamic reduction based on depth and eval
|
||||
Depth R = std::min(int(eval - beta) / 215, 7) + depth / 3 + 5;
|
||||
Depth R = std::min(int(eval - beta) / 237, 6) + depth / 3 + 5;
|
||||
|
||||
ss->currentMove = Move::null();
|
||||
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
|
||||
@@ -894,29 +909,25 @@ Value Search::Worker::search(
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10. Internal iterative reductions (~9 Elo)
|
||||
improving |= ss->staticEval >= beta + 97;
|
||||
|
||||
// Step 10. Internal iterative reductions
|
||||
// For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth.
|
||||
// This heuristic is known to scale non-linearly, current version was tested at VVLTC.
|
||||
// Further improvements need to be tested at similar time control if they make IIR
|
||||
// more aggressive.
|
||||
if ((PvNode || (cutNode && depth >= 7)) && !ttData.move)
|
||||
depth -= 2;
|
||||
// (* Scaler) Especially if they make IIR more aggressive.
|
||||
if (((PvNode || cutNode) && depth >= 7 - 3 * PvNode) && !ttData.move)
|
||||
depth--;
|
||||
|
||||
// Use qsearch if depth <= 0
|
||||
if (depth <= 0)
|
||||
return qsearch<PV>(pos, ss, alpha, beta);
|
||||
|
||||
// Step 11. ProbCut (~10 Elo)
|
||||
// Step 11. ProbCut
|
||||
// If we have a good enough capture (or queen promotion) and a reduced search
|
||||
// returns a value much above beta, we can (almost) safely prune the previous move.
|
||||
probCutBeta = beta + 174 - 56 * improving;
|
||||
probCutBeta = beta + 187 - 55 * improving;
|
||||
if (depth >= 3
|
||||
&& !is_decisive(beta)
|
||||
// If value from transposition table is lower than probCutBeta, don't attempt
|
||||
// probCut there and in further interactions with transposition table cutoff
|
||||
// depth is set to depth - 3 because probCut search has depth set to depth - 4
|
||||
// but we also do a move before it. So effective depth is equal to depth - 3.
|
||||
&& !(ttData.depth >= depth - 3 && is_valid(ttData.value) && ttData.value < probCutBeta))
|
||||
&& !(is_valid(ttData.value) && ttData.value < probCutBeta))
|
||||
{
|
||||
assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta);
|
||||
|
||||
@@ -941,6 +952,7 @@ Value Search::Worker::search(
|
||||
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
ss->currentMove = move;
|
||||
ss->isTTMove = (move == ttData.move);
|
||||
ss->continuationHistory =
|
||||
&this->continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()];
|
||||
ss->continuationCorrectionHistory =
|
||||
@@ -971,8 +983,8 @@ Value Search::Worker::search(
|
||||
|
||||
moves_loop: // When in check, search starts here
|
||||
|
||||
// Step 12. A small Probcut idea (~4 Elo)
|
||||
probCutBeta = beta + 412;
|
||||
// Step 12. A small Probcut idea
|
||||
probCutBeta = beta + 413;
|
||||
if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta
|
||||
&& !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value))
|
||||
return probCutBeta;
|
||||
@@ -1032,15 +1044,19 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
Depth r = reduction(improving, depth, moveCount, delta);
|
||||
|
||||
// Decrease reduction if position is or has been on the PV (~7 Elo)
|
||||
if (ss->ttPv)
|
||||
r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960;
|
||||
r -= 32 * moveCount;
|
||||
|
||||
// Step 14. Pruning at shallow depth (~120 Elo).
|
||||
// Increase reduction for ttPv nodes (*Scaler)
|
||||
// Smaller or even negative value is better for short time controls
|
||||
// Bigger value is better for long time controls
|
||||
if (ss->ttPv)
|
||||
r += 1031;
|
||||
|
||||
// Step 14. Pruning at shallow depth.
|
||||
// Depth conditions are important for mate finding.
|
||||
if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue))
|
||||
{
|
||||
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo)
|
||||
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
|
||||
if (moveCount >= futility_move_count(improving, depth))
|
||||
mp.skip_quiet_moves();
|
||||
|
||||
@@ -1053,18 +1069,18 @@ moves_loop: // When in check, search starts here
|
||||
int captHist =
|
||||
thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)];
|
||||
|
||||
// Futility pruning for captures (~2 Elo)
|
||||
// Futility pruning for captures
|
||||
if (!givesCheck && lmrDepth < 7 && !ss->inCheck)
|
||||
{
|
||||
Value futilityValue = ss->staticEval + 271 + 243 * lmrDepth
|
||||
+ PieceValue[capturedPiece] + captHist / 7;
|
||||
Value futilityValue = ss->staticEval + 242 + 238 * lmrDepth
|
||||
+ PieceValue[capturedPiece] + 95 * captHist / 700;
|
||||
if (futilityValue <= alpha)
|
||||
continue;
|
||||
}
|
||||
|
||||
// SEE based pruning for captures and checks (~11 Elo)
|
||||
int seeHist = std::clamp(captHist / 37, -152 * depth, 141 * depth);
|
||||
if (!pos.see_ge(move, -156 * depth - seeHist))
|
||||
// SEE based pruning for captures and checks
|
||||
int seeHist = std::clamp(captHist / 36, -153 * depth, 134 * depth);
|
||||
if (!pos.see_ge(move, -157 * depth - seeHist))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -1074,18 +1090,22 @@ moves_loop: // When in check, search starts here
|
||||
+ (*contHist[1])[movedPiece][move.to_sq()]
|
||||
+ thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()];
|
||||
|
||||
// Continuation history based pruning (~2 Elo)
|
||||
if (history < -3901 * depth)
|
||||
// Continuation history based pruning
|
||||
if (history < -4107 * depth)
|
||||
continue;
|
||||
|
||||
history += 2 * thisThread->mainHistory[us][move.from_to()];
|
||||
history += 68 * thisThread->mainHistory[us][move.from_to()] / 32;
|
||||
|
||||
lmrDepth += history / 3459;
|
||||
lmrDepth += history / 3576;
|
||||
|
||||
Value futilityValue =
|
||||
ss->staticEval + (bestValue < ss->staticEval - 47 ? 137 : 47) + 142 * lmrDepth;
|
||||
Value futilityValue = ss->staticEval + (bestMove ? 49 : 143) + 116 * lmrDepth;
|
||||
|
||||
// Futility pruning: parent node (~13 Elo)
|
||||
if (bestValue < ss->staticEval - 150 && lmrDepth < 7)
|
||||
futilityValue += 108;
|
||||
|
||||
// Futility pruning: parent node
|
||||
// (*Scaler): Generally, more frequent futility pruning
|
||||
// scales well with respect to time and threads
|
||||
if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha)
|
||||
{
|
||||
if (bestValue <= futilityValue && !is_decisive(bestValue)
|
||||
@@ -1096,35 +1116,32 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
lmrDepth = std::max(lmrDepth, 0);
|
||||
|
||||
// Prune moves with negative SEE (~4 Elo)
|
||||
if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth))
|
||||
// Prune moves with negative SEE
|
||||
if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 15. Extensions (~100 Elo)
|
||||
// Step 15. Extensions
|
||||
// We take care to not overdo to avoid search getting stuck.
|
||||
if (ss->ply < thisThread->rootDepth * 2)
|
||||
{
|
||||
// Singular extension search (~76 Elo, ~170 nElo). If all moves but one
|
||||
// Singular extension search. If all moves but one
|
||||
// fail low on a search of (alpha-s, beta-s), and just one fails high on
|
||||
// (alpha, beta), then that move is singular and should be extended. To
|
||||
// verify this we do a reduced search on the position excluding the ttMove
|
||||
// and if the result is lower than ttValue minus a margin, then we will
|
||||
// extend the ttMove. Recursive singular search is avoided.
|
||||
|
||||
// Note: the depth margin and singularBeta margin are known for having
|
||||
// non-linear scaling. Their values are optimized to time controls of
|
||||
// 180+1.8 and longer so changing them requires tests at these types of
|
||||
// time controls. Generally, higher singularBeta (i.e closer to ttValue)
|
||||
// (* Scaler) Generally, higher singularBeta (i.e closer to ttValue)
|
||||
// and lower extension margins scale well.
|
||||
|
||||
if (!rootNode && move == ttData.move && !excludedMove
|
||||
&& depth >= 5 - (thisThread->completedDepth > 33) + ss->ttPv
|
||||
&& depth >= 5 - (thisThread->completedDepth > 32) + ss->ttPv
|
||||
&& is_valid(ttData.value) && !is_decisive(ttData.value)
|
||||
&& (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3)
|
||||
{
|
||||
Value singularBeta = ttData.value - (52 + 74 * (ss->ttPv && !PvNode)) * depth / 64;
|
||||
Value singularBeta = ttData.value - (55 + 81 * (ss->ttPv && !PvNode)) * depth / 58;
|
||||
Depth singularDepth = newDepth / 2;
|
||||
|
||||
ss->excludedMove = move;
|
||||
@@ -1134,18 +1151,16 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
if (value < singularBeta)
|
||||
{
|
||||
int corrValAdj = std::abs(correctionValue) / 262144;
|
||||
int doubleMargin = 249 * PvNode - 194 * !ttCapture - corrValAdj;
|
||||
int corrValAdj1 = std::abs(correctionValue) / 265083;
|
||||
int corrValAdj2 = std::abs(correctionValue) / 253680;
|
||||
int doubleMargin = 267 * PvNode - 181 * !ttCapture - corrValAdj1;
|
||||
int tripleMargin =
|
||||
94 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv - corrValAdj;
|
||||
int quadMargin =
|
||||
394 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv - corrValAdj;
|
||||
96 + 282 * PvNode - 250 * !ttCapture + 103 * ss->ttPv - corrValAdj2;
|
||||
|
||||
extension = 1 + (value < singularBeta - doubleMargin)
|
||||
+ (value < singularBeta - tripleMargin)
|
||||
+ (value < singularBeta - quadMargin);
|
||||
+ (value < singularBeta - tripleMargin);
|
||||
|
||||
depth += ((!PvNode) && (depth < 15));
|
||||
depth++;
|
||||
}
|
||||
|
||||
// Multi-cut pruning
|
||||
@@ -1164,22 +1179,15 @@ moves_loop: // When in check, search starts here
|
||||
// if the ttMove is singular or can do a multi-cut, so we reduce the
|
||||
// ttMove in favor of other moves based on some conditions:
|
||||
|
||||
// If the ttMove is assumed to fail high over current beta (~7 Elo)
|
||||
// If the ttMove is assumed to fail high over current beta
|
||||
else if (ttData.value >= beta)
|
||||
extension = -3;
|
||||
|
||||
// If we are on a cutNode but the ttMove is not assumed to fail high
|
||||
// over current beta (~1 Elo)
|
||||
// over current beta
|
||||
else if (cutNode)
|
||||
extension = -2;
|
||||
}
|
||||
|
||||
// Extension for capturing the previous moved piece (~1 Elo at LTC)
|
||||
else if (PvNode && move.to_sq() == prevSq
|
||||
&& thisThread->captureHistory[movedPiece][move.to_sq()]
|
||||
[type_of(pos.piece_on(move.to_sq()))]
|
||||
> 4126)
|
||||
extension = 1;
|
||||
}
|
||||
|
||||
// Step 16. Make the move
|
||||
@@ -1191,57 +1199,54 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
// Update the current move (this must be done after singular extension search)
|
||||
ss->currentMove = move;
|
||||
ss->isTTMove = (move == ttData.move);
|
||||
ss->continuationHistory =
|
||||
&thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()];
|
||||
ss->continuationCorrectionHistory =
|
||||
&thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()];
|
||||
uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0;
|
||||
|
||||
// These reduction adjustments have proven non-linear scaling.
|
||||
// They are optimized to time controls of 180 + 1.8 and longer,
|
||||
// so changing them or adding conditions that are similar requires
|
||||
// tests at these types of time controls.
|
||||
|
||||
// Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC)
|
||||
if (PvNode)
|
||||
r -= 1018;
|
||||
// Decrease reduction for PvNodes (*Scaler)
|
||||
if (ss->ttPv)
|
||||
r -= 2230 + PvNode * 1013 + (ttData.value > alpha) * 925
|
||||
+ (ttData.depth >= depth) * (971 + cutNode * 1159);
|
||||
|
||||
// These reduction adjustments have no proven non-linear scaling
|
||||
|
||||
r += 307;
|
||||
r += 316 - moveCount * 32;
|
||||
|
||||
r -= std::abs(correctionValue) / 34112;
|
||||
r -= std::abs(correctionValue) / 31568;
|
||||
|
||||
// Increase reduction for cut nodes (~4 Elo)
|
||||
// Increase reduction for cut nodes
|
||||
if (cutNode)
|
||||
r += 2355 - (ttData.depth >= depth && ss->ttPv) * 1141;
|
||||
r += 2608 + 1024 * !ttData.move;
|
||||
|
||||
// Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo)
|
||||
// Increase reduction if ttMove is a capture but the current move is not a capture
|
||||
if (ttCapture && !capture)
|
||||
r += 1087 + (depth < 8) * 990;
|
||||
r += 1123 + (depth < 8) * 982;
|
||||
|
||||
// Increase reduction if next ply has a lot of fail high (~5 Elo)
|
||||
// Increase reduction if next ply has a lot of fail high
|
||||
if ((ss + 1)->cutoffCnt > 3)
|
||||
r += 940 + allNode * 887;
|
||||
r += 981 + allNode * 833;
|
||||
|
||||
// For first picked move (ttMove) reduce reduction (~3 Elo)
|
||||
// For first picked move (ttMove) reduce reduction
|
||||
else if (move == ttData.move)
|
||||
r -= 1960;
|
||||
r -= 1982;
|
||||
|
||||
if (capture)
|
||||
ss->statScore =
|
||||
7 * int(PieceValue[pos.captured_piece()])
|
||||
688 * int(PieceValue[pos.captured_piece()]) / 100
|
||||
+ thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]
|
||||
- 4666;
|
||||
- 4653;
|
||||
else
|
||||
ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()]
|
||||
+ (*contHist[0])[movedPiece][move.to_sq()]
|
||||
+ (*contHist[1])[movedPiece][move.to_sq()] - 3874;
|
||||
+ (*contHist[1])[movedPiece][move.to_sq()] - 3591;
|
||||
|
||||
// Decrease/increase reduction for moves with a good/bad history (~8 Elo)
|
||||
r -= ss->statScore * 1451 / 16384;
|
||||
// Decrease/increase reduction for moves with a good/bad history
|
||||
r -= ss->statScore * 1407 / 16384;
|
||||
|
||||
// Step 17. Late moves reduction / extension (LMR, ~117 Elo)
|
||||
// Step 17. Late moves reduction / extension (LMR)
|
||||
if (depth >= 2 && moveCount > 1)
|
||||
{
|
||||
// In general we want to cap the LMR depth search at newDepth, but when
|
||||
@@ -1254,10 +1259,10 @@ moves_loop: // When in check, search starts here
|
||||
Depth d = std::max(
|
||||
1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove)));
|
||||
|
||||
(ss + 1)->reduction = newDepth - d;
|
||||
ss->reduction = newDepth - d;
|
||||
|
||||
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, d, true);
|
||||
(ss + 1)->reduction = 0;
|
||||
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, d, true);
|
||||
ss->reduction = 0;
|
||||
|
||||
|
||||
// Do a full-depth search when reduced LMR search fails high
|
||||
@@ -1265,16 +1270,16 @@ moves_loop: // When in check, search starts here
|
||||
{
|
||||
// Adjust full-depth search based on LMR results - if the result was
|
||||
// good enough search deeper, if it was bad enough search shallower.
|
||||
const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); // (~1 Elo)
|
||||
const bool doShallowerSearch = value < bestValue + 10; // (~2 Elo)
|
||||
const bool doDeeperSearch = value > (bestValue + 41 + 2 * newDepth);
|
||||
const bool doShallowerSearch = value < bestValue + 9;
|
||||
|
||||
newDepth += doDeeperSearch - doShallowerSearch;
|
||||
|
||||
if (newDepth > d)
|
||||
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode);
|
||||
|
||||
// Post LMR continuation history updates (~1 Elo)
|
||||
int bonus = (value >= beta) * 2048;
|
||||
// Post LMR continuation history updates
|
||||
int bonus = (value >= beta) * 2010;
|
||||
update_continuation_histories(ss, movedPiece, move.to_sq(), bonus);
|
||||
}
|
||||
}
|
||||
@@ -1282,13 +1287,13 @@ moves_loop: // When in check, search starts here
|
||||
// Step 18. Full-depth search when LMR is skipped
|
||||
else if (!PvNode || moveCount > 1)
|
||||
{
|
||||
// Increase reduction if ttMove is not present (~6 Elo)
|
||||
// Increase reduction if ttMove is not present
|
||||
if (!ttData.move)
|
||||
r += 2111;
|
||||
r += 1111;
|
||||
|
||||
// Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo)
|
||||
value =
|
||||
-search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3444), !cutNode);
|
||||
// Note that if expected reduction is high, we reduce search depth here
|
||||
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha,
|
||||
newDepth - (r > 3554) - (r > 5373 && newDepth > 2), !cutNode);
|
||||
}
|
||||
|
||||
// For PV nodes only, do a full PV search on the first move or after a fail high,
|
||||
@@ -1299,7 +1304,7 @@ moves_loop: // When in check, search starts here
|
||||
(ss + 1)->pv[0] = Move::none();
|
||||
|
||||
// Extend move from transposition table if we are about to dive into qsearch.
|
||||
if (move == ttData.move && ss->ply <= thisThread->rootDepth * 2)
|
||||
if (move == ttData.move && thisThread->rootDepth > 8)
|
||||
newDepth = std::max(newDepth, 1);
|
||||
|
||||
value = -search<PV>(pos, ss + 1, -beta, -alpha, newDepth, false);
|
||||
@@ -1387,14 +1392,15 @@ moves_loop: // When in check, search starts here
|
||||
|
||||
if (value >= beta)
|
||||
{
|
||||
ss->cutoffCnt += !ttData.move + (extension < 2);
|
||||
// (* Scaler) Especially if they make cutoffCnt increment more often.
|
||||
ss->cutoffCnt += (extension < 2) || PvNode;
|
||||
assert(value >= beta); // Fail high
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reduce other moves if we have found at least one score improvement (~2 Elo)
|
||||
if (depth > 2 && depth < 14 && !is_decisive(value))
|
||||
// Reduce other moves if we have found at least one score improvement
|
||||
if (depth > 2 && depth < 15 && !is_decisive(value))
|
||||
depth -= 2;
|
||||
|
||||
assert(depth > 0);
|
||||
@@ -1433,30 +1439,30 @@ moves_loop: // When in check, search starts here
|
||||
// we update the stats of searched moves.
|
||||
else if (bestMove)
|
||||
update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth,
|
||||
bestMove == ttData.move);
|
||||
bestMove == ttData.move, moveCount);
|
||||
|
||||
// Bonus for prior countermove that caused the fail low
|
||||
else if (!priorCapture && prevSq != SQ_NONE)
|
||||
{
|
||||
int bonusScale = (118 * (depth > 5) + 37 * !allNode + 169 * ((ss - 1)->moveCount > 8)
|
||||
+ 128 * (!ss->inCheck && bestValue <= ss->staticEval - 102)
|
||||
+ 115 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82));
|
||||
|
||||
// Proportional to "how much damage we have to undo"
|
||||
bonusScale += std::min(-(ss - 1)->statScore / 106, 318);
|
||||
int bonusScale = (118 * (depth > 5) + 36 * !allNode + 161 * ((ss - 1)->moveCount > 8)
|
||||
+ 133 * (!ss->inCheck && bestValue <= ss->staticEval - 107)
|
||||
+ 120 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 84)
|
||||
+ 81 * ((ss - 1)->isTTMove) + 100 * (ss->cutoffCnt <= 3)
|
||||
+ std::min(-(ss - 1)->statScore / 108, 320));
|
||||
|
||||
bonusScale = std::max(bonusScale, 0);
|
||||
|
||||
const int scaledBonus = stat_bonus(depth) * bonusScale / 32;
|
||||
const int scaledBonus = std::min(160 * depth - 106, 1523) * bonusScale;
|
||||
|
||||
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
|
||||
scaledBonus * 436 / 1024);
|
||||
scaledBonus * 416 / 32768);
|
||||
|
||||
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 207 / 1024;
|
||||
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()]
|
||||
<< scaledBonus * 219 / 32768;
|
||||
|
||||
if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION)
|
||||
thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq]
|
||||
<< scaledBonus * 1195 / 1024;
|
||||
<< scaledBonus * 1103 / 32768;
|
||||
}
|
||||
|
||||
else if (priorCapture && prevSq != SQ_NONE)
|
||||
@@ -1465,16 +1471,16 @@ moves_loop: // When in check, search starts here
|
||||
Piece capturedPiece = pos.captured_piece();
|
||||
assert(capturedPiece != NO_PIECE);
|
||||
thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)]
|
||||
<< stat_bonus(depth) * 2;
|
||||
<< std::min(330 * depth - 198, 3320);
|
||||
}
|
||||
|
||||
if (PvNode)
|
||||
bestValue = std::min(bestValue, maxValue);
|
||||
|
||||
// If no good move is found and the previous position was ttPv, then the previous
|
||||
// opponent move is probably good and the new position is added to the search tree. (~7 Elo)
|
||||
// opponent move is probably good and the new position is added to the search tree.
|
||||
if (bestValue <= alpha)
|
||||
ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3);
|
||||
ss->ttPv = ss->ttPv || (ss - 1)->ttPv;
|
||||
|
||||
// Write gathered information in transposition table. Note that the
|
||||
// static evaluation is saved as it was before correction history.
|
||||
@@ -1491,22 +1497,9 @@ moves_loop: // When in check, search starts here
|
||||
&& ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high
|
||||
|| (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low
|
||||
{
|
||||
const auto m = (ss - 1)->currentMove;
|
||||
constexpr int nonPawnWeight = 165;
|
||||
|
||||
auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8,
|
||||
-CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4);
|
||||
thisThread->pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)]
|
||||
<< bonus * 114 / 128;
|
||||
thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 163 / 128;
|
||||
thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 146 / 128;
|
||||
thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)]
|
||||
<< bonus * nonPawnWeight / 128;
|
||||
thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)]
|
||||
<< bonus * nonPawnWeight / 128;
|
||||
|
||||
if (m.is_ok())
|
||||
(*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus;
|
||||
update_correction_history(pos, ss, *thisThread, bonus);
|
||||
}
|
||||
|
||||
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
|
||||
@@ -1518,7 +1511,7 @@ moves_loop: // When in check, search starts here
|
||||
// Quiescence search function, which is called by the main search function with
|
||||
// depth zero, or recursively with further decreasing depth. With depth <= 0, we
|
||||
// "should" be using static eval only, but tactical moves may confuse the static eval.
|
||||
// To fight this horizon effect, we implement this qsearch of tactical moves (~155 Elo).
|
||||
// To fight this horizon effect, we implement this qsearch of tactical moves.
|
||||
// See https://www.chessprogramming.org/Horizon_Effect
|
||||
// and https://www.chessprogramming.org/Quiescence_Search
|
||||
template<NodeType nodeType>
|
||||
@@ -1530,7 +1523,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
|
||||
assert(PvNode || (alpha == beta - 1));
|
||||
|
||||
// Check if we have an upcoming move that draws by repetition (~1 Elo)
|
||||
// Check if we have an upcoming move that draws by repetition
|
||||
if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply))
|
||||
{
|
||||
alpha = value_draw(this->nodes);
|
||||
@@ -1547,7 +1540,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
Value bestValue, value, futilityBase;
|
||||
bool pvHit, givesCheck, capture;
|
||||
int moveCount;
|
||||
Color us = pos.side_to_move();
|
||||
|
||||
// Step 1. Initialize node
|
||||
if (PvNode)
|
||||
@@ -1602,7 +1594,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
ss->staticEval = bestValue =
|
||||
to_corrected_static_eval(unadjustedStaticEval, correctionValue);
|
||||
|
||||
// ttValue can be used as a better position evaluation (~13 Elo)
|
||||
// ttValue can be used as a better position evaluation
|
||||
if (is_valid(ttData.value) && !is_decisive(ttData.value)
|
||||
&& (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER)))
|
||||
bestValue = ttData.value;
|
||||
@@ -1633,7 +1625,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
if (bestValue > alpha)
|
||||
alpha = bestValue;
|
||||
|
||||
futilityBase = ss->staticEval + 301;
|
||||
futilityBase = ss->staticEval + 325;
|
||||
}
|
||||
|
||||
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
|
||||
@@ -1662,9 +1654,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
moveCount++;
|
||||
|
||||
// Step 6. Pruning
|
||||
if (!is_loss(bestValue) && pos.non_pawn_material(us))
|
||||
if (!is_loss(bestValue))
|
||||
{
|
||||
// Futility pruning and moveCount pruning (~10 Elo)
|
||||
// Futility pruning and moveCount pruning
|
||||
if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase)
|
||||
&& move.type_of() != PROMOTION)
|
||||
{
|
||||
@@ -1674,7 +1666,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
Value futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())];
|
||||
|
||||
// If static eval + value of piece we are going to capture is
|
||||
// much lower than alpha, we can prune this move. (~2 Elo)
|
||||
// much lower than alpha, we can prune this move.
|
||||
if (futilityValue <= alpha)
|
||||
{
|
||||
bestValue = std::max(bestValue, futilityValue);
|
||||
@@ -1682,7 +1674,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
}
|
||||
|
||||
// If static exchange evaluation is low enough
|
||||
// we can prune this move. (~2 Elo)
|
||||
// we can prune this move.
|
||||
if (!pos.see_ge(move, alpha - futilityBase))
|
||||
{
|
||||
bestValue = std::min(alpha, futilityBase);
|
||||
@@ -1690,17 +1682,17 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
}
|
||||
}
|
||||
|
||||
// Continuation history based pruning (~3 Elo)
|
||||
// Continuation history based pruning
|
||||
if (!capture
|
||||
&& (*contHist[0])[pos.moved_piece(move)][move.to_sq()]
|
||||
+ (*contHist[1])[pos.moved_piece(move)][move.to_sq()]
|
||||
+ thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)]
|
||||
[move.to_sq()]
|
||||
<= 5228)
|
||||
<= 5389)
|
||||
continue;
|
||||
|
||||
// Do not search moves with bad enough SEE values (~5 Elo)
|
||||
if (!pos.see_ge(move, -80))
|
||||
// Do not search moves with bad enough SEE values
|
||||
if (!pos.see_ge(move, -75))
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1767,7 +1759,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
|
||||
|
||||
Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const {
|
||||
int reductionScale = reductions[d] * reductions[mn];
|
||||
return reductionScale - delta * 768 / rootDelta + !i * reductionScale * 108 / 300 + 1168;
|
||||
return reductionScale - delta * 735 / rootDelta + !i * reductionScale * 191 / 512 + 1132;
|
||||
}
|
||||
|
||||
// elapsed() returns the time elapsed since the search started. If the
|
||||
@@ -1856,41 +1848,42 @@ void update_all_stats(const Position& pos,
|
||||
ValueList<Move, 32>& quietsSearched,
|
||||
ValueList<Move, 32>& capturesSearched,
|
||||
Depth depth,
|
||||
bool isTTMove) {
|
||||
bool isTTMove,
|
||||
int moveCount) {
|
||||
|
||||
CapturePieceToHistory& captureHistory = workerThread.captureHistory;
|
||||
Piece moved_piece = pos.moved_piece(bestMove);
|
||||
PieceType captured;
|
||||
|
||||
int bonus = stat_bonus(depth) + 300 * isTTMove;
|
||||
int malus = stat_malus(depth);
|
||||
int bonus = std::min(162 * depth - 92, 1587) + 298 * isTTMove;
|
||||
int malus = std::min(694 * depth - 230, 2503) - 32 * (moveCount - 1);
|
||||
|
||||
if (!pos.capture_stage(bestMove))
|
||||
{
|
||||
update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1216 / 1024);
|
||||
update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1202 / 1024);
|
||||
|
||||
// Decrease stats for all non-best quiet moves
|
||||
for (Move move : quietsSearched)
|
||||
update_quiet_histories(pos, ss, workerThread, move, -malus * 1062 / 1024);
|
||||
update_quiet_histories(pos, ss, workerThread, move, -malus * 1152 / 1024);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Increase stats for the best move in case it was a capture move
|
||||
captured = type_of(pos.piece_on(bestMove.to_sq()));
|
||||
captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1272 / 1024;
|
||||
captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1236 / 1024;
|
||||
}
|
||||
|
||||
// Extra penalty for a quiet early move that was not a TT move in
|
||||
// previous ply when it gets refuted.
|
||||
if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece())
|
||||
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 966 / 1024);
|
||||
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 976 / 1024);
|
||||
|
||||
// Decrease stats for all non-best capture moves
|
||||
for (Move move : capturesSearched)
|
||||
{
|
||||
moved_piece = pos.moved_piece(move);
|
||||
captured = type_of(pos.piece_on(move.to_sq()));
|
||||
captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1205 / 1024;
|
||||
captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1224 / 1024;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1899,7 +1892,7 @@ void update_all_stats(const Position& pos,
|
||||
// at ply -1, -2, -3, -4, and -6 with current move.
|
||||
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
|
||||
static constexpr std::array<ConthistBonus, 6> conthist_bonuses = {
|
||||
{{1, 1025}, {2, 621}, {3, 325}, {4, 512}, {5, 122}, {6, 534}}};
|
||||
{{1, 1029}, {2, 656}, {3, 326}, {4, 536}, {5, 120}, {6, 537}}};
|
||||
|
||||
for (const auto [i, weight] : conthist_bonuses)
|
||||
{
|
||||
@@ -1920,12 +1913,12 @@ void update_quiet_histories(
|
||||
workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort
|
||||
|
||||
if (ss->ply < LOW_PLY_HISTORY_SIZE)
|
||||
workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 879 / 1024;
|
||||
workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 844 / 1024;
|
||||
|
||||
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 888 / 1024);
|
||||
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 964 / 1024);
|
||||
|
||||
int pIndex = pawn_structure_index(pos);
|
||||
workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 634 / 1024;
|
||||
workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 615 / 1024;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2013,6 +2006,7 @@ void syzygy_extend_pv(const OptionsMap& options,
|
||||
|
||||
auto t_start = std::chrono::steady_clock::now();
|
||||
int moveOverhead = int(options["Move Overhead"]);
|
||||
bool rule50 = bool(options["Syzygy50MoveRule"]);
|
||||
|
||||
// Do not use more than moveOverhead / 2 time, if time management is active
|
||||
auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool {
|
||||
@@ -2050,7 +2044,7 @@ void syzygy_extend_pv(const OptionsMap& options,
|
||||
pos.do_move(pvMove, st);
|
||||
|
||||
// Do not allow for repetitions or drawing moves along the PV in TB regime
|
||||
if (config.rootInTB && pos.is_draw(ply))
|
||||
if (config.rootInTB && ((rule50 && pos.is_draw(ply)) || pos.is_repetition(ply)))
|
||||
{
|
||||
pos.undo_move(pvMove);
|
||||
ply--;
|
||||
@@ -2069,7 +2063,7 @@ void syzygy_extend_pv(const OptionsMap& options,
|
||||
// Step 2, now extend the PV to mate, as if the user explored syzygy-tables.info
|
||||
// using top ranked moves (minimal DTZ), which gives optimal mates only for simple
|
||||
// endgames e.g. KRvK.
|
||||
while (!pos.is_draw(0))
|
||||
while (!(rule50 && pos.is_draw(0)))
|
||||
{
|
||||
if (time_abort())
|
||||
break;
|
||||
@@ -2112,7 +2106,7 @@ void syzygy_extend_pv(const OptionsMap& options,
|
||||
pos.do_move(pvMove, st);
|
||||
}
|
||||
|
||||
// Finding a draw in this function is an exceptional case, that cannot happen
|
||||
// Finding a draw in this function is an exceptional case, that cannot happen when rule50 is false or
|
||||
// during engine game play, since we have a winning score, and play correctly
|
||||
// with TB support. However, it can be that a position is draw due to the 50 move
|
||||
// rule if it has been been reached on the board with a non-optimal 50 move counter
|
||||
|
||||
+1
-1
@@ -77,6 +77,7 @@ struct Stack {
|
||||
bool ttHit;
|
||||
int cutoffCnt;
|
||||
int reduction;
|
||||
bool isTTMove;
|
||||
};
|
||||
|
||||
|
||||
@@ -299,7 +300,6 @@ class Worker {
|
||||
PawnHistory pawnHistory;
|
||||
|
||||
CorrectionHistory<Pawn> pawnCorrectionHistory;
|
||||
CorrectionHistory<Major> majorPieceCorrectionHistory;
|
||||
CorrectionHistory<Minor> minorPieceCorrectionHistory;
|
||||
CorrectionHistory<NonPawn> nonPawnCorrectionHistory[COLOR_NB];
|
||||
CorrectionHistory<Continuation> continuationCorrectionHistory;
|
||||
|
||||
@@ -1622,7 +1622,7 @@ bool Tablebases::root_probe(Position& pos,
|
||||
WDLScore wdl = -probe_wdl(pos, &result);
|
||||
dtz = dtz_before_zeroing(wdl);
|
||||
}
|
||||
else if (pos.is_draw(1))
|
||||
else if ((rule50 && pos.is_draw(1)) || pos.is_repetition(1))
|
||||
{
|
||||
// In case a root move leads to a draw by repetition or 50-move rule,
|
||||
// we set dtz to zero. Note: since we are only 1 ply from the root,
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ void TimeManagement::init(Search::LimitsType& limits,
|
||||
const TimePoint scaledTime = limits.time[us] / scaleFactor;
|
||||
const TimePoint scaledInc = limits.inc[us] / scaleFactor;
|
||||
|
||||
// Maximum move horizon of 50 moves
|
||||
// Maximum move horizon
|
||||
int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051;
|
||||
|
||||
// If less than one second, gradually reduce mtg
|
||||
|
||||
@@ -57,6 +57,8 @@ struct TTData {
|
||||
bool is_pv;
|
||||
|
||||
TTData() = delete;
|
||||
|
||||
// clang-format off
|
||||
TTData(Move m, Value v, Value ev, Depth d, Bound b, bool pv) :
|
||||
move(m),
|
||||
value(v),
|
||||
@@ -64,6 +66,7 @@ struct TTData {
|
||||
depth(d),
|
||||
bound(b),
|
||||
is_pv(pv) {};
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ void Tune::make_option(OptionsMap* opts, const string& n, int v, const SetRange&
|
||||
if (TuneResults.count(n))
|
||||
v = TuneResults[n];
|
||||
|
||||
(*opts)[n] << Option(v, r(v).first, r(v).second, on_tune);
|
||||
opts->add(n, Option(v, r(v).first, r(v).second, on_tune));
|
||||
LastOption = &((*opts)[n]);
|
||||
|
||||
// Print formatted parameters, ready to be copy-pasted in Fishtest
|
||||
|
||||
+21
-19
@@ -21,6 +21,7 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
@@ -58,17 +59,31 @@ void OptionsMap::setoption(std::istringstream& is) {
|
||||
sync_cout << "No such option: " << name << sync_endl;
|
||||
}
|
||||
|
||||
Option OptionsMap::operator[](const std::string& name) const {
|
||||
const Option& OptionsMap::operator[](const std::string& name) const {
|
||||
auto it = options_map.find(name);
|
||||
return it != options_map.end() ? it->second : Option(this);
|
||||
assert(it != options_map.end());
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Option& OptionsMap::operator[](const std::string& name) {
|
||||
// Inits options and assigns idx in the correct printing order
|
||||
void OptionsMap::add(const std::string& name, const Option& option) {
|
||||
if (!options_map.count(name))
|
||||
options_map[name] = Option(this);
|
||||
return options_map[name];
|
||||
{
|
||||
static size_t insert_order = 0;
|
||||
|
||||
options_map[name] = option;
|
||||
|
||||
options_map[name].parent = this;
|
||||
options_map[name].idx = insert_order++;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Option \"" << name << "\" was already added!" << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); }
|
||||
|
||||
Option::Option(const OptionsMap* map) :
|
||||
@@ -131,19 +146,6 @@ bool Option::operator==(const char* s) const {
|
||||
bool Option::operator!=(const char* s) const { return !(*this == s); }
|
||||
|
||||
|
||||
// Inits options and assigns idx in the correct printing order
|
||||
|
||||
void Option::operator<<(const Option& o) {
|
||||
|
||||
static size_t insert_order = 0;
|
||||
|
||||
auto p = this->parent;
|
||||
*this = o;
|
||||
|
||||
this->parent = p;
|
||||
idx = insert_order++;
|
||||
}
|
||||
|
||||
// Updates currentValue and triggers on_change() action. It's up to
|
||||
// the GUI to check for option's limits, but we could receive the new value
|
||||
// from the user by console window, so let's check the bounds anyway.
|
||||
@@ -162,7 +164,7 @@ Option& Option::operator=(const std::string& v) {
|
||||
std::string token;
|
||||
std::istringstream ss(defaultValue);
|
||||
while (ss >> token)
|
||||
comboMap[token] << Option();
|
||||
comboMap.add(token, Option());
|
||||
if (!comboMap.count(v) || v == "var")
|
||||
return *this;
|
||||
}
|
||||
|
||||
+5
-3
@@ -54,12 +54,13 @@ class Option {
|
||||
|
||||
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
|
||||
|
||||
int operator<<(const Option&) = delete;
|
||||
|
||||
private:
|
||||
friend class OptionsMap;
|
||||
friend class Engine;
|
||||
friend class Tune;
|
||||
|
||||
void operator<<(const Option&);
|
||||
|
||||
std::string defaultValue, currentValue, type;
|
||||
int min, max;
|
||||
@@ -82,8 +83,9 @@ class OptionsMap {
|
||||
|
||||
void setoption(std::istringstream&);
|
||||
|
||||
Option operator[](const std::string&) const;
|
||||
Option& operator[](const std::string&);
|
||||
const Option& operator[](const std::string&) const;
|
||||
|
||||
void add(const std::string&, const Option& option);
|
||||
|
||||
std::size_t count(const std::string&) const;
|
||||
|
||||
|
||||
+9
-3
@@ -97,14 +97,17 @@ class Syzygy:
|
||||
tarball_path = os.path.join(tmpdirname, f"{file}.tar.gz")
|
||||
|
||||
response = requests.get(url, stream=True)
|
||||
with open(tarball_path, 'wb') as f:
|
||||
with open(tarball_path, "wb") as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
with tarfile.open(tarball_path, "r:gz") as tar:
|
||||
tar.extractall(tmpdirname)
|
||||
|
||||
shutil.move(os.path.join(tmpdirname, file), os.path.join(PATH, "syzygy"))
|
||||
shutil.move(
|
||||
os.path.join(tmpdirname, file), os.path.join(PATH, "syzygy")
|
||||
)
|
||||
|
||||
|
||||
class OrderedClassMembers(type):
|
||||
@classmethod
|
||||
@@ -307,7 +310,10 @@ class Stockfish:
|
||||
text=True,
|
||||
)
|
||||
|
||||
self.process.stdout
|
||||
if self.process.returncode != 0:
|
||||
print(self.process.stdout)
|
||||
print(self.process.stderr)
|
||||
print(f"Process failed with return code {self.process.returncode}")
|
||||
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user